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
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/cli/commands/audit.ts", "../src/audit/run.ts", "../src/audit/locate.ts", "../src/audit/url.ts", "../src/audit/checks/assets.ts", "../src/audit/checks/content.ts", "../src/audit/types.ts", "../src/audit/checks/dns-aid.ts", "../src/audit/checks/duplicates.ts", "../src/audit/checks/i18n.ts", "../src/audit/checks/indexability.ts", "../src/audit/graph.ts", "../src/audit/checks/links.ts", "../src/audit/checks/llms.ts", "../src/audit/checks/network.ts", "../src/audit/checks/og-image.ts", "../src/audit/image-size.ts", "../src/audit/checks/redirects.ts", "../src/audit/checks/robots.ts", "../src/audit/checks/sitemap.ts", "../src/audit/checks/social.ts", "../src/audit/crawl.ts", "../src/audit/snapshot.ts", "../src/audit/html.ts", "../src/audit/redirects.ts", "../src/audit/terms.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { defineCommand } from \"citty\";\n\nimport {\n AGENTS,\n fixPrompt,\n launchInstalledAgent,\n writeAgentReport,\n} from \"../../audit/agent.ts\";\nimport type { AgentKind } from \"../../audit/agent.ts\";\nimport { formatCatalog, formatReport, reportJson } from \"../../audit/report.ts\";\nimport { NoBuildError, runAudit } from \"../../audit/run.ts\";\nimport type { AuditResult } from \"../../audit/run.ts\";\nimport { checkTerms, unknownCheckTerms } from \"../../audit/terms.ts\";\nimport { BlumeError } from \"../../core/diagnostics.ts\";\nimport { scanProject } from \"../../core/project-graph.ts\";\nimport type { DiagnosticSeverity } from \"../../core/types.ts\";\nimport { commandMeta } from \"../command-meta.ts\";\nimport { reportInternalError } from \"../internal-error.ts\";\nimport { flushStdout, logger, reportDiagnostics } from \"../log.ts\";\nimport { closestMatch } from \"../unknown-flags.ts\";\n\nconst SEVERITIES: DiagnosticSeverity[] = [\"error\", \"warning\", \"info\"];\n\n/** Severities at or above the gate, e.g. `warning` -> error + warning. */\nconst failingSeverities = (gate: DiagnosticSeverity): Set<DiagnosticSeverity> =>\n new Set(SEVERITIES.slice(0, SEVERITIES.indexOf(gate) + 1));\n\nconst splitTerms = (value: string | undefined): string[] =>\n value\n ? value\n .split(\",\")\n .map((term) => term.trim())\n .filter(Boolean)\n : [];\n\n/**\n * Exit when `--only`/`--skip` name a check or category that doesn't exist,\n * suggesting the closest real one — a typo would otherwise filter every\n * finding out and pass the gate.\n */\nconst rejectUnknownTerms = (terms: string[]): void => {\n const unknown = unknownCheckTerms(terms);\n if (unknown.length === 0) {\n return;\n }\n const known = checkTerms();\n const listed = unknown\n .map((term) => {\n const suggestion = closestMatch(term.trim().toLowerCase(), known);\n return suggestion\n ? `\"${term}\" (did you mean \"${suggestion}\"?)`\n : `\"${term}\"`;\n })\n .join(\", \");\n logger.error(\n `--only/--skip name no check or category: ${listed}. Run \\`blume audit --list-checks\\` for every check id and category.`\n );\n process.exit(1);\n};\n\n/** Whether the run should exit non-zero, given the gate. */\nexport const shouldFail = (\n result: AuditResult,\n gate: DiagnosticSeverity\n): boolean => {\n const failing = failingSeverities(gate);\n return result.diagnostics.some((d) => failing.has(d.severity));\n};\n\n/** Launch the agent CLI, turning a missing executable into the install hint. */\nconst launchAgentCode = async (\n agent: AgentKind,\n prompt: string\n): Promise<number> => {\n const cli = AGENTS[agent];\n const code = await launchInstalledAgent(cli.bin, prompt);\n if (code === null) {\n logger.error(\n `${cli.name} (\\`${cli.bin}\\`) was not found on PATH. Install it with \\`${cli.install}\\`.`\n );\n return process.exit(1);\n }\n return code;\n};\n\nexport const auditCommand = defineCommand({\n args: {\n claude: {\n description: \"Hand the findings to Claude Code to fix interactively.\",\n type: \"boolean\",\n },\n codex: {\n description: \"Hand the findings to Codex to fix interactively.\",\n type: \"boolean\",\n },\n external: {\n description: \"Probe outbound links over the network.\",\n type: \"boolean\",\n },\n \"fail-on\": {\n description:\n \"Exit non-zero at this severity or above: error | warning | info. Defaults to error.\",\n type: \"string\",\n },\n json: {\n description: \"Emit the report as JSON on stdout (for CI/editors).\",\n type: \"boolean\",\n },\n \"list-checks\": {\n description: \"Print every check the audit can report, then exit.\",\n type: \"boolean\",\n },\n only: {\n description: \"Only report these checks or categories (comma-separated).\",\n type: \"string\",\n },\n skip: {\n description: \"Suppress these checks or categories (comma-separated).\",\n type: \"string\",\n },\n strict: {\n description: \"Alias for --fail-on warning.\",\n type: \"boolean\",\n },\n url: {\n description:\n \"Also probe a live deployment (e.g. https://docs.example.com) for status codes, headers, and redirects.\",\n type: \"string\",\n },\n verbose: {\n description: \"List every affected page instead of the first few.\",\n type: \"boolean\",\n },\n },\n meta: commandMeta.audit,\n async run({ args }) {\n if (args[\"list-checks\"]) {\n process.stdout.write(formatCatalog());\n return;\n }\n\n const root = process.cwd();\n const gateInput = args[\"fail-on\"] ?? (args.strict ? \"warning\" : \"error\");\n const gate = SEVERITIES.find((severity) => severity === gateInput);\n if (gate === undefined) {\n logger.error(\n `Invalid --fail-on \"${gateInput}\" (use ${SEVERITIES.join(\" | \")}).`\n );\n process.exit(1);\n }\n // SAFETY: AGENTS is a closed record keyed by AgentKind, so its keys are\n // exactly the agent kinds.\n const agents = (Object.keys(AGENTS) as AgentKind[]).filter(\n (kind) => args[kind]\n );\n if (agents.length > 1) {\n logger.error(\"Pass at most one of --codex or --claude.\");\n process.exit(1);\n }\n const [agent] = agents;\n if (agent && args.json) {\n logger.error(`--json and --${agent} are mutually exclusive.`);\n process.exit(1);\n }\n const only = splitTerms(args.only);\n const skip = splitTerms(args.skip);\n rejectUnknownTerms([...only, ...skip]);\n let result: AuditResult;\n try {\n // `scanProject`, not `prepareProject`: the audit reads the *existing*\n // build and never regenerates the runtime, so it doesn't contend with a\n // running dev server. Same reasoning as `blume validate`.\n const project = await scanProject(root, { mode: \"build\" });\n result = await runAudit({\n external: args.external,\n only,\n origin: args.url,\n project,\n skip,\n });\n } catch (error) {\n if (error instanceof NoBuildError) {\n logger.error(`${error.message} Run \\`blume build\\` first.`);\n process.exit(1);\n }\n if (error instanceof BlumeError) {\n reportDiagnostics([error.diagnostic], root);\n process.exit(1);\n }\n reportInternalError(error);\n process.exit(1);\n }\n\n if (agent) {\n // Show the same report a plain run would, so the terminal records what\n // was handed off before the agent's own UI takes over the screen.\n process.stderr.write(\n formatReport(result, root, { verbose: args.verbose })\n );\n if (result.diagnostics.length === 0) {\n return;\n }\n const cli = AGENTS[agent];\n const report = await writeAgentReport(result, root);\n const count = result.diagnostics.length;\n // Straight to stderr like the report above it, not `logger.info` —\n // consola drops info-level lines in test and CI environments.\n process.stderr.write(\n ` Handing ${count} finding${count === 1 ? \"\" : \"s\"} to ${cli.name}…\\n\\n`\n );\n const code = await launchAgentCode(agent, fixPrompt(report));\n if (code !== 0) {\n process.exit(code);\n }\n // The gate is a CI concern; a handoff run succeeds when the agent\n // session does, not when the pre-fix site was already clean.\n return;\n }\n\n if (args.json) {\n process.stdout.write(reportJson(result, root));\n if (shouldFail(result, gate)) {\n // `process.exit` doesn't flush a piped stdout — without this the JSON is\n // truncated mid-write in exactly the CI setups that consume it.\n await flushStdout();\n process.exit(1);\n }\n return;\n }\n\n process.stderr.write(formatReport(result, root, { verbose: args.verbose }));\n\n if (shouldFail(result, gate)) {\n process.exit(1);\n }\n },\n});\n",
|
|
6
|
-
"import { readFile } from \"node:fs/promises\";\n\nimport pMap from \"p-map\";\n\nimport { normalizeBasePath, withBasePath } from \"../core/base-path.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport type { Diagnostic } from \"../core/types.ts\";\nimport { deployStaticDir } from \"../deploy/adapter-output.ts\";\nimport { CHECKS } from \"./catalog.ts\";\nimport type { CheckId } from \"./catalog.ts\";\nimport { assetChecks } from \"./checks/assets.ts\";\nimport { contentChecks } from \"./checks/content.ts\";\nimport { dnsAidChecks } from \"./checks/dns-aid.ts\";\nimport { duplicateChecks } from \"./checks/duplicates.ts\";\nimport { i18nChecks } from \"./checks/i18n.ts\";\nimport { indexabilityChecks } from \"./checks/indexability.ts\";\nimport { linkChecks } from \"./checks/links.ts\";\nimport { llmsChecks } from \"./checks/llms.ts\";\nimport { externalChecks, networkChecks } from \"./checks/network.ts\";\nimport { ogImageChecks } from \"./checks/og-image.ts\";\nimport { redirectChecks } from \"./checks/redirects.ts\";\nimport { robotsChecks } from \"./checks/robots.ts\";\nimport { sitemapChecks } from \"./checks/sitemap.ts\";\nimport {\n socialChecks,\n structuredDataChecks,\n urlChecks,\n} from \"./checks/social.ts\";\nimport { crawlStaticDir } from \"./crawl.ts\";\nimport { buildGraph } from \"./graph.ts\";\nimport { resolveRedirects } from \"./redirects.ts\";\nimport { shortId } from \"./terms.ts\";\nimport { DEFAULT_THRESHOLDS } from \"./types.ts\";\nimport type {\n AuditContext,\n AuditTier,\n CheckModule,\n PageSnapshot,\n} from \"./types.ts\";\nimport { isServed, siteOrigin } from \"./url.ts\";\n\nconst MODULES: CheckModule[] = [\n contentChecks,\n duplicateChecks,\n indexabilityChecks,\n linkChecks,\n redirectChecks,\n socialChecks,\n ogImageChecks,\n i18nChecks,\n assetChecks,\n sitemapChecks,\n robotsChecks,\n llmsChecks,\n structuredDataChecks,\n urlChecks,\n networkChecks,\n dnsAidChecks,\n externalChecks,\n];\n\nexport interface AuditOptions {\n project: BlumeProject;\n /** Origin to probe for the network tier (`--url`). */\n origin?: string;\n /** Probe outbound links (`--external`). */\n external?: boolean;\n /** Only report these check ids or categories. */\n only?: string[];\n /** Suppress these check ids or categories. */\n skip?: string[];\n}\n\nexport interface AuditResult {\n diagnostics: Diagnostic[];\n staticDir: string;\n pages: number;\n origin: string | null;\n /** Which tiers actually ran. A skipped tier is reported, never hidden. */\n tiers: Record<AuditTier, boolean>;\n}\n\n/** Thrown when there's no build to audit. */\nexport class NoBuildError extends Error {\n readonly staticDir: string;\n\n constructor(staticDir: string) {\n super(`No build found at ${staticDir}.`);\n this.name = \"NoBuildError\";\n this.staticDir = staticDir;\n }\n}\n\n/** Ceiling on concurrent source reads; unbounded fan-out risks EMFILE. */\nconst READ_CONCURRENCY = 16;\n\n/** Read every page's source file once, so findings can cite front matter lines. */\nconst readSources = async (\n pages: PageSnapshot[]\n): Promise<Map<string, string>> => {\n const paths = [\n ...new Set(pages.flatMap((page) => (page.source ? [page.source] : []))),\n ];\n const entries = await pMap(\n paths,\n async (path) => {\n try {\n return [path, await readFile(path, \"utf-8\")] as const;\n } catch {\n // A staged (non-filesystem) source may not exist on disk. The finding\n // still names the URL; it just can't cite a line.\n return null;\n }\n },\n { concurrency: READ_CONCURRENCY }\n );\n return new Map(entries.filter((entry) => entry !== null));\n};\n\n/** Does a check id or its category match one of the user's `--only`/`--skip` terms? */\nconst matches = (id: CheckId, terms: string[]): boolean => {\n const meta = CHECKS.find((check) => check.id === id);\n const short = shortId(id);\n return terms.some((raw) => {\n const term = raw.trim().toLowerCase();\n return (\n term === short || term === id.toLowerCase() || term === meta?.category\n );\n });\n};\n\n/** Audit a built site. */\nexport const runAudit = async (options: AuditOptions): Promise<AuditResult> => {\n const { project } = options;\n const staticDir = deployStaticDir(project.config, project.context);\n const basePath = normalizeBasePath(project.config.basePath);\n\n const crawl = await crawlStaticDir({\n basePath,\n manifest: project.manifest,\n staticDir,\n });\n if (crawl.pages.length === 0) {\n throw new NoBuildError(staticDir);\n }\n\n const origin = options.origin ?? null;\n const byUrl = new Map(crawl.pages.map((page) => [page.url, page]));\n const context: AuditContext = {\n byUrl,\n files: crawl.files,\n graph: buildGraph(\n crawl.pages,\n siteOrigin(project.config.deployment.options.site),\n normalizeBasePath(project.config.deployment.options.base)\n ),\n llms: crawl.llms,\n origin,\n pages: crawl.pages,\n project,\n redirects: resolveRedirects(\n // Redirects are authored as if mounted at root; the built page URLs they\n // are checked against carry `basePath` (it's a real directory in the\n // build), so both sides gain it here — mirroring what\n // `applyBaseToAstroRedirects` does at build time. `withBasePath` is\n // idempotent and leaves external `to` URLs untouched.\n project.config.redirects.map((redirect) => ({\n ...redirect,\n from: withBasePath(basePath, redirect.from),\n to: withBasePath(basePath, redirect.to),\n })),\n // Pages, static files, and server routes alike: a redirect may\n // legitimately land on a served asset (`/old-whitepaper` ->\n // `/files/whitepaper.pdf`), and the same predicate the link and llms.txt\n // checks use decides what counts.\n (path) => isServed({ byUrl, files: crawl.files, project }, path)\n ),\n robots: crawl.robots,\n sitemap: crawl.sitemap,\n sources: await readSources(crawl.pages),\n staticDir,\n thresholds: DEFAULT_THRESHOLDS,\n };\n\n const tiers = {\n external: Boolean(options.external),\n network: origin !== null,\n static: true,\n } satisfies Record<AuditTier, boolean>;\n\n const results = await Promise.all(\n MODULES.filter((module) => tiers[module.tier]).map((module) =>\n module.run(context)\n )\n );\n\n let diagnostics = results.flat();\n if (options.only?.length) {\n // SAFETY: audit diagnostics are created through `finding()`, whose codes\n // all come from the check catalog's `CheckId` set.\n diagnostics = diagnostics.filter((d) =>\n matches(d.code as CheckId, options.only ?? [])\n );\n }\n if (options.skip?.length) {\n // SAFETY: same invariant — every audit diagnostic code is a catalog `CheckId`.\n diagnostics = diagnostics.filter(\n (d) => !matches(d.code as CheckId, options.skip ?? [])\n );\n }\n\n return {\n diagnostics,\n origin,\n pages: crawl.pages.length,\n staticDir,\n tiers,\n };\n};\n",
|
|
7
|
-
"import { locateFrontmatterKey } from \"../core/diagnostics.ts\";\nimport type { FindingSite } from \"./catalog.ts\";\nimport type { AuditContext, PageSnapshot } from \"./types.ts\";\n\n/**\n * Where to report a finding about `page`: the built URL, plus — when the page\n * came from authored content — the source file and the exact front matter line\n * to edit.\n *\n * This is the feature. A crawler can only tell you that `/docs/api` has no\n * description; Blume knows the page was built from `docs/api.mdx` and can put\n * the cursor on the line. Pass the front matter key path a fix would touch\n * (`[\"description\"]`, `[\"seo\", \"canonical\"]`); omit it when the key doesn't\n * exist yet, and the finding anchors to the file instead.\n */\nexport const pageSite = (\n context: AuditContext,\n page: PageSnapshot,\n key?: readonly (string | number)[]\n): FindingSite => {\n const file = page.source;\n if (!file) {\n return { url: page.url };\n }\n const source = key ? context.sources.get(file) : undefined;\n const position = source ? locateFrontmatterKey(source, key ?? []) : undefined;\n return {\n column: position?.column,\n file,\n line: position?.line,\n url: page.url,\n };\n};\n",
|
|
8
|
-
"import { normalizePath, stripBasePath } from \"../core/base-path.ts\";\nimport type { AuditContext } from \"./types.ts\";\n\n// Re-exported from its home next to the other path helpers; the audit checks\n// (and their tests) import it from here.\nexport { normalizePath } from \"../core/base-path.ts\";\n\n/** The slice of an audit context that says what the build serves. */\nexport type ServedContext = Pick<AuditContext, \"byUrl\" | \"files\" | \"project\">;\n\n/**\n * Routes the server answers that the build writes no file for.\n *\n * The MCP endpoint is streamable HTTP: llms.txt advertises `agents.mcp.route`\n * whenever the server is enabled, but it appears in neither the page snapshots\n * nor the static file index, so a check that only consults those reads the\n * site's own index as broken. The exemption is the configured route while the\n * server is on, and nothing wider — with `agents.mcp` off, a listed `/mcp` is as\n * dead as any other missing target. Any future server route (a search API, a\n * playground proxy) belongs here too, so every check recognizes it at once.\n */\nconst serverRoutes = (context: ServedContext): string[] => {\n const mcp = context.project.config.agents?.mcp;\n return mcp?.enabled ? [normalizePath(mcp.route)] : [];\n};\n\n/**\n * Whether a normalized path is served by the build — as a page, as a static\n * file, or as a route the server answers. The one definition of \"served\" for\n * the link, llms.txt, and redirect checks, so they can never disagree about\n * the same target.\n */\nexport const isServed = (context: ServedContext, path: string): boolean =>\n context.byUrl.has(path) ||\n context.files.has(path) ||\n // Astro's directory format serves `/docs/api` from `/docs/api/index.html`.\n context.files.has(`${path}/index.html`) ||\n serverRoutes(context).includes(path);\n\n/** What an `href` in built HTML turned out to point at. */\nexport type ResolvedHref =\n /** A path on this site. */\n | { kind: \"internal\"; path: string; hash: string }\n /** An absolute URL that resolves back to this site — should have been a path. */\n | { kind: \"self-origin\"; path: string; hash: string }\n /** An absolute URL on another origin. */\n | { kind: \"external\"; url: string }\n /** In-page anchor, `mailto:`, `tel:`, `javascript:`, data URI — not a page link. */\n | { kind: \"ignored\" };\n\nconst NON_HTTP_SCHEME = /^(?!https?:)[a-z][a-z0-9+.-]*:/iu;\n\n/**\n * Percent-decode a pathname for comparison against the built file tree. Page\n * URLs and file-index keys come from raw on-disk names, while `URL#pathname`\n * (and the `encodeURI`'d sitemap `<loc>`s) are percent-encoded — a Japanese\n * route would never match its own page without this. Malformed sequences are\n * kept as-is: they can't have come from our own encoder.\n */\nexport const decodePath = (path: string): string => {\n try {\n return decodeURI(path);\n } catch {\n return path;\n }\n};\n\n/** The origin of `deployment.site`, or null when no site is configured. */\nexport const siteOrigin = (site?: string): string | null => {\n if (!site) {\n return null;\n }\n try {\n return new URL(site).origin;\n } catch {\n return null;\n }\n};\n\n/**\n * Resolve an `href` found on `pageUrl` into something the link graph can use.\n *\n * An absolute URL pointing back at our own origin is reported separately from a\n * genuine external link: it's an internal link that hardcoded the production\n * domain, which silently breaks on preview deploys and under `basePath`.\n *\n * `deployBase` is the normalized `deployment.base`: emitted hrefs carry it, but\n * the built file tree (and so every page URL and file-index key) does not, so it\n * is stripped here to keep resolved paths comparable. `basePath` is different —\n * Blume mounts it as a real directory in the build, so it stays.\n */\nexport const resolveHref = (\n pageUrl: string,\n href: string,\n origin: string | null,\n deployBase = \"\"\n): ResolvedHref => {\n const target = href.trim();\n if (target === \"\" || target.startsWith(\"#\")) {\n return { kind: \"ignored\" };\n }\n if (NON_HTTP_SCHEME.test(target)) {\n return { kind: \"ignored\" };\n }\n\n // Protocol-relative (`//host/x`) is an absolute URL with the page's scheme.\n const absolute = /^https?:\\/\\//iu.test(target) || target.startsWith(\"//\");\n if (absolute) {\n let parsed: URL;\n try {\n parsed = new URL(target.startsWith(\"//\") ? `https:${target}` : target);\n } catch {\n return { kind: \"ignored\" };\n }\n if (origin && parsed.origin === origin) {\n return {\n hash: parsed.hash.slice(1),\n kind: \"self-origin\",\n path: normalizePath(\n stripBasePath(deployBase, decodePath(parsed.pathname))\n ),\n };\n }\n return { kind: \"external\", url: parsed.toString() };\n }\n\n // A relative href resolves the way a browser resolves it from the page's\n // canonical URL. `URL` needs an origin to do that, so borrow a placeholder\n // one and keep only the path. The canonical URL is slashless — the generated\n // config sets `trailingSlash: \"never\"`, Vercel redirects `/docs/api/` onto\n // `/docs/api`, and the dev server 404s the slashed form — so `./auth` there\n // means `/docs/auth`, exactly where a reader's click lands. Content links\n // never hit this: the Markdown pipeline rewrites them root-relative (see\n // `resolveRelativeHref`); what's left is hand-written HTML, which a browser\n // reads this way.\n const base = `https://blume.invalid${pageUrl}`;\n let resolved: URL;\n try {\n resolved = new URL(target, base);\n } catch {\n return { kind: \"ignored\" };\n }\n return {\n hash: resolved.hash.slice(1),\n kind: \"internal\",\n path: normalizePath(\n stripBasePath(deployBase, decodePath(resolved.pathname))\n ),\n };\n};\n",
|
|
9
|
-
"import { normalizeBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport type { CheckId } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type {\n AuditContext,\n CheckModule,\n PageSnapshot,\n SnapshotAsset,\n} from \"../types.ts\";\nimport { resolveHref, siteOrigin } from \"../url.ts\";\n\nconst formatBytes = (bytes: number): string =>\n bytes < 1024 * 1024\n ? `${Math.round(bytes / 1024)} kB`\n : `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n\n/**\n * Resolve a subresource reference to its file in the build, reporting a missing\n * file or an insecure (`http://`) reference. Returns the file's size when it\n * resolves to something real and local.\n */\nconst resolveAsset = (\n context: AuditContext,\n page: PageSnapshot,\n asset: SnapshotAsset,\n missingId: CheckId,\n kind: string,\n found: Diagnostic[]\n): number | null => {\n if (asset.src.startsWith(\"http://\")) {\n found.push(\n finding(\n \"BLUME_AUDIT_MIXED_CONTENT\",\n pageSite(context, page),\n `Page loads ${kind} over plain HTTP: ${asset.src}`\n )\n );\n return null;\n }\n if (asset.src.startsWith(\"data:\")) {\n return null;\n }\n\n const origin = siteOrigin(context.project.config.deployment.options.site);\n const resolved = resolveHref(\n page.url,\n asset.src,\n origin,\n normalizeBasePath(context.project.config.deployment.options.base)\n );\n // A subresource on another origin (a CDN, an analytics script) is outside the\n // build; we can't check whether it exists without the network.\n if (resolved.kind === \"external\" || resolved.kind === \"ignored\") {\n return null;\n }\n\n const bytes = context.files.get(resolved.path);\n if (bytes === undefined) {\n found.push(\n finding(\n missingId,\n pageSite(context, page),\n `Page references ${asset.src}, which is not in the build.`\n )\n );\n return null;\n }\n return bytes;\n};\n\n/**\n * Images, scripts, and stylesheets: do they exist, are they secure, are they\n * too heavy, and (for images) are they accessible and layout-stable.\n *\n * Ahrefs devotes ~18 rows to this across its Images, JavaScript, and CSS\n * sections — \"JS redirects\", \"page has redirected CSS\", \"HTTPS page links to\n * HTTP JavaScript\", and both an asset-scoped and a page-scoped copy of each.\n * Vite emits content-hashed, existent, non-redirecting bundles, so nearly all of\n * that is unfireable; what's left is this.\n */\nexport const assetChecks: CheckModule = {\n category: \"assets\",\n run(context) {\n const found: Diagnostic[] = [];\n const { maxAssetBytes } = context.thresholds;\n\n const weigh = (\n page: PageSnapshot,\n asset: SnapshotAsset,\n bytes: number | null,\n kind: string\n ): void => {\n if (bytes !== null && bytes > maxAssetBytes) {\n found.push(\n finding(\n \"BLUME_AUDIT_ASSET_TOO_LARGE\",\n pageSite(context, page),\n `${kind} ${asset.src} is ${formatBytes(bytes)} (over ${formatBytes(maxAssetBytes)}).`\n )\n );\n }\n };\n\n for (const page of context.pages) {\n for (const image of page.images) {\n const bytes = resolveAsset(\n context,\n page,\n image,\n \"BLUME_AUDIT_IMAGE_BROKEN\",\n \"an image\",\n found\n );\n weigh(page, image, bytes, \"Image\");\n\n // `alt=\"\"` is a deliberate \"this image is decorative\" and is correct.\n // A missing `alt` attribute is the finding.\n if (image.alt === null) {\n found.push(\n finding(\n \"BLUME_AUDIT_IMAGE_ALT_MISSING\",\n pageSite(context, page),\n `Image ${image.src} has no alt attribute.`\n )\n );\n }\n\n if (!(image.width && image.height)) {\n found.push(\n finding(\n \"BLUME_AUDIT_IMAGE_MISSING_DIMENSIONS\",\n pageSite(context, page),\n `Image ${image.src} has no width/height, so it will shift the layout as it loads.`\n )\n );\n }\n }\n\n for (const script of page.scripts) {\n weigh(\n page,\n script,\n resolveAsset(\n context,\n page,\n script,\n \"BLUME_AUDIT_SUBRESOURCE_MISSING\",\n \"a script\",\n found\n ),\n \"Script\"\n );\n }\n\n for (const style of page.styles) {\n weigh(\n page,\n style,\n resolveAsset(\n context,\n page,\n style,\n \"BLUME_AUDIT_SUBRESOURCE_MISSING\",\n \"a stylesheet\",\n found\n ),\n \"Stylesheet\"\n );\n }\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
10
|
-
"import stringWidth from \"string-width\";\n\nimport matter from \"../../core/frontmatter.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport { ERROR_ROUTES } from \"../types.ts\";\nimport type { AuditContext, CheckModule, PageSnapshot } from \"../types.ts\";\n\n// Extraction trims only the ends; interior runs of whitespace (a <title>\n// authored across indented source lines) survive. A search engine collapses\n// them to single spaces before rendering, so the width is measured on the\n// collapsed text — source formatting must not move a page across a threshold.\nconst WHITESPACE = /\\s+/gu;\n\n/** Whether front matter `date` arrived in one of YAML's two date spellings. */\nconst isDateValue = (\n value: string | Date | undefined\n): value is string | Date => typeof value === \"string\" || value instanceof Date;\n\n/**\n * The `date` a source file's front matter declares, when it parses to a real\n * date. YAML hands back a `Date` for an unquoted `2026-01-01` and a string for\n * a quoted one, so both spellings are accepted; malformed front matter or an\n * unparseable value simply yields nothing — `BLUME_FRONTMATTER_INVALID` is the\n * build's finding, not the audit's.\n */\nconst frontmatterDate = (source: string): Date | null => {\n try {\n const { date } = matter(source).data;\n if (!isDateValue(date)) {\n return null;\n }\n const parsed = date instanceof Date ? date : new Date(date);\n return Number.isNaN(parsed.getTime()) ? null : parsed;\n } catch {\n return null;\n }\n};\n\nconst titleChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n const { titleMin, titleMax } = context.thresholds;\n const [title] = page.titles;\n\n if (!title) {\n return [\n finding(\n \"BLUME_AUDIT_TITLE_MISSING\",\n pageSite(context, page),\n \"Page has no <title>.\"\n ),\n ];\n }\n\n const found: Diagnostic[] = [];\n if (page.titles.length > 1) {\n found.push(\n finding(\n \"BLUME_AUDIT_TITLE_MULTIPLE\",\n pageSite(context, page),\n `Page has ${page.titles.length} <title> tags; search engines use only the first.`\n )\n );\n }\n // Columns, not characters: a search engine truncates by the space the text\n // takes up. See {@link AuditThresholds}.\n const width = stringWidth(title.replaceAll(WHITESPACE, \" \"));\n if (width > titleMax || width < titleMin) {\n const direction = width > titleMax ? \"long\" : \"short\";\n found.push(\n finding(\n \"BLUME_AUDIT_TITLE_LENGTH\",\n pageSite(context, page, [\"title\"]),\n `Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax}).`\n )\n );\n }\n return found;\n};\n\nconst descriptionChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n // An error route is noindex by design, so its description never renders as\n // a search snippet — grading it would put a guaranteed finding on every\n // site whose 404 inherits the site-default description.\n if (ERROR_ROUTES.has(page.url)) {\n return [];\n }\n\n const { descriptionMin, descriptionMax } = context.thresholds;\n const [description] = page.descriptions;\n\n if (!description) {\n return [\n finding(\n \"BLUME_AUDIT_DESCRIPTION_MISSING\",\n pageSite(context, page),\n \"Page has no meta description.\"\n ),\n ];\n }\n\n const found: Diagnostic[] = [];\n if (page.descriptions.length > 1) {\n found.push(\n finding(\n \"BLUME_AUDIT_DESCRIPTION_MULTIPLE\",\n pageSite(context, page),\n `Page has ${page.descriptions.length} meta description tags.`\n )\n );\n }\n // Columns, not characters. See {@link AuditThresholds}.\n const width = stringWidth(description.replaceAll(WHITESPACE, \" \"));\n if (width > descriptionMax || width < descriptionMin) {\n const direction = width > descriptionMax ? \"long\" : \"short\";\n found.push(\n finding(\n \"BLUME_AUDIT_DESCRIPTION_LENGTH\",\n pageSite(context, page, [\"description\"]),\n `Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax}).`\n )\n );\n }\n return found;\n};\n\nconst headingChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n const h1s = page.headings.filter((heading) => heading.depth === 1);\n if (h1s.length === 0) {\n return [\n finding(\n \"BLUME_AUDIT_H1_MISSING\",\n pageSite(context, page),\n \"Page has no <h1>.\"\n ),\n ];\n }\n if (h1s.length > 1) {\n return [\n finding(\n \"BLUME_AUDIT_H1_MULTIPLE\",\n pageSite(context, page),\n `Page has ${h1s.length} <h1> tags: ${h1s.map((h) => `\"${h.text}\"`).join(\", \")}.`\n ),\n ];\n }\n\n // A skipped level (h2 -> h4) breaks table-of-contents nesting and screen-\n // reader outlines. The first skip is the finding — one wrong heading early\n // in a page cascades, and listing every knock-on skip buries the fix.\n let previous: number | null = null;\n for (const heading of page.headings) {\n if (previous !== null && heading.depth > previous + 1) {\n return [\n finding(\n \"BLUME_AUDIT_HEADING_SKIP\",\n pageSite(context, page),\n `Headings jump from h${previous} to h${heading.depth} at \"${heading.text}\".`\n ),\n ];\n }\n previous = heading.depth;\n }\n return [];\n};\n\n/**\n * A page dated in the future usually means scheduled content that leaked into\n * the build early. Only the front matter knows — the rendered page looks\n * perfectly ordinary.\n */\nconst futureDateChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n const source = page.source && context.sources.get(page.source);\n if (!source) {\n return [];\n }\n const date = frontmatterDate(source);\n if (date && date.getTime() > Date.now()) {\n return [\n finding(\n \"BLUME_AUDIT_FUTURE_DATED_PAGE\",\n pageSite(context, page, [\"date\"]),\n `Page is dated ${date.toISOString().slice(0, 10)}, which is in the future.`\n ),\n ];\n }\n return [];\n};\n\n/**\n * Per-page head and body content: title, description, headings, word count,\n * viewport.\n *\n * Ahrefs reports each of these twice — once for indexable pages and once for\n * non-indexable ones. That's a crawler artifact: the finding is the same either\n * way, so it's reported once and the page's indexability rides along on the\n * snapshot.\n */\nexport const contentChecks: CheckModule = {\n category: \"content\",\n run(context) {\n const found: Diagnostic[] = [];\n for (const page of context.pages) {\n found.push(\n ...titleChecks(context, page),\n ...descriptionChecks(context, page),\n ...headingChecks(context, page),\n ...futureDateChecks(context, page)\n );\n\n if (page.wordCount < context.thresholds.minWordCount && page.indexable) {\n found.push(\n finding(\n \"BLUME_AUDIT_LOW_WORD_COUNT\",\n pageSite(context, page),\n `Page has ${page.wordCount} words of prose (excluding code blocks).`\n )\n );\n }\n\n if (!page.viewport) {\n found.push(\n finding(\n \"BLUME_AUDIT_VIEWPORT_MISSING\",\n pageSite(context, page),\n \"Page has no viewport <meta> — it will not render correctly on mobile.\"\n )\n );\n }\n }\n return found;\n },\n tier: \"static\",\n};\n",
|
|
11
|
-
"import type { BlumeProject } from \"../core/project-graph.ts\";\nimport type {\n Diagnostic,\n DiagnosticSeverity,\n RouteManifestEntry,\n} from \"../core/types.ts\";\n\n/**\n * What a check needs in order to run. Anything above `static` is opt-in, and a\n * skipped tier is reported rather than silently omitted — a crawler that\n * quietly doesn't check something is worse than one that says it didn't.\n */\nexport type AuditTier = \"static\" | \"network\" | \"external\";\n\nexport type AuditCategory =\n | \"content\"\n | \"duplicates\"\n | \"indexability\"\n | \"links\"\n | \"redirects\"\n | \"social\"\n | \"i18n\"\n | \"assets\"\n | \"sitemap\"\n | \"robots\"\n | \"structured-data\"\n | \"ai\"\n | \"network\";\n\n/** A check's static metadata. The catalog is the source of truth for all of it. */\nexport interface CheckMeta {\n readonly id: string;\n readonly category: AuditCategory;\n readonly severity: DiagnosticSeverity;\n /** Human title used as the report's group header, e.g. \"Title too long\". */\n readonly title: string;\n readonly tier: AuditTier;\n /** Default remediation, used as the finding's `suggestion`. */\n readonly fix?: string;\n}\n\n/** A `<link>`/`<a>` discovered in built HTML. */\nexport interface SnapshotLink {\n href: string;\n rel: string | null;\n text: string;\n /**\n * Whether the link sits in the page's prose (`<main>`/`<article>`) rather\n * than site chrome (nav/sidebar/header/footer). Load-bearing: Blume's sidebar\n * links every page from every page, so a link graph that can't tell the two\n * apart reports zero orphans, forever.\n */\n content: boolean;\n}\n\n/** An image/script/stylesheet referenced by a built page. */\nexport interface SnapshotAsset {\n src: string;\n alt?: string | null;\n width?: string | null;\n height?: string | null;\n /** Absolute path in the static dir, when the ref resolves to a local file. */\n file?: string;\n bytes?: number;\n}\n\n/** Everything one built HTML page contributes to the audit. */\nexport interface PageSnapshot {\n /** Absolute path of the built `.html`. */\n file: string;\n /** Site-root-relative URL, e.g. `/docs/getting-started`. */\n url: string;\n bytes: number;\n /** The manifest entry this page renders, when it maps to authored content. */\n route?: RouteManifestEntry;\n /** `route.sourcePath` — the `.mdx` a finding should point the user at. */\n source?: string;\n indexable: boolean;\n\n lang: string | null;\n /** Every `<title>`; more than one is itself a finding. */\n titles: string[];\n descriptions: string[];\n canonical: string | null;\n robots: string | null;\n viewport: string | null;\n metaRefresh: string | null;\n headings: { depth: number; text: string }[];\n og: Record<string, string>;\n twitter: Record<string, string>;\n hreflang: { lang: string; href: string }[];\n jsonld: unknown[];\n /** JSON-LD blocks that failed to parse, with the parser's message. */\n jsonldErrors: string[];\n links: SnapshotLink[];\n images: SnapshotAsset[];\n scripts: SnapshotAsset[];\n styles: SnapshotAsset[];\n wordCount: number;\n /** Hash of the normalized prose, for exact-duplicate detection. */\n contentHash: string;\n /** Every element `id` on the page — the targets `#fragment` links can hit. */\n ids: Set<string>;\n}\n\n/** A configured redirect resolved through to its final destination. */\nexport interface RedirectResolution {\n from: string;\n to: string;\n status: number;\n /** Every hop from `from` to the final target, inclusive. */\n chain: string[];\n outcome: \"ok\" | \"loop\" | \"broken\" | \"chain\";\n}\n\n/** A parsed `sitemap.xml`. */\nexport interface SitemapDoc {\n file: string;\n bytes: number;\n /** Absolute `<loc>` URLs, in document order. */\n urls: string[];\n /** Each `<url>` block's `<lastmod>`, keyed by its `<loc>`. */\n lastmod?: Map<string, string>;\n /** Parse failure, when the document isn't usable. */\n error?: string;\n}\n\n/** A parsed `llms.txt` index. */\nexport interface LlmsDoc {\n file: string;\n /** Markdown link targets in document order, with their 1-based line. */\n entries: { url: string; line: number }[];\n}\n\n/** A parsed `robots.txt`. */\nexport interface RobotsDoc {\n file: string;\n /**\n * The file's full text. Rule matching runs through robots-parser (which\n * owns longest-match Allow/Disallow semantics), so the raw text is the\n * source of truth rather than a pre-extracted rule list.\n */\n raw: string;\n /** `Sitemap:` declarations. */\n sitemaps: string[];\n /** Lines that aren't a recognized directive, with their 1-based line number. */\n invalid: { line: number; text: string }[];\n}\n\n/** Incoming/outgoing internal-link edges, split by where the link sits. */\nexport interface LinkGraph {\n /** url -> urls it links to from its prose. */\n contentOut: Map<string, Set<string>>;\n /** url -> urls whose prose links to it. */\n contentIn: Map<string, Set<string>>;\n /** url -> urls it links to from chrome (nav/sidebar/footer). */\n chromeOut: Map<string, Set<string>>;\n chromeIn: Map<string, Set<string>>;\n}\n\n/** Astro's reserved error routes. Never indexable, never crawlable — by design. */\nexport const ERROR_ROUTES: ReadonlySet<string> = new Set([\"/404\", \"/500\"]);\n\n/**\n * Tunable limits. Not yet configurable — CLI-only until the ids settle.\n *\n * The title and description limits are measured in **display columns**, where\n * a fullwidth or wide character counts 2 and everything else 1 — not in\n * characters. What a search engine truncates is the space the text takes up,\n * and a character count only stands in for that where every character is one\n * column wide, which is true of Latin text and of nothing else. Counted in\n * characters one range cannot serve both scripts: it is at once too strict for\n * a Japanese description (which says in ~60 characters what English needs ~120\n * for, and so reads as \"too short\") and too loose for a Japanese title (60\n * characters render as wide as 120 Latin ones, and truncate). ASCII and\n * precomposed (NFC) Latin text score identically either way; a decomposed\n * (NFD) combining mark counts 0 where a character count saw 1. East-Asian\n * \"ambiguous\"-width characters (★ ※ ①) count 1 per `string-width`'s default,\n * though CJK fonts render them fullwidth — resolving that would need the\n * page's language, which the audit does not thread through yet.\n */\nexport interface AuditThresholds {\n /** Display columns, not characters. */\n titleMin: number;\n /** Display columns, not characters. */\n titleMax: number;\n /** Display columns, not characters. */\n descriptionMin: number;\n /** Display columns, not characters. */\n descriptionMax: number;\n minWordCount: number;\n maxHtmlBytes: number;\n maxAssetBytes: number;\n maxRedirectHops: number;\n}\n\nexport const DEFAULT_THRESHOLDS: AuditThresholds = {\n // Ahrefs' guidance: 110–160. Under ~110 wastes the snippet space search\n // results give you; over ~160 gets truncated. Stated there in characters of\n // English, which is the same number of columns.\n descriptionMax: 160,\n descriptionMin: 110,\n maxAssetBytes: 500 * 1024,\n // Googlebot stops reading an HTML document at 2 MB.\n maxHtmlBytes: 2 * 1024 * 1024,\n maxRedirectHops: 3,\n minWordCount: 50,\n titleMax: 60,\n titleMin: 10,\n};\n\n/** Everything the check modules read. Assembled once per run. */\nexport interface AuditContext {\n project: BlumeProject;\n staticDir: string;\n /** Origin passed via `--url`, for the network tier. */\n origin: string | null;\n pages: PageSnapshot[];\n byUrl: Map<string, PageSnapshot>;\n /** Every file in the static dir: URL path -> size in bytes. */\n files: Map<string, number>;\n /**\n * Raw text of every page's source file, keyed by absolute path. Read once so\n * findings can be anchored to the exact front matter line that fixes them.\n */\n sources: Map<string, string>;\n graph: LinkGraph;\n redirects: RedirectResolution[];\n sitemap: SitemapDoc | null;\n robots: RobotsDoc | null;\n llms: LlmsDoc | null;\n thresholds: AuditThresholds;\n}\n\n/** One category's checks. Modules, not per-check closures — see catalog.ts. */\nexport interface CheckModule {\n readonly category: AuditCategory;\n readonly tier: AuditTier;\n readonly run: (context: AuditContext) => Diagnostic[] | Promise<Diagnostic[]>;\n}\n",
|
|
12
|
-
"import type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport type { CheckModule } from \"../types.ts\";\n\n/**\n * DNS-AID (draft-mozleywilliams-dnsop-dnsaid): agents discover a site's AI\n * surface by querying ServiceMode SVCB/HTTPS records under the `_agents` DNS\n * namespace — the well-known entrypoint is `_index._agents.<host>`. The\n * records live in the DNS zone, which no build artifact can publish, so this\n * check probes the live DNS over DoH and reports what to add. It queries the\n * `deployment.site` host (the domain agents actually probe), not the `--url`\n * origin, which is often a localhost preview.\n */\n\n/** SVCB and its HTTPS specialization (RFC 9460). */\nconst RR_SVCB = 64;\nconst RR_HTTPS = 65;\n\nconst DOH_TIMEOUT_MS = 5000;\n\n/**\n * DoH JSON resolvers, tried in order until one answers. Both accept the same\n * `?name=&type=` query and the `application/dns-json` accept header (Google\n * ignores it, Cloudflare requires it). `BLUME_DOH_URL` overrides the list for\n * networks that block the public resolvers (and for tests, which must never\n * touch the real DNS).\n */\nconst DOH_RESOLVERS = [\n \"https://dns.google/resolve\",\n \"https://cloudflare-dns.com/dns-query\",\n];\n\nconst resolvers = (): string[] => {\n const override = process.env.BLUME_DOH_URL;\n return override ? [override] : DOH_RESOLVERS;\n};\n\n/** The subset of the DoH JSON response this check reads (RFC 8427-shaped). */\ninterface DohResponse {\n /** DNSSEC-validated by the resolver (the AD flag). */\n AD?: boolean;\n Answer?: { data?: string; type?: number }[];\n Status?: number;\n}\n\nexport interface DnsAidResult {\n /** Whether every record set found came back DNSSEC-authenticated. */\n authenticated: boolean;\n /** Whether any SVCB/HTTPS record exists at the well-known entrypoint. */\n found: boolean;\n}\n\n/** One resolver's answer, or null when it's unreachable or not DoH-JSON. */\nconst fetchDohJson = async (\n base: string,\n name: string,\n type: number,\n fetchFn: typeof fetch\n): Promise<DohResponse | null> => {\n try {\n const response = await fetchFn(\n `${base}?name=${encodeURIComponent(name)}&type=${type}`,\n {\n headers: { accept: \"application/dns-json\" },\n signal: AbortSignal.timeout(DOH_TIMEOUT_MS),\n }\n );\n if (!response.ok) {\n return null;\n }\n // SAFETY: the resolver answered a dns-json query, whose response schema\n // DohResponse models with every field optional; a malformed body that\n // fails to parse rejects into the catch below.\n return (await response.json()) as DohResponse;\n } catch {\n return null;\n }\n};\n\nconst queryDoh = async (\n name: string,\n type: number,\n fetchFn: typeof fetch\n): Promise<DohResponse | null> => {\n for (const base of resolvers()) {\n // oxlint-disable-next-line no-await-in-loop -- fallback chain: the next resolver is only tried when this one fails\n const response = await fetchDohJson(base, name, type, fetchFn);\n if (response) {\n return response;\n }\n }\n return null;\n};\n\n/**\n * Query the DNS-AID entrypoint for a host over DoH: both SVCB and HTTPS\n * rrtypes at `_index._agents.<host>` (the checker-facing convention accepts\n * either). Returns null when no resolver answered at all — \"couldn't check\"\n * must stay distinct from \"checked and absent\", or a flaky network would\n * report every site as undiscoverable.\n */\nexport const lookupDnsAid = async (\n host: string,\n fetchFn: typeof fetch = fetch\n): Promise<DnsAidResult | null> => {\n const name = `_index._agents.${host}`;\n let answered = false;\n let found = false;\n let authenticated = true;\n const responses = await Promise.all(\n [RR_HTTPS, RR_SVCB].map((type) => queryDoh(name, type, fetchFn))\n );\n for (const response of responses) {\n if (!response) {\n continue;\n }\n answered = true;\n const records = (response.Answer ?? []).filter(\n (answer) => answer.type === RR_SVCB || answer.type === RR_HTTPS\n );\n if (records.length > 0) {\n found = true;\n authenticated &&= response.AD === true;\n }\n }\n return answered ? { authenticated, found } : null;\n};\n\n/** The findings for a lookup result — pure, so it's testable without DoH. */\nexport const dnsAidFindings = (\n host: string,\n result: DnsAidResult\n): Diagnostic[] => {\n if (!result.found) {\n return [\n finding(\n \"BLUME_AUDIT_DNS_AID_MISSING\",\n { url: \"/\" },\n `No SVCB or HTTPS records at _index._agents.${host} — agents probing DNS for AI Discovery (DNS-AID) find nothing.`,\n `Publish a ServiceMode record with your DNS provider, e.g. \\`_index._agents.${host}. 3600 IN HTTPS 1 ${host}. alpn=h2\\`, and sign the zone with DNSSEC if the provider supports it.`\n ),\n ];\n }\n if (!result.authenticated) {\n return [\n finding(\n \"BLUME_AUDIT_DNS_AID_UNSIGNED\",\n { url: \"/\" },\n `The DNS-AID records at _index._agents.${host} exist but are not DNSSEC-authenticated, so validating resolvers can't prove they're genuine.`\n ),\n ];\n }\n return [];\n};\n\n/** The `deployment.site` hostname, or null when unset/unusable for DNS. */\nexport const dnsAidHost = (site?: string): string | null => {\n if (!site) {\n return null;\n }\n let hostname: string;\n try {\n ({ hostname } = new URL(site));\n } catch {\n return null;\n }\n // A local or dotless host has no public zone to query.\n if (\n hostname === \"localhost\" ||\n hostname.endsWith(\".local\") ||\n !hostname.includes(\".\")\n ) {\n return null;\n }\n return hostname;\n};\n\nexport const dnsAidChecks: CheckModule = {\n category: \"ai\",\n async run(context) {\n if (!context.origin) {\n return [];\n }\n const host = dnsAidHost(context.project.config.deployment.options.site);\n if (!host) {\n return [];\n }\n const result = await lookupDnsAid(host);\n // No resolver answered: report nothing rather than guess.\n return result ? dnsAidFindings(host, result) : [];\n },\n tier: \"network\",\n};\n",
|
|
13
|
-
"import { normalizeBasePath, stripBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport type { CheckId } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { AuditContext, CheckModule, PageSnapshot } from \"../types.ts\";\nimport { decodePath } from \"../url.ts\";\n\nconst isNonCanonical = (page: PageSnapshot, deployBase: string): boolean => {\n if (!page.canonical) {\n return false;\n }\n try {\n // Canonicals are emitted as `site + base + route`; page URLs carry no\n // deployment base — without stripping it, every page of a subpath\n // deployment would look non-canonical and escape these checks entirely.\n return (\n stripBasePath(\n deployBase,\n decodePath(new URL(page.canonical).pathname)\n ).replace(/\\/$/u, \"\") !== page.url.replace(/\\/$/u, \"\")\n );\n } catch {\n return false;\n }\n};\n\n/** Pages that can meaningfully be compared against each other for duplication. */\nconst comparable = (context: AuditContext): PageSnapshot[] => {\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n return context.pages.filter(\n (page) =>\n page.indexable &&\n // A fallback page renders the default locale's content at a localized URL.\n // It is *supposed* to be an exact copy, so comparing it would make every\n // i18n site with an untranslated page a wall of false positives.\n !page.route?.fallback &&\n // A page that points its canonical elsewhere has already declared itself a\n // duplicate; that's the mechanism working, not a finding.\n !isNonCanonical(page, deployBase)\n );\n};\n\n/**\n * Group pages by a value and report every group with more than one member.\n * Translations are kept apart by keying on locale as well as the value — two\n * pages titled \"Overview\" in different languages are not duplicates.\n */\nconst reportGroups = (\n context: AuditContext,\n pages: PageSnapshot[],\n id: CheckId,\n key: (page: PageSnapshot) => string | undefined,\n describe: (value: string, others: PageSnapshot[]) => string,\n frontmatterKey?: readonly string[]\n): Diagnostic[] => {\n const groups = new Map<string, PageSnapshot[]>();\n for (const page of pages) {\n const value = key(page);\n if (!value) {\n continue;\n }\n const groupKey = `${page.route?.locale ?? \"\"}\\u0000${value}`;\n const group = groups.get(groupKey);\n if (group) {\n group.push(page);\n } else {\n groups.set(groupKey, [page]);\n }\n }\n\n const found: Diagnostic[] = [];\n for (const group of groups.values()) {\n if (group.length < 2) {\n continue;\n }\n for (const page of group) {\n const others = group.filter((other) => other !== page);\n found.push(\n finding(\n id,\n pageSite(context, page, frontmatterKey),\n describe(key(page) ?? \"\", others)\n )\n );\n }\n }\n return found;\n};\n\nconst list = (pages: PageSnapshot[]): string => {\n const shown = pages.slice(0, 3).map((page) => page.url);\n const rest = pages.length - shown.length;\n return rest > 0 ? `${shown.join(\", \")} and ${rest} more` : shown.join(\", \");\n};\n\n/**\n * Pages that collide on title, description, or body text.\n *\n * Ahrefs only reports \"duplicate pages without canonical\". Duplicate *titles*\n * and *descriptions* are the ones that actually bite a docs site — five pages\n * called \"Overview\" all compete for the same result headline — so they're\n * reported too.\n */\nexport const duplicateChecks: CheckModule = {\n category: \"duplicates\",\n run(context) {\n const pages = comparable(context);\n return [\n ...reportGroups(\n context,\n pages,\n \"BLUME_AUDIT_DUPLICATE_TITLE\",\n (page) => page.titles[0],\n (value, others) => `Title \"${value}\" is also used by ${list(others)}.`,\n [\"title\"]\n ),\n ...reportGroups(\n context,\n pages,\n \"BLUME_AUDIT_DUPLICATE_DESCRIPTION\",\n (page) => page.descriptions[0],\n (_value, others) => `Meta description is identical to ${list(others)}.`,\n [\"description\"]\n ),\n ...reportGroups(\n context,\n // An empty page (no prose at all) hashes the same as every other empty\n // page; that's a content problem LOW_WORD_COUNT already reports, not a\n // duplication one.\n pages.filter((page) => page.wordCount > 0),\n \"BLUME_AUDIT_DUPLICATE_CONTENT\",\n (page) => page.contentHash,\n (_value, others) =>\n `Page content is byte-identical to ${list(others)}, and none declares a canonical.`\n ),\n ];\n },\n tier: \"static\",\n};\n",
|
|
14
|
-
"import { normalizeBasePath, stripBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { AuditContext, CheckModule, PageSnapshot } from \"../types.ts\";\nimport { normalizePath } from \"../url.ts\";\n\n/** The normalized `deployment.base` — hreflang hrefs carry it, page URLs don't. */\nconst deployBaseOf = (context: AuditContext): string =>\n normalizeBasePath(context.project.config.deployment.options.base);\n\n/** The hreflang value meaning \"use this when nothing else matches\". Not a locale. */\nconst X_DEFAULT = \"x-default\";\n\nconst isValidBcp47 = (tag: string): boolean => {\n try {\n Intl.getCanonicalLocales(tag);\n return true;\n } catch {\n return false;\n }\n};\n\n/** The site path an hreflang `href` points at, or null when it isn't parseable. */\nconst alternatePath = (href: string, deployBase: string): string | null => {\n try {\n return normalizePath(stripBasePath(deployBase, new URL(href).pathname));\n } catch {\n return null;\n }\n};\n\nconst langChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n if (!page.lang) {\n return [\n finding(\n \"BLUME_AUDIT_HTML_LANG_MISSING\",\n pageSite(context, page),\n \"The <html> element has no lang attribute.\"\n ),\n ];\n }\n if (!isValidBcp47(page.lang)) {\n return [\n finding(\n \"BLUME_AUDIT_HTML_LANG_INVALID\",\n pageSite(context, page),\n `\"${page.lang}\" is not a valid BCP 47 language tag.`\n ),\n ];\n }\n return [];\n};\n\n/**\n * Every hreflang finding for one page: validity, self-reference, x-default, and\n * whether each alternate points at a real, canonical page.\n */\nconst hreflangChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n const found: Diagnostic[] = [];\n const seen = new Map<string, string[]>();\n const deployBase = deployBaseOf(context);\n\n for (const alternate of page.hreflang) {\n if (alternate.lang !== X_DEFAULT && !isValidBcp47(alternate.lang)) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_INVALID\",\n pageSite(context, page),\n `hreflang=\"${alternate.lang}\" is not a valid BCP 47 language tag.`\n )\n );\n continue;\n }\n\n const hrefs = seen.get(alternate.lang) ?? [];\n hrefs.push(alternate.href);\n seen.set(alternate.lang, hrefs);\n\n const path = alternatePath(alternate.href, deployBase);\n if (path === null) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_BAD_TARGET\",\n pageSite(context, page),\n `hreflang=\"${alternate.lang}\" points at \"${alternate.href}\", which is not an absolute URL.`\n )\n );\n continue;\n }\n\n const target = context.byUrl.get(path);\n const redirect = context.redirects.find(\n (entry) => normalizePath(entry.from) === path\n );\n if (redirect) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_BAD_TARGET\",\n pageSite(context, page),\n `hreflang=\"${alternate.lang}\" points at ${path}, which redirects to ${redirect.to}.`\n )\n );\n } else if (!target) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_BAD_TARGET\",\n pageSite(context, page),\n `hreflang=\"${alternate.lang}\" points at ${path}, which the build does not serve.`\n )\n );\n } else if (\n target.canonical &&\n alternatePath(target.canonical, deployBase) !== normalizePath(target.url)\n ) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_BAD_TARGET\",\n pageSite(context, page),\n `hreflang=\"${alternate.lang}\" points at ${path}, which canonicalizes elsewhere.`\n )\n );\n }\n }\n\n // One language may name exactly one page.\n for (const [lang, hrefs] of seen) {\n if (hrefs.length > 1) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_CONFLICT\",\n pageSite(context, page),\n `hreflang=\"${lang}\" is declared ${hrefs.length} times, pointing at ${hrefs.join(\", \")}.`\n )\n );\n }\n }\n\n const self = page.hreflang.find(\n (alternate) =>\n alternatePath(alternate.href, deployBase) === normalizePath(page.url)\n );\n if (self) {\n if (page.lang && self.lang !== X_DEFAULT && self.lang !== page.lang) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_LANG_MISMATCH\",\n pageSite(context, page),\n `Page declares <html lang=\"${page.lang}\"> but its own hreflang is \"${self.lang}\".`\n )\n );\n }\n } else {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_SELF_MISSING\",\n pageSite(context, page),\n \"Page's hreflang set does not include a self-reference.\"\n )\n );\n }\n\n if (!page.hreflang.some((alternate) => alternate.lang === X_DEFAULT)) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_XDEFAULT_MISSING\",\n pageSite(context, page),\n \"Page's hreflang set has no x-default alternate.\"\n )\n );\n }\n\n return found;\n};\n\n/**\n * Reciprocity: if A names B as its alternate, B must name A back. Google ignores\n * a whole hreflang group when the return tags don't line up.\n *\n * This is the hardest check for a real crawler — it has to hold the entire site\n * in memory and cross-reference it. We already do.\n */\nconst returnTagChecks = (context: AuditContext): Diagnostic[] => {\n const found: Diagnostic[] = [];\n const deployBase = deployBaseOf(context);\n for (const page of context.pages) {\n for (const alternate of page.hreflang) {\n if (alternate.lang === X_DEFAULT) {\n continue;\n }\n const path = alternatePath(alternate.href, deployBase);\n if (path === null || path === normalizePath(page.url)) {\n continue;\n }\n const target = context.byUrl.get(path);\n if (!target || target.hreflang.length === 0) {\n // A missing or hreflang-less target is already reported as a bad target.\n continue;\n }\n const returns = target.hreflang.some(\n (back) =>\n alternatePath(back.href, deployBase) === normalizePath(page.url)\n );\n if (!returns) {\n found.push(\n finding(\n \"BLUME_AUDIT_HREFLANG_NO_RETURN_TAG\",\n pageSite(context, page),\n `Page names ${path} as its \"${alternate.lang}\" alternate, but ${path} does not name it back.`\n )\n );\n }\n }\n }\n return found;\n};\n\n/**\n * Language declarations: `<html lang>` on every page, plus the full hreflang\n * cluster on pages that have translations.\n *\n * The hreflang checks are gated on the page actually carrying hreflang tags\n * rather than on `config.i18n`, so a monolingual site sees exactly zero of them\n * — but still gets its `<html lang>` validated, which is a real bug on any site.\n */\nexport const i18nChecks: CheckModule = {\n category: \"i18n\",\n run(context) {\n const found: Diagnostic[] = [];\n for (const page of context.pages) {\n found.push(...langChecks(context, page));\n if (page.hreflang.length > 0) {\n found.push(...hreflangChecks(context, page));\n }\n }\n found.push(...returnTagChecks(context));\n return found;\n },\n tier: \"static\",\n};\n",
|
|
15
|
-
"import { normalizeBasePath, stripBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { deployPlatform } from \"../../deploy/platforms/index.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport { ERROR_ROUTES } from \"../types.ts\";\nimport type { AuditContext, CheckModule, PageSnapshot } from \"../types.ts\";\nimport { decodePath, normalizePath, siteOrigin } from \"../url.ts\";\n\n/** The canonical URL parsed, or null when it isn't a usable absolute URL. */\nconst parseCanonical = (canonical: string): URL | null => {\n try {\n return new URL(canonical);\n } catch {\n return null;\n }\n};\n\nconst canonicalChecks = (\n context: AuditContext,\n page: PageSnapshot\n): Diagnostic[] => {\n const { site } = context.project.config.deployment.options;\n const origin = siteOrigin(site);\n\n // Error routes are noindex by design, so a canonical on them is meaningless\n // and its absence isn't a defect — flagging /404 would be a guaranteed\n // finding on every site that has an error page.\n if (ERROR_ROUTES.has(page.url)) {\n return [];\n }\n\n if (!page.canonical) {\n // Without `deployment.site` Blume has no absolute URL to canonicalize to, so\n // report the root cause once (in the sitemap/robots checks) rather than\n // flagging every page for a config field they can't fix individually.\n return site\n ? [\n finding(\n \"BLUME_AUDIT_CANONICAL_MISSING\",\n pageSite(context, page),\n \"Page has no canonical URL.\"\n ),\n ]\n : [];\n }\n\n const canonical = parseCanonical(page.canonical);\n if (!canonical) {\n return [\n finding(\n \"BLUME_AUDIT_CANONICAL_BAD_TARGET\",\n pageSite(context, page, [\"seo\", \"canonical\"]),\n `Canonical \"${page.canonical}\" is not a valid absolute URL.`\n ),\n ];\n }\n\n const found: Diagnostic[] = [];\n if (origin && canonical.origin !== origin) {\n const sameHost = canonical.host === new URL(origin).host;\n // A different *protocol* on the same host is a misconfiguration worth\n // calling out; a different host entirely is a deliberate cross-site\n // canonical, which is legitimate.\n if (sameHost) {\n found.push(\n finding(\n \"BLUME_AUDIT_CANONICAL_PROTOCOL_MISMATCH\",\n pageSite(context, page, [\"seo\", \"canonical\"]),\n `Canonical uses ${canonical.protocol}// but the site is ${new URL(origin).protocol}//.`\n )\n );\n }\n return found;\n }\n\n // Canonicals are emitted as `site + base + route`; page URLs and `byUrl`\n // keys carry no deployment base, so strip it (and percent-encoding) before\n // comparing.\n const target = normalizePath(\n stripBasePath(\n normalizeBasePath(context.project.config.deployment.options.base),\n decodePath(canonical.pathname)\n )\n );\n if (target === normalizePath(page.url)) {\n return found;\n }\n\n // The canonical points at another page on this site. It must exist, and it\n // must not itself redirect — a canonical to a redirect is a dead end.\n const redirect = context.redirects.find(\n (entry) => normalizePath(entry.from) === target\n );\n if (redirect) {\n found.push(\n finding(\n \"BLUME_AUDIT_CANONICAL_BAD_TARGET\",\n pageSite(context, page, [\"seo\", \"canonical\"]),\n `Canonical points at ${target}, which is a redirect to ${redirect.to}.`\n )\n );\n } else if (context.byUrl.has(target)) {\n // An archived-version page pointing at its latest equivalent is Blume's\n // own default (versions.archived[].canonical: \"latest\"), not a defect —\n // the live page is the authoritative one by design.\n const version = page.route?.version;\n const archived = version\n ? context.project.config.versions?.archived.find(\n (entry) => entry.id === version\n )\n : undefined;\n const latestAlternate = page.route?.versionAlternates.find(\n (alternate) => alternate.version === \"\"\n );\n const intentional =\n archived?.canonical === \"latest\" &&\n latestAlternate !== undefined &&\n normalizePath(latestAlternate.path) === target;\n if (!intentional) {\n found.push(\n finding(\n \"BLUME_AUDIT_CANONICAL_NOT_SELF\",\n pageSite(context, page, [\"seo\", \"canonical\"]),\n `Page declares ${target} as its canonical, so it will not be indexed itself.`\n )\n );\n }\n } else {\n found.push(\n finding(\n \"BLUME_AUDIT_CANONICAL_BAD_TARGET\",\n pageSite(context, page, [\"seo\", \"canonical\"]),\n `Canonical points at ${target}, which is not a page on this site.`\n )\n );\n }\n return found;\n};\n\n/**\n * Whether and how a page can be indexed: its canonical, its robots meta, and\n * whether Googlebot will read all of it.\n */\nexport const indexabilityChecks: CheckModule = {\n category: \"indexability\",\n run(context) {\n const found: Diagnostic[] = [];\n\n // Without `deployment.site` Blume has no absolute URL to build from, so it\n // cannot emit a canonical, an Open Graph image, or a sitemap on *any* page.\n // That's one fact about the config, not a defect on each of 200 pages —\n // report it once, and let the checks that depend on it stay quiet.\n if (!context.project.config.deployment.options.site) {\n const { deployment } = context.project.config;\n const site = {\n file: context.project.context.configFile ?? undefined,\n url: \"/\",\n };\n // On a platform adapter the value arrives from the platform's env vars\n // at deploy time (`applyDeploymentEnv`), so only this local artifact is\n // missing it — the deployed site won't be. Hardcoding `deployment.site`\n // would duplicate state the platform owns, so the finding (which agents\n // apply verbatim via `--claude`/`--codex`) must not suggest it.\n found.push(\n deployPlatform(deployment).env\n ? finding(\n \"BLUME_AUDIT_SITE_INFERRED_AT_DEPLOY\",\n site,\n `deployment.site is not set in this build — on ${deployment.kind} it is inferred from the platform env at deploy time, so canonical URLs, Open Graph images, and the sitemap are only missing from this local artifact.`\n )\n : finding(\n \"BLUME_AUDIT_SITE_NOT_SET\",\n site,\n \"deployment.site is not set, so no canonical URLs, Open Graph images, or sitemap can be generated.\"\n )\n );\n }\n\n for (const page of context.pages) {\n found.push(...canonicalChecks(context, page));\n\n // Error routes are *meant* to carry noindex, so reporting them would be a\n // guaranteed finding on every site that has a 404 page. A robots meta\n // that doesn't noindex (an ejected layout emitting `index, follow`)\n // leaves the page indexable and isn't this finding.\n if (page.robots && !page.indexable && !ERROR_ROUTES.has(page.url)) {\n found.push(\n finding(\n \"BLUME_AUDIT_ROBOTS_META_UNEXPECTED\",\n pageSite(context, page, [\"noindex\"]),\n `Page declares robots \"${page.robots}\" and will not be indexed.`\n )\n );\n\n // Google warns against pairing the two: the canonical says \"index that\n // URL\", the robots meta says \"don't trust this page's signals\" — and\n // which one wins is undefined.\n if (!page.indexable && page.canonical) {\n found.push(\n finding(\n \"BLUME_AUDIT_CANONICAL_ON_NOINDEX\",\n pageSite(context, page, [\"noindex\"]),\n `Page is noindex but declares ${page.canonical} as its canonical.`\n )\n );\n }\n }\n\n // Only the manifest knows a page was a draft — the built HTML looks like\n // any other page, which is exactly why deploying a `--preview` build is\n // so easy to miss.\n if (page.route?.draft) {\n found.push(\n finding(\n \"BLUME_AUDIT_DRAFT_PAGE_PUBLISHED\",\n pageSite(context, page, [\"draft\"]),\n `${page.url} is marked draft in its front matter but is in the build.`\n )\n );\n }\n\n if (page.bytes > context.thresholds.maxHtmlBytes) {\n const mb = (page.bytes / 1024 / 1024).toFixed(1);\n found.push(\n finding(\n \"BLUME_AUDIT_HTML_TOO_LARGE\",\n pageSite(context, page),\n `Page HTML is ${mb} MB — past Googlebot's 2 MB limit, the rest is not crawled.`\n )\n );\n }\n }\n return found;\n },\n tier: \"static\",\n};\n",
|
|
16
|
-
"import type { LinkGraph, PageSnapshot } from \"./types.ts\";\nimport { resolveHref } from \"./url.ts\";\n\nconst add = (\n map: Map<string, Set<string>>,\n key: string,\n value: string\n): void => {\n const existing = map.get(key);\n if (existing) {\n existing.add(value);\n } else {\n map.set(key, new Set([value]));\n }\n};\n\n/**\n * Build the internal link graph, keeping prose links and chrome links apart.\n *\n * The split is the whole point. Blume's sidebar links every navigable page from\n * every page, so a graph that lumps the two together concludes that every page\n * has hundreds of inbound links — which makes \"orphan page\" unfireable and makes\n * a single broken sidebar link look like N broken links, one per page.\n */\nexport const buildGraph = (\n pages: PageSnapshot[],\n origin: string | null,\n deployBase = \"\"\n): LinkGraph => {\n const graph: LinkGraph = {\n chromeIn: new Map(),\n chromeOut: new Map(),\n contentIn: new Map(),\n contentOut: new Map(),\n };\n\n for (const page of pages) {\n for (const link of page.links) {\n const resolved = resolveHref(page.url, link.href, origin, deployBase);\n if (resolved.kind !== \"internal\" && resolved.kind !== \"self-origin\") {\n continue;\n }\n const out = link.content ? graph.contentOut : graph.chromeOut;\n const incoming = link.content ? graph.contentIn : graph.chromeIn;\n add(out, page.url, resolved.path);\n add(incoming, resolved.path, page.url);\n }\n }\n\n return graph;\n};\n\n/**\n * Pages nothing links to from prose — reachable only through the sidebar.\n *\n * This is what an SEO means by \"orphan\": the page ships, but no other page's\n * body ever points a reader (or a crawler following editorial links) at it.\n * Non-indexable pages are excluded — a `noindex` page is *meant* to be\n * unreachable — as is the home page, which is nobody's job to link to. Under a\n * `basePath` the home page's built URL is the base itself, so the caller names\n * it via `homeUrl`.\n */\nexport const orphanPages = (\n pages: PageSnapshot[],\n graph: LinkGraph,\n homeUrl = \"/\"\n): PageSnapshot[] =>\n pages.filter(\n (page) =>\n page.indexable &&\n page.url !== \"/\" &&\n page.url !== homeUrl &&\n (graph.contentIn.get(page.url)?.size ?? 0) === 0\n );\n",
|
|
17
|
-
"import { normalizeBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { orphanPages } from \"../graph.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type {\n AuditContext,\n CheckModule,\n PageSnapshot,\n SnapshotLink,\n} from \"../types.ts\";\nimport { isServed, normalizePath, resolveHref, siteOrigin } from \"../url.ts\";\n\n/** Browser-magic fragments that scroll without needing a matching id. */\nconst MAGIC_FRAGMENTS = new Set([\"\", \"top\"]);\n\n/** Whether a fragment lands on an id of the target page. */\nconst anchorResolves = (target: PageSnapshot, fragment: string): boolean => {\n if (MAGIC_FRAGMENTS.has(fragment)) {\n return true;\n }\n // Hrefs usually carry the fragment percent-encoded while the HTML id is not;\n // check both spellings so an encoded match isn't reported as broken.\n try {\n return (\n target.ids.has(fragment) || target.ids.has(decodeURIComponent(fragment))\n );\n } catch {\n return target.ids.has(fragment);\n }\n};\n\nconst redirectFrom = (context: AuditContext, path: string) =>\n context.redirects.find((entry) => normalizePath(entry.from) === path);\n\n/**\n * Internal links, and what they land on.\n *\n * Broken *chrome* links are deduplicated by target: Blume renders the sidebar on\n * every page, so one bad nav entry would otherwise be reported once per page —\n * hundreds of findings for a single typo. Body links are reported per\n * occurrence, because each one lives in a different `.mdx` a reader can go fix.\n */\nexport const linkChecks: CheckModule = {\n category: \"links\",\n run(context) {\n const found: Diagnostic[] = [];\n const origin = siteOrigin(context.project.config.deployment.options.site);\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n /** Broken chrome targets, and the first page each was seen on. */\n const brokenChrome = new Map<string, PageSnapshot>();\n const redirectedChrome = new Map<string, PageSnapshot>();\n\n /** Broken anchor targets in chrome (`url#frag`), first page each was seen on. */\n const brokenChromeAnchors = new Map<string, PageSnapshot>();\n\n /**\n * A fragment must land on an id of the page it targets — a miss loads the\n * page but silently dumps the reader at the top, which no crawler reports\n * because the HTTP response is a healthy 200.\n */\n const checkAnchor = (\n page: PageSnapshot,\n link: SnapshotLink,\n target: PageSnapshot,\n fragment: string\n ): void => {\n if (anchorResolves(target, fragment)) {\n return;\n }\n const where =\n target === page ? \"this page\" : `${target.url}, which has no such id`;\n if (link.content) {\n found.push(\n finding(\n \"BLUME_AUDIT_ANCHOR_BROKEN\",\n pageSite(context, page),\n `Link to ${link.href} points at #${fragment} on ${where}.`\n )\n );\n } else {\n const key = `${target.url}#${fragment}`;\n if (!brokenChromeAnchors.has(key)) {\n brokenChromeAnchors.set(key, page);\n }\n }\n };\n\n const classify = (page: PageSnapshot, link: SnapshotLink): void => {\n // Same-page anchors (`#setup`) never leave the page, so resolveHref\n // files them under \"ignored\" — but their fragment still has to exist.\n if (link.href.startsWith(\"#\")) {\n checkAnchor(page, link, page, link.href.slice(1));\n return;\n }\n\n const resolved = resolveHref(page.url, link.href, origin, deployBase);\n if (resolved.kind === \"external\" || resolved.kind === \"ignored\") {\n return;\n }\n\n const anchorTarget = resolved.hash\n ? context.byUrl.get(resolved.path)\n : undefined;\n if (anchorTarget) {\n checkAnchor(page, link, anchorTarget, resolved.hash);\n }\n\n if (resolved.kind === \"self-origin\") {\n found.push(\n finding(\n \"BLUME_AUDIT_INTERNAL_LINK_ABSOLUTE\",\n pageSite(context, page),\n `Link to ${link.href} hardcodes the site's origin; use ${resolved.path} instead.`\n )\n );\n }\n\n if (link.rel?.includes(\"nofollow\")) {\n found.push(\n finding(\n \"BLUME_AUDIT_INTERNAL_LINK_NOFOLLOW\",\n pageSite(context, page),\n `Internal link to ${resolved.path} is rel=\"nofollow\".`\n )\n );\n }\n\n const { path } = resolved;\n const redirect = redirectFrom(context, path);\n if (redirect) {\n if (link.content) {\n found.push(\n finding(\n \"BLUME_AUDIT_LINK_TO_REDIRECT\",\n pageSite(context, page),\n `Link to ${path} goes through a redirect to ${redirect.to}.`\n )\n );\n } else if (!redirectedChrome.has(path)) {\n redirectedChrome.set(path, page);\n }\n return;\n }\n\n if (isServed(context, path)) {\n return;\n }\n\n if (link.content) {\n found.push(\n finding(\n \"BLUME_AUDIT_LINK_TO_BROKEN\",\n pageSite(context, page),\n `Link to ${link.href} resolves to ${path}, which the build does not serve.`\n )\n );\n } else if (!brokenChrome.has(path)) {\n brokenChrome.set(path, page);\n }\n };\n\n for (const page of context.pages) {\n for (const link of page.links) {\n classify(page, link);\n }\n }\n\n for (const [path, page] of brokenChrome) {\n found.push(\n finding(\n \"BLUME_AUDIT_LINK_TO_BROKEN\",\n { url: page.url },\n `Navigation links to ${path}, which the build does not serve.`,\n \"Fix the entry in your navigation config or meta file.\"\n )\n );\n }\n for (const [path, page] of redirectedChrome) {\n const redirect = redirectFrom(context, path);\n found.push(\n finding(\n \"BLUME_AUDIT_LINK_TO_REDIRECT\",\n { url: page.url },\n `Navigation links to ${path}, which redirects to ${redirect?.to}.`,\n \"Point the navigation entry straight at the destination.\"\n )\n );\n }\n for (const [key, page] of brokenChromeAnchors) {\n found.push(\n finding(\n \"BLUME_AUDIT_ANCHOR_BROKEN\",\n { url: page.url },\n `Navigation links to ${key}, but the target page has no such id.`\n )\n );\n }\n\n const homeUrl = normalizeBasePath(context.project.config.basePath) || \"/\";\n for (const page of orphanPages(context.pages, context.graph, homeUrl)) {\n found.push(\n finding(\n \"BLUME_AUDIT_ORPHAN_PAGE\",\n pageSite(context, page),\n \"No other page's body links here — it is reachable only from the sidebar.\"\n )\n );\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
18
|
-
"import { normalizeBasePath, stripBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport { ERROR_ROUTES } from \"../types.ts\";\nimport type { AuditContext, CheckModule } from \"../types.ts\";\nimport { isServed, normalizePath, siteOrigin } from \"../url.ts\";\n\n/** The object form of `agents.llmsTxt`. The schema always emits it, but hand-built\n * audit contexts (tests, partial configs) may still carry the raw boolean. */\nconst isLlmsToggleObject = (\n value: boolean | { enabled: boolean; openapi: boolean } | undefined\n): value is { enabled: boolean; openapi: boolean } =>\n typeof value === \"object\" && value !== null;\n\n/** The `agents.llmsTxt` config normalized to what the checks need. */\nconst llmsConfig = (context: AuditContext) => {\n const value = context.project.config.agents?.llmsTxt;\n if (isLlmsToggleObject(value)) {\n return { enabled: value.enabled, openapi: value.openapi };\n }\n return { enabled: value !== false, openapi: true };\n};\n\n/**\n * An llms.txt link target reduced to a site path, or null when it's off-site.\n * Entry URLs carry the deployment base; page URLs (from the file tree) don't.\n */\nconst entryPath = (\n url: string,\n origin: string | null,\n deployBase: string\n): string | null => {\n if (/^https?:\\/\\//iu.test(url)) {\n try {\n const parsed = new URL(url);\n if (origin && parsed.origin !== origin) {\n return null;\n }\n return normalizePath(\n stripBasePath(deployBase, decodeURI(parsed.pathname))\n );\n } catch {\n return null;\n }\n }\n if (!url.startsWith(\"/\")) {\n return null;\n }\n try {\n return normalizePath(stripBasePath(deployBase, decodeURI(url)));\n } catch {\n return normalizePath(stripBasePath(deployBase, url));\n }\n};\n\n/**\n * The `llms.txt` index, held to the sitemap's standard.\n *\n * SEO crawlers audit for Google and stop there. But Blume's promise is\n * AI-ready docs, and `llms.txt` is the sitemap of that surface: a stale entry\n * sends an agent to a page that is not there, and an unlisted page is\n * invisible to every tool that starts from the index.\n */\nexport const llmsChecks: CheckModule = {\n category: \"ai\",\n run(context) {\n const { enabled, openapi } = llmsConfig(context);\n if (!enabled) {\n return [];\n }\n\n const { llms } = context;\n if (!llms) {\n return [\n finding(\n \"BLUME_AUDIT_LLMS_TXT_MISSING\",\n { url: \"/llms.txt\" },\n \"The build has no llms.txt.\"\n ),\n ];\n }\n\n const found: Diagnostic[] = [];\n const origin = siteOrigin(context.project.config.deployment.options.site);\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n\n const listed = new Set<string>();\n for (const entry of llms.entries) {\n const path = entryPath(entry.url, origin, deployBase);\n if (path === null) {\n // Off-site or unparseable targets aren't pages this build can vouch\n // for; external link health is the `--external` tier's job.\n continue;\n }\n listed.add(path);\n // A listed target may be a served asset rather than a page — Blume's own\n // llms.txt links the changelog RSS feed — or a route the server answers\n // rather than a file the build writes, like the MCP endpoint. `isServed`\n // vouches for all three, the same way it does for links and redirects.\n if (!isServed(context, path)) {\n found.push(\n finding(\n \"BLUME_AUDIT_LLMS_TXT_STALE_ENTRY\",\n { file: llms.file, line: entry.line, url: entry.url },\n `llms.txt lists ${path}, which the build does not serve.`\n )\n );\n }\n }\n\n // The reverse direction, mirroring INDEXABLE_PAGE_NOT_IN_SITEMAP: a page\n // that is built, indexable, and in the nav belongs in the index. Pages the\n // generator deliberately skips — hidden, drafts, error routes, API\n // reference when `agents.llmsTxt.openapi` is off, custom pages with no\n // manifest route — are skipped here for the same reasons.\n for (const page of context.pages) {\n const { route } = page;\n if (\n !route ||\n route.hidden ||\n route.draft ||\n !page.indexable ||\n ERROR_ROUTES.has(page.url) ||\n (!openapi && route.source.name === \"openapi\") ||\n listed.has(normalizePath(page.url))\n ) {\n continue;\n }\n found.push(\n finding(\n \"BLUME_AUDIT_LLMS_TXT_PAGE_MISSING\",\n pageSite(context, page),\n `${page.url} is built and indexable but is not listed in llms.txt.`\n )\n );\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
19
|
-
"import { normalizeBasePath } from \"../../core/base-path.ts\";\nimport { gradeExternal, probeAll } from \"../../core/probe.ts\";\nimport type { ProbeResult } from \"../../core/probe.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { AuditContext, CheckModule, PageSnapshot } from \"../types.ts\";\nimport { resolveHref, siteOrigin } from \"../url.ts\";\n\nconst CLIENT_ERROR = 400;\nconst SERVER_ERROR = 500;\n/** Past this, a page is slow enough that it costs you crawl budget and readers. */\nconst SLOW_MS = 1500;\n\n/**\n * The live URL a built page is served at, under the `--url` origin. Page URLs\n * come from the built file tree and carry no `deployment.base`, but the live\n * site serves everything under it — probing without the base would 4xx every\n * page of a healthy subpath deployment.\n */\nconst liveUrl = (\n origin: string,\n page: PageSnapshot,\n deployBase: string\n): string => new URL(`${deployBase}${page.url}`, origin).toString();\n\n/**\n * Whether the response failed outright, and how. Null when the page is served.\n *\n * Exported so the grading can be unit-tested against synthetic responses: a\n * timeout, an HTTPS-to-HTTP downgrade, and a slow first byte are all awkward to\n * provoke from a local test server, and faking the network to test them would\n * only be testing the fake.\n */\nexport const badResponse = (\n context: AuditContext,\n page: PageSnapshot,\n result: ProbeResult\n): Diagnostic | null => {\n const site = pageSite(context, page);\n\n if (result.timedOut) {\n return finding(\n \"BLUME_AUDIT_HTTP_TIMEOUT\",\n site,\n `${page.url} did not respond within the timeout.`\n );\n }\n\n const { status } = result;\n if (status !== undefined && status >= SERVER_ERROR) {\n return finding(\n \"BLUME_AUDIT_HTTP_5XX\",\n site,\n `${page.url} returned HTTP ${status}.`\n );\n }\n if (status !== undefined && status >= CLIENT_ERROR) {\n return finding(\n \"BLUME_AUDIT_HTTP_4XX\",\n site,\n `${page.url} is in the build but returned HTTP ${status}.`\n );\n }\n if (!result.ok) {\n return finding(\n \"BLUME_AUDIT_HTTP_5XX\",\n site,\n `${page.url} is unreachable: ${result.error ?? \"no response\"}.`\n );\n }\n return null;\n};\n\n/** What a *successful* response still gets wrong: headers, timing, protocol. */\nexport const servedPageChecks = (\n context: AuditContext,\n page: PageSnapshot,\n result: ProbeResult,\n origin: string\n): Diagnostic[] => {\n const site = pageSite(context, page);\n const found: Diagnostic[] = [];\n\n // A page served over HTTPS that redirects down to HTTP hands the reader to an\n // insecure connection — the opposite of the usual upgrade.\n if (\n result.redirected &&\n result.finalUrl?.startsWith(\"http://\") &&\n origin.startsWith(\"https://\")\n ) {\n found.push(\n finding(\n \"BLUME_AUDIT_REDIRECT_TO_HTTP\",\n site,\n `${page.url} redirects to ${result.finalUrl}, downgrading to HTTP.`\n )\n );\n }\n\n if (!result.encoding) {\n found.push(\n finding(\n \"BLUME_AUDIT_NOT_COMPRESSED\",\n site,\n `${page.url} is served without gzip or brotli compression.`\n )\n );\n }\n\n if (result.ms !== undefined && result.ms > SLOW_MS) {\n found.push(\n finding(\n \"BLUME_AUDIT_SLOW_RESPONSE\",\n site,\n `${page.url} took ${result.ms}ms to respond.`\n )\n );\n }\n\n // The header wins over the meta tag, so a stray `X-Robots-Tag: noindex` —\n // Vercel sets one on password-protected and preview deploys — silently\n // deindexes a page whose HTML looks perfectly indexable.\n const tag = result.robotsTag;\n if (tag?.includes(\"noindex\") && page.indexable) {\n found.push(\n finding(\n \"BLUME_AUDIT_ROBOTS_HEADER_CONFLICT\",\n site,\n `${page.url} sends \"X-Robots-Tag: ${tag}\" but its HTML has no noindex — the header wins.`\n )\n );\n }\n\n return found;\n};\n\n/**\n * The built site, checked against a live deployment.\n *\n * Everything here needs the network, which is why it only runs with `--url`: a\n * page that exists in `dist/` can still 404 in production behind a bad rewrite,\n * and only the real response carries the headers (`Content-Encoding`,\n * `X-Robots-Tag`) that decide whether the page is compressed and indexable.\n */\nexport const networkChecks: CheckModule = {\n category: \"network\",\n async run(context) {\n const { origin } = context;\n if (!origin) {\n return [];\n }\n\n const found: Diagnostic[] = [];\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n const targets = context.pages.map((page) =>\n liveUrl(origin, page, deployBase)\n );\n // robots.txt and sitemap.xml are fetched alongside the pages: they're the\n // two files a crawler asks for first, and a deploy that hides them silently\n // undoes everything else the audit checks. They sit at the root of the\n // build output, which the host serves under the deployment base.\n const robotsUrl = new URL(`${deployBase}/robots.txt`, origin).toString();\n const sitemapUrl = new URL(`${deployBase}/sitemap.xml`, origin).toString();\n\n const results = await probeAll([...targets, robotsUrl, sitemapUrl]);\n\n for (const page of context.pages) {\n const result = results.get(liveUrl(origin, page, deployBase));\n if (!result) {\n continue;\n }\n const failure = badResponse(context, page, result);\n if (failure) {\n found.push(failure);\n continue;\n }\n found.push(...servedPageChecks(context, page, result, origin));\n }\n\n const robots = results.get(robotsUrl);\n if (robots && !robots.ok) {\n found.push(\n finding(\n \"BLUME_AUDIT_ROBOTS_NOT_ACCESSIBLE\",\n { url: \"/robots.txt\" },\n `robots.txt is not reachable at ${robotsUrl}.`\n )\n );\n }\n\n const sitemap = results.get(sitemapUrl);\n if (context.sitemap && sitemap && !sitemap.ok) {\n found.push(\n finding(\n \"BLUME_AUDIT_SITEMAP_NOT_ACCESSIBLE\",\n { url: \"/sitemap.xml\" },\n `sitemap.xml is in the build but is not reachable at ${sitemapUrl}.`\n )\n );\n }\n\n return found;\n },\n tier: \"network\",\n};\n\n/**\n * Outbound links, probed over the network (`--external`).\n *\n * Severity is graded rather than flat: a 404 is the author's bug, but a 403 or a\n * 5xx is usually rate limiting or someone else's outage, and failing a build on\n * that would make the check useless.\n */\nexport const externalChecks: CheckModule = {\n category: \"network\",\n async run(context) {\n const origin = siteOrigin(context.project.config.deployment.options.site);\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n\n /** Every outbound URL, and the pages that link to it. */\n const linkers = new Map<string, PageSnapshot[]>();\n for (const page of context.pages) {\n for (const link of page.links) {\n const resolved = resolveHref(page.url, link.href, origin, deployBase);\n if (resolved.kind !== \"external\") {\n continue;\n }\n const pages = linkers.get(resolved.url);\n if (pages) {\n if (!pages.includes(page)) {\n pages.push(page);\n }\n } else {\n linkers.set(resolved.url, [page]);\n }\n }\n }\n\n if (linkers.size === 0) {\n return [];\n }\n const results = await probeAll([...linkers.keys()]);\n\n const found: Diagnostic[] = [];\n for (const [url, pages] of linkers) {\n const result = results.get(url);\n if (!result) {\n continue;\n }\n // SAFETY: every `linkers` entry is created with its linking page and only\n // ever appended to, so the array is never empty.\n const site = pageSite(context, pages[0] as PageSnapshot);\n\n const grade = gradeExternal(result);\n if (grade) {\n // Severity is graded, not flat. A 404 is a bug the author can fix; a 403\n // or 5xx is usually rate limiting or someone else's outage, and failing\n // a build on that would get `--external` switched off for good.\n found.push({\n ...finding(\n \"BLUME_AUDIT_EXTERNAL_LINK_BROKEN\",\n site,\n `${url} is unreachable (${grade.detail}), linked from ${pages.length} page(s).`\n ),\n severity: grade.severity,\n });\n continue;\n }\n\n if (result.redirected && result.finalUrl && result.finalUrl !== url) {\n found.push(\n finding(\n \"BLUME_AUDIT_EXTERNAL_LINK_REDIRECT\",\n site,\n `${url} redirects to ${result.finalUrl}.`\n )\n );\n }\n }\n return found;\n },\n tier: \"external\",\n};\n",
|
|
20
|
-
"import { readFile } from \"node:fs/promises\";\n\nimport { join } from \"pathe\";\n\nimport { normalizeBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { imageSize } from \"../image-size.ts\";\nimport type { ImageSize } from \"../image-size.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { CheckModule, PageSnapshot } from \"../types.ts\";\nimport { resolveHref, siteOrigin } from \"../url.ts\";\n\n/**\n * Below this, large social cards render the image blurry or fall back to a\n * small-card layout: Facebook's floor for a full-width card is 600×315\n * (1200×630 recommended), X's for `summary_large_image` is 300×157.\n */\nconst MIN_WIDTH = 600;\nconst MIN_HEIGHT = 315;\n\n/**\n * The Open Graph image as bytes, not just a tag. `OG_IMAGE_MISSING` proves the\n * meta tag exists; only the build can prove the tag points at a real file of a\n * shareable size — a crawler needs the live site for either.\n */\nexport const ogImageChecks: CheckModule = {\n category: \"social\",\n async run(context) {\n const found: Diagnostic[] = [];\n /** Pending reads per file path — the site-wide default OG image is read once. */\n const sizes = new Map<string, Promise<ImageSize | null>>();\n\n const read = async (path: string): Promise<ImageSize | null> => {\n try {\n return imageSize(await readFile(join(context.staticDir, path)));\n } catch {\n // Unreadable bytes: existence was already established via the file\n // index, and an unknown format is not a finding.\n return null;\n }\n };\n\n // Cache the promise, not the result, so concurrent pages sharing one\n // image (the site-wide default) share a single read.\n const measure = (path: string): Promise<ImageSize | null> => {\n const cached = sizes.get(path);\n if (cached) {\n return cached;\n }\n const pending = read(path);\n sizes.set(path, pending);\n return pending;\n };\n\n const origin = siteOrigin(context.project.config.deployment.options.site);\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n const candidates: { page: PageSnapshot; path: string }[] = [];\n\n for (const page of context.pages) {\n const src = page.og[\"og:image\"];\n if (!src) {\n continue;\n }\n // An og:image on another origin (a CDN, an external host) is outside the\n // build — its existence can't be proven without the network, so it's the\n // network tier's business, not this check's.\n const resolved = resolveHref(page.url, src, origin, deployBase);\n if (resolved.kind === \"external\" || resolved.kind === \"ignored\") {\n continue;\n }\n\n const { path } = resolved;\n if (\n !(context.files.has(path) || context.files.has(`${path}/index.html`))\n ) {\n found.push(\n finding(\n \"BLUME_AUDIT_OG_IMAGE_BROKEN\",\n pageSite(context, page, [\"seo\", \"image\"]),\n `og:image points at ${path}, which is not in the build.`\n )\n );\n continue;\n }\n\n candidates.push({ page, path });\n }\n\n const measured = await Promise.all(\n candidates.map(async (candidate) => ({\n ...candidate,\n size: await measure(candidate.path),\n }))\n );\n for (const { page, path, size } of measured) {\n if (size && (size.width < MIN_WIDTH || size.height < MIN_HEIGHT)) {\n found.push(\n finding(\n \"BLUME_AUDIT_OG_IMAGE_SMALL\",\n pageSite(context, page, [\"seo\", \"image\"]),\n `og:image ${path} is ${size.width}×${size.height} — below the ${MIN_WIDTH}×${MIN_HEIGHT} floor for large social cards.`\n )\n );\n }\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
21
|
-
"import sharp from \"sharp\";\n\n/**\n * Pixel dimensions read from an image header via sharp, which is already the\n * build's image optimizer and so covers every format the pipeline can emit —\n * WebP and AVIF included. libvips reads only the header and rejects malformed\n * input with an error rather than looping on it, which is why the previous\n * pure-JS parser was retired: it could be driven into an infinite loop by a\n * crafted ICNS, HEIF, or JXL file and hang the build. An unrecognized or\n * truncated buffer yields null and its checks simply don't run.\n */\nexport interface ImageSize {\n width: number;\n height: number;\n}\n\n/** The image's pixel dimensions, or null when the format isn't recognized. */\nexport const imageSize = async (bytes: Buffer): Promise<ImageSize | null> => {\n try {\n const { width, height } = await sharp(bytes).metadata();\n return width > 0 && height > 0 ? { height, width } : null;\n } catch {\n return null;\n }\n};\n",
|
|
22
|
-
"import type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { CheckModule } from \"../types.ts\";\nimport { normalizePath } from \"../url.ts\";\n\n/**\n * Configured redirects, walked through to their destinations.\n *\n * Ahrefs also lists \"3XX redirect\" and \"302 redirect\" as issues. Having a\n * redirect is inventory, not a defect, and \"HTTP to HTTPS redirect\" — which it\n * also flags — is correct behavior. None of those are reported. What is: a\n * redirect that loops, dead-ends, takes the long way round, or is shadowed by a\n * real page and therefore never fires at all.\n */\nexport const redirectChecks: CheckModule = {\n category: \"redirects\",\n run(context) {\n const found: Diagnostic[] = [];\n const configFile = context.project.context.configFile ?? undefined;\n const site = { file: configFile, url: \"/\" };\n\n for (const redirect of context.redirects) {\n const from = normalizePath(redirect.from);\n\n // A redirect whose source is also a built page never fires — the page\n // wins. Ahrefs can't see this: it only observes the served response, which\n // looks perfectly healthy.\n if (context.byUrl.has(from)) {\n found.push(\n finding(\n \"BLUME_AUDIT_REDIRECT_SOURCE_IS_PAGE\",\n { ...site, url: from },\n `A redirect is configured from ${from}, but ${from} is also a real page — the redirect never fires.`\n )\n );\n continue;\n }\n\n if (redirect.outcome === \"loop\") {\n found.push(\n finding(\n \"BLUME_AUDIT_REDIRECT_LOOP\",\n { ...site, url: from },\n `Redirect loop: ${redirect.chain.join(\" → \")}`\n )\n );\n } else if (redirect.outcome === \"broken\") {\n found.push(\n finding(\n \"BLUME_AUDIT_REDIRECT_BROKEN\",\n { ...site, url: from },\n `Redirect from ${from} lands on ${redirect.chain.at(-1)}, which the build does not serve.`\n )\n );\n } else if (redirect.outcome === \"chain\") {\n const hops = redirect.chain.length - 1;\n const severity =\n hops > context.thresholds.maxRedirectHops ? \" (too long)\" : \"\";\n found.push(\n finding(\n \"BLUME_AUDIT_REDIRECT_CHAIN\",\n { ...site, url: from },\n `Redirect from ${from} passes through ${hops} hops${severity}: ${redirect.chain.join(\" → \")}`\n )\n );\n }\n }\n\n // A meta refresh is a redirect the framework doesn't know about, so it can't\n // be validated, cached, or followed reliably by crawlers.\n for (const page of context.pages) {\n if (page.metaRefresh) {\n found.push(\n finding(\n \"BLUME_AUDIT_META_REFRESH\",\n pageSite(context, page),\n `Page uses a meta refresh redirect (\"${page.metaRefresh}\").`\n )\n );\n }\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
23
|
-
"import robotsParser from \"robots-parser\";\n\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport type { CheckModule } from \"../types.ts\";\nimport { normalizePath } from \"../url.ts\";\n\n/**\n * robots-parser needs full URLs on a single origin; the origin itself is\n * irrelevant to path matching, so a fixed placeholder keeps the check\n * independent of whether the project configured `deployment.site`.\n */\nconst MATCH_ORIGIN = \"https://robots-audit.invalid\";\n\n/**\n * robots.txt: is it there, is it well-formed, does it point at the sitemap, and\n * — the one that matters — does it block a page the sitemap is advertising?\n *\n * Ahrefs also tracks \"robots.txt has too many redirects\". A static host serves\n * the file directly, so that is effectively unreachable here and isn't checked.\n */\nexport const robotsChecks: CheckModule = {\n category: \"robots\",\n run(context) {\n const { robots } = context;\n const { site } = context.project.config.deployment.options;\n\n if (!context.project.config.seo.robots) {\n return [];\n }\n\n if (!robots) {\n return [\n finding(\n \"BLUME_AUDIT_ROBOTS_MISSING\",\n { url: \"/robots.txt\" },\n \"The build has no robots.txt.\"\n ),\n ];\n }\n\n const found: Diagnostic[] = robots.invalid.map((line) =>\n finding(\n \"BLUME_AUDIT_ROBOTS_INVALID\",\n { file: robots.file, line: line.line, url: \"/robots.txt\" },\n `robots.txt line ${line.line} is not a directive: \"${line.text}\"`\n )\n );\n\n if (site && robots.sitemaps.length === 0) {\n found.push(\n finding(\n \"BLUME_AUDIT_ROBOTS_SITEMAP_MISSING\",\n { file: robots.file, url: \"/robots.txt\" },\n \"robots.txt does not declare a Sitemap.\"\n )\n );\n }\n\n // A page can't be both blocked from crawling and advertised for indexing.\n // Checking the rules against the sitemap (rather than against every built\n // file) keeps this to the pages the site actually wants indexed.\n // robots-parser resolves Allow/Disallow by longest match, so the common\n // `Disallow: /` + `Allow: /docs/` pattern doesn't flag every page, and\n // consecutive User-agent lines form one group as the spec requires.\n const parser = robotsParser(`${MATCH_ORIGIN}/robots.txt`, robots.raw);\n const lines = robots.raw.split(/\\r?\\n/u);\n for (const loc of context.sitemap?.urls ?? []) {\n let pathname: string;\n try {\n ({ pathname } = new URL(loc));\n } catch {\n continue;\n }\n // Match the pathname as served: robots.txt rules are literal prefixes,\n // so `Disallow: /page/` must see the trailing slash to match.\n const path = normalizePath(pathname);\n const url = `${MATCH_ORIGIN}${pathname}`;\n if (parser.isDisallowed(url, \"*\")) {\n const line = parser.getMatchingLineNumber(url, \"*\");\n const rule = line > 0 ? lines[line - 1]?.trim() : undefined;\n found.push(\n finding(\n \"BLUME_AUDIT_ROBOTS_DISALLOWS_INDEXABLE\",\n { file: robots.file, url: path },\n `robots.txt \"${rule ?? \"Disallow\"}\" blocks ${path}, which sitemap.xml advertises.`\n )\n );\n }\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
24
|
-
"import { normalizeBasePath, stripBasePath } from \"../../core/base-path.ts\";\nimport type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { AuditContext, CheckModule } from \"../types.ts\";\nimport { decodePath, normalizePath, siteOrigin } from \"../url.ts\";\n\nconst MAX_SITEMAP_BYTES = 50 * 1024 * 1024;\nconst MAX_SITEMAP_URLS = 50_000;\n\n/**\n * Slack for future `<lastmod>` values. A date-only stamp written in a timezone\n * ahead of UTC parses as \"tomorrow\" from behind it; a day of grace keeps that\n * from being reported as a lie.\n */\nconst LASTMOD_SLACK_MS = 24 * 60 * 60 * 1000;\n\n/** Error routes are never crawlable destinations, so they belong out of the sitemap. */\nconst ERROR_ROUTES = new Set([\"/404\", \"/500\"]);\n\n/**\n * Site paths listed in the sitemap, normalized for comparison against page\n * URLs. `<loc>`s carry the deployment base and are `encodeURI`'d; page URLs\n * (from the file tree) carry neither, so both are undone here.\n */\nconst sitemapPaths = (\n context: AuditContext,\n deployBase: string\n): Map<string, string> => {\n const paths = new Map<string, string>();\n for (const loc of context.sitemap?.urls ?? []) {\n try {\n paths.set(\n normalizePath(\n stripBasePath(deployBase, decodePath(new URL(loc).pathname))\n ),\n loc\n );\n } catch {\n // A malformed <loc> is reported by SITEMAP_INVALID, not here.\n }\n }\n return paths;\n};\n\n/**\n * The path of a canonical URL, or null when it isn't parseable\n * (CANONICAL_BAD_TARGET reports that). Canonicals are emitted as\n * `site + base + route`, so the deployment base is stripped to keep the result\n * comparable against base-less page URLs.\n */\nconst canonicalPath = (\n canonical: string,\n deployBase: string\n): string | null => {\n try {\n return normalizePath(\n stripBasePath(deployBase, decodePath(new URL(canonical).pathname))\n );\n } catch {\n return null;\n }\n};\n\n/** Validate one `<loc>` against the build: does it exist, and is it indexable? */\nconst checkListedUrl = (\n context: AuditContext,\n loc: string,\n origin: string | null,\n file: string,\n deployBase: string\n): Diagnostic[] => {\n let parsed: URL;\n try {\n parsed = new URL(loc);\n } catch {\n return [\n finding(\n \"BLUME_AUDIT_SITEMAP_INVALID\",\n { file, url: \"/sitemap.xml\" },\n `sitemap.xml lists \"${loc}\", which is not a valid absolute URL.`\n ),\n ];\n }\n\n if (origin && parsed.origin !== origin) {\n return [\n finding(\n \"BLUME_AUDIT_SITEMAP_OUT_OF_SCOPE\",\n { file, url: loc },\n `sitemap.xml lists ${loc}, which is on another origin.`\n ),\n ];\n }\n\n // `<loc>`s carry the deployment base and are `encodeURI`'d; page URLs (from\n // the file tree) are neither.\n const path = normalizePath(\n stripBasePath(deployBase, decodePath(parsed.pathname))\n );\n const page = context.byUrl.get(path);\n if (!page) {\n const redirect = context.redirects.find(\n (entry) => normalizePath(entry.from) === path\n );\n return [\n finding(\n \"BLUME_AUDIT_SITEMAP_BAD_URL\",\n { file, url: loc },\n redirect\n ? `sitemap.xml lists ${path}, which redirects to ${redirect.to}.`\n : `sitemap.xml lists ${path}, which the build does not serve.`\n ),\n ];\n }\n\n const found: Diagnostic[] = [];\n if (!page.indexable) {\n found.push(\n finding(\n \"BLUME_AUDIT_NOINDEX_IN_SITEMAP\",\n pageSite(context, page, [\"noindex\"]),\n `${path} is in the sitemap but declares robots \"${page.robots}\".`\n )\n );\n }\n\n const canonical = page.canonical && canonicalPath(page.canonical, deployBase);\n if (canonical && canonical !== path) {\n found.push(\n finding(\n \"BLUME_AUDIT_NON_CANONICAL_IN_SITEMAP\",\n pageSite(context, page, [\"seo\", \"canonical\"]),\n `${path} is in the sitemap but canonicalizes to ${canonical}.`\n )\n );\n }\n return found;\n};\n\n/**\n * The sitemap, cross-checked against what was actually built.\n *\n * The highest-value check here is the one Ahrefs buries at info severity:\n * `INDEXABLE_PAGE_NOT_IN_SITEMAP`. A page that a stray `draft: true` or\n * `sidebar.hidden` quietly kept out of the sitemap is invisible to search, and\n * nothing else in the toolchain tells you.\n */\nexport const sitemapChecks: CheckModule = {\n category: \"sitemap\",\n run(context) {\n const { sitemap } = context;\n const { site } = context.project.config.deployment.options;\n\n // Without `deployment.site` Blume can't emit a sitemap at all (absolute URLs\n // are required), and that's a config choice, not a defect. Stay quiet.\n if (!(site && context.project.config.seo.sitemap)) {\n return [];\n }\n\n if (!sitemap) {\n return [\n finding(\n \"BLUME_AUDIT_SITEMAP_INVALID\",\n { url: \"/sitemap.xml\" },\n \"The build has no sitemap.xml.\",\n \"Set `seo.sitemap: true` and `deployment.site` in blume.config.ts.\"\n ),\n ];\n }\n\n const found: Diagnostic[] = [];\n if (sitemap.error) {\n found.push(\n finding(\n \"BLUME_AUDIT_SITEMAP_INVALID\",\n { file: sitemap.file, url: \"/sitemap.xml\" },\n `sitemap.xml is not a valid urlset: ${sitemap.error}`\n )\n );\n return found;\n }\n\n if (\n sitemap.bytes > MAX_SITEMAP_BYTES ||\n sitemap.urls.length > MAX_SITEMAP_URLS\n ) {\n found.push(\n finding(\n \"BLUME_AUDIT_SITEMAP_TOO_LARGE\",\n { file: sitemap.file, url: \"/sitemap.xml\" },\n `sitemap.xml holds ${sitemap.urls.length} URLs in ${Math.round(sitemap.bytes / 1024 / 1024)} MB.`\n )\n );\n }\n\n // A `<lastmod>` that lies — malformed, or claiming the future — teaches\n // search engines to distrust every lastmod in the file, which throws away\n // the recrawl-priority signal the field exists to provide.\n for (const [loc, lastmod] of sitemap.lastmod ?? []) {\n const time = Date.parse(lastmod);\n if (Number.isNaN(time)) {\n found.push(\n finding(\n \"BLUME_AUDIT_SITEMAP_LASTMOD_INVALID\",\n { file: sitemap.file, url: loc },\n `sitemap.xml gives ${loc} a lastmod of \"${lastmod}\", which is not a valid W3C date.`\n )\n );\n } else if (time > Date.now() + LASTMOD_SLACK_MS) {\n found.push(\n finding(\n \"BLUME_AUDIT_SITEMAP_LASTMOD_INVALID\",\n { file: sitemap.file, url: loc },\n `sitemap.xml gives ${loc} a lastmod of ${lastmod}, which is in the future.`\n )\n );\n }\n }\n\n const origin = siteOrigin(site);\n const deployBase = normalizeBasePath(\n context.project.config.deployment.options.base\n );\n const listed = sitemapPaths(context, deployBase);\n\n for (const loc of sitemap.urls) {\n found.push(\n ...checkListedUrl(context, loc, origin, sitemap.file, deployBase)\n );\n }\n\n // The other direction: a page that was built, is indexable, and should be\n // findable — but never made it into the sitemap.\n for (const page of context.pages) {\n if (\n !page.indexable ||\n ERROR_ROUTES.has(page.url) ||\n listed.has(normalizePath(page.url))\n ) {\n continue;\n }\n found.push(\n finding(\n \"BLUME_AUDIT_INDEXABLE_PAGE_NOT_IN_SITEMAP\",\n pageSite(context, page),\n `${page.url} is built and indexable but is not listed in sitemap.xml.`\n )\n );\n }\n\n return found;\n },\n tier: \"static\",\n};\n",
|
|
25
|
-
"import type { Diagnostic } from \"../../core/types.ts\";\nimport { finding } from \"../catalog.ts\";\nimport { pageSite } from \"../locate.ts\";\nimport type { CheckModule, PageSnapshot } from \"../types.ts\";\nimport { normalizePath } from \"../url.ts\";\n\n/** The Open Graph properties a share card is unusable without. */\nconst OG_REQUIRED = [\"og:title\", \"og:type\", \"og:description\"];\n\n/**\n * `og:url` must be an absolute URL, so Blume can only emit it once\n * `deployment.site` is known. Requiring it on a site that hasn't set one would\n * report the same missing config on every page; SITE_NOT_SET says it once.\n */\nconst ogRequired = (hasSite: boolean): string[] =>\n hasSite ? [...OG_REQUIRED, \"og:url\"] : OG_REQUIRED;\n\n/**\n * Open Graph and X (Twitter) card tags.\n *\n * Ahrefs splits each of these into \"missing\" and \"incomplete\". That's a\n * distinction without a difference — a card with no tags and a card with half\n * its tags both fail to render — so each is one check that names exactly which\n * properties are absent.\n */\nexport const socialChecks: CheckModule = {\n category: \"social\",\n run(context) {\n const found: Diagnostic[] = [];\n const hasSite = Boolean(context.project.config.deployment.options.site);\n const required = ogRequired(hasSite);\n\n for (const page of context.pages) {\n // Error pages are never shared, so their cards don't matter.\n if (!page.indexable) {\n continue;\n }\n\n const missing = required.filter((property) => !page.og[property]);\n if (missing.length > 0) {\n found.push(\n finding(\n \"BLUME_AUDIT_OG_INCOMPLETE\",\n pageSite(context, page, [\"description\"]),\n missing.length === required.length\n ? \"Page has no Open Graph tags.\"\n : `Open Graph is missing ${missing.join(\", \")}.`\n )\n );\n }\n\n // The generated OG card is served from an absolute URL, so it too depends\n // on `deployment.site`; SITE_NOT_SET already covers that case.\n if (hasSite && !page.og[\"og:image\"]) {\n found.push(\n finding(\n \"BLUME_AUDIT_OG_IMAGE_MISSING\",\n pageSite(context, page, [\"seo\", \"image\"]),\n \"Page has no og:image — shares will render without a preview card.\"\n )\n );\n }\n\n const ogUrl = page.og[\"og:url\"];\n if (ogUrl && page.canonical && ogUrl !== page.canonical) {\n found.push(\n finding(\n \"BLUME_AUDIT_OG_URL_MISMATCH\",\n pageSite(context, page),\n `og:url is ${ogUrl} but the canonical is ${page.canonical}.`\n )\n );\n }\n\n // X reads title/description/image from the Open Graph tags, so the only\n // thing it can't infer is the card type and the account attribution.\n if (!page.twitter[\"twitter:card\"]) {\n found.push(\n finding(\n \"BLUME_AUDIT_TWITTER_CARD_INCOMPLETE\",\n pageSite(context, page),\n \"Page has no twitter:card — X will render a plain link, not a card.\"\n )\n );\n }\n }\n return found;\n },\n tier: \"static\",\n};\n\n/** The JSON-LD keywords the audit reads off a parsed block. */\ninterface JsonLdNode {\n \"@context\"?: unknown;\n \"@graph\"?: unknown;\n \"@type\"?: unknown;\n}\n\n/** Whether one parsed JSON-LD value is an object the keyword checks can read. */\nconst isJsonLdNode = (\n value: PageSnapshot[\"jsonld\"][number]\n): value is JsonLdNode => typeof value === \"object\" && value !== null;\n\n/**\n * What's missing from one JSON-LD block.\n *\n * A block may either be a single node (`{@context, @type, …}`) or a `@graph`\n * container (`{@context, @graph: [{@type, …}, …]}`), which is the shape Blume\n * itself emits. In the container form `@context` is declared once at the root\n * and each entry carries its own `@type` — so demanding `@type` on the root, or\n * `@context` on each entry, would flag perfectly valid structured data.\n */\nconst jsonLdProblems = (node: PageSnapshot[\"jsonld\"][number]): string[] => {\n if (!isJsonLdNode(node)) {\n return [\"it is not an object\"];\n }\n const problems: string[] = [];\n if (!node[\"@context\"]) {\n problems.push(\"@context\");\n }\n\n const graph = node[\"@graph\"];\n if (Array.isArray(graph)) {\n const untyped = graph.filter(\n (entry) => !isJsonLdNode(entry) || !entry[\"@type\"]\n ).length;\n if (untyped > 0) {\n problems.push(`@type on ${untyped} of its ${graph.length} @graph nodes`);\n }\n } else if (!node[\"@type\"]) {\n problems.push(\"@type\");\n }\n\n return problems;\n};\n\n/** Structured data. We validate what Blume emits, and don't pretend to do more. */\nexport const structuredDataChecks: CheckModule = {\n category: \"structured-data\",\n run(context) {\n const found: Diagnostic[] = [];\n for (const page of context.pages) {\n for (const error of page.jsonldErrors) {\n found.push(\n finding(\n \"BLUME_AUDIT_JSONLD_INVALID\",\n pageSite(context, page),\n `A JSON-LD block failed to parse: ${error}`\n )\n );\n }\n\n for (const node of page.jsonld) {\n const problems = jsonLdProblems(node);\n if (problems.length > 0) {\n found.push(\n finding(\n \"BLUME_AUDIT_JSONLD_INCOMPLETE\",\n pageSite(context, page),\n `A JSON-LD block is missing ${problems.join(\" and \")}.`\n )\n );\n }\n }\n }\n return found;\n },\n tier: \"static\",\n};\n\n/** What makes a slug untidy, with the human name for each offense. */\nconst URL_STYLE: { name: string; test: RegExp }[] = [\n { name: \"uppercase letters\", test: /[A-Z]/u },\n { name: \"underscores\", test: /_/u },\n { name: \"spaces\", test: /%20| /u },\n];\n\n/** A protocol-relative URL names a dotted host (`//cdn.example.com/x`). */\nconst DOTTED_HOST = /^\\/\\/[^/]*\\./u;\n\n/**\n * Whether an href carries a doubled slash from a trailing-slash `basePath` /\n * `deployment.base`. `//docs/x` is the telltale: a browser reads it as a\n * protocol-relative URL with host `docs`, so the link silently leaves the site\n * — and every link checker skips it as external. An interior `//` (`/docs//x`)\n * is the same mistake composed mid-path.\n */\nconst doubledSlash = (href: string): boolean => {\n if (href.startsWith(\"//\")) {\n return !DOTTED_HOST.test(href);\n }\n if (!href.startsWith(\"/\")) {\n return false;\n }\n // Only the path — a query param may legitimately carry a URL.\n const [path] = href.split(/[?#]/u);\n return (path ?? \"\").includes(\"//\");\n};\n\nexport const urlChecks: CheckModule = {\n category: \"links\",\n run(context) {\n const found: Diagnostic[] = [];\n /** Doubled hrefs are site-wide (a bad base) — one finding per target. */\n const doubled = new Map<string, PageSnapshot>();\n for (const page of context.pages) {\n if (page.url.includes(\"//\")) {\n found.push(\n finding(\n \"BLUME_AUDIT_DOUBLE_SLASH_URL\",\n pageSite(context, page),\n `URL ${normalizePath(page.url)} contains a double slash.`\n )\n );\n }\n for (const link of page.links) {\n const href = link.href.trim();\n if (doubledSlash(href) && !doubled.has(href)) {\n doubled.set(href, page);\n }\n }\n\n const untidy = URL_STYLE.filter((style) => style.test.test(page.url));\n if (untidy.length > 0) {\n found.push(\n finding(\n \"BLUME_AUDIT_URL_STYLE\",\n pageSite(context, page),\n `URL ${page.url} contains ${untidy.map((style) => style.name).join(\" and \")}.`\n )\n );\n }\n }\n for (const [href, page] of doubled) {\n found.push(\n finding(\n \"BLUME_AUDIT_DOUBLE_SLASH_URL\",\n pageSite(context, page),\n `Link to ${href} contains a double slash.`\n )\n );\n }\n return found;\n },\n tier: \"static\",\n};\n",
|
|
26
|
-
"import { readFile, stat } from \"node:fs/promises\";\n\nimport { XMLParser } from \"fast-xml-parser\";\nimport type { Nodes } from \"mdast\";\nimport { fromMarkdown } from \"mdast-util-from-markdown\";\nimport pMap from \"p-map\";\nimport { join, relative } from \"pathe\";\nimport { glob } from \"tinyglobby\";\n\nimport { examplesRouteBase } from \"../astro/templates.ts\";\nimport { stripBasePath } from \"../core/base-path.ts\";\nimport type { BlumeManifest, RouteManifestEntry } from \"../core/types.ts\";\nimport { buildSnapshot } from \"./snapshot.ts\";\nimport type { LlmsDoc, PageSnapshot, RobotsDoc, SitemapDoc } from \"./types.ts\";\n\n/**\n * The route prefix `<Component />` preview frames live under. They are bare\n * documents rendered for iframes — deliberately noindex, no title worth\n * grading, no front matter to fix — so auditing them as pages only produces\n * findings nobody can act on.\n */\nconst EXAMPLES_PREFIX = `${examplesRouteBase(\"\")}/`;\n\n/**\n * Ceiling on concurrent file reads/stats while crawling. Unbounded fan-out\n * over a large `dist` risks EMFILE and holds every page's HTML in memory at\n * once.\n */\nconst CRAWL_CONCURRENCY = 16;\n\n/** Everything read off disk in one pass over the built site. */\nexport interface CrawlResult {\n pages: PageSnapshot[];\n /** Every file in the static dir: URL path -> size in bytes. */\n files: Map<string, number>;\n sitemap: SitemapDoc | null;\n robots: RobotsDoc | null;\n llms: LlmsDoc | null;\n}\n\n/**\n * The URL a built HTML file is served at. Astro's directory build format emits\n * `docs/api/index.html` for `/docs/api`, so the `index.html` leaf collapses;\n * a flat `404.html` keeps its name.\n */\nexport const fileToUrl = (staticDir: string, file: string): string => {\n const rel = relative(staticDir, file).replaceAll(\"\\\\\", \"/\");\n const path = rel.replace(/(?:^|\\/)index\\.html$/u, \"\").replace(/\\.html$/u, \"\");\n return path === \"\" ? \"/\" : `/${path}`;\n};\n\n/**\n * Every file in the static dir, keyed the way an HTML `src`/`href` would name\n * it, with its size — so a reference can be resolved and weighed in one pass.\n */\nconst indexFiles = async (staticDir: string): Promise<Map<string, number>> => {\n const found = await glob(\"**/*\", { cwd: staticDir, dot: true });\n const sized = await pMap(\n found,\n async (file) => {\n const path = `/${file.replaceAll(\"\\\\\", \"/\")}`;\n const info = await stat(join(staticDir, file));\n return [path, info.size] as const;\n },\n { concurrency: CRAWL_CONCURRENCY }\n );\n return new Map(sized);\n};\n\n/**\n * Look a built URL up in the route manifest. Routes carry `basePath` while the\n * built file tree may not, so both spellings are tried before giving up — a page\n * that fails to join is still audited, it just can't name a source file to fix.\n */\nconst routeIndex = (\n manifest: BlumeManifest,\n basePath: string\n): Map<string, RouteManifestEntry> => {\n const index = new Map<string, RouteManifestEntry>();\n for (const route of manifest.routes) {\n index.set(route.path, route);\n const stripped = stripBasePath(basePath, route.path);\n if (!index.has(stripped)) {\n index.set(stripped, route);\n }\n }\n return index;\n};\n\n/**\n * Sitemaps arrive from arbitrary generators (the audit also fetches remote\n * ones), so parsing is fast-xml-parser's job: CDATA sections, numeric\n * entities, and namespace-prefixed elements are all legal there and all\n * invisible to a regex scan. Values stay strings (`parseTagValue: false`) so\n * a numeric-looking `<lastmod>` isn't coerced.\n */\nconst sitemapParser = new XMLParser({\n // htmlEntities adds numeric character references (&) on top of the\n // default XML five; a sitemap loc legitimately carries either form.\n htmlEntities: true,\n ignoreAttributes: true,\n parseTagValue: false,\n removeNSPrefix: true,\n});\n\n/**\n * What fast-xml-parser produces for a parsed element: a string for text\n * content (`parseTagValue: false`), an object of child elements, an array for\n * a repeated element, or null for a self-closed one.\n */\ntype XmlValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | XmlValue[]\n | { [element: string]: XmlValue };\n\n/**\n * fast-xml-parser models an element with child elements as an object; a\n * text-only or self-closed element parses to a string or null instead, which\n * these guards reject the same way the crawler always has.\n */\nconst isUrlsetElement = (value: XmlValue): value is { url?: XmlValue } =>\n typeof value === \"object\" && value !== null;\n\nconst isUrlEntry = (\n value: XmlValue\n): value is { lastmod?: XmlValue; loc?: XmlValue } =>\n typeof value === \"object\" && value !== null;\n\nconst isText = (value: XmlValue): value is string => typeof value === \"string\";\n\n/**\n * Parse `sitemap.xml`. Deliberately shallow: the checks only need the `<loc>`\n * list, each loc's `<lastmod>`, and whether the document is a urlset at all.\n */\nexport const parseSitemap = (\n file: string,\n xml: string,\n bytes: number\n): SitemapDoc => {\n const doc: SitemapDoc = { bytes, file, lastmod: new Map(), urls: [] };\n let parsed: { sitemapindex?: XmlValue; urlset?: XmlValue };\n try {\n parsed = sitemapParser.parse(xml);\n } catch {\n doc.error = \"no <urlset> element\";\n return doc;\n }\n if (!Object.hasOwn(parsed, \"urlset\")) {\n doc.error = Object.hasOwn(parsed, \"sitemapindex\")\n ? \"sitemap is an index, not a urlset\"\n : \"no <urlset> element\";\n return doc;\n }\n const { urlset } = parsed;\n const entries = isUrlsetElement(urlset) ? [urlset.url].flat() : [];\n for (const entry of entries) {\n if (!isUrlEntry(entry)) {\n continue;\n }\n const { loc, lastmod } = entry;\n const locText = isText(loc) ? loc.trim() : \"\";\n if (!locText) {\n continue;\n }\n doc.urls.push(locText);\n if (isText(lastmod) && lastmod.trim() !== \"\") {\n doc.lastmod?.set(locText, lastmod.trim());\n }\n }\n return doc;\n};\n\n/**\n * Parse the `llms.txt` index into its Markdown link targets, with the line\n * each target sits on so findings can point at it. A real parse rather than a\n * `](url)` regex: angle-bracket destinations, link titles, reference-style\n * links (the definition line carries the URL), and autolinks all resolve, and\n * a link-shaped string inside a fenced code block is no longer reported as a\n * claim. Blume's own llms.txt only emits inline links, but the file is also\n * hand-edited.\n */\nexport const parseLlms = (file: string, text: string): LlmsDoc => {\n const entries: LlmsDoc[\"entries\"] = [];\n const collect = (node: Nodes): void => {\n if (\n (node.type === \"link\" ||\n node.type === \"image\" ||\n node.type === \"definition\") &&\n node.url\n ) {\n entries.push({ line: node.position?.start.line ?? 1, url: node.url });\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n collect(child);\n }\n }\n };\n collect(fromMarkdown(text));\n return { entries, file };\n};\n\nconst ROBOTS_DIRECTIVE = /^(?<field>[a-z-]+)\\s*:\\s*(?<value>.*)$/iu;\n\n/**\n * Parse `robots.txt` into the pieces the audit cares about. Sitemap\n * declarations and a not-a-directive lint come from a line scan; rule\n * *matching* is robots-parser's job at check time (see `checks/robots.ts`),\n * so the raw text rides along instead of a pre-extracted rule list.\n */\nexport const parseRobots = (file: string, text: string): RobotsDoc => {\n const doc: RobotsDoc = { file, invalid: [], raw: text, sitemaps: [] };\n for (const [index, raw] of text.split(/\\r?\\n/u).entries()) {\n const line = raw.trim();\n if (line === \"\" || line.startsWith(\"#\")) {\n continue;\n }\n const match = ROBOTS_DIRECTIVE.exec(line);\n if (!match) {\n doc.invalid.push({ line: index + 1, text: line });\n continue;\n }\n const field = (match.groups?.field ?? \"\").toLowerCase();\n const value = (match.groups?.value ?? \"\").trim();\n if (field === \"sitemap\" && value) {\n doc.sitemaps.push(value);\n }\n }\n return doc;\n};\n\n/**\n * Whether an emitted `.html` file is a real page rather than a fragment.\n *\n * Astro writes standalone HTML for some components (`_home/Footer/index.html`\n * and friends) — markup with no `<html>` or `<head>`, never served as a route.\n * Auditing those as pages reports every one of them as missing a title, a\n * viewport, and a `lang` attribute, which is noise about markup nobody visits.\n * An SEO audit is about documents, so that's what we keep.\n */\nconst isDocument = (html: string): boolean => /<head[\\s>]/iu.test(html);\n\nconst readIfPresent = async (file: string): Promise<string | null> => {\n try {\n return await readFile(file, \"utf-8\");\n } catch {\n return null;\n }\n};\n\n/**\n * Read the built site: every HTML page reduced to a snapshot, the full file\n * index (for resolving subresource references), plus sitemap.xml and robots.txt.\n */\nexport const crawlStaticDir = async (options: {\n staticDir: string;\n manifest: BlumeManifest;\n basePath: string;\n}): Promise<CrawlResult> => {\n const { staticDir, manifest, basePath } = options;\n const routes = routeIndex(manifest, basePath);\n\n const htmlFiles = await glob(\"**/*.html\", { absolute: true, cwd: staticDir });\n const snapshots = await pMap(\n htmlFiles.toSorted(),\n async (file) => {\n const url = fileToUrl(staticDir, file);\n if (stripBasePath(basePath, url).startsWith(EXAMPLES_PREFIX)) {\n return null;\n }\n const html = await readFile(file, \"utf-8\");\n if (!isDocument(html)) {\n return null;\n }\n return buildSnapshot({\n file,\n html,\n route: routes.get(url) ?? routes.get(stripBasePath(basePath, url)),\n url,\n });\n },\n { concurrency: CRAWL_CONCURRENCY }\n );\n const pages = snapshots.filter((page) => page !== null);\n\n const sitemapFile = join(staticDir, \"sitemap.xml\");\n const sitemapXml = await readIfPresent(sitemapFile);\n const robotsFile = join(staticDir, \"robots.txt\");\n const robotsTxt = await readIfPresent(robotsFile);\n const llmsFile = join(staticDir, \"llms.txt\");\n const llmsText = await readIfPresent(llmsFile);\n const files = await indexFiles(staticDir);\n\n return {\n files,\n llms: llmsText === null ? null : parseLlms(llmsFile, llmsText),\n pages,\n robots: robotsTxt === null ? null : parseRobots(robotsFile, robotsTxt),\n sitemap:\n sitemapXml === null\n ? null\n : parseSitemap(\n sitemapFile,\n sitemapXml,\n files.get(\"/sitemap.xml\") ?? Buffer.byteLength(sitemapXml, \"utf-8\")\n ),\n };\n};\n",
|
|
27
|
-
"import { createHash } from \"node:crypto\";\n\nimport type { RouteManifestEntry } from \"../core/types.ts\";\nimport { attr, metaContents, parseHtml, visibleText } from \"./html.ts\";\nimport type { HtmlDocument } from \"./html.ts\";\nimport type { PageSnapshot, SnapshotAsset, SnapshotLink } from \"./types.ts\";\n\n/** Site chrome: links here are navigation, not editorial. */\nconst CHROME = \"nav, aside, header, footer\";\n\n/**\n * Count prose words with `Intl.Segmenter`, seeded with the page's declared\n * language. The whitespace split this replaces counted a fully written\n * Japanese or Chinese page — scripts with no interword spaces — as a handful\n * of \"words\", tripping BLUME_AUDIT_LOW_WORD_COUNT on every page of a CJK\n * site. An unparseable `lang` attribute falls back to the default locale\n * rather than failing the crawl.\n */\nconst countWords = (prose: string, lang: string | null): number => {\n let segmenter: Intl.Segmenter;\n try {\n segmenter = new Intl.Segmenter(lang ?? undefined, { granularity: \"word\" });\n } catch {\n segmenter = new Intl.Segmenter(undefined, { granularity: \"word\" });\n }\n let count = 0;\n for (const segment of segmenter.segment(prose)) {\n if (segment.isWordLike) {\n count += 1;\n }\n }\n return count;\n};\n\n/** Where a page's prose lives, in preference order. */\nconst CONTENT_ROOTS = [\"main\", \"article\", \"body\"];\n\n/**\n * The elements holding the page's prose. Falls back through `main` → `article`\n * → `body` so a custom `.astro` page with no semantic landmark still yields a\n * word count and a content hash instead of silently measuring as empty.\n */\nconst contentRoot = (document: HtmlDocument) => {\n for (const selector of CONTENT_ROOTS) {\n const found = document.querySelector(selector);\n if (found) {\n return found;\n }\n }\n return null;\n};\n\n/**\n * The `<a>` elements that sit in the page's prose rather than its chrome.\n *\n * This split is what makes the link graph mean anything. Blume renders a sidebar\n * linking every nav page from every page, so a graph that treats a sidebar link\n * the same as a body link finds that every page has hundreds of inbound links\n * and no page is ever an orphan.\n */\nconst contentLinks = (document: HtmlDocument): Set<unknown> => {\n const root = contentRoot(document);\n if (!root) {\n return new Set();\n }\n return new Set(\n root.querySelectorAll(\"a[href]\").filter((anchor) => !anchor.closest(CHROME))\n );\n};\n\nconst collectAssets = (\n document: HtmlDocument,\n selector: string,\n srcAttr: string\n): SnapshotAsset[] =>\n document\n .querySelectorAll(selector)\n .map((element) => ({\n alt: element.getAttribute(\"alt\") ?? null,\n height: attr(element, \"height\"),\n src: element.getAttribute(srcAttr)?.trim() ?? \"\",\n width: attr(element, \"width\"),\n }))\n .filter((asset) => asset.src.length > 0);\n\n/** Parse each JSON-LD block, keeping the parse failures rather than dropping them. */\nconst collectJsonLd = (document: HtmlDocument) => {\n const jsonld: unknown[] = [];\n const jsonldErrors: string[] = [];\n for (const script of document.querySelectorAll(\n 'script[type=\"application/ld+json\"]'\n )) {\n try {\n jsonld.push(JSON.parse(script.rawText));\n } catch (error) {\n jsonldErrors.push(error instanceof Error ? error.message : String(error));\n }\n }\n return { jsonld, jsonldErrors };\n};\n\n/** `meta` tags whose key lives in `property` (Open Graph) or `name` (X). */\nconst prefixedMeta = (\n document: HtmlDocument,\n keyAttr: \"property\" | \"name\",\n prefix: string\n) => {\n const found: Record<string, string> = {};\n for (const element of document.querySelectorAll(\n `meta[${keyAttr}^=\"${prefix}\"]`\n )) {\n const key = element.getAttribute(keyAttr)?.trim();\n const content = element.getAttribute(\"content\")?.trim();\n if (key && content) {\n found[key] = content;\n }\n }\n return found;\n};\n\n/**\n * Reduce one built HTML page to everything the checks read. Parsed once — every\n * check works off this record, so a page is never re-parsed per check.\n */\nexport const buildSnapshot = (options: {\n file: string;\n url: string;\n html: string;\n route?: RouteManifestEntry;\n}): PageSnapshot => {\n const { file, url, html, route } = options;\n const document = parseHtml(html);\n\n const inContent = contentLinks(document);\n const links: SnapshotLink[] = document\n .querySelectorAll(\"a[href]\")\n .map((anchor) => ({\n content: inContent.has(anchor),\n href: anchor.getAttribute(\"href\")?.trim() ?? \"\",\n rel: attr(anchor, \"rel\"),\n text: anchor.text.trim(),\n }))\n .filter((link) => link.href.length > 0);\n\n const root = contentRoot(document);\n const prose = root ? visibleText(root) : \"\";\n const robots = document.querySelector('meta[name=\"robots\"]');\n const lang = attr(document.querySelector(\"html\") ?? document, \"lang\");\n const { jsonld, jsonldErrors } = collectJsonLd(document);\n\n return {\n bytes: Buffer.byteLength(html, \"utf-8\"),\n canonical:\n document\n .querySelector('link[rel=\"canonical\"]')\n ?.getAttribute(\"href\")\n ?.trim() ?? null,\n contentHash: createHash(\"sha256\").update(prose).digest(\"hex\").slice(0, 16),\n descriptions: metaContents(document, 'meta[name=\"description\"]'),\n file,\n headings: document\n .querySelectorAll(\"h1, h2, h3, h4, h5, h6\")\n .map((heading) => ({\n depth: Number(heading.tagName.slice(1)),\n text: heading.text.trim(),\n })),\n hreflang: document\n .querySelectorAll(\"link[rel=alternate][hreflang]\")\n .map((link) => ({\n href: link.getAttribute(\"href\")?.trim() ?? \"\",\n lang: link.getAttribute(\"hreflang\")?.trim() ?? \"\",\n }))\n .filter((alternate) => alternate.href && alternate.lang),\n ids: new Set(\n document\n .querySelectorAll(\"[id]\")\n .map((element) => element.getAttribute(\"id\") ?? \"\")\n .filter((id) => id.length > 0)\n ),\n images: collectAssets(document, \"img[src]\", \"src\"),\n // A page is indexable unless it says otherwise. Blume only ever emits\n // `noindex` (never `nofollow`), but an ejected layout could emit either.\n indexable: !robots?.getAttribute(\"content\")?.includes(\"noindex\"),\n jsonld,\n jsonldErrors,\n lang,\n links,\n metaRefresh:\n document\n .querySelector('meta[http-equiv=\"refresh\" i]')\n ?.getAttribute(\"content\")\n ?.trim() ?? null,\n og: prefixedMeta(document, \"property\", \"og:\"),\n robots: robots?.getAttribute(\"content\")?.trim() ?? null,\n route,\n scripts: collectAssets(document, \"script[src]\", \"src\"),\n source: route?.sourcePath,\n styles: collectAssets(document, 'link[rel=\"stylesheet\"][href]', \"href\"),\n titles: document\n .querySelectorAll(\"title\")\n .map((title) => title.text.trim())\n .filter((text) => text.length > 0),\n twitter: prefixedMeta(document, \"name\", \"twitter:\"),\n url,\n viewport:\n document\n .querySelector('meta[name=\"viewport\"]')\n ?.getAttribute(\"content\")\n ?.trim() ?? null,\n wordCount: countWords(prose, lang),\n };\n};\n",
|
|
28
|
-
"import { parse } from \"node-html-parser\";\nimport type { HTMLElement } from \"node-html-parser\";\n\n/**\n * A parsed HTML document. Re-exported so the rest of the audit types against\n * this module rather than the parser: `node-html-parser` is deliberately\n * quarantined here, so swapping it stays a one-file change.\n */\nexport type HtmlDocument = HTMLElement;\n\n/**\n * Parse built HTML into a queryable tree.\n *\n * `blockTextElements` keeps the raw text of `<script>`/`<style>`/`<pre>` intact\n * instead of parsing it as markup — the audit reads `<script\n * type=\"application/ld+json\">` bodies verbatim to validate structured data.\n */\nexport const parseHtml = (html: string): HtmlDocument =>\n parse(html, {\n blockTextElements: { noscript: true, pre: true, script: true, style: true },\n comment: false,\n });\n\n/** Trimmed attribute value, or null when absent or empty. */\nexport const attr = (element: HTMLElement, name: string): string | null => {\n const value = element.getAttribute(name)?.trim();\n return value || null;\n};\n\n/** `content` of every `<meta>` matching a selector, in document order. */\nexport const metaContents = (\n document: HtmlDocument,\n selector: string\n): string[] =>\n document\n .querySelectorAll(selector)\n .map((element) => element.getAttribute(\"content\")?.trim() ?? \"\")\n .filter((value) => value.length > 0);\n\n/**\n * Collapse an element's visible text. Script/style bodies and code blocks are\n * dropped: a fenced code sample isn't prose, and counting it would let a page\n * that is 90% code pass the word-count check on the strength of its snippets.\n */\nexport const visibleText = (element: HTMLElement): string => {\n const clone = parse(element.outerHTML, {\n blockTextElements: { noscript: true, pre: true, script: true, style: true },\n comment: false,\n });\n for (const node of clone.querySelectorAll(\"script, style, pre, code\")) {\n node.remove();\n }\n return clone.structuredText.replaceAll(/\\s+/gu, \" \").trim();\n};\n",
|
|
29
|
-
"import type { RedirectResolution } from \"./types.ts\";\nimport { normalizePath } from \"./url.ts\";\n\ninterface ConfiguredRedirect {\n from: string;\n to: string;\n status: number;\n}\n\n/**\n * Follow every configured redirect through to its destination, classifying what\n * it lands on.\n *\n * - `loop` — the chain revisits a hop it has already been to. Never resolves.\n * - `broken` — the chain ends somewhere the build does not serve.\n * - `chain` — it resolves, but through at least one intermediate redirect.\n * - `ok` — one hop, straight to a real page.\n *\n * An external destination (`https://…`) is always `ok`: it's outside the site,\n * so there's no local page to check it against.\n */\n/**\n * A destination's page path: the part before any query string or fragment. A\n * redirect to `/guide#setup` or `/search?q=x` lands on the `/guide` / `/search`\n * page — the suffix belongs to the browser, not the file tree, so keeping it\n * would report a working redirect as broken.\n */\nconst pathOnly = (value: string): string => {\n const cut = value.search(/[?#]/u);\n return cut === -1 ? value : value.slice(0, cut);\n};\n\nexport const resolveRedirects = (\n redirects: readonly ConfiguredRedirect[],\n /** Whether the build serves a normalized path — see `isServed`. */\n served: (path: string) => boolean\n): RedirectResolution[] => {\n const byFrom = new Map<string, ConfiguredRedirect>();\n for (const redirect of redirects) {\n byFrom.set(normalizePath(redirect.from), redirect);\n }\n\n return redirects.map((redirect) => {\n const from = normalizePath(redirect.from);\n const chain: string[] = [from];\n const seen = new Set<string>([from]);\n let current = redirect.to;\n\n for (;;) {\n // An external hop ends the walk — we can't follow it locally.\n if (/^https?:\\/\\//iu.test(current)) {\n chain.push(current);\n break;\n }\n const next = normalizePath(pathOnly(current));\n if (seen.has(next)) {\n chain.push(next);\n return {\n ...redirect,\n chain,\n outcome: \"loop\" as const,\n };\n }\n chain.push(next);\n seen.add(next);\n const hop = byFrom.get(next);\n if (!hop) {\n break;\n }\n current = hop.to;\n }\n\n const destination = chain.at(-1) ?? from;\n const external = /^https?:\\/\\//iu.test(destination);\n if (!(external || served(destination))) {\n return { ...redirect, chain, outcome: \"broken\" as const };\n }\n // `chain` is [from, …hops, destination]; more than two entries means at\n // least one intermediate redirect.\n return {\n ...redirect,\n chain,\n outcome: chain.length > 2 ? (\"chain\" as const) : (\"ok\" as const),\n };\n });\n};\n",
|
|
30
|
-
"import { CHECKS } from \"./catalog.ts\";\nimport type { CheckId } from \"./catalog.ts\";\n\n/** A check id without its `BLUME_AUDIT_` prefix, lowercased: `link_to_broken`. */\nexport const shortId = (id: CheckId): string =>\n id.replace(\"BLUME_AUDIT_\", \"\").toLowerCase();\n\n/**\n * Every term `--only`/`--skip` accept, as the audit's filter compares them:\n * each check's short id and each category. The full `BLUME_AUDIT_…` id is\n * accepted too, but it's the short id with a prefix, so it isn't listed.\n */\nexport const checkTerms = (): string[] => [\n ...new Set([\n ...CHECKS.map((check) => check.category),\n ...CHECKS.map((check) => shortId(check.id)),\n ]),\n];\n\n/**\n * The `--only`/`--skip` terms that name no check and no category. A typo there\n * (`--only link` for `links`) would otherwise match nothing, report nothing,\n * and pass a CI gate that was meant to catch something.\n */\nexport const unknownCheckTerms = (terms: readonly string[]): string[] => {\n const known = new Set(checkTerms());\n return terms.filter((raw) => {\n const term = raw.trim().toLowerCase();\n return !(known.has(term) || known.has(term.replace(/^blume_audit_/u, \"\")));\n });\n};\n"
|
|
31
|
-
],
|
|
32
|
-
"mappings": "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",
|
|
33
|
-
"debugId": "5DFBDEAD3AD54A7664756E2164756E21",
|
|
34
|
-
"names": []
|
|
35
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
distDir,
|
|
4
|
-
deployPlatform
|
|
5
|
-
} from "./chunk-qkqwkpte.js";
|
|
6
|
-
|
|
7
|
-
// src/deploy/adapter-output.ts
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { cp, mkdir, rm } from "node:fs/promises";
|
|
10
|
-
import { dirname, join } from "pathe";
|
|
11
|
-
var deployOutputDir = (config, context) => {
|
|
12
|
-
const { deployment } = config;
|
|
13
|
-
return deployment.options.output === "server" ? deployPlatform(deployment).serverOutputDir(context) : distDir(context);
|
|
14
|
-
};
|
|
15
|
-
var deployStaticDir = (config, context) => {
|
|
16
|
-
const { deployment } = config;
|
|
17
|
-
return deployment.options.output === "server" ? deployPlatform(deployment).serverStaticDir(context) : distDir(context);
|
|
18
|
-
};
|
|
19
|
-
var surfaceAdapterOutput = async (config, context) => {
|
|
20
|
-
const { deployment } = config;
|
|
21
|
-
if (deployment.options.output !== "server") {
|
|
22
|
-
return { moved: false };
|
|
23
|
-
}
|
|
24
|
-
const rel = deployPlatform(deployment).hiddenRuntime.surfacePath;
|
|
25
|
-
if (!rel) {
|
|
26
|
-
return { moved: false };
|
|
27
|
-
}
|
|
28
|
-
const from = join(context.outDir, rel);
|
|
29
|
-
const to = join(context.root, rel);
|
|
30
|
-
if (!existsSync(from)) {
|
|
31
|
-
return { moved: false };
|
|
32
|
-
}
|
|
33
|
-
await mkdir(dirname(to), { recursive: true });
|
|
34
|
-
await rm(to, { force: true, recursive: true });
|
|
35
|
-
try {
|
|
36
|
-
await cp(from, to, { recursive: true, verbatimSymlinks: true });
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (error.code !== "EPERM") {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
await rm(to, { force: true, recursive: true });
|
|
42
|
-
await cp(from, to, { dereference: true, recursive: true });
|
|
43
|
-
}
|
|
44
|
-
await rm(from, { force: true, recursive: true });
|
|
45
|
-
return { from, moved: true, to };
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export { deployOutputDir, deployStaticDir, surfaceAdapterOutput };
|
|
49
|
-
|
|
50
|
-
//# debugId=8B79889B6B3550F964756E2164756E21
|
|
51
|
-
//# sourceMappingURL=chunk-82bbrxdn.js.map
|