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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
|
|
3
|
+
import { dirname, join, normalize } from "pathe";
|
|
4
|
+
|
|
5
|
+
import { RUNTIME_MODULE_FILES } from "../astro/runtime-modules.ts";
|
|
6
|
+
import { contentAssetUrl } from "../core/content-assets.ts";
|
|
7
|
+
import {
|
|
8
|
+
resolveRelativeFile,
|
|
9
|
+
resolveRelativeImage,
|
|
10
|
+
} from "../core/relative-files.ts";
|
|
11
|
+
import {
|
|
12
|
+
isLinkElementUrl,
|
|
13
|
+
rewriteElementUrls,
|
|
14
|
+
} from "../core/sources/normalize.ts";
|
|
15
|
+
import type { MdastNode, MdastValue } from "./mdast.ts";
|
|
16
|
+
import { snapshotReader } from "./route-snapshot.ts";
|
|
17
|
+
|
|
18
|
+
/** An MDX JSX attribute, as the plugin reads it. */
|
|
19
|
+
interface JsxAttribute {
|
|
20
|
+
[key: string]: MdastValue;
|
|
21
|
+
name?: string;
|
|
22
|
+
type?: string;
|
|
23
|
+
value?: MdastValue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** An MDX JSX element (`<Card img="./cover.png" />`), as the plugin reads it. */
|
|
27
|
+
interface JsxNode extends MdastNode {
|
|
28
|
+
attributes?: JsxAttribute[];
|
|
29
|
+
children?: MdastValue[];
|
|
30
|
+
name?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A link or a reference definition (`[spec]: ./spec.pdf`). */
|
|
34
|
+
interface UrlNode extends MdastNode {
|
|
35
|
+
url?: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Raw HTML in a `.md` page. */
|
|
39
|
+
interface HtmlNode extends MdastNode {
|
|
40
|
+
value: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The visitor-context slice this plugin uses (see `mdast.ts` for the model). */
|
|
44
|
+
interface ContentAssetsContext {
|
|
45
|
+
fileURL: URL | undefined;
|
|
46
|
+
replaceNode: (node: MdastNode, replacement: MdastNode) => void;
|
|
47
|
+
setProperty: (node: MdastNode, key: "url" | "value", value: string) => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ContentAssetsPluginOptions {
|
|
51
|
+
/**
|
|
52
|
+
* The `blume:data` snapshot file an ejected app aliases. The served
|
|
53
|
+
* content-assets map is written beside it, and read from there when no CLI
|
|
54
|
+
* publishes it.
|
|
55
|
+
*/
|
|
56
|
+
dataFile?: string;
|
|
57
|
+
/** Astro's `deployment.base` subdirectory (`""` or `/seg`). */
|
|
58
|
+
deployBase?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A plain string attribute value; an expression value isn't a path. */
|
|
62
|
+
const isStringValue = (value: MdastValue): value is string =>
|
|
63
|
+
typeof value === "string";
|
|
64
|
+
|
|
65
|
+
/** The directory of the page being compiled, when the compiler names it. */
|
|
66
|
+
const pageDir = (ctx: ContentAssetsContext): string | undefined =>
|
|
67
|
+
ctx.fileURL ? dirname(normalize(fileURLToPath(ctx.fileURL))) : undefined;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Satteri MDAST plugin that points a page's references to files beside it at
|
|
71
|
+
* their served copies (`/blume-assets/content/…`), the way a colocated
|
|
72
|
+
* `` is published: a `<Card img="./cover.png">`, a link or a
|
|
73
|
+
* reference definition naming a file (`[spec](./spec.pdf)`), and an
|
|
74
|
+
* element's `src` or `href` (`<img src="./diagram.png">`, `<a href>`, a
|
|
75
|
+
* component's `href`) in `.mdx`, or in a `.md` page's raw HTML. Left as
|
|
76
|
+
* written, each ships as a relative URL the browser resolves against the
|
|
77
|
+
* page's URL, where nothing is served. A card's image is resolved from
|
|
78
|
+
* beside the page as an embed is (`resolveRelativeImage`), and any other
|
|
79
|
+
* file as `resolveRelativeFile` finds one; an included partial's were
|
|
80
|
+
* already rebased onto the page by the include expander. A reference is
|
|
81
|
+
* rewritten only when the content-assets endpoint serves its file: the map
|
|
82
|
+
* `collectContentAssets` builds over the same references. Anything else is
|
|
83
|
+
* left as written, and `blume validate` reports it.
|
|
84
|
+
*/
|
|
85
|
+
export const contentAssetsPlugin = (
|
|
86
|
+
options: ContentAssetsPluginOptions = {}
|
|
87
|
+
) => {
|
|
88
|
+
const assetsFile = options.dataFile
|
|
89
|
+
? join(
|
|
90
|
+
dirname(options.dataFile),
|
|
91
|
+
RUNTIME_MODULE_FILES.get("blume:content-assets") ?? ""
|
|
92
|
+
)
|
|
93
|
+
: undefined;
|
|
94
|
+
const readAssets = snapshotReader("blume:content-assets", assetsFile);
|
|
95
|
+
|
|
96
|
+
// The map is published as `param → absolute path`; parsed once per
|
|
97
|
+
// published text and inverted, so each reference looks its file up directly.
|
|
98
|
+
let cached: { params: Map<string, string>; text: string } | undefined;
|
|
99
|
+
const servedParams = (): Map<string, string> | undefined => {
|
|
100
|
+
const text = readAssets();
|
|
101
|
+
if (text === undefined) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (cached?.text !== text) {
|
|
105
|
+
const files: Record<string, string> = JSON.parse(text);
|
|
106
|
+
cached = {
|
|
107
|
+
params: new Map(
|
|
108
|
+
Object.entries(files).map(([param, path]) => [normalize(path), param])
|
|
109
|
+
),
|
|
110
|
+
text,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return cached.params;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** The served URL of `file`, or undefined when the endpoint doesn't serve it. */
|
|
117
|
+
const servedUrl = (file: string | null, suffix = ""): string | undefined => {
|
|
118
|
+
const param = file === null ? undefined : servedParams()?.get(file);
|
|
119
|
+
return param === undefined
|
|
120
|
+
? undefined
|
|
121
|
+
: `${contentAssetUrl(param, options.deployBase)}${suffix}`;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** The served URL of a file a link or an element URL names beside the page. */
|
|
125
|
+
const fileUrl = (dir: string, target: string): string | undefined => {
|
|
126
|
+
const file = resolveRelativeFile(dir, target);
|
|
127
|
+
return file ? servedUrl(file.path, file.suffix) : undefined;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const rewriteUrl = (node: UrlNode, ctx: ContentAssetsContext): void => {
|
|
131
|
+
const dir = pageDir(ctx);
|
|
132
|
+
const next =
|
|
133
|
+
dir && isStringValue(node.url) ? fileUrl(dir, node.url) : undefined;
|
|
134
|
+
if (next !== undefined) {
|
|
135
|
+
ctx.setProperty(node, "url", next);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const rewriteHtml = (node: HtmlNode, ctx: ContentAssetsContext): void => {
|
|
140
|
+
const dir = pageDir(ctx);
|
|
141
|
+
if (dir === undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const next = rewriteElementUrls(node.value, (url) =>
|
|
145
|
+
isLinkElementUrl(url) ? (fileUrl(dir, url.value) ?? null) : null
|
|
146
|
+
);
|
|
147
|
+
if (next !== node.value) {
|
|
148
|
+
ctx.setProperty(node, "value", next);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** The served URL an element's attribute is rewritten to, if any. */
|
|
153
|
+
const attributeUrl = (
|
|
154
|
+
dir: string,
|
|
155
|
+
tag: string,
|
|
156
|
+
attribute: JsxAttribute
|
|
157
|
+
): string | undefined => {
|
|
158
|
+
const { name, value } = attribute;
|
|
159
|
+
if (
|
|
160
|
+
attribute.type !== "mdxJsxAttribute" ||
|
|
161
|
+
name === undefined ||
|
|
162
|
+
!isStringValue(value)
|
|
163
|
+
) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if (tag === "Card" && name === "img") {
|
|
167
|
+
return servedUrl(resolveRelativeImage(dir, value));
|
|
168
|
+
}
|
|
169
|
+
return isLinkElementUrl({ attribute: name, tag, value })
|
|
170
|
+
? fileUrl(dir, value)
|
|
171
|
+
: undefined;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const rewriteElement = (node: JsxNode, ctx: ContentAssetsContext): void => {
|
|
175
|
+
const dir = pageDir(ctx);
|
|
176
|
+
const { name } = node;
|
|
177
|
+
if (!(dir && name)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const attributes = node.attributes ?? [];
|
|
181
|
+
let changed = false;
|
|
182
|
+
const next = attributes.map((attribute) => {
|
|
183
|
+
const url = attributeUrl(dir, name, attribute);
|
|
184
|
+
if (url === undefined) {
|
|
185
|
+
return attribute;
|
|
186
|
+
}
|
|
187
|
+
changed = true;
|
|
188
|
+
return { name: attribute.name, type: attribute.type, value: url };
|
|
189
|
+
});
|
|
190
|
+
if (changed) {
|
|
191
|
+
// Sätteri can't set a JSX element's attributes in place, so the element
|
|
192
|
+
// is swapped for a copy; its children carry over and are still visited.
|
|
193
|
+
ctx.replaceNode(node, {
|
|
194
|
+
attributes: next,
|
|
195
|
+
children: node.children ?? [],
|
|
196
|
+
name,
|
|
197
|
+
type: node.type,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
definition: rewriteUrl,
|
|
204
|
+
html: rewriteHtml,
|
|
205
|
+
link: rewriteUrl,
|
|
206
|
+
mdxJsxFlowElement: rewriteElement,
|
|
207
|
+
mdxJsxTextElement: rewriteElement,
|
|
208
|
+
name: "blume-content-assets",
|
|
209
|
+
};
|
|
210
|
+
};
|
|
@@ -56,6 +56,72 @@ export const calloutTypeFor = (name: string): string | null => {
|
|
|
56
56
|
return CALLOUT_ALIASES[lower] ?? null;
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
// The characters that start inline Markdown in plain text, escaped so the
|
|
60
|
+
// text reads back as written.
|
|
61
|
+
const MARKDOWN_SPECIAL = /[\\`*_~[\]<]/gu;
|
|
62
|
+
|
|
63
|
+
const escapeMarkdown = (text: string): string =>
|
|
64
|
+
text.replaceAll(MARKDOWN_SPECIAL, String.raw`\$&`);
|
|
65
|
+
|
|
66
|
+
const BACKTICK_RUN = /`+/gu;
|
|
67
|
+
|
|
68
|
+
/** A code span holding `code` verbatim. */
|
|
69
|
+
const codeSpan = (code: string): string => {
|
|
70
|
+
const runs = [...code.matchAll(BACKTICK_RUN)].map(([run]) => run.length);
|
|
71
|
+
const fence = "`".repeat(Math.max(0, ...runs) + 1);
|
|
72
|
+
// A backtick at either end would join the fence, and CommonMark strips one
|
|
73
|
+
// space from each end of a span that has both: a space pads either case.
|
|
74
|
+
const pad =
|
|
75
|
+
code.startsWith("`") ||
|
|
76
|
+
code.endsWith("`") ||
|
|
77
|
+
(code.startsWith(" ") && code.endsWith(" ") && code.trim() !== "")
|
|
78
|
+
? " "
|
|
79
|
+
: "";
|
|
80
|
+
return `${fence}${pad}${code}${pad}${fence}`;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// What can't sit inside a `<…>` link destination.
|
|
84
|
+
const DESTINATION_UNSAFE = /[<>\n]/gu;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A node's phrasing children (a directive label's, a paragraph's) written
|
|
88
|
+
* back as inline Markdown, for a component prop that renders it — a callout
|
|
89
|
+
* title. Text, emphasis, strong, strikethrough, code, and links keep their
|
|
90
|
+
* formatting; anything else reads as its text (an image as nothing, raw HTML
|
|
91
|
+
* as its literal source), the way a flattened title always did.
|
|
92
|
+
*/
|
|
93
|
+
export const phrasingMarkdown = (parent: MdastNode): string =>
|
|
94
|
+
// SAFETY: a phrasing parent's `children` is a node list.
|
|
95
|
+
((parent.children ?? []) as MdastNode[])
|
|
96
|
+
.map((node) => {
|
|
97
|
+
switch (node.type) {
|
|
98
|
+
case "strong": {
|
|
99
|
+
return `**${phrasingMarkdown(node)}**`;
|
|
100
|
+
}
|
|
101
|
+
case "emphasis": {
|
|
102
|
+
return `*${phrasingMarkdown(node)}*`;
|
|
103
|
+
}
|
|
104
|
+
case "delete": {
|
|
105
|
+
return `~~${phrasingMarkdown(node)}~~`;
|
|
106
|
+
}
|
|
107
|
+
case "inlineCode": {
|
|
108
|
+
return codeSpan(mdastToString(node));
|
|
109
|
+
}
|
|
110
|
+
case "link": {
|
|
111
|
+
const url = String(node.url ?? "").replaceAll(
|
|
112
|
+
DESTINATION_UNSAFE,
|
|
113
|
+
encodeURIComponent
|
|
114
|
+
);
|
|
115
|
+
return `[${phrasingMarkdown(node)}](<${url}>)`;
|
|
116
|
+
}
|
|
117
|
+
default: {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return escapeMarkdown(mdastToString(node, { includeImageAlt: false }));
|
|
122
|
+
})
|
|
123
|
+
.join("");
|
|
124
|
+
|
|
59
125
|
/** The markers that open a text (`:name`) and a leaf (`::name`) directive. */
|
|
60
126
|
const LITERAL_MARKERS = new Map([
|
|
61
127
|
["leafDirective", "::"],
|
|
@@ -194,12 +260,10 @@ const renderContainer = (
|
|
|
194
260
|
];
|
|
195
261
|
}
|
|
196
262
|
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
// the historical child-values-only behavior.
|
|
263
|
+
// The label's phrasing as Markdown, which `<Callout>` renders in its title:
|
|
264
|
+
// `:::note[Read **this**]` keeps its bold.
|
|
200
265
|
const title =
|
|
201
|
-
node.attributes?.title ??
|
|
202
|
-
(label ? mdastToString(label, { includeImageAlt: false }) : undefined);
|
|
266
|
+
node.attributes?.title ?? (label ? phrasingMarkdown(label) : undefined);
|
|
203
267
|
const attributes = [jsxAttribute("type", type)];
|
|
204
268
|
if (title) {
|
|
205
269
|
attributes.push(jsxAttribute("title", title));
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A code fence's language, read the way authors coming from other docs tools
|
|
3
|
+
* write it. Shiki matches language ids exactly, so two common spellings used
|
|
4
|
+
* to fall back to plain text with only a console line:
|
|
5
|
+
*
|
|
6
|
+
* - a capitalized id (```` ```JSON ````, ```` ```Dockerfile ````): Shiki's ids
|
|
7
|
+
* and aliases are all lowercase, so the id is lowercased when that names a
|
|
8
|
+
* language and the spelling as written doesn't.
|
|
9
|
+
* - a line range glued to the id (```` ```js{2} ````, the VitePress and
|
|
10
|
+
* VuePress form): the range moves into the fence meta, ahead of any meta
|
|
11
|
+
* already there, where the line-highlight transformer reads it.
|
|
12
|
+
* - a line range glued to a bracketed title (```` ```ts [file.ts]{2} ````,
|
|
13
|
+
* the Docus form): a space splits it off, so the title is the bracketed
|
|
14
|
+
* word alone and the range stays a range.
|
|
15
|
+
*
|
|
16
|
+
* Every other fence keeps its language and meta exactly as written.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { bundledLanguages } from "shiki/langs";
|
|
20
|
+
|
|
21
|
+
import type { MdastNode } from "./mdast.ts";
|
|
22
|
+
|
|
23
|
+
/** The ids Shiki renders without a grammar: plain text and ANSI output. */
|
|
24
|
+
const PLAIN_LANGUAGES: ReadonlySet<string> = new Set([
|
|
25
|
+
"ansi",
|
|
26
|
+
"plain",
|
|
27
|
+
"plaintext",
|
|
28
|
+
"text",
|
|
29
|
+
"txt",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/** Whether Shiki highlights `lang` as written: a bundled id, alias, or plain. */
|
|
33
|
+
export const isKnownLanguage = (lang: string): boolean =>
|
|
34
|
+
PLAIN_LANGUAGES.has(lang) || Object.hasOwn(bundledLanguages, lang);
|
|
35
|
+
|
|
36
|
+
/** A language id with a line range glued on: `js{2}`, `ts{1,3-5}`. */
|
|
37
|
+
const GLUED_RANGE = /^(?<lang>[^{]+)(?<range>\{[^}]*\})$/u;
|
|
38
|
+
|
|
39
|
+
/** A bracketed title with a line range glued on: `[file.ts]{2}`. */
|
|
40
|
+
const GLUED_TITLE_RANGE =
|
|
41
|
+
/(?<=^|\s)(?<title>\[[^\]]*\])(?<range>\{[^}]*\})(?=\s|$)/u;
|
|
42
|
+
|
|
43
|
+
/** A fence's language and meta, after {@link normalizeFence}. */
|
|
44
|
+
export interface Fence {
|
|
45
|
+
lang: string;
|
|
46
|
+
meta: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The language and meta a fence means, or null when it means what it says.
|
|
51
|
+
* A range glued to the language moves into the meta and one glued to a
|
|
52
|
+
* bracketed title splits off it, then a capitalized language that only names
|
|
53
|
+
* a known language in lowercase is lowercased.
|
|
54
|
+
*/
|
|
55
|
+
export const normalizeFence = (
|
|
56
|
+
lang: string | null | undefined,
|
|
57
|
+
meta: string | null | undefined
|
|
58
|
+
): Fence | null => {
|
|
59
|
+
if (!lang) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const glued = GLUED_RANGE.exec(lang)?.groups;
|
|
63
|
+
const id = glued?.lang ?? lang;
|
|
64
|
+
const fenceMeta =
|
|
65
|
+
(glued?.range
|
|
66
|
+
? [glued.range, meta].filter(Boolean).join(" ")
|
|
67
|
+
: meta
|
|
68
|
+
)?.replace(GLUED_TITLE_RANGE, "$<title> $<range>") ?? null;
|
|
69
|
+
const lower = id.toLowerCase();
|
|
70
|
+
const language = !isKnownLanguage(id) && isKnownLanguage(lower) ? lower : id;
|
|
71
|
+
return language === lang && fenceMeta === (meta ?? null)
|
|
72
|
+
? null
|
|
73
|
+
: { lang: language, meta: fenceMeta };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** The code node slice the plugin reads. */
|
|
77
|
+
interface CodeNode extends MdastNode {
|
|
78
|
+
lang?: string | null;
|
|
79
|
+
meta?: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The visitor context slice the plugin writes through. */
|
|
83
|
+
interface FenceContext {
|
|
84
|
+
setProperty: (
|
|
85
|
+
node: CodeNode,
|
|
86
|
+
key: "lang" | "meta",
|
|
87
|
+
value: string | null
|
|
88
|
+
) => void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Satteri MDAST plugin applying {@link normalizeFence} to every fence. It runs
|
|
93
|
+
* before the plugins that read a fence's language (`package-install`, `ts2js`,
|
|
94
|
+
* mermaid) and before Shiki, so all of them see the normalized fence.
|
|
95
|
+
*/
|
|
96
|
+
export const fenceLanguagePlugin = () => ({
|
|
97
|
+
code(node: CodeNode, ctx: FenceContext) {
|
|
98
|
+
const fence = normalizeFence(node.lang, node.meta);
|
|
99
|
+
if (!fence) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
ctx.setProperty(node, "lang", fence.lang);
|
|
103
|
+
ctx.setProperty(node, "meta", fence.meta);
|
|
104
|
+
},
|
|
105
|
+
name: "blume-fence-language",
|
|
106
|
+
});
|
|
@@ -48,5 +48,47 @@ export const RESERVED_META_KEYWORDS: ReadonlySet<string> = new Set([
|
|
|
48
48
|
"wrap",
|
|
49
49
|
]);
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Bare keywords other docs tools read after the language, which Blume
|
|
53
|
+
* doesn't: Mintlify's `lines` and Docusaurus' `showLineNumbers` (line
|
|
54
|
+
* numbers) and Fern's `wordWrap` on any fence, and rustdoc's and mdBook's test
|
|
55
|
+
* and playground attributes on a Rust one. They do nothing here, so they're
|
|
56
|
+
* never promoted to a block title, and `blume check` warns about each
|
|
57
|
+
* (`BLUME_CODE_FENCE_OPTION`).
|
|
58
|
+
*/
|
|
59
|
+
const FOREIGN_META_KEYWORDS: ReadonlySet<string> = new Set([
|
|
60
|
+
"lines",
|
|
61
|
+
"wordWrap",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// Docusaurus' line-numbers keyword, with an optional start line
|
|
65
|
+
// (`showLineNumbers{3}`).
|
|
66
|
+
const SHOW_LINE_NUMBERS = /^showLineNumbers(?:\{\d*\})?$/u;
|
|
67
|
+
|
|
68
|
+
export const RUST_META_KEYWORDS: ReadonlySet<string> = new Set([
|
|
69
|
+
"compile_fail",
|
|
70
|
+
"edition2015",
|
|
71
|
+
"edition2018",
|
|
72
|
+
"edition2021",
|
|
73
|
+
"edition2024",
|
|
74
|
+
"editable",
|
|
75
|
+
"ignore",
|
|
76
|
+
"no_run",
|
|
77
|
+
"noplayground",
|
|
78
|
+
"should_panic",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
/** The fence languages rustdoc's and mdBook's keywords apply to. */
|
|
82
|
+
export const RUST_LANGUAGES: ReadonlySet<string> = new Set(["rs", "rust"]);
|
|
83
|
+
|
|
84
|
+
/** Whether `token` is another tool's keyword in a fence of language `lang`. */
|
|
85
|
+
export const isForeignKeyword = (
|
|
86
|
+
token: string,
|
|
87
|
+
lang: string | null | undefined
|
|
88
|
+
): boolean =>
|
|
89
|
+
FOREIGN_META_KEYWORDS.has(token) ||
|
|
90
|
+
SHOW_LINE_NUMBERS.test(token) ||
|
|
91
|
+
(RUST_LANGUAGES.has(lang ?? "") && RUST_META_KEYWORDS.has(token));
|
|
92
|
+
|
|
51
93
|
/** A Shiki `{1,3-5}` line-range token. */
|
|
52
94
|
export const isLineRange = (token: string): boolean => token.startsWith("{");
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { phrasingMarkdown } from "./directives.ts";
|
|
2
|
+
import { jsxAttribute, jsxFlowElement } from "./mdast.ts";
|
|
3
|
+
import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The callout each GitHub alert renders as. `IMPORTANT` has no callout of its
|
|
7
|
+
* own and reads as a note (`:::important` does too), and `CAUTION` — GitHub's
|
|
8
|
+
* red "risks or negative outcomes" alert — as danger.
|
|
9
|
+
*/
|
|
10
|
+
export const ALERT_CALLOUTS: ReadonlyMap<string, string> = new Map([
|
|
11
|
+
["caution", "danger"],
|
|
12
|
+
["important", "note"],
|
|
13
|
+
["note", "note"],
|
|
14
|
+
["tip", "tip"],
|
|
15
|
+
["warning", "warning"],
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// `[!NOTE]` opening a quote's first line, any case, as GitHub reads it.
|
|
19
|
+
const ALERT_MARKER = /^\[!(?<kind>[a-z]+)\]/iu;
|
|
20
|
+
|
|
21
|
+
/** An alert marker as written (`[!NOTE]`) and the callout it renders as. */
|
|
22
|
+
export interface AlertMarker {
|
|
23
|
+
callout: string;
|
|
24
|
+
marker: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The alert marker a quote's first text opens with, if any. */
|
|
28
|
+
export const alertMarkerOf = (text: string): AlertMarker | undefined => {
|
|
29
|
+
const match = ALERT_MARKER.exec(text);
|
|
30
|
+
const callout = ALERT_CALLOUTS.get(match?.groups?.kind?.toLowerCase() ?? "");
|
|
31
|
+
return match && callout ? { callout, marker: match[0] } : undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** The visitor-context slice the alert visitor uses. */
|
|
35
|
+
interface AlertVisitorContext extends MdastVisitorContext {
|
|
36
|
+
parent?: (node: MdastNode) => MdastNode | undefined;
|
|
37
|
+
source?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The node's children. */
|
|
41
|
+
const childrenOf = (node: MdastNode | undefined): MdastNode[] =>
|
|
42
|
+
// SAFETY: a parent's `children` is always a node list; leaves carry none.
|
|
43
|
+
(node?.children ?? []) as MdastNode[];
|
|
44
|
+
|
|
45
|
+
/** A text node's value, or undefined for any other node. */
|
|
46
|
+
const textOf = (node: MdastNode | undefined): string | undefined =>
|
|
47
|
+
node?.type === "text" ? String(node.value) : undefined;
|
|
48
|
+
|
|
49
|
+
/** A text node's start offset into the page, when the parse recorded one. */
|
|
50
|
+
const startOffset = (node: MdastNode): number | undefined => {
|
|
51
|
+
// SAFETY: Satteri positions carry numeric offsets.
|
|
52
|
+
const position = node.position as { start?: { offset?: number } } | undefined;
|
|
53
|
+
return position?.start?.offset;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** The alert a blockquote opens with: its callout type and first paragraph. */
|
|
57
|
+
interface Alert {
|
|
58
|
+
paragraph: MdastNode;
|
|
59
|
+
type: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The alert a blockquote is, or undefined. Its first paragraph must open with
|
|
64
|
+
* the marker as written: an escaped `\[!NOTE]` (text once parsed) starts with
|
|
65
|
+
* a backslash in the page.
|
|
66
|
+
*/
|
|
67
|
+
const alertOf = (node: MdastNode, source: string): Alert | undefined => {
|
|
68
|
+
const [paragraph] = childrenOf(node);
|
|
69
|
+
const first = childrenOf(paragraph).at(0);
|
|
70
|
+
const text = textOf(first);
|
|
71
|
+
if (paragraph?.type !== "paragraph" || first === undefined || !text) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const marker = alertMarkerOf(text);
|
|
75
|
+
const offset = startOffset(first);
|
|
76
|
+
if (
|
|
77
|
+
marker === undefined ||
|
|
78
|
+
(offset !== undefined && source[offset] === "\\")
|
|
79
|
+
) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return { paragraph, type: marker.callout };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** A paragraph's phrasing split at its first line break. */
|
|
86
|
+
interface Lines {
|
|
87
|
+
first: MdastNode[];
|
|
88
|
+
rest: MdastNode[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Split phrasing at its first line break: a newline in text, or a hard break. */
|
|
92
|
+
const splitFirstLine = (nodes: readonly MdastNode[]): Lines => {
|
|
93
|
+
for (const [index, node] of nodes.entries()) {
|
|
94
|
+
const after = nodes.slice(index + 1);
|
|
95
|
+
if (node.type === "break") {
|
|
96
|
+
return { first: nodes.slice(0, index), rest: after };
|
|
97
|
+
}
|
|
98
|
+
const newline = textOf(node)?.indexOf("\n") ?? -1;
|
|
99
|
+
if (newline !== -1) {
|
|
100
|
+
const value = String(node.value);
|
|
101
|
+
const head = value.slice(0, newline);
|
|
102
|
+
const tail = value.slice(newline + 1);
|
|
103
|
+
return {
|
|
104
|
+
first: [...nodes.slice(0, index), { type: "text", value: head }],
|
|
105
|
+
rest: tail === "" ? after : [{ type: "text", value: tail }, ...after],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { first: [...nodes], rest: [] };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A GitHub alert as a `<Callout>`: the marker comes off the first line, text
|
|
114
|
+
* after it on that line becomes the title (VitePress and Obsidian write
|
|
115
|
+
* `> [!WARNING] Heads up`), and the rest of the quote is the body.
|
|
116
|
+
*/
|
|
117
|
+
const alertCallout = ({ paragraph, type }: Alert, quote: MdastNode) => {
|
|
118
|
+
const [first, ...phrasing] = childrenOf(paragraph);
|
|
119
|
+
const opening = String(first?.value).replace(ALERT_MARKER, "");
|
|
120
|
+
const lines = splitFirstLine([{ type: "text", value: opening }, ...phrasing]);
|
|
121
|
+
const title = phrasingMarkdown({ children: lines.first, type: "paragraph" });
|
|
122
|
+
const attributes = [jsxAttribute("type", type)];
|
|
123
|
+
if (title.trim() !== "") {
|
|
124
|
+
attributes.push(jsxAttribute("title", title.trim()));
|
|
125
|
+
}
|
|
126
|
+
const body = lines.rest.length
|
|
127
|
+
? [{ children: lines.rest, type: "paragraph" }]
|
|
128
|
+
: [];
|
|
129
|
+
return jsxFlowElement("Callout", attributes, [
|
|
130
|
+
...body,
|
|
131
|
+
...childrenOf(quote).slice(1),
|
|
132
|
+
]);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** Whether `node` sits inside a quote that is itself an alert. */
|
|
136
|
+
const insideAlert = (node: MdastNode, ctx: AlertVisitorContext): boolean => {
|
|
137
|
+
for (
|
|
138
|
+
let parent = ctx.parent?.(node);
|
|
139
|
+
parent !== undefined;
|
|
140
|
+
parent = ctx.parent?.(parent)
|
|
141
|
+
) {
|
|
142
|
+
if (
|
|
143
|
+
parent.type === "blockquote" &&
|
|
144
|
+
alertOf(parent, ctx.source ?? "") !== undefined
|
|
145
|
+
) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Satteri MDAST plugin for GitHub alerts in `.mdx`: a quote whose first line
|
|
154
|
+
* is `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!CAUTION]`
|
|
155
|
+
* renders as the matching `<Callout>` (see {@link ALERT_CALLOUTS}), as on
|
|
156
|
+
* GitHub. An alert quoted inside another stays a quote, as GitHub leaves it.
|
|
157
|
+
* `.md` pages render no components, so their alerts stay quotes, which
|
|
158
|
+
* `blume check` warns about (`core/alert-diagnostics.ts`).
|
|
159
|
+
*/
|
|
160
|
+
export const githubAlertsPlugin = () => ({
|
|
161
|
+
blockquote(node: MdastNode, ctx: AlertVisitorContext) {
|
|
162
|
+
const alert = alertOf(node, ctx.source ?? "");
|
|
163
|
+
if (alert === undefined || insideAlert(node, ctx)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
ctx.replaceNode(node, alertCallout(alert, node));
|
|
167
|
+
},
|
|
168
|
+
name: "blume-github-alerts",
|
|
169
|
+
// Positions are opt-in since satteri 0.10; they tell an escaped marker
|
|
170
|
+
// from a written one.
|
|
171
|
+
options: { position: true },
|
|
172
|
+
});
|