blume 2.1.3 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +134 -0
- package/README.md +4 -4
- package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
- package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
- package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
- package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
- package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
- package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
- package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
- package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
- package/dist/cli/chunk-487b95qy.js +329 -0
- package/dist/cli/chunk-487b95qy.js.map +13 -0
- package/dist/cli/chunk-4zxh25hc.js +157 -0
- package/dist/cli/chunk-4zxh25hc.js.map +10 -0
- package/dist/cli/chunk-69rjf786.js +146 -0
- package/dist/cli/chunk-69rjf786.js.map +11 -0
- package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
- package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
- package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
- package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
- package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
- package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
- package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
- package/dist/cli/chunk-b680sv30.js.map +13 -0
- package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
- package/dist/cli/chunk-beb0z983.js.map +10 -0
- package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
- package/dist/cli/chunk-e1n41w9m.js +155 -0
- package/dist/cli/chunk-e1n41w9m.js.map +11 -0
- package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
- package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
- package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
- package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
- package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
- package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
- package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
- package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
- package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
- package/dist/cli/chunk-hktq4j5r.js.map +36 -0
- package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
- package/dist/cli/chunk-hn3g2b89.js.map +10 -0
- package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
- package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
- package/dist/cli/chunk-k45sq16q.js.map +18 -0
- package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
- package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
- package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
- package/dist/cli/chunk-nqna8dzp.js.map +10 -0
- package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
- package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
- package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
- package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
- package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
- package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
- package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
- package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
- package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
- package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
- package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
- package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
- package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
- package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
- package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
- package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
- package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
- package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
- package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
- package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
- package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
- package/dist/cli/index.js +78 -42
- package/dist/cli/index.js.map +7 -6
- package/dist/types/ai/changelog-components.d.ts +18 -0
- package/dist/types/ai/changelog-markdown.d.ts +12 -0
- package/dist/types/ai/openapi-components.d.ts +7 -7
- package/dist/types/ai/operation-details.d.ts +39 -0
- package/dist/types/ai/relative-links.d.ts +3 -1
- package/dist/types/ai/serializers.d.ts +6 -4
- package/dist/types/analytics/head.d.ts +4 -2
- package/dist/types/analytics/script.d.ts +8 -2
- package/dist/types/components/content/youtube.d.ts +20 -0
- package/dist/types/components/layout/nav-utils.d.ts +8 -0
- package/dist/types/components/openapi/example-panels.d.ts +33 -0
- package/dist/types/components/openapi/helpers.d.ts +53 -11
- package/dist/types/components/openapi/snippets.d.ts +7 -1
- package/dist/types/core/alert-diagnostics.d.ts +10 -0
- package/dist/types/core/builtin-tags.d.ts +16 -0
- package/dist/types/core/changelog-list.d.ts +48 -0
- package/dist/types/core/closest-match.d.ts +6 -0
- package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
- package/dist/types/core/code-fences.d.ts +8 -1
- package/dist/types/core/config-input.d.ts +38 -6
- package/dist/types/core/content-assets.d.ts +16 -18
- package/dist/types/core/data.d.ts +6 -2
- package/dist/types/core/diagnostics.d.ts +14 -0
- package/dist/types/core/directive-diagnostics.d.ts +22 -8
- package/dist/types/core/former-routes.d.ts +14 -0
- package/dist/types/core/github.d.ts +5 -4
- package/dist/types/core/heading-markers.d.ts +65 -2
- package/dist/types/core/hidden-pages.d.ts +3 -0
- package/dist/types/core/includes.d.ts +23 -0
- package/dist/types/core/last-modified.d.ts +18 -6
- package/dist/types/core/links.d.ts +42 -2
- package/dist/types/core/load-module.d.ts +19 -4
- package/dist/types/core/manifest.d.ts +14 -1
- package/dist/types/core/markdown-mirrors.d.ts +22 -0
- package/dist/types/core/mcp-clients.d.ts +47 -0
- package/dist/types/core/mdx-elements.d.ts +32 -0
- package/dist/types/core/mdx-syntax.d.ts +100 -0
- package/dist/types/core/meta-diagnostics.d.ts +7 -0
- package/dist/types/core/meta.d.ts +32 -5
- package/dist/types/core/nav-diagnostics.d.ts +16 -2
- package/dist/types/core/navigation.d.ts +13 -0
- package/dist/types/core/ordering-prefix.d.ts +6 -0
- package/dist/types/core/pinned-ids.d.ts +23 -0
- package/dist/types/core/project-graph.d.ts +9 -1
- package/dist/types/core/redirect-patterns.d.ts +18 -0
- package/dist/types/core/relative-files.d.ts +31 -0
- package/dist/types/core/schema.d.ts +322 -11
- package/dist/types/core/sources/normalize.d.ts +101 -9
- package/dist/types/core/sources/types.d.ts +16 -0
- package/dist/types/core/static-files.d.ts +10 -0
- package/dist/types/core/static-host.d.ts +42 -0
- package/dist/types/core/svg-images.d.ts +26 -0
- package/dist/types/core/syntax-diagnostics.d.ts +14 -0
- package/dist/types/core/types.d.ts +42 -1
- package/dist/types/deploy/generated-files.d.ts +41 -0
- package/dist/types/deploy/redirects.d.ts +21 -7
- package/dist/types/markdown/code-title.d.ts +9 -5
- package/dist/types/markdown/directives.d.ts +8 -0
- package/dist/types/markdown/fence-language.d.ts +51 -0
- package/dist/types/markdown/fence-meta.d.ts +5 -0
- package/dist/types/markdown/github-alerts.d.ts +35 -0
- package/dist/types/openapi/asyncapi.d.ts +6 -5
- package/dist/types/openapi/checks.d.ts +10 -5
- package/dist/types/openapi/model.d.ts +55 -8
- package/dist/types/openapi/parse.d.ts +4 -1
- package/dist/types/ratelimit/index.d.ts +2 -0
- package/dist/types/ratelimit/memory.d.ts +1 -1
- package/dist/types/ratelimit/schema.d.ts +40 -0
- package/dist/types/ratelimit/unkey.d.ts +48 -0
- package/dist/types/sources/filesystem.d.ts +14 -4
- package/dist/types/sources/mdx-remote.d.ts +6 -3
- package/dist/types/sources/registry.d.ts +3 -3
- package/dist/types/theme/color.d.ts +33 -0
- package/dist/types/theme/palette.d.ts +122 -0
- package/docs/01-quickstart.mdx +1 -1
- package/docs/02-deployment.mdx +24 -8
- package/docs/04-migrating.mdx +16 -4
- package/docs/advanced/changelog.mdx +27 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +1 -1
- package/docs/cli/index.mdx +4 -2
- package/docs/cli/validate.mdx +10 -7
- package/docs/configuration/analytics.mdx +2 -0
- package/docs/configuration/customization.mdx +3 -1
- package/docs/configuration/index.mdx +7 -6
- package/docs/configuration/rate-limiting.mdx +20 -1
- package/docs/configuration/theming.mdx +5 -1
- package/docs/content/components.mdx +26 -6
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/includes.mdx +6 -2
- package/docs/content/index.mdx +11 -1
- package/docs/content/islands.mdx +2 -0
- package/docs/content/meta.mdx +9 -3
- package/docs/content/navigation.mdx +14 -6
- package/docs/content/sources/obsidian.mdx +1 -1
- package/docs/content/sources/remote-mdx.mdx +3 -1
- package/docs/content/syntax.mdx +76 -8
- package/docs/content/variables.mdx +3 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/json-api.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/mcp.mdx +40 -8
- package/docs/discoverability/open-graph.mdx +1 -1
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/graphql.mdx +2 -0
- package/docs/references/openapi.mdx +45 -7
- package/package.json +4 -4
- package/skills/blume/SKILL.md +2 -2
- package/skills/blume-migrate/SKILL.md +51 -29
- package/skills/blume-migrate/references/docsify.md +262 -0
- package/skills/blume-migrate/references/docus.md +298 -0
- package/skills/blume-migrate/references/docusaurus.md +5 -5
- package/skills/blume-migrate/references/fern.md +327 -0
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/gitbook.md +267 -0
- package/skills/blume-migrate/references/github-wiki.md +259 -0
- package/skills/blume-migrate/references/jekyll.md +253 -0
- package/skills/blume-migrate/references/mdbook.md +256 -0
- package/skills/blume-migrate/references/mintlify.md +5 -5
- package/skills/blume-migrate/references/mkdocs.md +236 -0
- package/skills/blume-migrate/references/monorepo.md +20 -8
- package/skills/blume-migrate/references/nextra.md +5 -5
- package/skills/blume-migrate/references/readme.md +257 -0
- package/skills/blume-migrate/references/redocly.md +198 -0
- package/skills/blume-migrate/references/starlight.md +3 -3
- package/skills/blume-migrate/references/vitepress.md +204 -0
- package/skills/blume-migrate/references/vuepress.md +271 -0
- package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
- package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
- package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
- package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
- package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
- package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
- package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
- package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
- package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
- package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
- package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
- package/src/ai/changelog-components.ts +24 -0
- package/src/ai/changelog-markdown.ts +45 -41
- package/src/ai/component-markdown.ts +22 -9
- package/src/ai/llms.ts +36 -5
- package/src/ai/markdown.ts +2 -2
- package/src/ai/openapi-components.ts +25 -9
- package/src/ai/operation-details.ts +208 -0
- package/src/ai/relative-links.ts +48 -8
- package/src/ai/serializers.ts +7 -6
- package/src/ai/site-skill.ts +6 -2
- package/src/analytics/head.ts +11 -5
- package/src/analytics/script.ts +13 -3
- package/src/astro/generate.ts +47 -11
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +27 -1
- package/src/astro/mdx-source-errors.ts +46 -0
- package/src/astro/templates.ts +93 -189
- package/src/audit/checks/redirects.ts +5 -1
- package/src/audit/redirects.ts +36 -19
- package/src/audit/report.ts +22 -9
- package/src/audit/run.ts +12 -28
- package/src/audit/terms.ts +23 -0
- package/src/audit/url.ts +2 -2
- package/src/cli/build-failure.ts +256 -7
- package/src/cli/command-meta.ts +1 -1
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +12 -3
- package/src/cli/commands/check.ts +13 -2
- package/src/cli/commands/dev.ts +1 -1
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/init.ts +3 -3
- package/src/cli/commands/preview.ts +17 -1
- package/src/cli/commands/validate.ts +22 -10
- package/src/cli/prepare.ts +1 -1
- package/src/cli/preview-pages.ts +161 -0
- package/src/cli/required-secrets.ts +0 -1
- package/src/cli/unknown-flags.ts +1 -42
- package/src/components/code-copy.ts +51 -0
- package/src/components/code-language.ts +75 -0
- package/src/components/content/AccordionItem.astro +16 -1
- package/src/components/content/Callout.astro +11 -1
- package/src/components/content/Changelog.astro +189 -0
- package/src/components/content/CodeBlock.astro +8 -11
- package/src/components/content/Tabs.astro +16 -2
- package/src/components/content/YouTube.astro +6 -4
- package/src/components/content/code-block.ts +33 -0
- package/src/components/content/content-strings.ts +5 -1
- package/src/components/content/inline-markdown.ts +35 -8
- package/src/components/content/youtube.ts +49 -7
- package/src/components/layout/Analytics.astro +5 -1
- package/src/components/layout/NavTree.astro +8 -3
- package/src/components/layout/PageActions.astro +81 -23
- package/src/components/layout/RootLayout.astro +35 -32
- package/src/components/layout/SiteFooter.astro +31 -8
- package/src/components/layout/nav-utils.ts +14 -0
- package/src/components/layout/search/pagefind.ts +2 -1
- package/src/components/openapi/Operation.astro +4 -0
- package/src/components/openapi/PanelTabs.astro +9 -3
- package/src/components/openapi/RequestBody.astro +42 -8
- package/src/components/openapi/RequestPanel.astro +34 -53
- package/src/components/openapi/example-panels.ts +110 -0
- package/src/components/openapi/helpers.ts +195 -20
- package/src/components/openapi/operation-model.ts +63 -26
- package/src/components/openapi/request.ts +13 -0
- package/src/components/openapi/snippets.ts +14 -2
- package/src/core/alert-diagnostics.ts +89 -0
- package/src/core/builtin-tags.ts +76 -0
- package/src/core/changelog-list.ts +87 -0
- package/src/core/closest-match.ts +41 -0
- package/src/core/code-fence-diagnostics.ts +267 -0
- package/src/core/code-fences.ts +43 -7
- package/src/core/component-diagnostics.ts +65 -12
- package/src/core/config-input.ts +39 -5
- package/src/core/config.ts +46 -1
- package/src/core/content-assets.ts +64 -144
- package/src/core/data.ts +10 -3
- package/src/core/diagnostics.ts +36 -3
- package/src/core/directive-diagnostics.ts +265 -47
- package/src/core/former-routes.ts +46 -0
- package/src/core/git-ignored.ts +53 -0
- package/src/core/github.ts +5 -4
- package/src/core/heading-markers.ts +156 -2
- package/src/core/hidden-pages.ts +64 -0
- package/src/core/includes.ts +181 -41
- package/src/core/last-modified.ts +47 -10
- package/src/core/links.ts +438 -83
- package/src/core/load-module.ts +98 -9
- package/src/core/manifest.ts +12 -9
- package/src/core/markdown-mirrors.ts +52 -0
- package/src/core/mcp-clients.ts +87 -0
- package/src/core/mdx-elements.ts +153 -0
- package/src/core/mdx-syntax.ts +377 -0
- package/src/core/meta-diagnostics.ts +134 -0
- package/src/core/meta.ts +154 -55
- package/src/core/nav-diagnostics.ts +41 -4
- package/src/core/navigation.ts +70 -10
- package/src/core/ordering-prefix.ts +22 -2
- package/src/core/pinned-ids.ts +141 -0
- package/src/core/project-graph.ts +221 -29
- package/src/core/redirect-patterns.ts +41 -0
- package/src/core/relative-files.ts +145 -0
- package/src/core/schema.ts +110 -6
- package/src/core/sources/mdx-remote.ts +75 -14
- package/src/core/sources/normalize.ts +885 -179
- package/src/core/sources/notion.ts +5 -4
- package/src/core/sources/obsidian.ts +2 -5
- package/src/core/sources/types.ts +17 -0
- package/src/core/static-files.ts +40 -0
- package/src/core/static-host.ts +125 -0
- package/src/core/svg-images.ts +98 -0
- package/src/core/syntax-diagnostics.ts +491 -0
- package/src/core/types.ts +43 -1
- package/src/deploy/artifacts.ts +2 -28
- package/src/deploy/generated-files.ts +128 -0
- package/src/deploy/platforms/vercel.ts +25 -1
- package/src/deploy/redirects.ts +43 -16
- package/src/deploy/sitemap.ts +2 -1
- package/src/markdown/base-links.ts +87 -6
- package/src/markdown/code-title.ts +29 -13
- package/src/markdown/content-assets.ts +210 -0
- package/src/markdown/directives.ts +69 -5
- package/src/markdown/fence-language.ts +106 -0
- package/src/markdown/fence-meta.ts +42 -0
- package/src/markdown/github-alerts.ts +172 -0
- package/src/markdown/heading-anchors.ts +265 -56
- package/src/markdown/include.ts +30 -3
- package/src/markdown/index.ts +145 -40
- package/src/markdown/prompt-text.ts +95 -0
- package/src/markdown/relative-links.ts +37 -2
- package/src/markdown/route-snapshot.ts +22 -12
- package/src/markdown/svg-fallback.ts +51 -0
- package/src/markdown/title-heading.ts +134 -0
- package/src/migrate/migrate.ts +127 -2
- package/src/og/card.ts +14 -4
- package/src/openapi/asyncapi.ts +14 -6
- package/src/openapi/checks.ts +160 -15
- package/src/openapi/model.ts +172 -33
- package/src/openapi/parse.ts +13 -4
- package/src/openapi/render-mdx.ts +55 -9
- package/src/openapi/source.ts +87 -6
- package/src/ratelimit/index.ts +2 -0
- package/src/ratelimit/memory.ts +1 -1
- package/src/ratelimit/runtime.ts +81 -0
- package/src/ratelimit/schema.ts +2 -0
- package/src/ratelimit/unkey.ts +61 -0
- package/src/registry/eject.ts +0 -1
- package/src/registry/registry.ts +1 -1
- package/src/search/documents.ts +10 -2
- package/src/sources/filesystem.ts +28 -3
- package/src/sources/mdx-remote.ts +7 -15
- package/src/theme/color.ts +14 -0
- package/src/theme/entry.ts +22 -1
- package/src/theme/palette.ts +74 -1
- package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
- package/dist/cli/chunk-0w6hs479.js.map +0 -10
- package/dist/cli/chunk-150jk7km.js +0 -52
- package/dist/cli/chunk-150jk7km.js.map +0 -10
- package/dist/cli/chunk-36bxs9c7.js.map +0 -10
- package/dist/cli/chunk-53jqw8z0.js +0 -89
- package/dist/cli/chunk-53jqw8z0.js.map +0 -10
- package/dist/cli/chunk-60hz265r.js.map +0 -10
- package/dist/cli/chunk-desvm72z.js.map +0 -14
- package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
- package/dist/cli/chunk-es8h0v3j.js +0 -441
- package/dist/cli/chunk-es8h0v3j.js.map +0 -11
- package/dist/cli/chunk-kdpzw38a.js.map +0 -11
- package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
- package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
- package/dist/cli/chunk-twy7psdz.js.map +0 -10
- package/dist/cli/chunk-ydp3065p.js +0 -64
- package/dist/cli/chunk-ydp3065p.js.map +0 -10
- /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
- /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
- /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
- /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
- /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
- /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
- /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
- /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
- /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
- /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
- /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import { isAbsolute, join, relative } from "pathe";
|
|
4
|
+
|
|
5
|
+
import { alertDiagnostics } from "./alert-diagnostics.ts";
|
|
3
6
|
import { normalizePath, withBasePath } from "./base-path.ts";
|
|
4
7
|
import { CHANGELOG_INDEX_ROUTE, hasChangelogIndex } from "./changelog-index.ts";
|
|
8
|
+
import { codeFenceDiagnostics } from "./code-fence-diagnostics.ts";
|
|
5
9
|
import { loadConfig } from "./config.ts";
|
|
6
10
|
import { customStaticRoutes, discoverPages } from "./custom-pages.ts";
|
|
7
|
-
import {
|
|
11
|
+
import { directiveDiagnostics } from "./directive-diagnostics.ts";
|
|
12
|
+
import { formerRouteRedirects } from "./former-routes.ts";
|
|
8
13
|
import { buildContentGraph } from "./graph.ts";
|
|
9
14
|
import { i18nDiagnostics } from "./i18n.ts";
|
|
10
15
|
import { expandIncludes, hasIncludeStatements } from "./includes.ts";
|
|
@@ -15,8 +20,13 @@ import {
|
|
|
15
20
|
realPath,
|
|
16
21
|
resolveLastModifiedConfig,
|
|
17
22
|
} from "./last-modified.ts";
|
|
23
|
+
import { routeSetFor } from "./locale-links.ts";
|
|
24
|
+
import type { RouteSet } from "./locale-links.ts";
|
|
18
25
|
import { buildManifest } from "./manifest.ts";
|
|
19
|
-
import {
|
|
26
|
+
import { mirroredPage } from "./markdown-mirrors.ts";
|
|
27
|
+
import { mdxSyntaxCheck } from "./mdx-syntax.ts";
|
|
28
|
+
import { folderMetaDiagnostics } from "./meta-diagnostics.ts";
|
|
29
|
+
import { discoverFolderMeta, withGeneratedFolderMeta } from "./meta.ts";
|
|
20
30
|
import type { FolderMetaSource } from "./meta.ts";
|
|
21
31
|
import { resolveProjectContext } from "./project.ts";
|
|
22
32
|
import { isPatternPath, pathsUnderPattern } from "./redirect-patterns.ts";
|
|
@@ -29,6 +39,8 @@ import type {
|
|
|
29
39
|
SourceEntry,
|
|
30
40
|
SourceLoadResult,
|
|
31
41
|
} from "./sources/types.ts";
|
|
42
|
+
import { unmeasurableSvgDiagnostics } from "./svg-images.ts";
|
|
43
|
+
import { indexPageNames, syntaxDiagnostics } from "./syntax-diagnostics.ts";
|
|
32
44
|
import type {
|
|
33
45
|
BlumeManifest,
|
|
34
46
|
ContentGraph,
|
|
@@ -96,8 +108,16 @@ export interface BlumeProject {
|
|
|
96
108
|
graph: ContentGraph;
|
|
97
109
|
manifest: BlumeManifest;
|
|
98
110
|
diagnostics: Diagnostic[];
|
|
99
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Entries excluded from the graph because their frontmatter failed
|
|
113
|
+
* validation, or (in a build) their MDX doesn't parse.
|
|
114
|
+
*/
|
|
100
115
|
droppedPages: number;
|
|
116
|
+
/**
|
|
117
|
+
* The local `.mdx` files a build dropped because they don't parse, which
|
|
118
|
+
* the docs collection leaves out so Astro never compiles them.
|
|
119
|
+
*/
|
|
120
|
+
unparsable: string[];
|
|
101
121
|
/** The instantiated content sources, for lazy entry reads (search/AI/raw). */
|
|
102
122
|
sources: ContentSource[];
|
|
103
123
|
/**
|
|
@@ -162,11 +182,14 @@ const entryIdDiagnostics = (
|
|
|
162
182
|
* collecting pages, diagnostics, and the count of entries dropped outright —
|
|
163
183
|
* an entry that yields no pages but did yield diagnostics was rejected for
|
|
164
184
|
* invalid frontmatter, and callers surface that count so a build with missing
|
|
165
|
-
* pages can't read as clean.
|
|
185
|
+
* pages can't read as clean. With `dropUnparsable`, an `.mdx` page whose own
|
|
186
|
+
* text doesn't parse is dropped too, and its file listed in `unparsable` so
|
|
187
|
+
* the docs collection leaves it out.
|
|
166
188
|
*/
|
|
167
189
|
const normalizeLoadedEntries = (
|
|
168
190
|
loaded: ({ source: ContentSource } & SourceLoadResult)[],
|
|
169
|
-
config: ResolvedConfig
|
|
191
|
+
config: ResolvedConfig,
|
|
192
|
+
dropUnparsable: boolean
|
|
170
193
|
) => {
|
|
171
194
|
// Only thread `frontmatter.extend` / `content.types` through when a project
|
|
172
195
|
// opts in, so the known-key split in `normalizeEntry` stays off the default
|
|
@@ -187,10 +210,25 @@ const normalizeLoadedEntries = (
|
|
|
187
210
|
|
|
188
211
|
const pages: PageRecord[] = [];
|
|
189
212
|
const allDiagnostics: Diagnostic[] = [];
|
|
213
|
+
const syntaxErrors: Diagnostic[] = [];
|
|
214
|
+
const unparsable: string[] = [];
|
|
215
|
+
// Published entries, checked for other tools' syntax once every page is
|
|
216
|
+
// known: a wiki link is reported when it names one.
|
|
217
|
+
const published: { entry: SourceEntry; locale: string; source: string }[] =
|
|
218
|
+
[];
|
|
190
219
|
let droppedPages = 0;
|
|
191
220
|
for (const { source, entries, diagnostics } of loaded) {
|
|
192
221
|
allDiagnostics.push(...diagnostics);
|
|
193
222
|
for (const entry of entries) {
|
|
223
|
+
const syntax = mdxSyntaxCheck(entry, source.name, config.variables);
|
|
224
|
+
syntaxErrors.push(...syntax.diagnostics);
|
|
225
|
+
// A build drops a page whose MDX doesn't parse, as it does one whose
|
|
226
|
+
// front matter fails: with --no-strict the rest of the site still
|
|
227
|
+
// builds. Dev keeps it, so opening it shows the error in place.
|
|
228
|
+
const drop = syntax.unparsable && dropUnparsable;
|
|
229
|
+
if (drop && entry.sourcePath && !source.staged) {
|
|
230
|
+
unparsable.push(entry.sourcePath);
|
|
231
|
+
}
|
|
194
232
|
const normalized = normalizeEntry(entry, {
|
|
195
233
|
basePath: config.basePath,
|
|
196
234
|
defaultType: config.content.defaultType,
|
|
@@ -206,17 +244,41 @@ const normalizeLoadedEntries = (
|
|
|
206
244
|
typeFrontmatter,
|
|
207
245
|
versions: config.versions,
|
|
208
246
|
});
|
|
209
|
-
|
|
247
|
+
const [page] = normalized.pages;
|
|
248
|
+
if (page ? drop : normalized.diagnostics.length > 0) {
|
|
210
249
|
droppedPages += 1;
|
|
211
250
|
}
|
|
212
|
-
|
|
251
|
+
if (!drop) {
|
|
252
|
+
pages.push(...normalized.pages);
|
|
253
|
+
}
|
|
213
254
|
allDiagnostics.push(...normalized.diagnostics);
|
|
214
|
-
if (
|
|
215
|
-
allDiagnostics.push(
|
|
255
|
+
if (page) {
|
|
256
|
+
allDiagnostics.push(
|
|
257
|
+
...directiveDiagnostics(entry, source.name),
|
|
258
|
+
...codeFenceDiagnostics(entry, source.name),
|
|
259
|
+
...alertDiagnostics(entry, source.name)
|
|
260
|
+
);
|
|
261
|
+
published.push({ entry, locale: page.locale, source: source.name });
|
|
216
262
|
}
|
|
217
263
|
}
|
|
218
264
|
}
|
|
219
|
-
|
|
265
|
+
const names = indexPageNames(pages);
|
|
266
|
+
for (const { entry, locale, source } of published) {
|
|
267
|
+
allDiagnostics.push(...syntaxDiagnostics(entry, source, names, locale));
|
|
268
|
+
}
|
|
269
|
+
// A parse error gives way to a diagnostic as severe at its line, which
|
|
270
|
+
// names the cause (`BLUME_MDX_CURLY_ANCHOR` for a `{#id}` heading marker,
|
|
271
|
+
// `BLUME_MDX_ATTRIBUTE_LIST` in a partial).
|
|
272
|
+
const covered = (diagnostic: Diagnostic): boolean =>
|
|
273
|
+
allDiagnostics.some(
|
|
274
|
+
(other) =>
|
|
275
|
+
other.file === diagnostic.file &&
|
|
276
|
+
other.line === diagnostic.line &&
|
|
277
|
+
(other.severity === "error" ||
|
|
278
|
+
(other.severity === "warning" && diagnostic.severity === "warning"))
|
|
279
|
+
);
|
|
280
|
+
allDiagnostics.push(...syntaxErrors.filter((error) => !covered(error)));
|
|
281
|
+
return { diagnostics: allDiagnostics, droppedPages, pages, unparsable };
|
|
220
282
|
};
|
|
221
283
|
|
|
222
284
|
/**
|
|
@@ -315,29 +377,45 @@ const substituteLoadedVariables = (
|
|
|
315
377
|
* Astro ranks its route above the page's catch-all, in dev and build alike,
|
|
316
378
|
* and writes the redirect page where the page would go, so the page never
|
|
317
379
|
* publishes. That's a warning rather than an error, since the outcome doesn't
|
|
318
|
-
* depend on the host.
|
|
380
|
+
* depend on the host. So is one from a page's Markdown copy (`/guide.md`, or
|
|
381
|
+
* the root's `/index.md`), which it takes over the same way. `mirrored` are
|
|
382
|
+
* the routes served with copies: the content pages.
|
|
319
383
|
*/
|
|
320
384
|
const redirectPageDiagnostics = (
|
|
321
385
|
config: ResolvedConfig,
|
|
322
386
|
pages: readonly string[],
|
|
387
|
+
mirrored: RouteSet,
|
|
323
388
|
configFile: string | null
|
|
324
389
|
): Diagnostic[] =>
|
|
325
390
|
config.redirects.flatMap((redirect): Diagnostic[] => {
|
|
326
391
|
const from = withBasePath(config.basePath, redirect.from);
|
|
327
392
|
if (!isPatternPath(redirect.from)) {
|
|
328
393
|
const page = normalizePath(from);
|
|
329
|
-
|
|
330
|
-
|
|
394
|
+
if (pages.includes(page)) {
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
398
|
+
file: configFile ?? undefined,
|
|
399
|
+
message: `The redirect from ${redirect.from} is also the page ${page}, which never publishes: its URL redirects to ${redirect.to} instead.`,
|
|
400
|
+
severity: "warning",
|
|
401
|
+
suggestion:
|
|
402
|
+
"If the page moved, delete it or give it a new slug; otherwise remove the redirect.",
|
|
403
|
+
} satisfies Diagnostic,
|
|
404
|
+
];
|
|
405
|
+
}
|
|
406
|
+
const owner = mirroredPage(mirrored, page);
|
|
407
|
+
return owner === undefined
|
|
408
|
+
? []
|
|
409
|
+
: [
|
|
331
410
|
{
|
|
332
411
|
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
333
412
|
file: configFile ?? undefined,
|
|
334
|
-
message: `The redirect from ${redirect.from} is also the page ${
|
|
413
|
+
message: `The redirect from ${redirect.from} is also the Markdown copy of the page ${owner}, so that copy never publishes: its URL redirects to ${redirect.to} instead.`,
|
|
335
414
|
severity: "warning",
|
|
336
415
|
suggestion:
|
|
337
|
-
"
|
|
416
|
+
"Remove the redirect: that URL serves the page's Markdown to agents.",
|
|
338
417
|
} satisfies Diagnostic,
|
|
339
|
-
]
|
|
340
|
-
: [];
|
|
418
|
+
];
|
|
341
419
|
}
|
|
342
420
|
const matched = pathsUnderPattern(from, pages);
|
|
343
421
|
const [first] = matched;
|
|
@@ -357,6 +435,83 @@ const redirectPageDiagnostics = (
|
|
|
357
435
|
];
|
|
358
436
|
});
|
|
359
437
|
|
|
438
|
+
/**
|
|
439
|
+
* The config with the redirects sources ask for added after its own (see
|
|
440
|
+
* `SourceLoadResult.redirects`), so every surface that reads
|
|
441
|
+
* `config.redirects` — Astro's redirect pages, the host files, the link
|
|
442
|
+
* checker — serves them alike. Each is repeated under every locale prefix
|
|
443
|
+
* whose copy of the target page is served (a translation, or the fallback
|
|
444
|
+
* copy), and is kept only where its target is a page and nothing else
|
|
445
|
+
* answers for its `from`: a page there, or a configured redirect, wins.
|
|
446
|
+
* `pages` are the served page paths, which carry `basePath`.
|
|
447
|
+
*/
|
|
448
|
+
const withSourceRedirects = (
|
|
449
|
+
config: ResolvedConfig,
|
|
450
|
+
loaded: readonly SourceLoadResult[],
|
|
451
|
+
pages: RouteSet
|
|
452
|
+
): ResolvedConfig => {
|
|
453
|
+
const requested = loaded.flatMap(({ redirects }) => redirects ?? []);
|
|
454
|
+
if (requested.length === 0) {
|
|
455
|
+
return config;
|
|
456
|
+
}
|
|
457
|
+
const served = (route: string): string =>
|
|
458
|
+
normalizePath(withBasePath(config.basePath, route));
|
|
459
|
+
const prefixes = [
|
|
460
|
+
"",
|
|
461
|
+
...(config.i18n?.locales.map((locale) => `/${locale.code}`) ?? []),
|
|
462
|
+
];
|
|
463
|
+
const taken = new Set(
|
|
464
|
+
config.redirects.map((redirect) => served(redirect.from))
|
|
465
|
+
);
|
|
466
|
+
const added: ResolvedConfig["redirects"] = [];
|
|
467
|
+
for (const redirect of requested) {
|
|
468
|
+
for (const prefix of prefixes) {
|
|
469
|
+
const from = `${prefix}${redirect.from}`;
|
|
470
|
+
const to = `${prefix}${redirect.to}`;
|
|
471
|
+
if (
|
|
472
|
+
!pages.has(served(to)) ||
|
|
473
|
+
pages.has(served(from)) ||
|
|
474
|
+
taken.has(served(from))
|
|
475
|
+
) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
taken.add(served(from));
|
|
479
|
+
added.push({ from, status: 301, to });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return added.length > 0
|
|
483
|
+
? { ...config, redirects: [...config.redirects, ...added] }
|
|
484
|
+
: config;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* A `public/openapi.json` is served at `/openapi.json`, where Blume
|
|
489
|
+
* publishes the OpenAPI description of the site's own JSON docs API: the
|
|
490
|
+
* build stops generating that description, while the API catalog
|
|
491
|
+
* (`/.well-known/api-catalog`) still lists `/openapi.json` as the docs API's
|
|
492
|
+
* `service-desc`, so agents reading it get the project's file instead. Only
|
|
493
|
+
* when the docs API is on; otherwise Blume publishes nothing there.
|
|
494
|
+
*/
|
|
495
|
+
const publicOpenApiDiagnostics = (
|
|
496
|
+
config: ResolvedConfig,
|
|
497
|
+
root: string
|
|
498
|
+
): Diagnostic[] => {
|
|
499
|
+
const file = join(root, "public", "openapi.json");
|
|
500
|
+
return config.agents.api && existsSync(file)
|
|
501
|
+
? [
|
|
502
|
+
{
|
|
503
|
+
code: "BLUME_PUBLIC_OPENAPI_JSON",
|
|
504
|
+
file,
|
|
505
|
+
message:
|
|
506
|
+
"public/openapi.json is served at /openapi.json, where Blume publishes the OpenAPI description of the site's JSON docs API, so that description isn't generated, and /.well-known/api-catalog still lists /openapi.json as the docs API's description.",
|
|
507
|
+
severity: "warning",
|
|
508
|
+
suggestion:
|
|
509
|
+
"Move the file to another path, like public/specs/openapi.json, and update what links to it. A spec an openapi() reference renders can live outside public/ altogether. Or set agents.api to false if the site shouldn't publish the JSON docs API.",
|
|
510
|
+
},
|
|
511
|
+
]
|
|
512
|
+
: [];
|
|
513
|
+
};
|
|
514
|
+
|
|
360
515
|
/** The configured banner link target, when the banner has a link. */
|
|
361
516
|
const bannerLinkHref = (
|
|
362
517
|
banner: ResolvedConfig["banner"]
|
|
@@ -495,19 +650,30 @@ export const scanProject = async (
|
|
|
495
650
|
// Folder meta contributed by the sources themselves (the OpenAPI source
|
|
496
651
|
// labels each tag directory with the spec's own tag name). It applies to
|
|
497
652
|
// every locale, so it merges into the shared map — beneath user-authored
|
|
498
|
-
// entries,
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
653
|
+
// entries, whose own fields win while the generated ones fill the rest.
|
|
654
|
+
const mergedFolderMeta = withGeneratedFolderMeta(
|
|
655
|
+
folderMeta,
|
|
656
|
+
new Map(
|
|
657
|
+
loaded.flatMap(({ folderMeta: sourceMeta }) =>
|
|
658
|
+
Object.entries(sourceMeta ?? {})
|
|
659
|
+
)
|
|
502
660
|
),
|
|
503
|
-
|
|
504
|
-
|
|
661
|
+
localeDirs
|
|
662
|
+
);
|
|
505
663
|
|
|
506
664
|
const {
|
|
507
665
|
diagnostics: contentDiagnostics,
|
|
508
666
|
droppedPages,
|
|
509
667
|
pages: allPages,
|
|
510
|
-
|
|
668
|
+
unparsable,
|
|
669
|
+
} = normalizeLoadedEntries(loaded, config, mode === "build");
|
|
670
|
+
|
|
671
|
+
// Checked against every page, drafts included: a `pages` entry naming a
|
|
672
|
+
// draft still orders it wherever the draft renders.
|
|
673
|
+
const metaDiagnostics = await folderMetaDiagnostics(folderMeta, allPages, {
|
|
674
|
+
localeDirs,
|
|
675
|
+
versionDirs: config.versions?.archived.map((version) => version.id),
|
|
676
|
+
});
|
|
511
677
|
|
|
512
678
|
// Drafts render in dev and in preview, but are excluded from production builds.
|
|
513
679
|
const pages =
|
|
@@ -573,10 +739,10 @@ export const scanProject = async (
|
|
|
573
739
|
basePath: config.basePath,
|
|
574
740
|
brandHref: logoHref(config.logo),
|
|
575
741
|
extraRoutes,
|
|
576
|
-
folderMeta:
|
|
742
|
+
folderMeta: mergedFolderMeta.meta,
|
|
577
743
|
i18n: config.i18n,
|
|
578
744
|
navigation: config.navigation,
|
|
579
|
-
sharedFolderMeta,
|
|
745
|
+
sharedFolderMeta: mergedFolderMeta.shared,
|
|
580
746
|
versions: config.versions,
|
|
581
747
|
});
|
|
582
748
|
const manifest = buildManifest({ config, context, graph });
|
|
@@ -584,18 +750,40 @@ export const scanProject = async (
|
|
|
584
750
|
const i18nWarnings = config.i18n
|
|
585
751
|
? i18nDiagnostics(pages, config.i18n, config.versions)
|
|
586
752
|
: [];
|
|
753
|
+
const svgWarnings = await unmeasurableSvgDiagnostics(pages);
|
|
587
754
|
const versionWarnings = config.versions
|
|
588
755
|
? versionsDiagnostics(pages, config.versions)
|
|
589
756
|
: [];
|
|
590
757
|
|
|
758
|
+
const servedPaths = [
|
|
759
|
+
...manifest.routes.map((route) => route.path),
|
|
760
|
+
...extraRoutes,
|
|
761
|
+
];
|
|
762
|
+
// A date-named page's old URL, from before its name was kept whole,
|
|
763
|
+
// redirects to its new one, beside the redirects the config lists.
|
|
764
|
+
const redirects = [
|
|
765
|
+
...config.redirects,
|
|
766
|
+
...formerRouteRedirects(pages, new Set(servedPaths), config),
|
|
767
|
+
];
|
|
768
|
+
|
|
591
769
|
return {
|
|
592
|
-
|
|
770
|
+
// Then the routes the API references served under earlier versions'
|
|
771
|
+
// rules redirect to where those operations live now.
|
|
772
|
+
config: withSourceRedirects(
|
|
773
|
+
redirects.length === config.redirects.length
|
|
774
|
+
? config
|
|
775
|
+
: { ...config, redirects },
|
|
776
|
+
loaded,
|
|
777
|
+
new Set([...routeSetFor(manifest.routes), ...extraRoutes])
|
|
778
|
+
),
|
|
593
779
|
context,
|
|
594
780
|
diagnostics: [
|
|
781
|
+
...configResult.diagnostics,
|
|
595
782
|
...contentDiagnostics,
|
|
596
783
|
...includeDiagnostics,
|
|
597
784
|
...variableDiagnostics,
|
|
598
785
|
...folderMeta.diagnostics,
|
|
786
|
+
...metaDiagnostics,
|
|
599
787
|
...entryIdDiagnostics(
|
|
600
788
|
pages,
|
|
601
789
|
resolveDocsCollection(config, context.root).base
|
|
@@ -604,9 +792,12 @@ export const scanProject = async (
|
|
|
604
792
|
...i18nWarnings,
|
|
605
793
|
...versionWarnings,
|
|
606
794
|
...lastModifiedWarnings,
|
|
795
|
+
...svgWarnings,
|
|
796
|
+
...publicOpenApiDiagnostics(config, context.root),
|
|
607
797
|
...redirectPageDiagnostics(
|
|
608
798
|
config,
|
|
609
|
-
|
|
799
|
+
servedPaths,
|
|
800
|
+
routeSetFor(manifest.routes),
|
|
610
801
|
context.configFile
|
|
611
802
|
),
|
|
612
803
|
],
|
|
@@ -616,5 +807,6 @@ export const scanProject = async (
|
|
|
616
807
|
mode,
|
|
617
808
|
sources,
|
|
618
809
|
themeFontsConfigured: configResult.themeFontsConfigured,
|
|
810
|
+
unparsable,
|
|
619
811
|
};
|
|
620
812
|
};
|
|
@@ -184,6 +184,21 @@ const replaceReferences = (
|
|
|
184
184
|
replace(referenceName(name), match.startsWith("/"))
|
|
185
185
|
);
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* The literal start of a `to` that reads a capture, up to its first
|
|
189
|
+
* reference: `/guides/` for `/guides/:slug*`, `/new/article-` for
|
|
190
|
+
* `/new/article-*`. `undefined` for a `to` that reads none, which is one
|
|
191
|
+
* exact path.
|
|
192
|
+
*/
|
|
193
|
+
export const destinationPrefix = (to: string): string | undefined => {
|
|
194
|
+
const at = to.search(REFERENCE);
|
|
195
|
+
if (at === -1) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
// A whole-segment reference starts at its slash, which the prefix keeps.
|
|
199
|
+
return to.slice(0, to[at] === "/" ? at + 1 : at);
|
|
200
|
+
};
|
|
201
|
+
|
|
187
202
|
/** Whether a path is a pattern, rather than one exact path. */
|
|
188
203
|
export const isPatternPath = (path: string): boolean => {
|
|
189
204
|
const parsed = parseFrom(path);
|
|
@@ -278,6 +293,32 @@ export const expandRedirect = (redirect: RedirectEnds): RedirectRule[] => {
|
|
|
278
293
|
];
|
|
279
294
|
};
|
|
280
295
|
|
|
296
|
+
/**
|
|
297
|
+
* The bare path a pattern ending in a rest also matches, as the exact
|
|
298
|
+
* redirect every host is given for it: `/beta/:slug*` (or `/beta/*`) to
|
|
299
|
+
* `/v2/:slug*` sends `/beta` itself to `/v2`. Null for an exact redirect,
|
|
300
|
+
* a pattern with no rest, and one whose bare path still holds a capture
|
|
301
|
+
* (`/:lang/*`), which names no single path.
|
|
302
|
+
*/
|
|
303
|
+
export const bareRedirect = (
|
|
304
|
+
redirect: RedirectEnds
|
|
305
|
+
): { from: string; to: string } | null => {
|
|
306
|
+
// A rest expands to two rules, the bare path first (see `expandRedirect`).
|
|
307
|
+
const rules = expandRedirect(redirect);
|
|
308
|
+
const [bare] = rules;
|
|
309
|
+
if (rules.length !== 2 || bare === undefined) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
let from = "";
|
|
313
|
+
for (const part of bare.parts) {
|
|
314
|
+
if (part.kind !== "text") {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
from += part.text;
|
|
318
|
+
}
|
|
319
|
+
return { from, to: bare.to };
|
|
320
|
+
};
|
|
321
|
+
|
|
281
322
|
/** Put exact redirects ahead of patterns, which hosts try in order. */
|
|
282
323
|
export const exactFirst = <Redirect extends { from: string }>(
|
|
283
324
|
redirects: readonly Redirect[]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { basename, dirname, extname, normalize, resolve } from "pathe";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The files a page names beside it: an image it embeds (``)
|
|
7
|
+
* and any other file it links (`[spec](./spec.pdf)`, `<img src>`). One
|
|
8
|
+
* reading, shared by everything that handles them: the include expander that
|
|
9
|
+
* rebases a partial's, the content-asset rewriter that publishes them, the
|
|
10
|
+
* render plugin that points the built page at them, and link validation.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// The formats Astro's image pipeline accepts, plus the web-safe pass-throughs;
|
|
14
|
+
// anything else referenced relatively (a `.pdf`, a source file) is left alone.
|
|
15
|
+
const IMAGE_EXTENSIONS = new Set([
|
|
16
|
+
".apng",
|
|
17
|
+
".avif",
|
|
18
|
+
".bmp",
|
|
19
|
+
".gif",
|
|
20
|
+
".ico",
|
|
21
|
+
".jpeg",
|
|
22
|
+
".jpg",
|
|
23
|
+
".png",
|
|
24
|
+
".svg",
|
|
25
|
+
".tiff",
|
|
26
|
+
".webp",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// A link to one of these names a page (see `resolveRelativeHref` in
|
|
30
|
+
// `links.ts`), never a file to publish.
|
|
31
|
+
const PAGE_EXTENSIONS = new Set([".md", ".mdx"]);
|
|
32
|
+
|
|
33
|
+
/** Whether a link target is a relative filesystem path (not URL/absolute/hash). */
|
|
34
|
+
const isRelativeTarget = (target: string): boolean =>
|
|
35
|
+
!(target.startsWith("/") || target.startsWith("#")) && !URL.canParse(target);
|
|
36
|
+
|
|
37
|
+
/** Percent-decode a target; malformed escapes stay verbatim. */
|
|
38
|
+
const decodeTarget = (target: string): string => {
|
|
39
|
+
try {
|
|
40
|
+
return decodeURI(target);
|
|
41
|
+
} catch {
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a target is *shaped* like a colocated image reference: a relative
|
|
48
|
+
* filesystem path with an image extension. Exported so link validation can
|
|
49
|
+
* tell "not a colocated candidate" apart from "a candidate that resolves
|
|
50
|
+
* nowhere" — the former falls through to the public-dir probe, the latter is
|
|
51
|
+
* a broken reference beside the page source.
|
|
52
|
+
*/
|
|
53
|
+
export const isRelativeImageTarget = (target: string): boolean =>
|
|
54
|
+
isRelativeTarget(target) &&
|
|
55
|
+
IMAGE_EXTENSIONS.has(extname(decodeTarget(target)).toLowerCase());
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether `abs` is a file whose trailing `segments` names match the on-disk
|
|
59
|
+
* entries exactly. A bare `existsSync` accepts `./Diagram.PNG` for
|
|
60
|
+
* `diagram.png` (or a directory named like an image) on a case-insensitive
|
|
61
|
+
* filesystem — the reference then validates and serves locally but breaks on
|
|
62
|
+
* the case-sensitive Linux build.
|
|
63
|
+
*/
|
|
64
|
+
const existsAsWritten = (abs: string, segments: number): boolean => {
|
|
65
|
+
let current = abs;
|
|
66
|
+
for (let i = 0; i < segments; i += 1) {
|
|
67
|
+
const parent = dirname(current);
|
|
68
|
+
let entries: string[];
|
|
69
|
+
try {
|
|
70
|
+
entries = readdirSync(parent);
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (!entries.includes(basename(current))) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
current = parent;
|
|
78
|
+
}
|
|
79
|
+
const stat = statSync(abs, { throwIfNoEntry: false });
|
|
80
|
+
return stat !== undefined && stat.isFile();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The file a decoded relative `path` names from `sourceDir`, when it's on
|
|
85
|
+
* disk as written. Only the segments the author wrote are checked against
|
|
86
|
+
* on-disk names; `sourceDir`'s own casing is the filesystem's business, not
|
|
87
|
+
* the target's.
|
|
88
|
+
*/
|
|
89
|
+
const fileAsWritten = (sourceDir: string, path: string): string | null => {
|
|
90
|
+
const segments = normalize(path)
|
|
91
|
+
.split("/")
|
|
92
|
+
.filter((part) => part !== "" && part !== "..").length;
|
|
93
|
+
const abs = resolve(sourceDir, path);
|
|
94
|
+
return existsAsWritten(abs, segments) ? abs : null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve one image target against its page's directory. Returns the absolute
|
|
99
|
+
* file path when the target is relative, is an image, and exists on disk —
|
|
100
|
+
* anything else (remote URLs, `public/` absolutes, broken refs, code-block
|
|
101
|
+
* examples that happen to look like paths) is null and left untouched. Shared
|
|
102
|
+
* with link validation, so what counts as a colocated image is decided once.
|
|
103
|
+
*/
|
|
104
|
+
export const resolveRelativeImage = (
|
|
105
|
+
sourceDir: string,
|
|
106
|
+
target: string
|
|
107
|
+
): string | null =>
|
|
108
|
+
isRelativeImageTarget(target)
|
|
109
|
+
? fileAsWritten(sourceDir, decodeTarget(target))
|
|
110
|
+
: null;
|
|
111
|
+
|
|
112
|
+
/** A file a link names beside its page, and the link's `?query#fragment`. */
|
|
113
|
+
export interface RelativeFile {
|
|
114
|
+
path: string;
|
|
115
|
+
suffix: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the file a link, an element's `src`, or an `href` names beside its
|
|
120
|
+
* page (`./spec.pdf`, `../files/data.csv#L2`): a relative path whose name
|
|
121
|
+
* carries an extension other than a page's (`.md`, `.mdx`), on disk as
|
|
122
|
+
* written (see `existsAsWritten`). Its `?query` or `#fragment` is set aside,
|
|
123
|
+
* so `./spec.pdf#page=2` opens the served file at that page. Anything else is
|
|
124
|
+
* null: a page link, a URL, a root path, a missing file. Shared with link
|
|
125
|
+
* validation, which accepts what this resolves.
|
|
126
|
+
*/
|
|
127
|
+
export const resolveRelativeFile = (
|
|
128
|
+
sourceDir: string,
|
|
129
|
+
target: string
|
|
130
|
+
): RelativeFile | null => {
|
|
131
|
+
const at = target.search(/[?#]/u);
|
|
132
|
+
const path = decodeTarget(at === -1 ? target : target.slice(0, at));
|
|
133
|
+
const extension = extname(path).toLowerCase();
|
|
134
|
+
if (
|
|
135
|
+
!isRelativeTarget(target) ||
|
|
136
|
+
extension === "" ||
|
|
137
|
+
PAGE_EXTENSIONS.has(extension)
|
|
138
|
+
) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const file = fileAsWritten(sourceDir, path);
|
|
142
|
+
return file === null
|
|
143
|
+
? null
|
|
144
|
+
: { path: file, suffix: at === -1 ? "" : target.slice(at) };
|
|
145
|
+
};
|