blume 2.1.3 → 2.2.0
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 +126 -0
- package/README.md +4 -4
- package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
- package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
- package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
- package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
- package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
- package/dist/cli/chunk-2nr29y95.js.map +13 -0
- package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
- package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
- package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
- package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
- package/dist/cli/chunk-79bzzkx4.js.map +10 -0
- package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
- package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
- package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
- package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
- package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
- package/dist/cli/chunk-96snx8yy.js +329 -0
- package/dist/cli/chunk-96snx8yy.js.map +13 -0
- package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
- package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
- package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
- package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
- package/dist/cli/chunk-as09pafp.js.map +18 -0
- package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
- package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
- package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
- package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
- package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
- package/dist/cli/chunk-cn3dy3hz.js +146 -0
- package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
- package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
- package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
- package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
- package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
- package/dist/cli/chunk-dpcahcrb.js.map +10 -0
- package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
- package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
- package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
- package/dist/cli/chunk-enk76qd5.js.map +10 -0
- package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
- package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
- package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
- package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
- package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
- package/dist/cli/chunk-gprwej7h.js.map +11 -0
- package/dist/cli/chunk-kkrteb15.js +157 -0
- package/dist/cli/chunk-kkrteb15.js.map +10 -0
- package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
- package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
- package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
- package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
- package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
- package/dist/cli/chunk-rkqae7kh.js.map +10 -0
- package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
- package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
- package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
- package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
- package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
- package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
- package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
- package/dist/cli/chunk-wehv26ry.js +155 -0
- package/dist/cli/chunk-wehv26ry.js.map +11 -0
- package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
- package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
- package/dist/cli/chunk-xt0qvz1t.js.map +10 -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 +1 -1
- package/dist/types/core/content-assets.d.ts +16 -18
- 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/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 +9 -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 +267 -7
- 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/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 +23 -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 +2 -2
- package/docs/configuration/theming.mdx +5 -1
- package/docs/content/components.mdx +26 -6
- package/docs/content/frontmatter.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/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 +49 -27
- 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 +3 -3
- 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 +2 -2
- 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 +32 -6
- 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 +89 -185
- 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/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/RootLayout.astro +18 -31
- 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 +1 -1
- package/src/core/config.ts +46 -1
- package/src/core/content-assets.ts +64 -144
- 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/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 +16 -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 +17 -3
- 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/registry/eject.ts +0 -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-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
- /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
- /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
- /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
- /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
- /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
- /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
- /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
- /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
- /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
- /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
- /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
- /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
package/src/astro/templates.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { buildHomeLinkHeader } from "../ai/link-headers.ts";
|
|
|
10
10
|
import type { CaptchaAdapter } from "../captcha/schema.ts";
|
|
11
11
|
import { CONSENT_CLIENT_MODULES } from "../consent/clients.ts";
|
|
12
12
|
import { normalizeBasePath } from "../core/base-path.ts";
|
|
13
|
-
import { TOC_HIDDEN_KEY } from "../core/heading-markers.ts";
|
|
13
|
+
import { TOC_HIDDEN_KEY, TOC_TEXT_KEY } from "../core/heading-markers.ts";
|
|
14
14
|
import {
|
|
15
15
|
compileEveryRedirect,
|
|
16
16
|
isPatternPath,
|
|
@@ -22,15 +22,18 @@ import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
|
|
|
22
22
|
import { trimChar } from "../core/trim.ts";
|
|
23
23
|
import type { ProjectContext } from "../core/types.ts";
|
|
24
24
|
import { hasVariables } from "../core/variables.ts";
|
|
25
|
+
import { getBlumeVersion } from "../core/version.ts";
|
|
25
26
|
import { deployPassthrough } from "../deploy/adapters/types.ts";
|
|
26
27
|
import { SVG_ASSET_HEADERS } from "../deploy/headers.ts";
|
|
27
28
|
import { deployPlatform } from "../deploy/platforms/index.ts";
|
|
28
29
|
import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
|
|
29
30
|
import {
|
|
30
31
|
applyBaseToAstroRedirects,
|
|
32
|
+
isPageIndexFile,
|
|
31
33
|
withMirrorRedirects,
|
|
32
34
|
} from "../deploy/redirects.ts";
|
|
33
35
|
import { API_RAIL_KEY } from "../markdown/api-rail.ts";
|
|
36
|
+
import { TITLE_ID_KEY } from "../markdown/title-heading.ts";
|
|
34
37
|
import { VIEWS_KEY } from "../markdown/views.ts";
|
|
35
38
|
import type { OgCache } from "../og/cache.ts";
|
|
36
39
|
import type { OgFont, OgFontFamilies, OgGoogleFont } from "../og/card.ts";
|
|
@@ -717,8 +720,16 @@ export const astroConfigTemplate = (options: {
|
|
|
717
720
|
// Only exact redirects: Astro can't prerender a pattern's redirect pages
|
|
718
721
|
// (it would need every path the pattern covers), so a pattern reaches the
|
|
719
722
|
// dev server through the integration and each host through its own rules.
|
|
723
|
+
// Nor one from a page's own `index.html` in a static build, where Astro
|
|
724
|
+
// would write its redirect page inside the page's file (see
|
|
725
|
+
// `isPageIndexFile`); the host files and the dev server still carry it.
|
|
726
|
+
const staticOutput = deployment.options.output === "static";
|
|
720
727
|
const exactRedirects = basedRedirects.filter(
|
|
721
|
-
(redirect) =>
|
|
728
|
+
(redirect) =>
|
|
729
|
+
!(
|
|
730
|
+
isPatternPath(redirect.from) ||
|
|
731
|
+
(staticOutput && isPageIndexFile(redirect.from, redirectPages))
|
|
732
|
+
)
|
|
722
733
|
);
|
|
723
734
|
const redirectsOption =
|
|
724
735
|
exactRedirects.length > 0
|
|
@@ -801,6 +812,7 @@ export const astroConfigTemplate = (options: {
|
|
|
801
812
|
const blumeImports = [
|
|
802
813
|
"blumeIntegration",
|
|
803
814
|
"includeHmrPlugin",
|
|
815
|
+
"mdxSourceErrorsPlugin",
|
|
804
816
|
"prerenderDepsPlugin",
|
|
805
817
|
...runtimeModuleImports,
|
|
806
818
|
...(adapterOption.includes("withAdapterRoot") ? ["withAdapterRoot"] : []),
|
|
@@ -909,10 +921,10 @@ ${userConfigSetup}export default defineConfig({
|
|
|
909
921
|
devToolbar: { enabled: false },
|
|
910
922
|
// One canonical URL per page: canonicals, the sitemap, and hreflang all use
|
|
911
923
|
// the slashless form, so the slashed spelling is not a second address. Astro
|
|
912
|
-
// applies this itself —
|
|
913
|
-
//
|
|
914
|
-
// turns it into the platform's 308 route, so the Build Output config
|
|
915
|
-
// no hand-spliced redirect (see deploy/vercel-negotiation.ts). Static hosts
|
|
924
|
+
// applies this itself — an on-demand route redirects, and the integration
|
|
925
|
+
// has the dev server redirect too, ahead of Astro's own 404 — and the Vercel
|
|
926
|
+
// adapter turns it into the platform's 308 route, so the Build Output config
|
|
927
|
+
// needs no hand-spliced redirect (see deploy/vercel-negotiation.ts). Static hosts
|
|
916
928
|
// serve the \`index.html\` directory layout as they always did.
|
|
917
929
|
trailingSlash: "never",
|
|
918
930
|
// The layouts render Astro's <ClientRouter />, and its in-place swaps read
|
|
@@ -930,7 +942,7 @@ ${userConfigSetup}export default defineConfig({
|
|
|
930
942
|
plugins: [${runtimeModulesPluginEntry}${variablesPluginEntry}tailwindcss(), includeHmrPlugin(${configPath(
|
|
931
943
|
`${context.outDir}/src/generated/includes.json`,
|
|
932
944
|
ejected
|
|
933
|
-
)}), prerenderDepsPlugin()],
|
|
945
|
+
)}), mdxSourceErrorsPlugin(), prerenderDepsPlugin()],
|
|
934
946
|
build: {${features.mermaid ? MERMAID_CHUNK_LIMIT : ""}
|
|
935
947
|
rolldownOptions: {
|
|
936
948
|
${ROLLDOWN_ON_LOG},
|
|
@@ -1031,6 +1043,13 @@ const runtimeDirWithin = (base: string, outDir: string): string | null => {
|
|
|
1031
1043
|
const astroGlobBase = (base: string): string =>
|
|
1032
1044
|
isAbsolute(base) ? pathToFileURL(base).href : base;
|
|
1033
1045
|
|
|
1046
|
+
// The characters a glob reads as syntax; a `(group)` folder has two.
|
|
1047
|
+
const GLOB_SYNTAX = /[!()*+?@[\\\]{|}]/gu;
|
|
1048
|
+
|
|
1049
|
+
/** A relative path as a glob that matches only that file. */
|
|
1050
|
+
const escapeGlob = (path: string): string =>
|
|
1051
|
+
path.replaceAll(GLOB_SYNTAX, String.raw`\$&`);
|
|
1052
|
+
|
|
1034
1053
|
/** Generate `.blume/src/content.config.ts`. */
|
|
1035
1054
|
export const contentConfigTemplate = (options: {
|
|
1036
1055
|
context: ProjectContext;
|
|
@@ -1051,6 +1070,13 @@ export const contentConfigTemplate = (options: {
|
|
|
1051
1070
|
* a non-filesystem source), the collection globs nothing — see below.
|
|
1052
1071
|
*/
|
|
1053
1072
|
filesystem?: boolean;
|
|
1073
|
+
/**
|
|
1074
|
+
* Absolute paths of `.mdx` files the build dropped because they don't
|
|
1075
|
+
* parse. Astro compiles every `.mdx` file in the collection, routed or
|
|
1076
|
+
* not, so each is left out of the glob, or a `--no-strict` build would
|
|
1077
|
+
* still fail on it.
|
|
1078
|
+
*/
|
|
1079
|
+
unparsable?: readonly string[];
|
|
1054
1080
|
}): string => {
|
|
1055
1081
|
const { context, config } = options;
|
|
1056
1082
|
const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);
|
|
@@ -1087,6 +1113,12 @@ export const contentConfigTemplate = (options: {
|
|
|
1087
1113
|
dir === ".blume" ? [] : [`!**/${dir}/**`]
|
|
1088
1114
|
),
|
|
1089
1115
|
...outDirIgnore,
|
|
1116
|
+
...(options.unparsable ?? []).flatMap((file) => {
|
|
1117
|
+
const rel = relative(collectionBase, file);
|
|
1118
|
+
return rel.startsWith("..") || isAbsolute(rel)
|
|
1119
|
+
? []
|
|
1120
|
+
: [`!${escapeGlob(rel)}`];
|
|
1121
|
+
}),
|
|
1090
1122
|
]
|
|
1091
1123
|
: [];
|
|
1092
1124
|
|
|
@@ -1118,6 +1150,10 @@ import { pageCollectionSchema } from "blume/core/schema.ts";
|
|
|
1118
1150
|
// pageCollectionSchema is the scan's page front-matter schema, so entry.data
|
|
1119
1151
|
// is typed and normalized the same way; it passes custom keys through and
|
|
1120
1152
|
// falls back to defaults for a page the scan already dropped as invalid.
|
|
1153
|
+
//
|
|
1154
|
+
// Astro's content store re-parses an entry only when its file or this config
|
|
1155
|
+
// changes, so naming the Blume version (${getBlumeVersion()}) here re-parses
|
|
1156
|
+
// every entry after an upgrade: none keeps what an older schema produced.
|
|
1121
1157
|
const docs = defineCollection({
|
|
1122
1158
|
loader: withIncludeRefresh(glob({
|
|
1123
1159
|
pattern: ${JSON.stringify(docsPattern)},
|
|
@@ -1831,7 +1867,10 @@ export function GET({ props }: { props: { route: string } }) {
|
|
|
1831
1867
|
* - `content/<project-relative path>` — colocated content images
|
|
1832
1868
|
* (``), served as originals for the agent-facing
|
|
1833
1869
|
* Markdown endpoints (the HTML render uses the `astro:assets`-optimized
|
|
1834
|
-
* copies instead)
|
|
1870
|
+
* copies instead), and the other files beside a page that its links and
|
|
1871
|
+
* element URLs name (`[spec](./spec.pdf)`, `<img src="./a.png">`), which
|
|
1872
|
+
* the HTML render points here too. The mapping comes from
|
|
1873
|
+
* `generated/content-assets.json`.
|
|
1835
1874
|
* - `<source>/<hash>.<ext>` — remote-source images the scan pipeline
|
|
1836
1875
|
* materializes under `.blume/public/blume-assets` (see
|
|
1837
1876
|
* `core/sources/assets.ts`). That directory is NOT Astro's `publicDir`
|
|
@@ -1874,11 +1913,25 @@ const TYPES: Record<string, string> = {
|
|
|
1874
1913
|
".webp": "image/webp",
|
|
1875
1914
|
};
|
|
1876
1915
|
|
|
1916
|
+
// The other files a page's links name beside it, typed so the dev server
|
|
1917
|
+
// opens them the way a static host would. Anything else downloads.
|
|
1918
|
+
const FILE_TYPES: Record<string, string> = {
|
|
1919
|
+
".csv": "text/csv; charset=utf-8",
|
|
1920
|
+
".json": "application/json",
|
|
1921
|
+
".mp3": "audio/mpeg",
|
|
1922
|
+
".pdf": "application/pdf",
|
|
1923
|
+
".txt": "text/plain; charset=utf-8",
|
|
1924
|
+
".wav": "audio/wav",
|
|
1925
|
+
".zip": "application/zip",
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1877
1928
|
const extensionOf = (path: string): string =>
|
|
1878
1929
|
path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
1879
1930
|
|
|
1880
1931
|
const contentType = (path: string): string =>
|
|
1881
|
-
TYPES[extensionOf(path)] ??
|
|
1932
|
+
TYPES[extensionOf(path)] ??
|
|
1933
|
+
FILE_TYPES[extensionOf(path)] ??
|
|
1934
|
+
"application/octet-stream";
|
|
1882
1935
|
|
|
1883
1936
|
// A source downloads only images and videos; anything else in the staged
|
|
1884
1937
|
// directory (a page an older build saved under its URL's extension) is never
|
|
@@ -2340,6 +2393,7 @@ import Badge from "blume/components/content/Badge.astro";
|
|
|
2340
2393
|
import Callout from "blume/components/content/Callout.astro";
|
|
2341
2394
|
import Card from "blume/components/content/Card.astro";
|
|
2342
2395
|
import CardGroup from "blume/components/content/CardGroup.astro";
|
|
2396
|
+
import Changelog from "blume/components/content/Changelog.astro";
|
|
2343
2397
|
import CodeBlock from "blume/components/content/CodeBlock.astro";
|
|
2344
2398
|
import CodeGroup from "blume/components/content/CodeGroup.astro";
|
|
2345
2399
|
import ColorRoot from "blume/components/content/Color.astro";
|
|
@@ -2393,6 +2447,7 @@ ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../gener
|
|
|
2393
2447
|
Callout,
|
|
2394
2448
|
Card,
|
|
2395
2449
|
CardGroup,
|
|
2450
|
+
Changelog,
|
|
2396
2451
|
CodeBlock,
|
|
2397
2452
|
CodeGroup,
|
|
2398
2453
|
Color,
|
|
@@ -2513,10 +2568,25 @@ const views = Array.isArray(viewsRaw)
|
|
|
2513
2568
|
: [];
|
|
2514
2569
|
const tocHiddenRaw = remarkPluginFrontmatter?.${TOC_HIDDEN_KEY};
|
|
2515
2570
|
const tocHidden = new Set(Array.isArray(tocHiddenRaw) ? tocHiddenRaw : []);
|
|
2516
|
-
|
|
2571
|
+
// A heading holding a \`<Badge>\` lists without the badge's text, which the
|
|
2572
|
+
// heading plugin reports by slug the same way.
|
|
2573
|
+
const tocTextRaw = remarkPluginFrontmatter?.${TOC_TEXT_KEY};
|
|
2574
|
+
const tocText = tocTextRaw && typeof tocTextRaw === "object" ? tocTextRaw : {};
|
|
2575
|
+
const headings = (
|
|
2517
2576
|
tocHidden.size > 0
|
|
2518
2577
|
? allHeadings.filter((heading) => !tocHidden.has(heading.slug))
|
|
2519
|
-
: allHeadings
|
|
2578
|
+
: allHeadings
|
|
2579
|
+
).map((heading) =>
|
|
2580
|
+
typeof tocText[heading.slug] === "string"
|
|
2581
|
+
? { ...heading, text: tocText[heading.slug] }
|
|
2582
|
+
: heading
|
|
2583
|
+
);
|
|
2584
|
+
// An untitled page's opening \`# Heading\` is its title, so the title-heading
|
|
2585
|
+
// plugin drops it from the body and reports its id here: the title \`<h1>\`
|
|
2586
|
+
// below takes it, so links to the heading's anchor still land (see
|
|
2587
|
+
// markdown/title-heading.ts).
|
|
2588
|
+
const titleIdRaw = remarkPluginFrontmatter?.${TITLE_ID_KEY};
|
|
2589
|
+
const titleId = typeof titleIdRaw === "string" ? titleIdRaw : undefined;
|
|
2520
2590
|
const frontmatter = entry.data;
|
|
2521
2591
|
|
|
2522
2592
|
const seo = frontmatter.seo;
|
|
@@ -2809,7 +2879,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2809
2879
|
noindex={effectiveNoindex}
|
|
2810
2880
|
structuredDataEnabled={data.config.structuredData}
|
|
2811
2881
|
>
|
|
2812
|
-
{pageChrome && <h1>{title}</h1>}
|
|
2882
|
+
{pageChrome && <h1 id={titleId}>{title}</h1>}
|
|
2813
2883
|
{pageChrome && frontmatter.description && <p class="text-lg text-muted-foreground">{frontmatter.description}</p>}
|
|
2814
2884
|
{narration && (
|
|
2815
2885
|
<NarrationPlayer
|
|
@@ -2843,42 +2913,22 @@ export const changelogIndexTemplate = (options: {
|
|
|
2843
2913
|
exportPdf: boolean;
|
|
2844
2914
|
/** Serialize the island-hooks snapshot; only needed when React is enabled. */
|
|
2845
2915
|
needsReact: boolean;
|
|
2846
|
-
/** Whether a `staged` collection exists (non-filesystem changelog sources). */
|
|
2847
|
-
staged: boolean;
|
|
2848
2916
|
}): string => {
|
|
2849
2917
|
const clientData = options.needsReact
|
|
2850
2918
|
? '\n clientData={{ config: data.config, navigation: data.navigation, page: { route: "/changelog", title: pageTitle } }}'
|
|
2851
2919
|
: "";
|
|
2852
|
-
// Staged sources (e.g. GitHub Releases) render through a parallel collection,
|
|
2853
|
-
// so fold them in alongside filesystem entries when one exists.
|
|
2854
|
-
const stagedSpread = options.staged
|
|
2855
|
-
? '\n ...(await getCollection("staged")),'
|
|
2856
|
-
: "";
|
|
2857
2920
|
|
|
2858
2921
|
return `---
|
|
2859
2922
|
// Generated by Blume. Do not edit.
|
|
2860
|
-
import { getCollection } from "astro:content";
|
|
2861
2923
|
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2924
|
+
import Changelog from "blume/components/content/Changelog.astro";
|
|
2862
2925
|
import { withMountedBase } from "blume/components/islands/base-path.ts";
|
|
2863
2926
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2864
|
-
import { resolveDateFormatOptions } from "blume/core/date-format.ts";
|
|
2865
2927
|
import { layoutOverrides } from "../generated/components.ts";
|
|
2866
2928
|
import data from "blume:data";
|
|
2867
2929
|
|
|
2868
2930
|
export const prerender = true;
|
|
2869
2931
|
|
|
2870
|
-
const entryDate = (entry: {
|
|
2871
|
-
data: { date?: string | null; changelog?: { date?: string | null } | null };
|
|
2872
|
-
}) => entry.data.date ?? entry.data.changelog?.date ?? null;
|
|
2873
|
-
|
|
2874
|
-
const parseDate = (value: string | null | undefined) => {
|
|
2875
|
-
if (!value) {
|
|
2876
|
-
return null;
|
|
2877
|
-
}
|
|
2878
|
-
const date = new Date(value);
|
|
2879
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
2880
|
-
};
|
|
2881
|
-
|
|
2882
2932
|
// The changelog is an unlocalized route, so its chrome renders in the default
|
|
2883
2933
|
// locale's dictionary and direction (\`data.ui\` is the default locale's resolved
|
|
2884
2934
|
// dictionary), mirroring the catch-all's locale wiring.
|
|
@@ -2889,109 +2939,6 @@ const localeMeta = i18n
|
|
|
2889
2939
|
const dir = localeMeta?.dir ?? "ltr";
|
|
2890
2940
|
const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
2891
2941
|
|
|
2892
|
-
// Formatted in the same locale as the chrome, and with the configured
|
|
2893
|
-
// \`dateFormat\` (UTC by default), to match the per-page "last updated" stamp.
|
|
2894
|
-
// The year heads each group, so a row shows the rest of the date: a preset
|
|
2895
|
-
// style keeps its month wording, a component format simply drops the year.
|
|
2896
|
-
const dateFormatOptions = resolveDateFormatOptions(data.config.dateFormat);
|
|
2897
|
-
const { dateStyle, year: _year, ...dateComponents } = dateFormatOptions;
|
|
2898
|
-
const rowDateFormat: Intl.DateTimeFormatOptions = dateStyle
|
|
2899
|
-
? {
|
|
2900
|
-
...dateComponents,
|
|
2901
|
-
day: "numeric",
|
|
2902
|
-
month: dateStyle === "medium" ? "short" : "long",
|
|
2903
|
-
}
|
|
2904
|
-
: dateComponents;
|
|
2905
|
-
const formatRowDate = (date: Date) =>
|
|
2906
|
-
new Intl.DateTimeFormat(htmlLang, rowDateFormat).format(date);
|
|
2907
|
-
const formatYear = (date: Date) =>
|
|
2908
|
-
new Intl.DateTimeFormat(htmlLang, {
|
|
2909
|
-
timeZone: dateFormatOptions.timeZone,
|
|
2910
|
-
year: "numeric",
|
|
2911
|
-
}).format(date);
|
|
2912
|
-
|
|
2913
|
-
const slugify = (text: string) =>
|
|
2914
|
-
text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") ||
|
|
2915
|
-
"update";
|
|
2916
|
-
|
|
2917
|
-
// Map each entry to its own generated page so the row can link to it. The
|
|
2918
|
-
// collection entry id matches the route manifest's \`entryId\` — and under
|
|
2919
|
-
// i18n every locale serves a page for it (its translations plus fallback
|
|
2920
|
-
// copies of an untranslated one), all sharing that id, so a map over every
|
|
2921
|
-
// route would keep whichever locale came last: a Portuguese permalink on an
|
|
2922
|
-
// unlocalized index. Only the default locale's routes are kept, and an entry
|
|
2923
|
-
// is listed only when it has one, so a translated changelog file (a distinct
|
|
2924
|
-
// entry that lives under its own locale) does not add a second row for the
|
|
2925
|
-
// same release.
|
|
2926
|
-
const defaultLocale = i18n ? i18n.defaultLocale : null;
|
|
2927
|
-
const routeByEntry = new Map<string, string>();
|
|
2928
|
-
for (const route of data.routes) {
|
|
2929
|
-
if (defaultLocale === null || route.locale === defaultLocale) {
|
|
2930
|
-
routeByEntry.set(route.entryId, route.path);
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
const changelogEntries = [
|
|
2935
|
-
...(await getCollection("docs")),${stagedSpread}
|
|
2936
|
-
]
|
|
2937
|
-
.filter(
|
|
2938
|
-
(entry) =>
|
|
2939
|
-
entry.data.type === "changelog" &&
|
|
2940
|
-
!entry.data.draft &&
|
|
2941
|
-
!entry.data.sidebar?.hidden &&
|
|
2942
|
-
(defaultLocale === null || routeByEntry.has(entry.id))
|
|
2943
|
-
)
|
|
2944
|
-
.toSorted(
|
|
2945
|
-
(a, b) =>
|
|
2946
|
-
(parseDate(entryDate(b))?.getTime() ?? 0) -
|
|
2947
|
-
(parseDate(entryDate(a))?.getTime() ?? 0)
|
|
2948
|
-
);
|
|
2949
|
-
|
|
2950
|
-
const items = changelogEntries.map((entry) => {
|
|
2951
|
-
const label =
|
|
2952
|
-
entry.data.title ??
|
|
2953
|
-
(entry.data.changelog?.version
|
|
2954
|
-
? "v" + entry.data.changelog.version
|
|
2955
|
-
: "Update");
|
|
2956
|
-
const date = parseDate(entryDate(entry));
|
|
2957
|
-
const route = routeByEntry.get(entry.id);
|
|
2958
|
-
return {
|
|
2959
|
-
date: date ? formatRowDate(date) : null,
|
|
2960
|
-
dateTime: date ? date.toISOString().slice(0, 10) : null,
|
|
2961
|
-
href: route ? withMountedBase(route) : null,
|
|
2962
|
-
id: slugify(label),
|
|
2963
|
-
label,
|
|
2964
|
-
tag: entry.data.changelog?.category ?? null,
|
|
2965
|
-
year: date ? formatYear(date) : null,
|
|
2966
|
-
};
|
|
2967
|
-
});
|
|
2968
|
-
|
|
2969
|
-
// Repeated labels slug to the same id (e.g. two entries with neither a title
|
|
2970
|
-
// nor a version both falling back to "update"); suffix the later ones -2, -3,
|
|
2971
|
-
// ... so every row keeps its own hash anchor. The first keeps the plain slug.
|
|
2972
|
-
const seenIds = new Set();
|
|
2973
|
-
for (const item of items) {
|
|
2974
|
-
let uniqueId = item.id;
|
|
2975
|
-
for (let n = 2; seenIds.has(uniqueId); n += 1) {
|
|
2976
|
-
uniqueId = item.id + "-" + n;
|
|
2977
|
-
}
|
|
2978
|
-
seenIds.add(uniqueId);
|
|
2979
|
-
item.id = uniqueId;
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
// Consecutive releases from the same year share a group, so the list reads as
|
|
2983
|
-
// a year rail beside the rows; undated entries (sorted last) form a final
|
|
2984
|
-
// group with no year label.
|
|
2985
|
-
const groups: { items: typeof items; year: string | null }[] = [];
|
|
2986
|
-
for (const item of items) {
|
|
2987
|
-
const last = groups[groups.length - 1];
|
|
2988
|
-
if (last && last.year === item.year) {
|
|
2989
|
-
last.items.push(item);
|
|
2990
|
-
} else {
|
|
2991
|
-
groups.push({ items: [item], year: item.year });
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
2942
|
const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
|
|
2996
2943
|
// The canonical URL carries the deployment base (the page is served under it),
|
|
2997
2944
|
// matching how the catch-all canonicalizes via \`withMountedBase(route)\`.
|
|
@@ -3004,15 +2951,16 @@ const canonical = base ? base + basedRoute : null;
|
|
|
3004
2951
|
const ogPath = data.config.og.enabled ? withMountedBase("/og/changelog.png") : null;
|
|
3005
2952
|
const ogImage = ogPath && base ? base + ogPath : ogPath;
|
|
3006
2953
|
|
|
3007
|
-
// The page chrome (h1, title, description
|
|
3008
|
-
//
|
|
2954
|
+
// The page chrome (h1, title, description) comes from the translatable
|
|
2955
|
+
// \`changelog\` group, which carries the \`changelog\` config's title and
|
|
3009
2956
|
// description when it sets them; optional chaining tolerates a
|
|
3010
|
-
// not-yet-regenerated data snapshot from before these keys existed.
|
|
2957
|
+
// not-yet-regenerated data snapshot from before these keys existed. The list
|
|
2958
|
+
// itself is \`<Changelog />\`, the same component an authored page can place to
|
|
2959
|
+
// put prose above it.
|
|
3011
2960
|
const changelogTitle = data.ui.changelog?.title ?? "Changelog";
|
|
3012
2961
|
const changelogDescription =
|
|
3013
2962
|
data.ui.changelog?.description ??
|
|
3014
2963
|
"Product updates, new features, and fixes from every release.";
|
|
3015
|
-
const changelogEmpty = data.ui.changelog?.empty ?? "No changelog entries yet.";
|
|
3016
2964
|
// The layout suffixes "- {site title}" itself, so the page title is just the
|
|
3017
2965
|
// changelog's own name — prefixing the site title too would double it
|
|
3018
2966
|
// ("Acme Changelog - Acme").
|
|
@@ -3063,51 +3011,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
3063
3011
|
>
|
|
3064
3012
|
<h1>{changelogTitle}</h1>
|
|
3065
3013
|
<p class="text-lg text-muted-foreground">{changelogDescription}</p>
|
|
3066
|
-
|
|
3067
|
-
items.length === 0 ? (
|
|
3068
|
-
<p>{changelogEmpty}</p>
|
|
3069
|
-
) : (
|
|
3070
|
-
<div class="not-prose mt-10 divide-y divide-border border-border border-y">
|
|
3071
|
-
{groups.map((group) => (
|
|
3072
|
-
<section
|
|
3073
|
-
aria-label={group.year ?? undefined}
|
|
3074
|
-
class="grid md:grid-cols-[6rem_minmax(0,1fr)] md:gap-x-8"
|
|
3075
|
-
>
|
|
3076
|
-
<h2 class="mt-0! pt-3! font-medium! text-muted-foreground text-sm! leading-5! tabular-nums max-md:pb-1">
|
|
3077
|
-
{group.year}
|
|
3078
|
-
</h2>
|
|
3079
|
-
<ul class="m-0! list-none divide-y divide-border p-0!">
|
|
3080
|
-
{group.items.map((item) => (
|
|
3081
|
-
<li class="m-0! p-0!" id={item.id}>
|
|
3082
|
-
<a
|
|
3083
|
-
class="group/release flex items-baseline gap-4 py-3 no-underline! hover:no-underline!"
|
|
3084
|
-
href={item.href ?? "#" + item.id}
|
|
3085
|
-
>
|
|
3086
|
-
<span class="min-w-0 flex-1 font-medium text-foreground text-sm transition-colors group-hover/release:text-accent">
|
|
3087
|
-
{item.label}
|
|
3088
|
-
</span>
|
|
3089
|
-
{item.tag && (
|
|
3090
|
-
<span class="shrink-0 rounded-full bg-muted px-2 py-0.5 font-medium text-[0.65rem] text-muted-foreground">
|
|
3091
|
-
{item.tag}
|
|
3092
|
-
</span>
|
|
3093
|
-
)}
|
|
3094
|
-
{item.date && (
|
|
3095
|
-
<time
|
|
3096
|
-
class="shrink-0 font-mono text-muted-foreground text-xs tabular-nums"
|
|
3097
|
-
datetime={item.dateTime}
|
|
3098
|
-
>
|
|
3099
|
-
{item.date}
|
|
3100
|
-
</time>
|
|
3101
|
-
)}
|
|
3102
|
-
</a>
|
|
3103
|
-
</li>
|
|
3104
|
-
))}
|
|
3105
|
-
</ul>
|
|
3106
|
-
</section>
|
|
3107
|
-
))}
|
|
3108
|
-
</div>
|
|
3109
|
-
)
|
|
3110
|
-
}
|
|
3014
|
+
<Changelog />
|
|
3111
3015
|
</LayoutComponent>
|
|
3112
3016
|
`;
|
|
3113
3017
|
};
|
|
@@ -48,11 +48,15 @@ export const redirectChecks: CheckModule = {
|
|
|
48
48
|
)
|
|
49
49
|
);
|
|
50
50
|
} else if (redirect.outcome === "broken") {
|
|
51
|
+
// A pattern's chain starts at the bare path it also matches.
|
|
52
|
+
const [start] = redirect.chain;
|
|
53
|
+
const what =
|
|
54
|
+
start === from ? from : `${from}, which also matches ${start},`;
|
|
51
55
|
found.push(
|
|
52
56
|
finding(
|
|
53
57
|
"BLUME_AUDIT_REDIRECT_BROKEN",
|
|
54
58
|
{ ...site, url: from },
|
|
55
|
-
`Redirect from ${
|
|
59
|
+
`Redirect from ${what} lands on ${redirect.chain.at(-1)}, which the build does not serve.`
|
|
56
60
|
)
|
|
57
61
|
);
|
|
58
62
|
} else if (redirect.outcome === "chain") {
|
package/src/audit/redirects.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
bareRedirect,
|
|
2
3
|
isPatternPath,
|
|
3
4
|
patternDestination,
|
|
4
5
|
} from "../core/redirect-patterns.ts";
|
|
@@ -22,11 +23,22 @@ const pathOnly = (value: string): string => {
|
|
|
22
23
|
return cut === -1 ? value : value.slice(0, cut);
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The most hops a walk follows before calling it a loop. A pattern can send a
|
|
28
|
+
* path somewhere it covers again, one segment longer each time (`/a/*` to
|
|
29
|
+
* `/a/b/:splat`), which never revisits a hop yet never resolves either.
|
|
30
|
+
*/
|
|
31
|
+
const MAX_HOPS = 32;
|
|
32
|
+
|
|
33
|
+
/** Where a walk from one path ended, and how. */
|
|
34
|
+
type Walk = Pick<RedirectResolution, "chain" | "outcome">;
|
|
35
|
+
|
|
25
36
|
/**
|
|
26
37
|
* Follow every configured redirect through to its destination, classifying what
|
|
27
38
|
* it lands on.
|
|
28
39
|
*
|
|
29
|
-
* - `loop` — the chain revisits a hop it has already been to
|
|
40
|
+
* - `loop` — the chain revisits a hop it has already been to, or runs past
|
|
41
|
+
* {@link MAX_HOPS}. Never resolves.
|
|
30
42
|
* - `broken` — the chain ends somewhere the build does not serve.
|
|
31
43
|
* - `chain` — it resolves, but through at least one intermediate redirect.
|
|
32
44
|
* - `ok` — one hop, straight to a real page.
|
|
@@ -34,6 +46,11 @@ const pathOnly = (value: string): string => {
|
|
|
34
46
|
* naming one, so there is no single chain to walk; {@link redirectAt}
|
|
35
47
|
* resolves a path it covers.
|
|
36
48
|
*
|
|
49
|
+
* A pattern ending in a rest also matches its bare path (`/beta`, see
|
|
50
|
+
* `bareRedirect`), which every host gets as an exact rule. That one path is
|
|
51
|
+
* walked like an exact redirect: when it loops or breaks, the pattern takes
|
|
52
|
+
* that outcome, its chain starting at the bare path.
|
|
53
|
+
*
|
|
37
54
|
* An external destination (`https://…`) is always `ok`: it's outside the site,
|
|
38
55
|
* so there's no local page to check it against. A hop onto a path a pattern
|
|
39
56
|
* covers continues through that pattern.
|
|
@@ -50,14 +67,10 @@ export const resolveRedirects = (
|
|
|
50
67
|
const hopFrom = (path: string): string | undefined =>
|
|
51
68
|
byFrom.get(path)?.to ?? patternDestination(redirects, path);
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
const from = normalizePath(redirect.from);
|
|
55
|
-
if (isPatternPath(redirect.from)) {
|
|
56
|
-
return { ...redirect, chain: [from], outcome: "pattern" as const };
|
|
57
|
-
}
|
|
70
|
+
const walk = (from: string, to: string): Walk => {
|
|
58
71
|
const chain: string[] = [from];
|
|
59
72
|
const seen = new Set<string>([from]);
|
|
60
|
-
let current =
|
|
73
|
+
let current = to;
|
|
61
74
|
|
|
62
75
|
for (;;) {
|
|
63
76
|
// An external hop ends the walk — we can't follow it locally.
|
|
@@ -66,13 +79,9 @@ export const resolveRedirects = (
|
|
|
66
79
|
break;
|
|
67
80
|
}
|
|
68
81
|
const next = normalizePath(pathOnly(current));
|
|
69
|
-
if (seen.has(next)) {
|
|
82
|
+
if (seen.has(next) || chain.length > MAX_HOPS) {
|
|
70
83
|
chain.push(next);
|
|
71
|
-
return {
|
|
72
|
-
...redirect,
|
|
73
|
-
chain,
|
|
74
|
-
outcome: "loop" as const,
|
|
75
|
-
};
|
|
84
|
+
return { chain, outcome: "loop" };
|
|
76
85
|
}
|
|
77
86
|
chain.push(next);
|
|
78
87
|
seen.add(next);
|
|
@@ -86,15 +95,23 @@ export const resolveRedirects = (
|
|
|
86
95
|
const destination = chain.at(-1) ?? from;
|
|
87
96
|
const external = /^https?:\/\//iu.test(destination);
|
|
88
97
|
if (!(external || served(destination))) {
|
|
89
|
-
return {
|
|
98
|
+
return { chain, outcome: "broken" };
|
|
90
99
|
}
|
|
91
100
|
// `chain` is [from, …hops, destination]; more than two entries means at
|
|
92
101
|
// least one intermediate redirect.
|
|
93
|
-
return {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
return { chain, outcome: chain.length > 2 ? "chain" : "ok" };
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return redirects.map((redirect) => {
|
|
106
|
+
const from = normalizePath(redirect.from);
|
|
107
|
+
if (!isPatternPath(redirect.from)) {
|
|
108
|
+
return { ...redirect, ...walk(from, redirect.to) };
|
|
109
|
+
}
|
|
110
|
+
const bare = bareRedirect(redirect);
|
|
111
|
+
const walked = bare && walk(normalizePath(bare.from), bare.to);
|
|
112
|
+
return walked && (walked.outcome === "loop" || walked.outcome === "broken")
|
|
113
|
+
? { ...redirect, ...walked }
|
|
114
|
+
: { ...redirect, chain: [from], outcome: "pattern" as const };
|
|
98
115
|
});
|
|
99
116
|
};
|
|
100
117
|
|
package/src/audit/report.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { Diagnostic, DiagnosticSeverity } from "../core/types.ts";
|
|
|
7
7
|
import { CHECKS, checkMeta } from "./catalog.ts";
|
|
8
8
|
import type { CheckId } from "./catalog.ts";
|
|
9
9
|
import type { AuditResult } from "./run.ts";
|
|
10
|
+
import { checkSelected } from "./terms.ts";
|
|
10
11
|
import type { AuditCategory, AuditTier } from "./types.ts";
|
|
11
12
|
|
|
12
13
|
const SEVERITY_COLOR = {
|
|
@@ -128,15 +129,27 @@ export const rollup = (diagnostics: Diagnostic[]): CheckRollup[] => {
|
|
|
128
129
|
};
|
|
129
130
|
|
|
130
131
|
/** Categories that had no findings but were never run, and the flag that runs them. */
|
|
131
|
-
const skippedTiers = (
|
|
132
|
-
TIER_FLAGS.
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
const skippedTiers = (result: AuditResult): string[] =>
|
|
133
|
+
TIER_FLAGS.flatMap(({ flag, tier }) => {
|
|
134
|
+
// Only the checks `--only`/`--skip` left in would have run.
|
|
135
|
+
const count = CHECKS.filter(
|
|
136
|
+
(check) => check.tier === tier && checkSelected(check.id, result)
|
|
137
|
+
).length;
|
|
138
|
+
return result.tiers[tier] || count === 0
|
|
139
|
+
? []
|
|
140
|
+
: [
|
|
141
|
+
` ${colors.dim(`⊘ ${tier.padEnd(12)} skipped — pass ${flag} (${count} checks)`)}`,
|
|
142
|
+
];
|
|
135
143
|
});
|
|
136
144
|
|
|
137
|
-
/**
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
/**
|
|
146
|
+
* How many checks the report covers: those whose tier was enabled, and that
|
|
147
|
+
* `--only`/`--skip` left in.
|
|
148
|
+
*/
|
|
149
|
+
const activeChecks = (result: AuditResult): number =>
|
|
150
|
+
CHECKS.filter(
|
|
151
|
+
(check) => result.tiers[check.tier] && checkSelected(check.id, result)
|
|
152
|
+
).length;
|
|
140
153
|
|
|
141
154
|
/**
|
|
142
155
|
* Individual checks performed: every rule that ran, against every page crawled.
|
|
@@ -144,7 +157,7 @@ const activeChecks = (tiers: Record<AuditTier, boolean>): number =>
|
|
|
144
157
|
* rather than a bare count.
|
|
145
158
|
*/
|
|
146
159
|
export const auditCount = (result: AuditResult): number =>
|
|
147
|
-
activeChecks(result
|
|
160
|
+
activeChecks(result) * result.pages;
|
|
148
161
|
|
|
149
162
|
const summaryLine = (
|
|
150
163
|
counts: Record<DiagnosticSeverity, number>,
|
|
@@ -244,7 +257,7 @@ export const formatReport = (
|
|
|
244
257
|
lines.push("");
|
|
245
258
|
}
|
|
246
259
|
|
|
247
|
-
const skipped = skippedTiers(result
|
|
260
|
+
const skipped = skippedTiers(result);
|
|
248
261
|
if (skipped.length > 0) {
|
|
249
262
|
lines.push(...skipped, "");
|
|
250
263
|
}
|