blume 2.1.2 → 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 +133 -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 +10 -9
- 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
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
localizeRoute,
|
|
5
5
|
localizeInternalPath,
|
|
6
6
|
i18nDiagnostics,
|
|
7
|
+
routeSetFor,
|
|
7
8
|
blumeReferences,
|
|
8
9
|
frontmatter_default,
|
|
9
10
|
resolveLocalizable,
|
|
@@ -13,11 +14,16 @@ import {
|
|
|
13
14
|
discoverPages,
|
|
14
15
|
customStaticRoutes,
|
|
15
16
|
nextFenceState,
|
|
17
|
+
resolveRelativeImage,
|
|
16
18
|
hashText,
|
|
17
19
|
pollingWatch,
|
|
18
20
|
snapshotCache,
|
|
19
21
|
loadWithCache,
|
|
22
|
+
MARKDOWN_BODY_FEATURES,
|
|
20
23
|
MDX_BODY_FEATURES,
|
|
24
|
+
isExpression,
|
|
25
|
+
pageKey,
|
|
26
|
+
folderChildKeys,
|
|
21
27
|
normalizeRef,
|
|
22
28
|
isPageRef,
|
|
23
29
|
pagesByRef,
|
|
@@ -28,7 +34,7 @@ import {
|
|
|
28
34
|
hasVariables,
|
|
29
35
|
substituteVariables,
|
|
30
36
|
undefinedVariables,
|
|
31
|
-
|
|
37
|
+
BUILT_IN_PAGE_META_KEYS,
|
|
32
38
|
folderMetaSchema,
|
|
33
39
|
versionizeRoute,
|
|
34
40
|
versionsDiagnostics,
|
|
@@ -36,6 +42,10 @@ import {
|
|
|
36
42
|
slugifyPath,
|
|
37
43
|
unloadablePathDiagnostic,
|
|
38
44
|
frontmatterYamlDiagnostic,
|
|
45
|
+
ATTRIBUTE,
|
|
46
|
+
trailingCurlyMarker,
|
|
47
|
+
HTML_COMMENT,
|
|
48
|
+
INLINE_CODE,
|
|
39
49
|
extractHeadings,
|
|
40
50
|
strippedLineOffset,
|
|
41
51
|
isStringValue,
|
|
@@ -45,7 +55,8 @@ import {
|
|
|
45
55
|
normalizeEntry,
|
|
46
56
|
hasIncludeStatements,
|
|
47
57
|
expandIncludes,
|
|
48
|
-
|
|
58
|
+
youtubeEmbedSrc,
|
|
59
|
+
buildManifest,
|
|
49
60
|
nodeRequire,
|
|
50
61
|
escapeMarkdownText,
|
|
51
62
|
guardBlockStart,
|
|
@@ -74,21 +85,24 @@ import {
|
|
|
74
85
|
asObject,
|
|
75
86
|
asNumber,
|
|
76
87
|
objectsIn,
|
|
88
|
+
mirroredPage,
|
|
77
89
|
createModuleLoader,
|
|
78
90
|
resolveProjectContext,
|
|
79
91
|
loadConfig
|
|
80
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-dm4mg9bj.js";
|
|
81
93
|
import {
|
|
82
|
-
getBlumeVersion,
|
|
83
94
|
BlumeError,
|
|
84
|
-
|
|
95
|
+
locatePath,
|
|
96
|
+
diagnosticsFromZod,
|
|
97
|
+
closestMatch
|
|
85
98
|
} from "./index.js";
|
|
86
99
|
import {
|
|
87
100
|
trimChar,
|
|
88
101
|
normalizePath,
|
|
89
102
|
isInternalPath,
|
|
90
103
|
withBasePath,
|
|
91
|
-
mountBasePath
|
|
104
|
+
mountBasePath,
|
|
105
|
+
stripBasePath
|
|
92
106
|
} from "./chunk-zccbjcj0.js";
|
|
93
107
|
import {
|
|
94
108
|
BLUME_IGNORE_DIRS,
|
|
@@ -195,9 +209,12 @@ var validateSearchPopularIcons = (popular) => {
|
|
|
195
209
|
return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
|
|
196
210
|
};
|
|
197
211
|
var resolvesToPages = (routes, path) => routes.has(path) || [...routes].some((route) => route.startsWith(`${path}/`));
|
|
198
|
-
var validateNavTargets = (navigation, routes) => {
|
|
212
|
+
var validateNavTargets = (navigation, routes, servesFile = () => false) => {
|
|
199
213
|
const targets = [
|
|
200
|
-
...navigation.tabs.map((tab) => ({
|
|
214
|
+
...navigation.tabs.map((tab) => ({
|
|
215
|
+
label: tab.label,
|
|
216
|
+
path: tab.href ?? tab.path
|
|
217
|
+
})),
|
|
201
218
|
...navigation.selectors.flatMap((selector) => selector.items.map((item) => ({ label: item.label, path: item.path }))),
|
|
202
219
|
...navigation.featured.map((link) => ({
|
|
203
220
|
label: link.label,
|
|
@@ -215,7 +232,8 @@ var validateNavTargets = (navigation, routes) => {
|
|
|
215
232
|
if (!isInternalPath(path) || path.startsWith("/#") || seen.has(path)) {
|
|
216
233
|
continue;
|
|
217
234
|
}
|
|
218
|
-
|
|
235
|
+
const bare = path.split(/[?#]/u)[0] ?? path;
|
|
236
|
+
if (!(resolvesToPages(routes, bare) || servesFile(bare))) {
|
|
219
237
|
seen.add(path);
|
|
220
238
|
diagnostics.push({
|
|
221
239
|
code: "BLUME_NAV_MISSING_PAGE",
|
|
@@ -324,133 +342,12 @@ var validateNavStructure = (navigation, pages) => [
|
|
|
324
342
|
...hiddenInSidebarDiagnostics(navigation, pages)
|
|
325
343
|
];
|
|
326
344
|
|
|
327
|
-
// src/core/manifest.ts
|
|
328
|
-
var versionAlternateKey = (versionKey, locale) => `${versionKey}\x00${locale}`;
|
|
329
|
-
var versionAlternatesFor = (byKey, versionKey, locale) => {
|
|
330
|
-
const key = versionAlternateKey(versionKey, locale);
|
|
331
|
-
const existing = byKey.get(key);
|
|
332
|
-
if (existing) {
|
|
333
|
-
return existing;
|
|
334
|
-
}
|
|
335
|
-
const list = [];
|
|
336
|
-
byKey.set(key, list);
|
|
337
|
-
return list;
|
|
338
|
-
};
|
|
339
|
-
var MANIFEST_VERSION = 1;
|
|
340
|
-
var contentIndexable = (page, config) => !page.meta.search.exclude && (!page.meta.sidebar.hidden || config.search.indexing.includeHiddenPages);
|
|
341
|
-
var routeDescription = (page) => page.meta.seo.description ?? page.description;
|
|
342
|
-
var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAlternatesByKey) => {
|
|
343
|
-
const fallback = resolveFallbackLocale(i18n);
|
|
344
|
-
if (!fallback) {
|
|
345
|
-
return [];
|
|
346
|
-
}
|
|
347
|
-
const fallbackPages = new Map(graph.pages.flatMap((page) => page.locale === fallback && !page.monolingual ? [[page.translationKey, page]] : []));
|
|
348
|
-
const routes = [];
|
|
349
|
-
for (const { code } of i18n.locales) {
|
|
350
|
-
if (code === fallback) {
|
|
351
|
-
continue;
|
|
352
|
-
}
|
|
353
|
-
const present = new Set(graph.pages.flatMap((page) => page.locale === code ? [page.translationKey] : []));
|
|
354
|
-
for (const [key, source] of fallbackPages) {
|
|
355
|
-
if (present.has(key)) {
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
const path = mountBasePath(basePath, localizeRoute(key, code, i18n));
|
|
359
|
-
const versionAlternates = versionAlternatesByKey ? versionAlternatesFor(versionAlternatesByKey, source.versionKey, code) : [];
|
|
360
|
-
if (versionAlternatesByKey) {
|
|
361
|
-
versionAlternates.push({ path, version: source.version });
|
|
362
|
-
}
|
|
363
|
-
routes.push({
|
|
364
|
-
alternates: alternatesByKey.get(key) ?? [],
|
|
365
|
-
collection: source.collection ?? "docs",
|
|
366
|
-
contentType: source.contentType,
|
|
367
|
-
description: routeDescription(source),
|
|
368
|
-
draft: source.meta.draft,
|
|
369
|
-
editUrl: source.editUrl,
|
|
370
|
-
entryId: source.entryId ?? source.source.ref,
|
|
371
|
-
fallback: true,
|
|
372
|
-
hidden: source.meta.sidebar.hidden,
|
|
373
|
-
id: source.id,
|
|
374
|
-
indexable: false,
|
|
375
|
-
lastModified: source.lastModified,
|
|
376
|
-
locale: code,
|
|
377
|
-
monolingual: source.monolingual,
|
|
378
|
-
path,
|
|
379
|
-
source: source.source,
|
|
380
|
-
sourcePath: source.sourcePath,
|
|
381
|
-
title: source.title,
|
|
382
|
-
version: source.version,
|
|
383
|
-
versionAlternates
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return routes;
|
|
388
|
-
};
|
|
389
|
-
var buildManifest = (options) => {
|
|
390
|
-
const { context, config, graph } = options;
|
|
391
|
-
const searchEnabled = config.search.provider.mode !== "none";
|
|
392
|
-
const { i18n } = config;
|
|
393
|
-
const alternatesByKey = new Map;
|
|
394
|
-
if (i18n) {
|
|
395
|
-
for (const page of graph.pages) {
|
|
396
|
-
const list = alternatesByKey.get(page.translationKey) ?? [];
|
|
397
|
-
list.push({ locale: page.locale, path: page.route });
|
|
398
|
-
alternatesByKey.set(page.translationKey, list);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const versionAlternatesByKey = config.versions ? new Map : undefined;
|
|
402
|
-
if (versionAlternatesByKey) {
|
|
403
|
-
for (const page of graph.pages) {
|
|
404
|
-
versionAlternatesFor(versionAlternatesByKey, page.versionKey, page.locale).push({ path: page.route, version: page.version });
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
const routes = graph.pages.map((page) => ({
|
|
408
|
-
aiExclude: page.meta.ai.exclude,
|
|
409
|
-
alternates: alternatesByKey.get(page.translationKey) ?? [],
|
|
410
|
-
collection: page.collection ?? "docs",
|
|
411
|
-
contentType: page.contentType,
|
|
412
|
-
description: routeDescription(page),
|
|
413
|
-
draft: page.meta.draft,
|
|
414
|
-
editUrl: page.editUrl,
|
|
415
|
-
entryId: page.entryId ?? page.source.ref,
|
|
416
|
-
hidden: page.meta.sidebar.hidden,
|
|
417
|
-
id: page.id,
|
|
418
|
-
indexable: searchEnabled && contentIndexable(page, config),
|
|
419
|
-
lastModified: page.lastModified,
|
|
420
|
-
locale: page.locale,
|
|
421
|
-
monolingual: page.monolingual,
|
|
422
|
-
path: page.route,
|
|
423
|
-
source: page.source,
|
|
424
|
-
sourcePath: page.sourcePath,
|
|
425
|
-
title: page.title,
|
|
426
|
-
version: page.version,
|
|
427
|
-
versionAlternates: versionAlternatesByKey ? versionAlternatesFor(versionAlternatesByKey, page.versionKey, page.locale) : []
|
|
428
|
-
}));
|
|
429
|
-
if (i18n) {
|
|
430
|
-
routes.push(...buildFallbackRoutes(graph, i18n, alternatesByKey, config.basePath, versionAlternatesByKey));
|
|
431
|
-
}
|
|
432
|
-
if (versionAlternatesByKey && config.versions) {
|
|
433
|
-
const rank = new Map(config.versions.archived.map((version, index) => [version.id, index + 1]));
|
|
434
|
-
for (const list of versionAlternatesByKey.values()) {
|
|
435
|
-
list.sort((a, b) => (rank.get(a.version) ?? 0) - (rank.get(b.version) ?? 0));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
routes.sort((a, b) => a.path.localeCompare(b.path));
|
|
439
|
-
return {
|
|
440
|
-
blumeVersion: getBlumeVersion(),
|
|
441
|
-
contentRoot: context.contentRoot,
|
|
442
|
-
output: config.deployment.options.output,
|
|
443
|
-
projectRoot: context.root,
|
|
444
|
-
routes,
|
|
445
|
-
version: MANIFEST_VERSION
|
|
446
|
-
};
|
|
447
|
-
};
|
|
448
|
-
|
|
449
345
|
// src/core/project-graph.ts
|
|
450
|
-
import {
|
|
346
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
347
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative5 } from "pathe";
|
|
451
348
|
|
|
452
|
-
// src/core/
|
|
453
|
-
import {
|
|
349
|
+
// src/core/alert-diagnostics.ts
|
|
350
|
+
import { markdownToMdast } from "satteri";
|
|
454
351
|
|
|
455
352
|
// src/markdown/directives.ts
|
|
456
353
|
import { toString as mdastToString } from "mdast-util-to-string";
|
|
@@ -498,25 +395,366 @@ var directiveSource = (node, marker, source) => {
|
|
|
498
395
|
return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
|
|
499
396
|
};
|
|
500
397
|
|
|
398
|
+
// src/markdown/github-alerts.ts
|
|
399
|
+
var ALERT_CALLOUTS = new Map([
|
|
400
|
+
["caution", "danger"],
|
|
401
|
+
["important", "note"],
|
|
402
|
+
["note", "note"],
|
|
403
|
+
["tip", "tip"],
|
|
404
|
+
["warning", "warning"]
|
|
405
|
+
]);
|
|
406
|
+
var ALERT_MARKER = /^\[!(?<kind>[a-z]+)\]/iu;
|
|
407
|
+
var alertMarkerOf = (text) => {
|
|
408
|
+
const match = ALERT_MARKER.exec(text);
|
|
409
|
+
const callout = ALERT_CALLOUTS.get(match?.groups?.kind?.toLowerCase() ?? "");
|
|
410
|
+
return match && callout ? { callout, marker: match[0] } : undefined;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/core/alert-diagnostics.ts
|
|
414
|
+
var ALERT_LINE = /^[\t >]*>[\t ]*\[![a-z]+\]/imu;
|
|
415
|
+
var alertFindings = (text) => {
|
|
416
|
+
if (!ALERT_LINE.test(text)) {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const tree = markdownToMdast(text, {
|
|
420
|
+
features: MARKDOWN_BODY_FEATURES
|
|
421
|
+
});
|
|
422
|
+
const found = [];
|
|
423
|
+
const walk = (node) => {
|
|
424
|
+
if (node.type === "blockquote") {
|
|
425
|
+
const [paragraph] = node.children;
|
|
426
|
+
const first = paragraph?.type === "paragraph" ? paragraph.children[0] : undefined;
|
|
427
|
+
const escaped = text[first?.position?.start.offset ?? 0] === "\\";
|
|
428
|
+
const alert = first?.type === "text" && !escaped ? alertMarkerOf(first.value) : undefined;
|
|
429
|
+
if (alert !== undefined) {
|
|
430
|
+
found.push({ ...alert, line: node.position?.start.line ?? 1 });
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if ("children" in node) {
|
|
435
|
+
for (const child of node.children) {
|
|
436
|
+
walk(child);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
walk(tree);
|
|
441
|
+
return found;
|
|
442
|
+
};
|
|
443
|
+
var alertDiagnostics = (entry, sourceName) => {
|
|
444
|
+
if (entry.body.format !== "md") {
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
448
|
+
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
449
|
+
return alertFindings(entry.expanded?.text ?? entry.body.text).map(({ callout, line, marker }) => {
|
|
450
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
451
|
+
return {
|
|
452
|
+
code: "BLUME_MD_GITHUB_ALERT",
|
|
453
|
+
file: origin?.file ?? page,
|
|
454
|
+
line: origin?.line ?? line + offset,
|
|
455
|
+
message: `\`${marker}\` makes a quote a callout only in .mdx; in this .md page it renders as a quote that shows \`${marker}\` as text.`,
|
|
456
|
+
severity: "warning",
|
|
457
|
+
suggestion: `Rename the page to .mdx to render it as a \`${callout}\` callout, or remove \`${marker}\` to keep a plain quote.`
|
|
458
|
+
};
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// src/core/code-fence-diagnostics.ts
|
|
463
|
+
import { markdownToMdast as markdownToMdast2, mdxToMdast } from "satteri";
|
|
464
|
+
|
|
465
|
+
// src/markdown/fence-language.ts
|
|
466
|
+
import { bundledLanguages } from "shiki/langs";
|
|
467
|
+
var PLAIN_LANGUAGES = new Set([
|
|
468
|
+
"ansi",
|
|
469
|
+
"plain",
|
|
470
|
+
"plaintext",
|
|
471
|
+
"text",
|
|
472
|
+
"txt"
|
|
473
|
+
]);
|
|
474
|
+
var isKnownLanguage = (lang) => PLAIN_LANGUAGES.has(lang) || Object.hasOwn(bundledLanguages, lang);
|
|
475
|
+
var GLUED_RANGE = /^(?<lang>[^{]+)(?<range>\{[^}]*\})$/u;
|
|
476
|
+
var GLUED_TITLE_RANGE = /(?<=^|\s)(?<title>\[[^\]]*\])(?<range>\{[^}]*\})(?=\s|$)/u;
|
|
477
|
+
var normalizeFence = (lang, meta) => {
|
|
478
|
+
if (!lang) {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
const glued = GLUED_RANGE.exec(lang)?.groups;
|
|
482
|
+
const id = glued?.lang ?? lang;
|
|
483
|
+
const fenceMeta = (glued?.range ? [glued.range, meta].filter(Boolean).join(" ") : meta)?.replace(GLUED_TITLE_RANGE, "$<title> $<range>") ?? null;
|
|
484
|
+
const lower = id.toLowerCase();
|
|
485
|
+
const language = !isKnownLanguage(id) && isKnownLanguage(lower) ? lower : id;
|
|
486
|
+
return language === lang && fenceMeta === (meta ?? null) ? null : { lang: language, meta: fenceMeta };
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// src/markdown/fence-meta.ts
|
|
490
|
+
var QUOTED_ATTR = /[\w-]+=(?:"[^"]*"|'[^']*')/gu;
|
|
491
|
+
var LINE_RANGE = /\{[^}]*\}/u;
|
|
492
|
+
var META_TOKEN = new RegExp(`${QUOTED_ATTR.source}|${LINE_RANGE.source}|\\S+`, "gu");
|
|
493
|
+
var metaTokens = (meta) => meta?.match(META_TOKEN) ?? [];
|
|
494
|
+
var RESERVED_META_KEYWORDS = new Set([
|
|
495
|
+
"expandable",
|
|
496
|
+
"lineNumbers",
|
|
497
|
+
"ts2js",
|
|
498
|
+
"twoslash",
|
|
499
|
+
"wrap"
|
|
500
|
+
]);
|
|
501
|
+
var FOREIGN_META_KEYWORDS = new Set([
|
|
502
|
+
"lines",
|
|
503
|
+
"wordWrap"
|
|
504
|
+
]);
|
|
505
|
+
var SHOW_LINE_NUMBERS = /^showLineNumbers(?:\{\d*\})?$/u;
|
|
506
|
+
var RUST_META_KEYWORDS = new Set([
|
|
507
|
+
"compile_fail",
|
|
508
|
+
"edition2015",
|
|
509
|
+
"edition2018",
|
|
510
|
+
"edition2021",
|
|
511
|
+
"edition2024",
|
|
512
|
+
"editable",
|
|
513
|
+
"ignore",
|
|
514
|
+
"no_run",
|
|
515
|
+
"noplayground",
|
|
516
|
+
"should_panic"
|
|
517
|
+
]);
|
|
518
|
+
var RUST_LANGUAGES = new Set(["rs", "rust"]);
|
|
519
|
+
var isForeignKeyword = (token, lang) => FOREIGN_META_KEYWORDS.has(token) || SHOW_LINE_NUMBERS.test(token) || RUST_LANGUAGES.has(lang ?? "") && RUST_META_KEYWORDS.has(token);
|
|
520
|
+
var isLineRange = (token) => token.startsWith("{");
|
|
521
|
+
|
|
522
|
+
// src/core/code-fence-diagnostics.ts
|
|
523
|
+
var BLUME_LANGUAGES = new Set([
|
|
524
|
+
"math",
|
|
525
|
+
"package-install"
|
|
526
|
+
]);
|
|
527
|
+
var lineRange = (value) => `{${value.trim().split(/[\s,]+/u).join(",")}}`;
|
|
528
|
+
var FOREIGN_OPTIONS = [
|
|
529
|
+
{
|
|
530
|
+
effect: "the block highlights no lines",
|
|
531
|
+
instead: (value) => `Put the lines in braces after the language instead: \`${lineRange(value)}\`.`,
|
|
532
|
+
pattern: /^hl_lines=["'](?<value>[^"']*)["']$/u
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
effect: "the block shows no line numbers",
|
|
536
|
+
instead: (value) => `Write \`lineNumbers\` after the language instead.${value.trim() === "1" ? "" : " Blume numbers every block from 1."}`,
|
|
537
|
+
pattern: /^linenums=["'](?<value>[^"']*)["']$/u
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
effect: "the block shows no line numbers",
|
|
541
|
+
instead: () => "Write `lineNumbers` after the language instead.",
|
|
542
|
+
pattern: /^showLineNumbers(?:\{\d*\})?$/u
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
effect: "the block shows no line numbers",
|
|
546
|
+
instead: () => 'Write `lineNumbers` after the language instead. To keep the word in the block\'s title, set the title with `title="…"`.',
|
|
547
|
+
pattern: /^lines$/u
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
effect: "the block doesn't wrap its long lines",
|
|
551
|
+
instead: () => "Write `wrap` after the language instead.",
|
|
552
|
+
pattern: /^wordWrap$/u
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
effect: "the block has no title",
|
|
556
|
+
instead: (value) => `Write \`title="${value}"\` instead.`,
|
|
557
|
+
pattern: /^filename=["'](?<value>[^"']*)["']$/u
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
effect: "it does nothing: Blume shows a code block as static text, and never compiles, tests, or runs it",
|
|
561
|
+
instead: () => "Remove it.",
|
|
562
|
+
languages: RUST_LANGUAGES,
|
|
563
|
+
pattern: new RegExp(`^(?:${[...RUST_META_KEYWORDS].join("|")})$`, "u")
|
|
564
|
+
}
|
|
565
|
+
];
|
|
566
|
+
var foreignOption = (token, lang) => {
|
|
567
|
+
for (const option of FOREIGN_OPTIONS) {
|
|
568
|
+
const match = option.pattern.exec(token);
|
|
569
|
+
if (match && (option.languages?.has(lang) ?? true)) {
|
|
570
|
+
return { option, value: match.groups?.value ?? "" };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
};
|
|
575
|
+
var rendersLanguage = (lang) => isKnownLanguage(lang) || BLUME_LANGUAGES.has(lang);
|
|
576
|
+
var fenceParts = (lang, meta) => {
|
|
577
|
+
const fence = normalizeFence(lang, meta) ?? {
|
|
578
|
+
lang: lang ?? "",
|
|
579
|
+
meta: meta ?? null
|
|
580
|
+
};
|
|
581
|
+
return { lang: fence.lang, meta: fence.meta, tokens: metaTokens(fence.meta) };
|
|
582
|
+
};
|
|
583
|
+
var unknownLanguage = (node, line) => {
|
|
584
|
+
const { lang } = fenceParts(node.lang, node.meta);
|
|
585
|
+
if (!lang || rendersLanguage(lang)) {
|
|
586
|
+
return [];
|
|
587
|
+
}
|
|
588
|
+
return [
|
|
589
|
+
{
|
|
590
|
+
code: "BLUME_UNKNOWN_CODE_LANGUAGE",
|
|
591
|
+
line,
|
|
592
|
+
message: `\`${lang}\` isn't a language Blume can highlight, so the code block renders as plain text.`,
|
|
593
|
+
suggestion: 'Use a Shiki language id or alias (https://shiki.style/languages), or `text` for plain text. Options like a title go after the language, as in `text title="notes.txt"`.'
|
|
594
|
+
}
|
|
595
|
+
];
|
|
596
|
+
};
|
|
597
|
+
var foreignOptions = (node, line) => {
|
|
598
|
+
const { lang, tokens } = fenceParts(node.lang, node.meta);
|
|
599
|
+
return [node.lang ?? "", ...tokens].flatMap((token) => {
|
|
600
|
+
const found = foreignOption(token, lang);
|
|
601
|
+
if (!found) {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
return [
|
|
605
|
+
{
|
|
606
|
+
code: "BLUME_CODE_FENCE_OPTION",
|
|
607
|
+
line,
|
|
608
|
+
message: `\`${token}\` isn't a Blume code block option, so ${found.option.effect}.`,
|
|
609
|
+
suggestion: found.option.instead(found.value)
|
|
610
|
+
}
|
|
611
|
+
];
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
var FENCE_INFO = /(?:`{3,}|~{3,})[\t ]*(?<info>[^`\n]*)/gu;
|
|
615
|
+
var suspectInfo = (info) => {
|
|
616
|
+
const [lang, ...rest] = info.trim().split(/\s+/u);
|
|
617
|
+
if (!lang) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
const parts = fenceParts(lang, rest.join(" ") || null);
|
|
621
|
+
return !rendersLanguage(parts.lang) || [lang, ...parts.tokens].some((token) => foreignOption(token, parts.lang) !== null);
|
|
622
|
+
};
|
|
623
|
+
var parse = (text, format) => {
|
|
624
|
+
try {
|
|
625
|
+
return format === "mdx" ? mdxToMdast(text, { features: MDX_BODY_FEATURES }) : markdownToMdast2(text, { features: MARKDOWN_BODY_FEATURES });
|
|
626
|
+
} catch {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
var fenceFindings = (text, format) => {
|
|
631
|
+
const suspect = [...text.matchAll(FENCE_INFO)].some((match) => suspectInfo(match.groups?.info ?? ""));
|
|
632
|
+
const tree = suspect ? parse(text, format) : null;
|
|
633
|
+
if (!tree) {
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
const found = [];
|
|
637
|
+
const walk = (node) => {
|
|
638
|
+
if (node.type === "code") {
|
|
639
|
+
const line = node.position?.start.line ?? 1;
|
|
640
|
+
found.push(...unknownLanguage(node, line), ...foreignOptions(node, line));
|
|
641
|
+
}
|
|
642
|
+
if ("children" in node) {
|
|
643
|
+
for (const child of node.children) {
|
|
644
|
+
walk(child);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
walk(tree);
|
|
649
|
+
return found;
|
|
650
|
+
};
|
|
651
|
+
var codeFenceDiagnostics = (entry, sourceName) => {
|
|
652
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
653
|
+
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
654
|
+
return fenceFindings(entry.expanded?.text ?? entry.body.text, entry.body.format).map(({ line, ...finding }) => {
|
|
655
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
656
|
+
return {
|
|
657
|
+
...finding,
|
|
658
|
+
file: origin?.file ?? page,
|
|
659
|
+
line: origin?.line ?? line + offset,
|
|
660
|
+
severity: "warning"
|
|
661
|
+
};
|
|
662
|
+
});
|
|
663
|
+
};
|
|
664
|
+
|
|
501
665
|
// src/core/directive-diagnostics.ts
|
|
666
|
+
import { markdownToMdast as markdownToMdast3, mdxToMdast as mdxToMdast2 } from "satteri";
|
|
502
667
|
var CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
|
|
668
|
+
var FENCE_WITH_TEXT = /^[\t >]*:{3,}(?:[\t ]+\S|[^\s:a-z])/imu;
|
|
669
|
+
var CLOSING_LINE = /^[\t >]*(?<written>(?<fence>:{3,})[\t ]*(?<text>.*?))\s*$/u;
|
|
670
|
+
var OPENING_TEXT = /^[\t >]*(?<written>(?<fence>:{3,})(?<name>[a-z][\w-]*)(?<label>\[[^\]\n]*\])?(?<attributes>\{[^}\n]*\})?[\t ]+(?<text>\S.*?))\s*$/iu;
|
|
671
|
+
var OPENING_TEXT_LINE = /^[\t >]*:{3,}[a-z][\w-]*(?:\[[^\]\n]*\])?(?:\{[^}\n]*\})?[\t ]+\S/imu;
|
|
672
|
+
var SPACED_OPENING = /^[\t >]*(?<written>(?<fence>:{3,})[\t ]+(?<name>[a-z][\w-]*)(?<title>.*?))\s*$/iu;
|
|
673
|
+
var MD_OPENING = /^[\t >]*(?<written>:{3,}(?<name>[a-z][\w-]*))/iu;
|
|
674
|
+
var MD_OPENING_LINE = /^[\t >]*:{3,}[a-z]/imu;
|
|
503
675
|
var codeList = (names) => [...names].map((name) => `\`${name}\``).join(", ");
|
|
504
676
|
var CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
|
|
505
|
-
var
|
|
506
|
-
|
|
507
|
-
|
|
677
|
+
var unknownContainer = (node) => calloutTypeFor(node.name) === null ? [
|
|
678
|
+
{
|
|
679
|
+
code: "BLUME_UNKNOWN_DIRECTIVE",
|
|
680
|
+
line: node.position?.start.line ?? 1,
|
|
681
|
+
message: `\`:::${node.name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
|
|
682
|
+
suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
|
|
683
|
+
}
|
|
684
|
+
] : [];
|
|
685
|
+
var closingText = (node, lines) => {
|
|
686
|
+
const start = node.position?.start.line ?? 1;
|
|
687
|
+
const end = node.position?.end.line ?? start;
|
|
688
|
+
const content = node.children.at(-1)?.position?.end.line ?? start;
|
|
689
|
+
const groups = end > content ? CLOSING_LINE.exec(lines[end - 1] ?? "")?.groups : undefined;
|
|
690
|
+
if (!(groups?.text && groups.fence && groups.written)) {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
const longer = ":".repeat(groups.fence.length + 1);
|
|
694
|
+
return [
|
|
695
|
+
{
|
|
696
|
+
code: "BLUME_DIRECTIVE_CLOSING_TEXT",
|
|
697
|
+
line: end,
|
|
698
|
+
message: `\`${groups.written}\` closes the \`:::${node.name}\` container above it, so \`${groups.text}\` never reaches the page.`,
|
|
699
|
+
suggestion: `Give the container a longer fence than this line's — \`${longer}${node.name}\`, closed by \`${longer}\` — to keep the line inside it as text, or remove \`${groups.text}\` from the line.`
|
|
700
|
+
}
|
|
701
|
+
];
|
|
702
|
+
};
|
|
703
|
+
var openingText = (node, lines) => {
|
|
704
|
+
const line = node.position?.start.line ?? 1;
|
|
705
|
+
const groups = OPENING_TEXT.exec(lines[line - 1] ?? "")?.groups;
|
|
706
|
+
if (!(groups?.fence && groups.name && groups.text && groups.written) || calloutTypeFor(node.name) === null) {
|
|
707
|
+
return [];
|
|
708
|
+
}
|
|
709
|
+
const opener = `${groups.fence}${groups.name}`;
|
|
710
|
+
return [
|
|
711
|
+
{
|
|
712
|
+
code: "BLUME_DIRECTIVE_OPENING_TEXT",
|
|
713
|
+
line,
|
|
714
|
+
message: `\`${groups.written}\` opens a \`${groups.name}\` callout, but the text after its name, \`${groups.text}\`, never reaches the page.`,
|
|
715
|
+
suggestion: groups.label || groups.attributes ? `Move \`${groups.text}\` into the callout's title, or onto the next line as its text.` : `Put the title in brackets: \`${opener}[${groups.text}]\`.`
|
|
716
|
+
}
|
|
717
|
+
];
|
|
718
|
+
};
|
|
719
|
+
var spacedOpenings = (node, lines) => {
|
|
720
|
+
const start = node.position?.start.line ?? 1;
|
|
721
|
+
const end = node.position?.end.line ?? start;
|
|
722
|
+
return lines.slice(start - 1, end).flatMap((text, index) => {
|
|
723
|
+
const groups = SPACED_OPENING.exec(text)?.groups;
|
|
724
|
+
if (!(groups?.fence && groups.name && groups.written) || calloutTypeFor(groups.name) === null) {
|
|
725
|
+
return [];
|
|
726
|
+
}
|
|
727
|
+
const opener = `${groups.fence}${groups.name}`;
|
|
728
|
+
const title = groups.title?.trim();
|
|
729
|
+
return [
|
|
730
|
+
{
|
|
731
|
+
code: "BLUME_DIRECTIVE_SPACED_NAME",
|
|
732
|
+
line: start + index,
|
|
733
|
+
message: `\`${groups.written}\` has a space between its colons and its name, so it isn't a callout and the page shows it as text.`,
|
|
734
|
+
suggestion: title ? `Remove the space and put the title in brackets: \`${opener}[${title}]\`.` : `Remove the space: \`${opener}\`.`
|
|
735
|
+
}
|
|
736
|
+
];
|
|
737
|
+
});
|
|
738
|
+
};
|
|
739
|
+
var directiveFindings = (text) => {
|
|
740
|
+
const suspect = FENCE_WITH_TEXT.test(text) || OPENING_TEXT_LINE.test(text) || [...text.matchAll(CONTAINER_OPENING)].some((match) => calloutTypeFor(match.groups?.name ?? "") === null);
|
|
741
|
+
if (!suspect) {
|
|
508
742
|
return [];
|
|
509
743
|
}
|
|
510
744
|
let tree;
|
|
511
745
|
try {
|
|
512
|
-
tree =
|
|
746
|
+
tree = mdxToMdast2(text, { features: MDX_BODY_FEATURES });
|
|
513
747
|
} catch {
|
|
514
748
|
return [];
|
|
515
749
|
}
|
|
750
|
+
const lines = text.split(`
|
|
751
|
+
`);
|
|
516
752
|
const found = [];
|
|
517
753
|
const walk = (node) => {
|
|
518
|
-
if (node.type === "containerDirective"
|
|
519
|
-
found.push(
|
|
754
|
+
if (node.type === "containerDirective") {
|
|
755
|
+
found.push(...unknownContainer(node), ...openingText(node, lines), ...closingText(node, lines));
|
|
756
|
+
} else if (node.type === "paragraph") {
|
|
757
|
+
found.push(...spacedOpenings(node, lines));
|
|
520
758
|
}
|
|
521
759
|
if ("children" in node) {
|
|
522
760
|
for (const child of node.children) {
|
|
@@ -525,27 +763,83 @@ var unknownContainers = (text) => {
|
|
|
525
763
|
}
|
|
526
764
|
};
|
|
527
765
|
walk(tree);
|
|
528
|
-
return found;
|
|
766
|
+
return found.toSorted((a, b) => a.line - b.line);
|
|
767
|
+
};
|
|
768
|
+
var mdOpenings = (node, lines) => {
|
|
769
|
+
const start = node.position?.start.line ?? 1;
|
|
770
|
+
const end = node.position?.end.line ?? start;
|
|
771
|
+
return lines.slice(start - 1, end).flatMap((text, index) => {
|
|
772
|
+
const groups = MD_OPENING.exec(text)?.groups;
|
|
773
|
+
if (!(groups?.name && groups.written)) {
|
|
774
|
+
return [];
|
|
775
|
+
}
|
|
776
|
+
const callout = calloutTypeFor(groups.name);
|
|
777
|
+
return [
|
|
778
|
+
{
|
|
779
|
+
code: "BLUME_MD_DIRECTIVE",
|
|
780
|
+
line: start + index,
|
|
781
|
+
message: `\`${groups.written}\` opens a directive, which Blume renders only in .mdx, so this .md page shows it and its closing \`:::\` as text.`,
|
|
782
|
+
suggestion: callout ? `Rename the page to .mdx to render it as a \`${callout}\` callout.` : `Rename the page to .mdx and use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
|
|
783
|
+
}
|
|
784
|
+
];
|
|
785
|
+
});
|
|
529
786
|
};
|
|
530
|
-
var
|
|
531
|
-
if (
|
|
787
|
+
var mdDirectiveFindings = (text) => {
|
|
788
|
+
if (!MD_OPENING_LINE.test(text)) {
|
|
532
789
|
return [];
|
|
533
790
|
}
|
|
791
|
+
const tree = markdownToMdast3(text, {
|
|
792
|
+
features: MARKDOWN_BODY_FEATURES
|
|
793
|
+
});
|
|
794
|
+
const lines = text.split(`
|
|
795
|
+
`);
|
|
796
|
+
const found = [];
|
|
797
|
+
const walk = (node) => {
|
|
798
|
+
if (node.type === "paragraph") {
|
|
799
|
+
found.push(...mdOpenings(node, lines));
|
|
800
|
+
} else if ("children" in node) {
|
|
801
|
+
for (const child of node.children) {
|
|
802
|
+
walk(child);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
};
|
|
806
|
+
walk(tree);
|
|
807
|
+
return found;
|
|
808
|
+
};
|
|
809
|
+
var directiveDiagnostics = (entry, sourceName) => {
|
|
534
810
|
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
535
811
|
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
536
|
-
|
|
812
|
+
const text = entry.expanded?.text ?? entry.body.text;
|
|
813
|
+
const findings = entry.body.format === "mdx" ? directiveFindings(text) : mdDirectiveFindings(text);
|
|
814
|
+
return findings.map(({ line, ...finding }) => {
|
|
537
815
|
const origin = entry.expanded?.origins[line - 1];
|
|
538
816
|
return {
|
|
539
|
-
|
|
817
|
+
...finding,
|
|
540
818
|
file: origin?.file ?? page,
|
|
541
819
|
line: origin?.line ?? line + offset,
|
|
542
|
-
|
|
543
|
-
severity: "warning",
|
|
544
|
-
suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
|
|
820
|
+
severity: "warning"
|
|
545
821
|
};
|
|
546
822
|
});
|
|
547
823
|
};
|
|
548
824
|
|
|
825
|
+
// src/core/former-routes.ts
|
|
826
|
+
var formerRouteRedirects = (pages, served, config) => {
|
|
827
|
+
const targets = new Map;
|
|
828
|
+
for (const page of pages) {
|
|
829
|
+
if (page.formerRoute !== undefined) {
|
|
830
|
+
targets.set(page.formerRoute, targets.has(page.formerRoute) ? undefined : page.route);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
const authored = new Set(config.redirects.map((redirect) => normalizePath(withBasePath(config.basePath, redirect.from))));
|
|
834
|
+
return [...targets].flatMap(([from, to]) => to === undefined || served.has(from) || authored.has(from) ? [] : [
|
|
835
|
+
{
|
|
836
|
+
from: stripBasePath(config.basePath, from),
|
|
837
|
+
status: 301,
|
|
838
|
+
to: stripBasePath(config.basePath, to)
|
|
839
|
+
}
|
|
840
|
+
]);
|
|
841
|
+
};
|
|
842
|
+
|
|
549
843
|
// src/core/graph.ts
|
|
550
844
|
var collectRoutes = (pages) => {
|
|
551
845
|
const routes = new Map;
|
|
@@ -810,13 +1104,33 @@ var gitEnv = () => Object.fromEntries(Object.entries(process.env).filter(([key])
|
|
|
810
1104
|
var resolveLastModifiedConfig = (value) => value === false ? { enabled: false, source: "git" } : { enabled: true, source: value };
|
|
811
1105
|
var parseGitLog = (output) => {
|
|
812
1106
|
const times = new Map;
|
|
1107
|
+
const currentNames = new Map;
|
|
1108
|
+
const renamedAt = new Map;
|
|
813
1109
|
let current = null;
|
|
814
1110
|
for (const line of output.split(`
|
|
815
1111
|
`)) {
|
|
816
1112
|
if (line.startsWith("\x00")) {
|
|
817
1113
|
current = line.slice(1);
|
|
818
|
-
|
|
819
|
-
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
const [status = "", from = "", to] = line.split("\t");
|
|
1117
|
+
const path = to ?? from;
|
|
1118
|
+
if (!(current && path)) {
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
const name = currentNames.get(path) ?? path;
|
|
1122
|
+
if (status.startsWith("R")) {
|
|
1123
|
+
currentNames.set(from, name);
|
|
1124
|
+
if (!renamedAt.has(name)) {
|
|
1125
|
+
renamedAt.set(name, current);
|
|
1126
|
+
}
|
|
1127
|
+
} else if (!times.has(name)) {
|
|
1128
|
+
times.set(name, current);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
for (const [name, date] of renamedAt) {
|
|
1132
|
+
if (!times.has(name)) {
|
|
1133
|
+
times.set(name, date);
|
|
820
1134
|
}
|
|
821
1135
|
}
|
|
822
1136
|
return times;
|
|
@@ -852,7 +1166,8 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths, repositoryRoot) =>
|
|
|
852
1166
|
"core.quotePath=false",
|
|
853
1167
|
"log",
|
|
854
1168
|
"--format=%x00%cI",
|
|
855
|
-
"--name-
|
|
1169
|
+
"--name-status",
|
|
1170
|
+
"-M100%",
|
|
856
1171
|
"--",
|
|
857
1172
|
...contentRoots.map(realPath)
|
|
858
1173
|
], { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 });
|
|
@@ -894,6 +1209,245 @@ var lastModifiedShallowWarning = (root, undatedCount) => {
|
|
|
894
1209
|
];
|
|
895
1210
|
};
|
|
896
1211
|
|
|
1212
|
+
// src/core/mdx-syntax.ts
|
|
1213
|
+
import { readFile } from "node:fs/promises";
|
|
1214
|
+
import { createMdxMdastHandle, dropHandle, mdxToMdast as mdxToMdast3 } from "satteri";
|
|
1215
|
+
var POSITIONED = /^(?<line>\d+):(?<column>\d+): (?<reason>[\s\S]*?)(?: \([\w-]+:[\w-]+\))?$/u;
|
|
1216
|
+
var INNER_POSITION = / \((?<line>\d+):(?<column>\d+)\)/gu;
|
|
1217
|
+
var mdxSyntaxError = (text) => {
|
|
1218
|
+
try {
|
|
1219
|
+
dropHandle(createMdxMdastHandle(text, MDX_BODY_FEATURES, false));
|
|
1220
|
+
return null;
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1223
|
+
const groups = POSITIONED.exec(message)?.groups;
|
|
1224
|
+
return {
|
|
1225
|
+
column: Number(groups?.column ?? 1),
|
|
1226
|
+
line: Number(groups?.line ?? 1),
|
|
1227
|
+
reason: groups?.reason ?? message
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
var sitesIn = (file, lineOffset, origins) => (line, column) => {
|
|
1232
|
+
const origin = origins?.[line - 1];
|
|
1233
|
+
return origin ? { column, file: origin.file, line: origin.line } : { column, file, line: line + lineOffset };
|
|
1234
|
+
};
|
|
1235
|
+
var placedReason = (reason, at, siteOf) => reason.replaceAll(INNER_POSITION, (written, line, column) => {
|
|
1236
|
+
const inner = siteOf(Number(line), Number(column));
|
|
1237
|
+
return inner.file === at.file ? ` (${inner.line}:${column})` : "";
|
|
1238
|
+
});
|
|
1239
|
+
var EXPRESSION_REASON = /could not parse expression/iu;
|
|
1240
|
+
var COMMENT_REASON = /to create a comment in MDX/u;
|
|
1241
|
+
var LINK_REASON = /to create a link in MDX/u;
|
|
1242
|
+
var syntaxSuggestion = (reason) => {
|
|
1243
|
+
if (EXPRESSION_REASON.test(reason)) {
|
|
1244
|
+
return "In .mdx, `{` starts a JavaScript expression. To show a brace as text, escape it (`\\{`) or put the text in inline code.";
|
|
1245
|
+
}
|
|
1246
|
+
if (COMMENT_REASON.test(reason)) {
|
|
1247
|
+
return "Write the comment as `{/* … */}`.";
|
|
1248
|
+
}
|
|
1249
|
+
if (LINK_REASON.test(reason)) {
|
|
1250
|
+
return "Write the link as `[text](https://…)`.";
|
|
1251
|
+
}
|
|
1252
|
+
return "MDX reads HTML as JSX: close every element (`<br />`, `<img … />`) and match each closing tag. A page that needs no components can be a .md file instead.";
|
|
1253
|
+
};
|
|
1254
|
+
var mdxSyntaxDiagnostic = (error, siteOf, options) => {
|
|
1255
|
+
const at = siteOf(error.line, error.column);
|
|
1256
|
+
const reason = placedReason(error.reason, at, siteOf);
|
|
1257
|
+
return {
|
|
1258
|
+
code: "BLUME_MDX_SYNTAX",
|
|
1259
|
+
column: at.column,
|
|
1260
|
+
file: at.file,
|
|
1261
|
+
line: at.line,
|
|
1262
|
+
message: options.includedIn ? `MDX can't parse this file once ${options.includedIn} includes it: ${reason}.` : `MDX can't parse this page: ${reason}.`,
|
|
1263
|
+
severity: options.severity,
|
|
1264
|
+
suggestion: syntaxSuggestion(reason)
|
|
1265
|
+
};
|
|
1266
|
+
};
|
|
1267
|
+
var mdxSyntaxCheck = (entry, sourceName, variables) => {
|
|
1268
|
+
if (entry.body.format !== "mdx") {
|
|
1269
|
+
return { diagnostics: [], unparsable: false };
|
|
1270
|
+
}
|
|
1271
|
+
const siteOf = (origins) => sitesIn(entry.sourcePath ?? `${sourceName}:${entry.ref}`, entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text), origins);
|
|
1272
|
+
const { expanded } = entry;
|
|
1273
|
+
const body = expanded ? substituteVariables(entry.body.text, variables) : entry.body.text;
|
|
1274
|
+
const own = mdxSyntaxError(body);
|
|
1275
|
+
if (own) {
|
|
1276
|
+
return {
|
|
1277
|
+
diagnostics: [mdxSyntaxDiagnostic(own, siteOf(), { severity: "error" })],
|
|
1278
|
+
unparsable: true
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
const spliced = expanded ? mdxSyntaxError(expanded.text) : null;
|
|
1282
|
+
if (!(expanded && spliced)) {
|
|
1283
|
+
return { diagnostics: [], unparsable: false };
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
diagnostics: [
|
|
1287
|
+
mdxSyntaxDiagnostic(spliced, siteOf(expanded.origins), {
|
|
1288
|
+
includedIn: entry.ref,
|
|
1289
|
+
severity: "warning"
|
|
1290
|
+
})
|
|
1291
|
+
],
|
|
1292
|
+
unparsable: false
|
|
1293
|
+
};
|
|
1294
|
+
};
|
|
1295
|
+
var compiledText = async (page) => {
|
|
1296
|
+
const raw = await readFile(page.sourcePath, "utf-8");
|
|
1297
|
+
const body = frontmatter_default(raw).content;
|
|
1298
|
+
const offset = strippedLineOffset(raw, body);
|
|
1299
|
+
const expansion = page.contentRoot && hasIncludeStatements(body) ? await expandIncludes(body, {
|
|
1300
|
+
contentRoot: page.contentRoot,
|
|
1301
|
+
lineOffset: offset,
|
|
1302
|
+
sourcePath: page.sourcePath
|
|
1303
|
+
}) : null;
|
|
1304
|
+
const written = expansion?.text ?? body;
|
|
1305
|
+
return {
|
|
1306
|
+
siteOf: sitesIn(page.sourcePath, offset, expansion?.origins),
|
|
1307
|
+
text: substituteVariables(written, page.variables),
|
|
1308
|
+
written
|
|
1309
|
+
};
|
|
1310
|
+
};
|
|
1311
|
+
var readsName = (expression, name) => new RegExp(String.raw`(?<![\w$.])${name.replaceAll("$", String.raw`\$`)}(?![\w$])`, "u").test(expression.replaceAll("...", " "));
|
|
1312
|
+
var expressionsOf = (node) => {
|
|
1313
|
+
if (node.type === "mdxFlowExpression" || node.type === "mdxTextExpression") {
|
|
1314
|
+
return [node.value];
|
|
1315
|
+
}
|
|
1316
|
+
if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
|
|
1317
|
+
return node.attributes.flatMap((attribute) => {
|
|
1318
|
+
if (attribute.type === "mdxJsxExpressionAttribute") {
|
|
1319
|
+
return [attribute.value];
|
|
1320
|
+
}
|
|
1321
|
+
return isExpression(attribute.value) ? [attribute.value.value] : [];
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
return [];
|
|
1325
|
+
};
|
|
1326
|
+
var expressionReading = (text, name) => {
|
|
1327
|
+
let tree;
|
|
1328
|
+
try {
|
|
1329
|
+
tree = mdxToMdast3(text, { features: MDX_BODY_FEATURES });
|
|
1330
|
+
} catch {
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1333
|
+
const visit = (node) => {
|
|
1334
|
+
const expression = expressionsOf(node).find((source) => readsName(source, name));
|
|
1335
|
+
if (expression !== undefined) {
|
|
1336
|
+
return { expression: `{${expression.trim()}}`, node };
|
|
1337
|
+
}
|
|
1338
|
+
if ("children" in node) {
|
|
1339
|
+
for (const child of node.children) {
|
|
1340
|
+
const found = visit(child);
|
|
1341
|
+
if (found) {
|
|
1342
|
+
return found;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return null;
|
|
1347
|
+
};
|
|
1348
|
+
return visit(tree);
|
|
1349
|
+
};
|
|
1350
|
+
var locateMdxFailure = async (page, failure) => {
|
|
1351
|
+
let compiled;
|
|
1352
|
+
try {
|
|
1353
|
+
compiled = await compiledText(page);
|
|
1354
|
+
} catch {
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
const { siteOf, text, written } = compiled;
|
|
1358
|
+
if (failure.kind === "undefined-name") {
|
|
1359
|
+
const found = expressionReading(text, failure.name);
|
|
1360
|
+
const start = found?.node.position?.start;
|
|
1361
|
+
if (!(found && start)) {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
const at = written.split(`
|
|
1365
|
+
`)[start.line - 1]?.indexOf(found.expression) ?? -1;
|
|
1366
|
+
return {
|
|
1367
|
+
...siteOf(start.line, at === -1 ? start.column : at + 1),
|
|
1368
|
+
expression: found.expression
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
const error = mdxSyntaxError(text);
|
|
1372
|
+
if (!error) {
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
const diagnostic = mdxSyntaxDiagnostic(error, siteOf, {
|
|
1376
|
+
includedIn: siteOf(error.line).file === page.sourcePath ? undefined : page.name,
|
|
1377
|
+
severity: "error"
|
|
1378
|
+
});
|
|
1379
|
+
return {
|
|
1380
|
+
column: diagnostic.column,
|
|
1381
|
+
diagnostic,
|
|
1382
|
+
file: diagnostic.file ?? page.sourcePath,
|
|
1383
|
+
line: diagnostic.line ?? 1
|
|
1384
|
+
};
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
// src/core/meta-diagnostics.ts
|
|
1388
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1389
|
+
var groupPathOf = (key, versionDirs, localeDirs) => {
|
|
1390
|
+
const parts = key.split("/");
|
|
1391
|
+
if (versionDirs.has(parts[0] ?? "")) {
|
|
1392
|
+
parts.shift();
|
|
1393
|
+
}
|
|
1394
|
+
if (localeDirs.has(parts[0] ?? "")) {
|
|
1395
|
+
parts.shift();
|
|
1396
|
+
}
|
|
1397
|
+
return parts.join("/");
|
|
1398
|
+
};
|
|
1399
|
+
var likelyChild = (entry, children) => {
|
|
1400
|
+
const key = pageKey(entry);
|
|
1401
|
+
return key !== entry && children.has(key) ? key : closestMatch(entry, [...children]);
|
|
1402
|
+
};
|
|
1403
|
+
var SLUG_HINT = "List each child by its slug: the file or folder name without its extension, numeric prefix, or parentheses.";
|
|
1404
|
+
var unknownPageDiagnostics = async (file, pages, group, children) => {
|
|
1405
|
+
const unknown = pages.flatMap((entry, index) => children.has(entry) ? [] : [{ entry, index }]);
|
|
1406
|
+
if (unknown.length === 0) {
|
|
1407
|
+
return [];
|
|
1408
|
+
}
|
|
1409
|
+
const source = await readFile2(file, "utf-8");
|
|
1410
|
+
const where = group ? `"${group}"` : "the content root";
|
|
1411
|
+
return unknown.map(({ entry, index }) => {
|
|
1412
|
+
const position = locatePath(source, ["pages", index]);
|
|
1413
|
+
const match = likelyChild(entry, children);
|
|
1414
|
+
return {
|
|
1415
|
+
code: "BLUME_META_UNKNOWN_PAGE",
|
|
1416
|
+
column: position?.column,
|
|
1417
|
+
file,
|
|
1418
|
+
line: position?.line,
|
|
1419
|
+
message: `pages lists "${entry}", but no page or folder in ${where} has that slug, so the entry orders nothing.`,
|
|
1420
|
+
severity: "warning",
|
|
1421
|
+
suggestion: match ? `Did you mean "${match}"? ${SLUG_HINT}` : SLUG_HINT
|
|
1422
|
+
};
|
|
1423
|
+
});
|
|
1424
|
+
};
|
|
1425
|
+
var unreadDiagnostic = ({ dir, file }, group) => ({
|
|
1426
|
+
code: "BLUME_META_OUTSIDE_INCLUDE",
|
|
1427
|
+
file,
|
|
1428
|
+
message: `This meta file is in the "${group}" sidebar group's folder, but no include glob of its content source reaches that folder, so Blume doesn't read it.`,
|
|
1429
|
+
severity: "warning",
|
|
1430
|
+
suggestion: `Add an include glob that reaches the folder, such as "${dir}/**/*.{md,mdx}".`
|
|
1431
|
+
});
|
|
1432
|
+
var folderMetaDiagnostics = async (discovered, pages, options = {}) => {
|
|
1433
|
+
const children = folderChildKeys(pages.map((page) => page.navPath));
|
|
1434
|
+
const versionDirs = new Set(options.versionDirs);
|
|
1435
|
+
const localeDirs = new Set(options.localeDirs);
|
|
1436
|
+
const groupOf = (key) => groupPathOf(key, versionDirs, localeDirs);
|
|
1437
|
+
const listed = await Promise.all([...discovered.meta, ...discovered.shared].flatMap(([key, meta]) => {
|
|
1438
|
+
const file = discovered.files.get(meta);
|
|
1439
|
+
const group = groupOf(key);
|
|
1440
|
+
return file && meta.pages ? [
|
|
1441
|
+
unknownPageDiagnostics(file, meta.pages, group, children.get(group) ?? new Set)
|
|
1442
|
+
] : [];
|
|
1443
|
+
}));
|
|
1444
|
+
const unread = discovered.unread.flatMap((entry) => {
|
|
1445
|
+
const group = groupOf(entry.key);
|
|
1446
|
+
return children.has(group) ? [unreadDiagnostic(entry, group)] : [];
|
|
1447
|
+
});
|
|
1448
|
+
return [...listed.flat(), ...unread];
|
|
1449
|
+
};
|
|
1450
|
+
|
|
897
1451
|
// src/core/meta.ts
|
|
898
1452
|
import { basename, dirname, relative as relative2 } from "pathe";
|
|
899
1453
|
import { glob, isDynamicPattern } from "tinyglobby";
|
|
@@ -921,19 +1475,21 @@ var globBase = (pattern) => {
|
|
|
921
1475
|
};
|
|
922
1476
|
var onPathOf = (dir, base) => dir === base || base === "" || dir === "" || dir.startsWith(`${base}/`) || base.startsWith(`${dir}/`);
|
|
923
1477
|
var withinInclude = (dir, include) => include === undefined || include.some((pattern) => !pattern.startsWith("!") && onPathOf(dir, globBase(pattern)));
|
|
1478
|
+
var globFolderMetaFiles = (source, patterns) => glob([...patterns], {
|
|
1479
|
+
absolute: true,
|
|
1480
|
+
cwd: source.root,
|
|
1481
|
+
ignore: [
|
|
1482
|
+
...source.exclude ?? [],
|
|
1483
|
+
"**/node_modules/**",
|
|
1484
|
+
"**/.blume/**",
|
|
1485
|
+
"**/dist/**"
|
|
1486
|
+
],
|
|
1487
|
+
onlyFiles: true
|
|
1488
|
+
});
|
|
1489
|
+
var isReadMetaFile = (source, file) => withinInclude(relative2(source.root, dirname(file)), source.include);
|
|
924
1490
|
var findFolderMetaFiles = async (source, patterns = META_FILES) => {
|
|
925
|
-
const found = await
|
|
926
|
-
|
|
927
|
-
cwd: source.root,
|
|
928
|
-
ignore: [
|
|
929
|
-
...source.exclude ?? [],
|
|
930
|
-
"**/node_modules/**",
|
|
931
|
-
"**/.blume/**",
|
|
932
|
-
"**/dist/**"
|
|
933
|
-
],
|
|
934
|
-
onlyFiles: true
|
|
935
|
-
});
|
|
936
|
-
return found.filter((file) => withinInclude(relative2(source.root, dirname(file)), source.include));
|
|
1491
|
+
const found = await globFolderMetaFiles(source, patterns);
|
|
1492
|
+
return found.filter((file) => isReadMetaFile(source, file));
|
|
937
1493
|
};
|
|
938
1494
|
var metaKeyFor = (prefix, dir) => {
|
|
939
1495
|
const clean = prefix ? trimChar(prefix, "/") : "";
|
|
@@ -949,10 +1505,31 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
949
1505
|
const load = createModuleLoader();
|
|
950
1506
|
const meta = new Map;
|
|
951
1507
|
const shared = new Map;
|
|
1508
|
+
const files = new Map;
|
|
1509
|
+
const unread = [];
|
|
952
1510
|
const diagnostics = [];
|
|
1511
|
+
const keyOf = (source, file) => {
|
|
1512
|
+
const dir = relative2(source.root, dirname(file));
|
|
1513
|
+
const [head, ...tail] = dir.split("/");
|
|
1514
|
+
const version = head && versionDirs.has(head) ? head : "";
|
|
1515
|
+
const afterVersion = version ? tail : [head ?? "", ...tail];
|
|
1516
|
+
const [localeHead, ...localeTail] = afterVersion;
|
|
1517
|
+
const locale = localeHead ? localeDirs.get(localeHead.toLowerCase()) ?? "" : "";
|
|
1518
|
+
const rest = (locale ? localeTail : afterVersion).filter(Boolean).join("/");
|
|
1519
|
+
return [version, locale, metaKeyFor(source.prefix, rest)].filter(Boolean).join("/");
|
|
1520
|
+
};
|
|
953
1521
|
const perSource = await Promise.all(list.map(async (source) => {
|
|
954
|
-
const
|
|
955
|
-
const
|
|
1522
|
+
const found = await globFolderMetaFiles(source, META_FILES);
|
|
1523
|
+
for (const file of found) {
|
|
1524
|
+
if (!isReadMetaFile(source, file)) {
|
|
1525
|
+
unread.push({
|
|
1526
|
+
dir: relative2(source.root, dirname(file)),
|
|
1527
|
+
file,
|
|
1528
|
+
key: keyOf(source, file)
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
const loaded = await Promise.all(found.filter((file) => isReadMetaFile(source, file)).map(async (file) => {
|
|
956
1533
|
try {
|
|
957
1534
|
return {
|
|
958
1535
|
file,
|
|
@@ -967,14 +1544,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
967
1544
|
}));
|
|
968
1545
|
for (const { loaded, source } of perSource) {
|
|
969
1546
|
for (const entry of loaded) {
|
|
970
|
-
const
|
|
971
|
-
const [head, ...tail] = dir.split("/");
|
|
972
|
-
const version = head && versionDirs.has(head) ? head : "";
|
|
973
|
-
const afterVersion = version ? tail : [head ?? "", ...tail];
|
|
974
|
-
const [localeHead, ...localeTail] = afterVersion;
|
|
975
|
-
const locale = localeHead ? localeDirs.get(localeHead.toLowerCase()) ?? "" : "";
|
|
976
|
-
const rest = (locale ? localeTail : afterVersion).filter(Boolean).join("/");
|
|
977
|
-
const key = [version, locale, metaKeyFor(source.prefix, rest)].filter(Boolean).join("/");
|
|
1547
|
+
const key = keyOf(source, entry.file);
|
|
978
1548
|
if (!entry.ok) {
|
|
979
1549
|
diagnostics.push({
|
|
980
1550
|
code: "BLUME_META_LOAD_FAILED",
|
|
@@ -988,6 +1558,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
988
1558
|
if (result.success) {
|
|
989
1559
|
const target = basename(entry.file).startsWith("meta.$.") ? shared : meta;
|
|
990
1560
|
target.set(key, result.data);
|
|
1561
|
+
files.set(result.data, entry.file);
|
|
991
1562
|
} else {
|
|
992
1563
|
diagnostics.push(...diagnosticsFromZod(result.error, {
|
|
993
1564
|
code: "BLUME_META_INVALID",
|
|
@@ -996,7 +1567,32 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
996
1567
|
}
|
|
997
1568
|
}
|
|
998
1569
|
}
|
|
999
|
-
|
|
1570
|
+
const read = new Set(perSource.flatMap(({ loaded }) => loaded.map((entry) => entry.file)));
|
|
1571
|
+
return {
|
|
1572
|
+
diagnostics,
|
|
1573
|
+
files,
|
|
1574
|
+
meta,
|
|
1575
|
+
shared,
|
|
1576
|
+
unread: unread.filter((entry) => !read.has(entry.file))
|
|
1577
|
+
};
|
|
1578
|
+
};
|
|
1579
|
+
var withGeneratedFolderMeta = (user, generated, localeDirs = []) => {
|
|
1580
|
+
const locales = new Set(localeDirs);
|
|
1581
|
+
const beneath = (key, meta) => {
|
|
1582
|
+
const [head, ...rest] = key.split("/");
|
|
1583
|
+
const generatedMeta = generated.get(head !== undefined && locales.has(head) ? rest.join("/") : key);
|
|
1584
|
+
return generatedMeta ? { ...generatedMeta, ...meta } : meta;
|
|
1585
|
+
};
|
|
1586
|
+
return {
|
|
1587
|
+
meta: new Map([...user.meta].map(([key, meta]) => [key, beneath(key, meta)])),
|
|
1588
|
+
shared: new Map([
|
|
1589
|
+
...generated,
|
|
1590
|
+
...[...user.shared].map(([key, meta]) => [
|
|
1591
|
+
key,
|
|
1592
|
+
{ ...generated.get(key), ...meta }
|
|
1593
|
+
])
|
|
1594
|
+
])
|
|
1595
|
+
};
|
|
1000
1596
|
};
|
|
1001
1597
|
|
|
1002
1598
|
// src/core/sources/resolve.ts
|
|
@@ -1390,7 +1986,7 @@ var contentfulSource = (options, ctx) => {
|
|
|
1390
1986
|
|
|
1391
1987
|
// src/core/sources/filesystem.ts
|
|
1392
1988
|
import { existsSync, watch as fsWatch } from "node:fs";
|
|
1393
|
-
import { readFile } from "node:fs/promises";
|
|
1989
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1394
1990
|
import { YAMLException } from "js-yaml";
|
|
1395
1991
|
import { extname, isAbsolute, join as join2, relative as relative3, resolve } from "pathe";
|
|
1396
1992
|
import { glob as glob2 } from "tinyglobby";
|
|
@@ -1415,7 +2011,7 @@ var filesystemSource = (options) => {
|
|
|
1415
2011
|
}
|
|
1416
2012
|
}
|
|
1417
2013
|
const read = await Promise.all(loadable.map(async (file) => {
|
|
1418
|
-
const source = await
|
|
2014
|
+
const source = await readFile3(file, "utf-8");
|
|
1419
2015
|
const ext = extname(file).toLowerCase();
|
|
1420
2016
|
const format = ext === ".mdx" ? "mdx" : "md";
|
|
1421
2017
|
let parsed;
|
|
@@ -1474,7 +2070,7 @@ var filesystemSource = (options) => {
|
|
|
1474
2070
|
load,
|
|
1475
2071
|
name: options.name,
|
|
1476
2072
|
prefix: options.prefix,
|
|
1477
|
-
read: (ref) =>
|
|
2073
|
+
read: (ref) => readFile3(join2(contentRoot, ref), "utf-8"),
|
|
1478
2074
|
staged: false,
|
|
1479
2075
|
validate,
|
|
1480
2076
|
watch
|
|
@@ -1735,19 +2331,13 @@ var includeMatcher = (include) => {
|
|
|
1735
2331
|
return (ref) => included(ref) && !excluded(ref);
|
|
1736
2332
|
};
|
|
1737
2333
|
var GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
|
|
2334
|
+
var isGithubUrl = (url) => URL.canParse(url) && GITHUB_HOSTS.has(new URL(url).hostname);
|
|
1738
2335
|
var githubHeaders2 = (url) => {
|
|
1739
2336
|
const token = process.env.GITHUB_TOKEN;
|
|
1740
|
-
|
|
1741
|
-
return {};
|
|
1742
|
-
}
|
|
1743
|
-
let host = "";
|
|
1744
|
-
try {
|
|
1745
|
-
host = new URL(url).hostname;
|
|
1746
|
-
} catch {
|
|
1747
|
-
return {};
|
|
1748
|
-
}
|
|
1749
|
-
return GITHUB_HOSTS.has(host) ? { authorization: `Bearer ${token}` } : {};
|
|
2337
|
+
return token && isGithubUrl(url) ? { authorization: `Bearer ${token}` } : {};
|
|
1750
2338
|
};
|
|
2339
|
+
var TOKEN_STATUSES = new Set([401, 403, 404]);
|
|
2340
|
+
var statusReason = (url, status) => TOKEN_STATUSES.has(status) && !process.env.GITHUB_TOKEN && isGithubUrl(url) ? `${url} -> ${status}; GITHUB_TOKEN is not set, which a private repository needs` : `${url} -> ${status}`;
|
|
1751
2341
|
var fetchText = async (url, doFetch) => {
|
|
1752
2342
|
try {
|
|
1753
2343
|
const res = await doFetch(url, {
|
|
@@ -1755,7 +2345,7 @@ var fetchText = async (url, doFetch) => {
|
|
|
1755
2345
|
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1756
2346
|
});
|
|
1757
2347
|
if (!res.ok) {
|
|
1758
|
-
throw new Error(
|
|
2348
|
+
throw new Error(statusReason(url, res.status));
|
|
1759
2349
|
}
|
|
1760
2350
|
return await res.text();
|
|
1761
2351
|
} catch (error) {
|
|
@@ -1797,6 +2387,24 @@ var enumerateGithub = async (github, include, doFetch) => {
|
|
|
1797
2387
|
truncated
|
|
1798
2388
|
};
|
|
1799
2389
|
};
|
|
2390
|
+
var ATX_HEADING = /^ {0,3}#/u;
|
|
2391
|
+
var withoutTitleHeading = (page) => {
|
|
2392
|
+
const lines = page.body.split(`
|
|
2393
|
+
`);
|
|
2394
|
+
const start = lines.findIndex((line) => line.trim() !== "");
|
|
2395
|
+
const span = ATX_HEADING.test(lines[start] ?? "") ? 1 : 2;
|
|
2396
|
+
const [heading] = start === -1 ? [] : extractHeadings(lines.slice(start, start + span).join(`
|
|
2397
|
+
`));
|
|
2398
|
+
if (heading?.depth !== 1 || heading.text === "" || page.data.title !== undefined && page.data.title !== heading.text) {
|
|
2399
|
+
return page;
|
|
2400
|
+
}
|
|
2401
|
+
lines.fill("", start, start + span);
|
|
2402
|
+
return {
|
|
2403
|
+
body: lines.join(`
|
|
2404
|
+
`),
|
|
2405
|
+
data: { ...page.data, title: heading.text }
|
|
2406
|
+
};
|
|
2407
|
+
};
|
|
1800
2408
|
var mdxRemoteSource = (options, ctx) => {
|
|
1801
2409
|
const doFetch = options.fetchImpl ?? globalThis.fetch;
|
|
1802
2410
|
const cache = snapshotCache(ctx.cacheDir);
|
|
@@ -1824,12 +2432,16 @@ var mdxRemoteSource = (options, ctx) => {
|
|
|
1824
2432
|
const text = await fetchText(item.fetchUrl, doFetch);
|
|
1825
2433
|
const parsed = frontmatter_default(text);
|
|
1826
2434
|
const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
|
|
2435
|
+
const page = withoutTitleHeading({
|
|
2436
|
+
body: parsed.content,
|
|
2437
|
+
data: parsed.data
|
|
2438
|
+
});
|
|
1827
2439
|
return {
|
|
1828
|
-
body: { format, text:
|
|
1829
|
-
data:
|
|
2440
|
+
body: { format, text: page.body },
|
|
2441
|
+
data: page.data,
|
|
1830
2442
|
editUrl: item.editUrl,
|
|
1831
2443
|
hash: hashText(text),
|
|
1832
|
-
raw: text,
|
|
2444
|
+
raw: text.slice(0, text.length - parsed.content.length) + page.body,
|
|
1833
2445
|
ref: item.ref
|
|
1834
2446
|
};
|
|
1835
2447
|
};
|
|
@@ -2140,7 +2752,7 @@ var FRAME_ESCAPED = /\\(?=[<>])/gu;
|
|
|
2140
2752
|
var YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
|
|
2141
2753
|
var isYouTubeUrl = (url) => {
|
|
2142
2754
|
const host = URL.parse(url)?.hostname ?? "";
|
|
2143
|
-
return YOUTUBE_HOST.test(host) &&
|
|
2755
|
+
return YOUTUBE_HOST.test(host) && youtubeEmbedSrc(url) !== null;
|
|
2144
2756
|
};
|
|
2145
2757
|
var renderVideo = (data) => {
|
|
2146
2758
|
const url = data.external?.url ?? data.file?.url;
|
|
@@ -2425,7 +3037,7 @@ ${nested}`;
|
|
|
2425
3037
|
|
|
2426
3038
|
// src/core/sources/obsidian.ts
|
|
2427
3039
|
import { existsSync as existsSync2, watch as fsWatch2, statSync } from "node:fs";
|
|
2428
|
-
import { readdir as readdir2, readFile as
|
|
3040
|
+
import { readdir as readdir2, readFile as readFile4, stat } from "node:fs/promises";
|
|
2429
3041
|
import { YAMLException as YAMLException2 } from "js-yaml";
|
|
2430
3042
|
import { basename as basename2, isAbsolute as isAbsolute2, join as join5, relative as relative4, resolve as resolve2 } from "pathe";
|
|
2431
3043
|
var MARKDOWN_FILE = /\.md$/iu;
|
|
@@ -2443,7 +3055,6 @@ var OBSIDIAN_NATIVE_KEYS = new Set([
|
|
|
2443
3055
|
"tag",
|
|
2444
3056
|
"tags"
|
|
2445
3057
|
]);
|
|
2446
|
-
var PAGE_META_KEYS = new Set(pageMetaSchema.keyof().options);
|
|
2447
3058
|
var entrySlugFor = (navPath) => slugifyPath(navPath.replace(MARKDOWN_FILE, ""));
|
|
2448
3059
|
var noTargets = () => ({
|
|
2449
3060
|
ambiguous: [],
|
|
@@ -2726,7 +3337,7 @@ var noteToEntry = (pair, index, options, unresolved) => {
|
|
|
2726
3337
|
const { note } = pair;
|
|
2727
3338
|
const title = titleFor(note.rel, note.data.title);
|
|
2728
3339
|
const keep = keptKeysFor(note.data, options);
|
|
2729
|
-
const data = Object.fromEntries(Object.entries(note.data).filter(([key]) => !OBSIDIAN_NATIVE_KEYS.has(key) && (
|
|
3340
|
+
const data = Object.fromEntries(Object.entries(note.data).filter(([key]) => !OBSIDIAN_NATIVE_KEYS.has(key) && (BUILT_IN_PAGE_META_KEYS.has(key) || keep.has(key))));
|
|
2730
3341
|
const merged = title === undefined ? data : { ...data, title };
|
|
2731
3342
|
const body = note.content.trim();
|
|
2732
3343
|
const text = transformBody(body, index, pair.self, unresolved);
|
|
@@ -2748,7 +3359,7 @@ var noteToEntry = (pair, index, options, unresolved) => {
|
|
|
2748
3359
|
var readNote = async (vaultDir, rel, options) => {
|
|
2749
3360
|
const absPath = join5(vaultDir, rel);
|
|
2750
3361
|
try {
|
|
2751
|
-
const file = await
|
|
3362
|
+
const file = await readFile4(absPath, "utf-8");
|
|
2752
3363
|
const { content, data } = frontmatter_default(file);
|
|
2753
3364
|
return {
|
|
2754
3365
|
absPath,
|
|
@@ -2877,7 +3488,7 @@ var obsidianSource = (options, ctx) => {
|
|
|
2877
3488
|
suggestion: "Reference notes by their vault-relative path."
|
|
2878
3489
|
});
|
|
2879
3490
|
}
|
|
2880
|
-
return await
|
|
3491
|
+
return await readFile4(absPath, "utf-8");
|
|
2881
3492
|
};
|
|
2882
3493
|
const validate = () => {
|
|
2883
3494
|
const stats = statSync(vaultDir, { throwIfNoEntry: false });
|
|
@@ -3593,6 +4204,284 @@ var resolveSources = (config, context, runtime) => {
|
|
|
3593
4204
|
return sources;
|
|
3594
4205
|
};
|
|
3595
4206
|
|
|
4207
|
+
// src/core/svg-images.ts
|
|
4208
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
4209
|
+
import { imageMetadata } from "astro/assets/utils";
|
|
4210
|
+
import { dirname as dirname2, extname as extname3 } from "pathe";
|
|
4211
|
+
var isUnmeasurableSvg = async (path) => {
|
|
4212
|
+
if (extname3(path).toLowerCase() !== ".svg") {
|
|
4213
|
+
return false;
|
|
4214
|
+
}
|
|
4215
|
+
let data;
|
|
4216
|
+
try {
|
|
4217
|
+
data = await readFile5(path);
|
|
4218
|
+
} catch {
|
|
4219
|
+
return false;
|
|
4220
|
+
}
|
|
4221
|
+
try {
|
|
4222
|
+
await imageMetadata(data, path);
|
|
4223
|
+
return false;
|
|
4224
|
+
} catch {
|
|
4225
|
+
return true;
|
|
4226
|
+
}
|
|
4227
|
+
};
|
|
4228
|
+
var unmeasurableSvgDiagnostics = async (pages) => {
|
|
4229
|
+
const seen = new Set;
|
|
4230
|
+
const embeds = pages.flatMap((page) => {
|
|
4231
|
+
const { sourcePath } = page;
|
|
4232
|
+
if (!sourcePath) {
|
|
4233
|
+
return [];
|
|
4234
|
+
}
|
|
4235
|
+
return page.links.flatMap((link) => {
|
|
4236
|
+
const file = link.file ?? sourcePath;
|
|
4237
|
+
const key = `${file}:${link.line}:${link.column}`;
|
|
4238
|
+
if (!link.image || seen.has(key)) {
|
|
4239
|
+
return [];
|
|
4240
|
+
}
|
|
4241
|
+
seen.add(key);
|
|
4242
|
+
const abs = resolveRelativeImage(dirname2(sourcePath), link.target);
|
|
4243
|
+
return abs ? [{ abs, file, link }] : [];
|
|
4244
|
+
});
|
|
4245
|
+
});
|
|
4246
|
+
const found = await Promise.all(embeds.map(async (embed) => await isUnmeasurableSvg(embed.abs) ? [embed] : []));
|
|
4247
|
+
return found.flat().map(({ file, link }) => ({
|
|
4248
|
+
code: "BLUME_SVG_UNOPTIMIZED",
|
|
4249
|
+
column: link.column,
|
|
4250
|
+
file,
|
|
4251
|
+
line: link.line,
|
|
4252
|
+
message: `\`${link.target}\` is served as it is, not optimized: Astro can't read its size, which takes the whole \`<svg>\` tag within the file's first 1,000 bytes, with a width and height or a viewBox.`,
|
|
4253
|
+
severity: "warning",
|
|
4254
|
+
suggestion: "The image still shows. To have it optimized, put the `<svg>` tag's width and height (or viewBox) near the start of the file. In a draw.io export, the `content` attribute pushes them out: export without a copy of the diagram, or remove that attribute."
|
|
4255
|
+
}));
|
|
4256
|
+
};
|
|
4257
|
+
|
|
4258
|
+
// src/core/syntax-diagnostics.ts
|
|
4259
|
+
import { slug } from "github-slugger";
|
|
4260
|
+
import { markdownToMdast as markdownToMdast4 } from "satteri";
|
|
4261
|
+
var UNESCAPED = String.raw`(?<=(?:^|[^\\])(?:\\\\)*)`;
|
|
4262
|
+
var TEMPLATE_TAG = new RegExp(String.raw`${UNESCAPED}\{%[^\n]*?%\}`, "gu");
|
|
4263
|
+
var TEMPLATE_OUTPUT = new RegExp(String.raw`${UNESCAPED}\{\{(?<expression>[^\n{}]*)\}\}`, "gu");
|
|
4264
|
+
var VARIABLE_REFERENCE = /^\s*[\w-]+\s*$/u;
|
|
4265
|
+
var WIKILINK2 = /(?<![!\\[])\[\[(?<inner>[^[\]\n]+)\]\](?![([:])/gu;
|
|
4266
|
+
var MDC_BLOCK = /^[\t ]*(?<written>::(?<name>[a-z][\w-]*)(?:\[[^\]\n]*\])?(?:\{[^}\n]*\})?)[\t ]*$/gimu;
|
|
4267
|
+
var MDC_INLINE = /(?<![\w:\\])(?<written>:{1,2}(?<name>[a-z][\w-]*)(?:\[[^\]\n]*\](?:\{[^}\n]*\})?|\{[^}\n]*\}))/giu;
|
|
4268
|
+
var ATTRIBUTE_LIST = new RegExp(String.raw`(?<![=\\])\{:?[\t ]*${ATTRIBUTE}(?:[\t ]+${ATTRIBUTE})*[\t ]*\}`, "gu");
|
|
4269
|
+
var DIRECTIVE_ATTRIBUTES = /:+[a-z][\w-]*(?:\[[^\]\n]*\])?\{[^}\n]*\}/giu;
|
|
4270
|
+
var ATX_HEADING2 = /^ {0,3}#{1,6}[\t ]/u;
|
|
4271
|
+
var SETEXT_UNDERLINE = /^ {0,3}(?:=+|-+)[\t ]*$/u;
|
|
4272
|
+
var VOID_ELEMENT = new RegExp(String.raw`${UNESCAPED}<(?<tag>area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)(?=[\s/>])(?:[^<>"'/]|"[^"]*"|'[^']*'|\/(?!>))*>`, "gu");
|
|
4273
|
+
var SUSPECT_WIKILINK = /\[\[/u;
|
|
4274
|
+
var SUSPECT_MDC = /(?:^|[^\w:]):{1,2}[a-z][\w-]*[[{]|^[\t ]*::[a-z]/imu;
|
|
4275
|
+
var MDX_COMMENT = /\{\s*\/\*[\s\S]*?\*\/\s*\}/gu;
|
|
4276
|
+
var ANGLE_DESTINATION = /(?<=\]\([\t ]*|^ {0,3}\[[^\]\n]+\]:[\t ]*)<[^<>\n]*>/gmu;
|
|
4277
|
+
var blank = (text) => text.replaceAll(/[^\n]/gu, " ");
|
|
4278
|
+
var maskMarkdown = (text) => {
|
|
4279
|
+
const ranges = [];
|
|
4280
|
+
const walk = (node) => {
|
|
4281
|
+
const start = node.position?.start.offset ?? 0;
|
|
4282
|
+
const end = node.position?.end.offset ?? start;
|
|
4283
|
+
if (node.type === "code" || node.type === "inlineCode" || node.type === "html" && node.value.startsWith("<!--")) {
|
|
4284
|
+
ranges.push([start, end]);
|
|
4285
|
+
} else if ("children" in node) {
|
|
4286
|
+
for (const child of node.children) {
|
|
4287
|
+
walk(child);
|
|
4288
|
+
}
|
|
4289
|
+
}
|
|
4290
|
+
};
|
|
4291
|
+
walk(markdownToMdast4(text, {
|
|
4292
|
+
features: { ...MARKDOWN_BODY_FEATURES, gfm: true }
|
|
4293
|
+
}));
|
|
4294
|
+
let masked = "";
|
|
4295
|
+
let cursor = 0;
|
|
4296
|
+
for (const [start, end] of ranges) {
|
|
4297
|
+
masked += text.slice(cursor, start) + blank(text.slice(start, end));
|
|
4298
|
+
cursor = end;
|
|
4299
|
+
}
|
|
4300
|
+
return masked + text.slice(cursor);
|
|
4301
|
+
};
|
|
4302
|
+
var maskMdx = (text) => {
|
|
4303
|
+
let fence = null;
|
|
4304
|
+
return text.split(`
|
|
4305
|
+
`).map((line) => {
|
|
4306
|
+
const next = nextFenceState(line, fence);
|
|
4307
|
+
const inFence = fence !== null || next !== null;
|
|
4308
|
+
fence = next;
|
|
4309
|
+
return inFence ? blank(line) : line.replaceAll(INLINE_CODE, blank);
|
|
4310
|
+
}).join(`
|
|
4311
|
+
`).replaceAll(MDX_COMMENT, blank).replaceAll(HTML_COMMENT, blank).replaceAll(ANGLE_DESTINATION, blank);
|
|
4312
|
+
};
|
|
4313
|
+
var lineStartsOf = (text) => {
|
|
4314
|
+
const starts = [0];
|
|
4315
|
+
for (let index = text.indexOf(`
|
|
4316
|
+
`);index !== -1; index = text.indexOf(`
|
|
4317
|
+
`, index + 1)) {
|
|
4318
|
+
starts.push(index + 1);
|
|
4319
|
+
}
|
|
4320
|
+
return starts;
|
|
4321
|
+
};
|
|
4322
|
+
var siteOf = (starts, at) => {
|
|
4323
|
+
const line = starts.findLastIndex((start) => start <= at);
|
|
4324
|
+
return { column: at - (starts[line] ?? 0) + 1, line: line + 1 };
|
|
4325
|
+
};
|
|
4326
|
+
var templateFindings = ({ starts, text }) => [
|
|
4327
|
+
...[...text.matchAll(TEMPLATE_TAG)].map((match) => ({
|
|
4328
|
+
...siteOf(starts, match.index),
|
|
4329
|
+
code: "BLUME_TEMPLATE_TAG",
|
|
4330
|
+
message: `\`${match[0]}\` is a Liquid or Markdoc tag, which Blume doesn't run, so the page shows it as written.`,
|
|
4331
|
+
suggestion: "Rewrite what it produced as Markdown or a Blume component, or remove it. To show it as text, put it in inline code."
|
|
4332
|
+
})),
|
|
4333
|
+
...[...text.matchAll(TEMPLATE_OUTPUT)].filter((match) => !VARIABLE_REFERENCE.test(match.groups?.expression ?? "")).map((match) => ({
|
|
4334
|
+
...siteOf(starts, match.index),
|
|
4335
|
+
code: "BLUME_TEMPLATE_TAG",
|
|
4336
|
+
message: `\`${match[0]}\` is a Liquid output tag, which Blume doesn't fill in, so the page shows it as written.`,
|
|
4337
|
+
suggestion: "Write the value in its place, or define it under `variables` in blume.config.ts and write `{{name}}`. To show it as text, put it in inline code."
|
|
4338
|
+
}))
|
|
4339
|
+
];
|
|
4340
|
+
var wikiKey = (name) => name.trim().toLowerCase().replaceAll(/[\s_-]+/gu, "-");
|
|
4341
|
+
var indexPageNames = (pages) => {
|
|
4342
|
+
const index = new Map;
|
|
4343
|
+
for (const page of pages) {
|
|
4344
|
+
const stem = (page.source.ref.split("/").at(-1) ?? "").replace(/\.mdx?$/iu, "");
|
|
4345
|
+
for (const key of new Set([wikiKey(stem), wikiKey(page.title)])) {
|
|
4346
|
+
const named = index.get(key);
|
|
4347
|
+
if (named) {
|
|
4348
|
+
named.push(page);
|
|
4349
|
+
} else {
|
|
4350
|
+
index.set(key, [page]);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
return index;
|
|
4355
|
+
};
|
|
4356
|
+
var partRoute = (text, pageRoute) => {
|
|
4357
|
+
const [name = "", heading] = text.split("#", 2);
|
|
4358
|
+
const route = pageRoute(name);
|
|
4359
|
+
return route && heading ? `${route}#${slug(heading)}` : route;
|
|
4360
|
+
};
|
|
4361
|
+
var wikilinkFindings = ({ pageRoute, starts, text }) => [...text.matchAll(WIKILINK2)].flatMap((match) => {
|
|
4362
|
+
const parts = (match.groups?.inner ?? "").split("|").map((part) => ({
|
|
4363
|
+
route: partRoute(part, pageRoute),
|
|
4364
|
+
text: part.trim()
|
|
4365
|
+
}));
|
|
4366
|
+
const target = parts.find((part) => part.route !== undefined);
|
|
4367
|
+
if (!target && parts.length === 1) {
|
|
4368
|
+
return [];
|
|
4369
|
+
}
|
|
4370
|
+
const label = parts.find((part) => part !== target) ?? target;
|
|
4371
|
+
return [
|
|
4372
|
+
{
|
|
4373
|
+
...siteOf(starts, match.index),
|
|
4374
|
+
code: "BLUME_WIKILINK_UNSUPPORTED",
|
|
4375
|
+
message: `\`${match[0]}\` is a wiki link, which Blume reads only in an Obsidian vault source, so the page shows it as written${target ? ` instead of linking to ${target.route}` : ""}.`,
|
|
4376
|
+
suggestion: target ? `Rewrite it as a Markdown link: \`[${label?.text}](${target.route})\`.` : "Rewrite it as a Markdown link to the page, like `[Text](./page.md)`."
|
|
4377
|
+
}
|
|
4378
|
+
];
|
|
4379
|
+
});
|
|
4380
|
+
var mdcFindings = ({ starts, text }) => {
|
|
4381
|
+
const blocks = [...text.matchAll(MDC_BLOCK)].map((match) => ({
|
|
4382
|
+
at: match.index + match[0].indexOf("::"),
|
|
4383
|
+
written: match.groups?.written
|
|
4384
|
+
}));
|
|
4385
|
+
const opened = new Set(blocks.map(({ at }) => at));
|
|
4386
|
+
return [
|
|
4387
|
+
...blocks.map(({ at, written }) => ({
|
|
4388
|
+
...siteOf(starts, at),
|
|
4389
|
+
code: "BLUME_MDC_SYNTAX",
|
|
4390
|
+
message: `\`${written}\` opens a Nuxt Content (MDC) block component, which Blume doesn't render, so the page shows its \`::\` lines as text.`,
|
|
4391
|
+
suggestion: "Rewrite the block as the matching Blume component (`<Callout>`, `<Card>`, `<Tabs>`, …) or a `:::` callout."
|
|
4392
|
+
})),
|
|
4393
|
+
...[...text.matchAll(MDC_INLINE)].filter((match) => !opened.has(match.index)).map((match) => ({
|
|
4394
|
+
...siteOf(starts, match.index),
|
|
4395
|
+
code: "BLUME_MDC_SYNTAX",
|
|
4396
|
+
message: `\`${match.groups?.written}\` is an inline component in Nuxt Content (MDC) syntax, which Blume doesn't render, so the page shows it as written.`,
|
|
4397
|
+
suggestion: "Rewrite it as the matching Blume component (`<Badge>`, `<Icon>`, …) or as plain text."
|
|
4398
|
+
}))
|
|
4399
|
+
];
|
|
4400
|
+
};
|
|
4401
|
+
var isHeadingMarker = (lines, site, written, format) => {
|
|
4402
|
+
const line = lines[site.line - 1] ?? "";
|
|
4403
|
+
const heading = ATX_HEADING2.test(line) || SETEXT_UNDERLINE.test(lines[site.line] ?? "");
|
|
4404
|
+
const marker = trailingCurlyMarker(line);
|
|
4405
|
+
return heading && marker?.marker === written && marker.index === site.column - 1 && (format === "md" || !marker.attributes);
|
|
4406
|
+
};
|
|
4407
|
+
var ATTRIBUTE_LIST_PROBLEMS = {
|
|
4408
|
+
md: {
|
|
4409
|
+
code: "BLUME_MD_ATTRIBUTE_LIST",
|
|
4410
|
+
effect: "which Blume doesn't read, so the page shows it as written",
|
|
4411
|
+
suggestion: 'Remove it. To keep the attributes, write the element as HTML (`<img src="…" width="300">`), or pin a heading\'s anchor with `[#id]`. To show the braces as text, put them in inline code.'
|
|
4412
|
+
},
|
|
4413
|
+
mdx: {
|
|
4414
|
+
code: "BLUME_MDX_ATTRIBUTE_LIST",
|
|
4415
|
+
effect: "which MDX reads as a JavaScript expression, so the page fails to build",
|
|
4416
|
+
suggestion: 'Remove it. To keep the attributes, write the element as JSX (`<img src="…" width="300" />`), or pin a heading\'s anchor with `[#id]`. To show the braces as text, escape them: `\\{…\\}`.'
|
|
4417
|
+
}
|
|
4418
|
+
};
|
|
4419
|
+
var attributeListFindings = (format) => ({ starts, text }) => {
|
|
4420
|
+
const { code, effect, suggestion } = ATTRIBUTE_LIST_PROBLEMS[format];
|
|
4421
|
+
const scanned = text.replaceAll(DIRECTIVE_ATTRIBUTES, blank).replaceAll(TEMPLATE_OUTPUT, blank);
|
|
4422
|
+
const lines = text.split(`
|
|
4423
|
+
`);
|
|
4424
|
+
return [...scanned.matchAll(ATTRIBUTE_LIST)].map((match) => ({
|
|
4425
|
+
site: siteOf(starts, match.index),
|
|
4426
|
+
written: match[0]
|
|
4427
|
+
})).filter(({ site, written }) => !isHeadingMarker(lines, site, written, format)).map(({ site, written }) => ({
|
|
4428
|
+
...site,
|
|
4429
|
+
code,
|
|
4430
|
+
message: `\`${written}\` is an attribute list, ${effect}.`,
|
|
4431
|
+
suggestion
|
|
4432
|
+
}));
|
|
4433
|
+
};
|
|
4434
|
+
var SUSPECT_ATTRIBUTE_LIST = /\{:?[\t ]*(?:[#.][A-Za-z_-]|[\w-]+=)/u;
|
|
4435
|
+
var voidElementFindings = ({ starts, text }) => [...text.matchAll(VOID_ELEMENT)].filter((match) => !text.slice(match.index + match[0].length).trimStart().startsWith(`</${match.groups?.tag}>`)).map((match) => ({
|
|
4436
|
+
...siteOf(starts, match.index),
|
|
4437
|
+
code: "BLUME_MDX_UNCLOSED_ELEMENT",
|
|
4438
|
+
message: `\`<${match.groups?.tag}>\` isn't closed. MDX reads HTML as JSX, where it needs \`/>\`: unclosed, it fails the page, or in an included file it takes in everything after it.`,
|
|
4439
|
+
suggestion: `Close it: \`<${match.groups?.tag} … />\`.`
|
|
4440
|
+
}));
|
|
4441
|
+
var CHECKS = {
|
|
4442
|
+
md: [
|
|
4443
|
+
{ find: templateFindings, suspect: /\{[%{]/u },
|
|
4444
|
+
{ find: wikilinkFindings, suspect: SUSPECT_WIKILINK },
|
|
4445
|
+
{ find: mdcFindings, suspect: SUSPECT_MDC },
|
|
4446
|
+
{ find: attributeListFindings("md"), suspect: SUSPECT_ATTRIBUTE_LIST }
|
|
4447
|
+
],
|
|
4448
|
+
mdx: [
|
|
4449
|
+
{ find: wikilinkFindings, suspect: SUSPECT_WIKILINK },
|
|
4450
|
+
{ find: mdcFindings, suspect: SUSPECT_MDC },
|
|
4451
|
+
{ find: attributeListFindings("mdx"), suspect: SUSPECT_ATTRIBUTE_LIST },
|
|
4452
|
+
{
|
|
4453
|
+
find: voidElementFindings,
|
|
4454
|
+
suspect: /<(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)[\s/>]/u
|
|
4455
|
+
}
|
|
4456
|
+
]
|
|
4457
|
+
};
|
|
4458
|
+
var syntaxFindings = (text, format, pageRoute) => {
|
|
4459
|
+
const checks = CHECKS[format].filter((check) => check.suspect.test(text));
|
|
4460
|
+
if (checks.length === 0) {
|
|
4461
|
+
return [];
|
|
4462
|
+
}
|
|
4463
|
+
const masked = format === "mdx" ? maskMdx(text) : maskMarkdown(text);
|
|
4464
|
+
const scan = { pageRoute, starts: lineStartsOf(masked), text: masked };
|
|
4465
|
+
return checks.flatMap((check) => check.find(scan)).toSorted((a, b) => a.line - b.line || a.column - b.column);
|
|
4466
|
+
};
|
|
4467
|
+
var syntaxDiagnostics = (entry, sourceName, pages = new Map, locale = "") => {
|
|
4468
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
4469
|
+
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
4470
|
+
const pageRoute = (name) => {
|
|
4471
|
+
const named = pages.get(wikiKey(name));
|
|
4472
|
+
return (named?.find((other) => other.locale === locale) ?? named?.[0])?.route;
|
|
4473
|
+
};
|
|
4474
|
+
return syntaxFindings(entry.expanded?.text ?? entry.body.text, entry.body.format, pageRoute).map(({ line, ...finding }) => {
|
|
4475
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
4476
|
+
return {
|
|
4477
|
+
...finding,
|
|
4478
|
+
file: origin?.file ?? page,
|
|
4479
|
+
line: origin?.line ?? line + offset,
|
|
4480
|
+
severity: "warning"
|
|
4481
|
+
};
|
|
4482
|
+
});
|
|
4483
|
+
};
|
|
4484
|
+
|
|
3596
4485
|
// src/core/project-graph.ts
|
|
3597
4486
|
var isWithin = (dir, path) => {
|
|
3598
4487
|
const rel = relative5(dir, path);
|
|
@@ -3631,16 +4520,25 @@ var entryIdDiagnostics = (pages, collectionBase) => {
|
|
|
3631
4520
|
}
|
|
3632
4521
|
return diagnostics;
|
|
3633
4522
|
};
|
|
3634
|
-
var normalizeLoadedEntries = (loaded, config) => {
|
|
4523
|
+
var normalizeLoadedEntries = (loaded, config, dropUnparsable) => {
|
|
3635
4524
|
const frontmatterExtend = Object.keys(config.frontmatter.extend).length > 0 ? config.frontmatter.extend : undefined;
|
|
3636
4525
|
const declaredTypes = Object.entries(config.content.types).filter(([, type]) => Object.keys(type.frontmatter).length > 0);
|
|
3637
4526
|
const typeFrontmatter = declaredTypes.length > 0 ? Object.fromEntries(declaredTypes.map(([name, type]) => [name, type.frontmatter])) : undefined;
|
|
3638
4527
|
const pages = [];
|
|
3639
4528
|
const allDiagnostics = [];
|
|
4529
|
+
const syntaxErrors = [];
|
|
4530
|
+
const unparsable = [];
|
|
4531
|
+
const published = [];
|
|
3640
4532
|
let droppedPages = 0;
|
|
3641
4533
|
for (const { source, entries, diagnostics } of loaded) {
|
|
3642
4534
|
allDiagnostics.push(...diagnostics);
|
|
3643
4535
|
for (const entry of entries) {
|
|
4536
|
+
const syntax = mdxSyntaxCheck(entry, source.name, config.variables);
|
|
4537
|
+
syntaxErrors.push(...syntax.diagnostics);
|
|
4538
|
+
const drop = syntax.unparsable && dropUnparsable;
|
|
4539
|
+
if (drop && entry.sourcePath && !source.staged) {
|
|
4540
|
+
unparsable.push(entry.sourcePath);
|
|
4541
|
+
}
|
|
3644
4542
|
const normalized = normalizeEntry(entry, {
|
|
3645
4543
|
basePath: config.basePath,
|
|
3646
4544
|
defaultType: config.content.defaultType,
|
|
@@ -3656,17 +4554,27 @@ var normalizeLoadedEntries = (loaded, config) => {
|
|
|
3656
4554
|
typeFrontmatter,
|
|
3657
4555
|
versions: config.versions
|
|
3658
4556
|
});
|
|
3659
|
-
|
|
4557
|
+
const [page] = normalized.pages;
|
|
4558
|
+
if (page ? drop : normalized.diagnostics.length > 0) {
|
|
3660
4559
|
droppedPages += 1;
|
|
3661
4560
|
}
|
|
3662
|
-
|
|
4561
|
+
if (!drop) {
|
|
4562
|
+
pages.push(...normalized.pages);
|
|
4563
|
+
}
|
|
3663
4564
|
allDiagnostics.push(...normalized.diagnostics);
|
|
3664
|
-
if (
|
|
3665
|
-
allDiagnostics.push(...
|
|
4565
|
+
if (page) {
|
|
4566
|
+
allDiagnostics.push(...directiveDiagnostics(entry, source.name), ...codeFenceDiagnostics(entry, source.name), ...alertDiagnostics(entry, source.name));
|
|
4567
|
+
published.push({ entry, locale: page.locale, source: source.name });
|
|
3666
4568
|
}
|
|
3667
4569
|
}
|
|
3668
4570
|
}
|
|
3669
|
-
|
|
4571
|
+
const names = indexPageNames(pages);
|
|
4572
|
+
for (const { entry, locale, source } of published) {
|
|
4573
|
+
allDiagnostics.push(...syntaxDiagnostics(entry, source, names, locale));
|
|
4574
|
+
}
|
|
4575
|
+
const covered = (diagnostic) => allDiagnostics.some((other) => other.file === diagnostic.file && other.line === diagnostic.line && (other.severity === "error" || other.severity === "warning" && diagnostic.severity === "warning"));
|
|
4576
|
+
allDiagnostics.push(...syntaxErrors.filter((error) => !covered(error)));
|
|
4577
|
+
return { diagnostics: allDiagnostics, droppedPages, pages, unparsable };
|
|
3670
4578
|
};
|
|
3671
4579
|
var gitContentRoots = (sources, gitRoot) => {
|
|
3672
4580
|
if (gitRoot === null) {
|
|
@@ -3701,19 +4609,31 @@ var substituteEntryVariables = (entry, variables) => {
|
|
|
3701
4609
|
return diagnostics;
|
|
3702
4610
|
};
|
|
3703
4611
|
var substituteLoadedVariables = (loaded, variables) => hasVariables(variables) ? loaded.flatMap(({ entries }) => entries.flatMap((entry) => substituteEntryVariables(entry, variables))) : [];
|
|
3704
|
-
var redirectPageDiagnostics = (config, pages, configFile) => config.redirects.flatMap((redirect) => {
|
|
4612
|
+
var redirectPageDiagnostics = (config, pages, mirrored, configFile) => config.redirects.flatMap((redirect) => {
|
|
3705
4613
|
const from = withBasePath(config.basePath, redirect.from);
|
|
3706
4614
|
if (!isPatternPath(redirect.from)) {
|
|
3707
4615
|
const page = normalizePath(from);
|
|
3708
|
-
|
|
4616
|
+
if (pages.includes(page)) {
|
|
4617
|
+
return [
|
|
4618
|
+
{
|
|
4619
|
+
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
4620
|
+
file: configFile ?? undefined,
|
|
4621
|
+
message: `The redirect from ${redirect.from} is also the page ${page}, which never publishes: its URL redirects to ${redirect.to} instead.`,
|
|
4622
|
+
severity: "warning",
|
|
4623
|
+
suggestion: "If the page moved, delete it or give it a new slug; otherwise remove the redirect."
|
|
4624
|
+
}
|
|
4625
|
+
];
|
|
4626
|
+
}
|
|
4627
|
+
const owner = mirroredPage(mirrored, page);
|
|
4628
|
+
return owner === undefined ? [] : [
|
|
3709
4629
|
{
|
|
3710
4630
|
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
3711
4631
|
file: configFile ?? undefined,
|
|
3712
|
-
message: `The redirect from ${redirect.from} is also the page ${
|
|
4632
|
+
message: `The redirect from ${redirect.from} is also the Markdown copy of the page ${owner}, so that copy never publishes: its URL redirects to ${redirect.to} instead.`,
|
|
3713
4633
|
severity: "warning",
|
|
3714
|
-
suggestion: "
|
|
4634
|
+
suggestion: "Remove the redirect: that URL serves the page's Markdown to agents."
|
|
3715
4635
|
}
|
|
3716
|
-
]
|
|
4636
|
+
];
|
|
3717
4637
|
}
|
|
3718
4638
|
const matched = pathsUnderPattern(from, pages);
|
|
3719
4639
|
const [first] = matched;
|
|
@@ -3731,6 +4651,43 @@ var redirectPageDiagnostics = (config, pages, configFile) => config.redirects.fl
|
|
|
3731
4651
|
}
|
|
3732
4652
|
];
|
|
3733
4653
|
});
|
|
4654
|
+
var withSourceRedirects = (config, loaded, pages) => {
|
|
4655
|
+
const requested = loaded.flatMap(({ redirects }) => redirects ?? []);
|
|
4656
|
+
if (requested.length === 0) {
|
|
4657
|
+
return config;
|
|
4658
|
+
}
|
|
4659
|
+
const served = (route) => normalizePath(withBasePath(config.basePath, route));
|
|
4660
|
+
const prefixes = [
|
|
4661
|
+
"",
|
|
4662
|
+
...config.i18n?.locales.map((locale) => `/${locale.code}`) ?? []
|
|
4663
|
+
];
|
|
4664
|
+
const taken = new Set(config.redirects.map((redirect) => served(redirect.from)));
|
|
4665
|
+
const added = [];
|
|
4666
|
+
for (const redirect of requested) {
|
|
4667
|
+
for (const prefix of prefixes) {
|
|
4668
|
+
const from = `${prefix}${redirect.from}`;
|
|
4669
|
+
const to = `${prefix}${redirect.to}`;
|
|
4670
|
+
if (!pages.has(served(to)) || pages.has(served(from)) || taken.has(served(from))) {
|
|
4671
|
+
continue;
|
|
4672
|
+
}
|
|
4673
|
+
taken.add(served(from));
|
|
4674
|
+
added.push({ from, status: 301, to });
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
return added.length > 0 ? { ...config, redirects: [...config.redirects, ...added] } : config;
|
|
4678
|
+
};
|
|
4679
|
+
var publicOpenApiDiagnostics = (config, root) => {
|
|
4680
|
+
const file = join8(root, "public", "openapi.json");
|
|
4681
|
+
return config.agents.api && existsSync3(file) ? [
|
|
4682
|
+
{
|
|
4683
|
+
code: "BLUME_PUBLIC_OPENAPI_JSON",
|
|
4684
|
+
file,
|
|
4685
|
+
message: "public/openapi.json is served at /openapi.json, where Blume publishes the OpenAPI description of the site's JSON docs API, so that description isn't generated, and /.well-known/api-catalog still lists /openapi.json as the docs API's description.",
|
|
4686
|
+
severity: "warning",
|
|
4687
|
+
suggestion: "Move the file to another path, like public/specs/openapi.json, and update what links to it. A spec an openapi() reference renders can live outside public/ altogether. Or set agents.api to false if the site shouldn't publish the JSON docs API."
|
|
4688
|
+
}
|
|
4689
|
+
] : [];
|
|
4690
|
+
};
|
|
3734
4691
|
var bannerLinkHref = (banner) => banner === undefined || isBannerShorthand(banner) ? undefined : banner.link?.href;
|
|
3735
4692
|
var validatedSources = (config, context, runtime, beforeSources) => {
|
|
3736
4693
|
beforeSources?.(config);
|
|
@@ -3786,15 +4743,17 @@ var scanProject = async (root, options = {}) => {
|
|
|
3786
4743
|
});
|
|
3787
4744
|
}));
|
|
3788
4745
|
const variableDiagnostics = substituteLoadedVariables(loaded, config.variables);
|
|
3789
|
-
const
|
|
3790
|
-
...loaded.flatMap(({ folderMeta: sourceMeta }) => Object.entries(sourceMeta ?? {})),
|
|
3791
|
-
...folderMeta.shared
|
|
3792
|
-
]);
|
|
4746
|
+
const mergedFolderMeta = withGeneratedFolderMeta(folderMeta, new Map(loaded.flatMap(({ folderMeta: sourceMeta }) => Object.entries(sourceMeta ?? {}))), localeDirs);
|
|
3793
4747
|
const {
|
|
3794
4748
|
diagnostics: contentDiagnostics,
|
|
3795
4749
|
droppedPages,
|
|
3796
|
-
pages: allPages
|
|
3797
|
-
|
|
4750
|
+
pages: allPages,
|
|
4751
|
+
unparsable
|
|
4752
|
+
} = normalizeLoadedEntries(loaded, config, mode === "build");
|
|
4753
|
+
const metaDiagnostics = await folderMetaDiagnostics(folderMeta, allPages, {
|
|
4754
|
+
localeDirs,
|
|
4755
|
+
versionDirs: config.versions?.archived.map((version) => version.id)
|
|
4756
|
+
});
|
|
3798
4757
|
const pages = mode === "build" && !preview ? allPages.filter((page) => !page.meta.draft) : allPages;
|
|
3799
4758
|
const lastModified = resolveLastModifiedConfig(config.lastModified);
|
|
3800
4759
|
const lastModifiedWarnings = [];
|
|
@@ -3821,40 +4780,54 @@ var scanProject = async (root, options = {}) => {
|
|
|
3821
4780
|
basePath: config.basePath,
|
|
3822
4781
|
brandHref: logoHref(config.logo),
|
|
3823
4782
|
extraRoutes,
|
|
3824
|
-
folderMeta:
|
|
4783
|
+
folderMeta: mergedFolderMeta.meta,
|
|
3825
4784
|
i18n: config.i18n,
|
|
3826
4785
|
navigation: config.navigation,
|
|
3827
|
-
sharedFolderMeta,
|
|
4786
|
+
sharedFolderMeta: mergedFolderMeta.shared,
|
|
3828
4787
|
versions: config.versions
|
|
3829
4788
|
});
|
|
3830
4789
|
const manifest = buildManifest({ config, context, graph });
|
|
3831
4790
|
const i18nWarnings = config.i18n ? i18nDiagnostics(pages, config.i18n, config.versions) : [];
|
|
4791
|
+
const svgWarnings = await unmeasurableSvgDiagnostics(pages);
|
|
3832
4792
|
const versionWarnings = config.versions ? versionsDiagnostics(pages, config.versions) : [];
|
|
4793
|
+
const servedPaths = [
|
|
4794
|
+
...manifest.routes.map((route) => route.path),
|
|
4795
|
+
...extraRoutes
|
|
4796
|
+
];
|
|
4797
|
+
const redirects = [
|
|
4798
|
+
...config.redirects,
|
|
4799
|
+
...formerRouteRedirects(pages, new Set(servedPaths), config)
|
|
4800
|
+
];
|
|
3833
4801
|
return {
|
|
3834
|
-
config,
|
|
4802
|
+
config: withSourceRedirects(redirects.length === config.redirects.length ? config : { ...config, redirects }, loaded, new Set([...routeSetFor(manifest.routes), ...extraRoutes])),
|
|
3835
4803
|
context,
|
|
3836
4804
|
diagnostics: [
|
|
4805
|
+
...configResult.diagnostics,
|
|
3837
4806
|
...contentDiagnostics,
|
|
3838
4807
|
...includeDiagnostics,
|
|
3839
4808
|
...variableDiagnostics,
|
|
3840
4809
|
...folderMeta.diagnostics,
|
|
4810
|
+
...metaDiagnostics,
|
|
3841
4811
|
...entryIdDiagnostics(pages, resolveDocsCollection(config, context.root).base),
|
|
3842
4812
|
...graph.diagnostics,
|
|
3843
4813
|
...i18nWarnings,
|
|
3844
4814
|
...versionWarnings,
|
|
3845
4815
|
...lastModifiedWarnings,
|
|
3846
|
-
...
|
|
4816
|
+
...svgWarnings,
|
|
4817
|
+
...publicOpenApiDiagnostics(config, context.root),
|
|
4818
|
+
...redirectPageDiagnostics(config, servedPaths, routeSetFor(manifest.routes), context.configFile)
|
|
3847
4819
|
],
|
|
3848
4820
|
droppedPages,
|
|
3849
4821
|
graph,
|
|
3850
4822
|
manifest,
|
|
3851
4823
|
mode,
|
|
3852
4824
|
sources,
|
|
3853
|
-
themeFontsConfigured: configResult.themeFontsConfigured
|
|
4825
|
+
themeFontsConfigured: configResult.themeFontsConfigured,
|
|
4826
|
+
unparsable
|
|
3854
4827
|
};
|
|
3855
4828
|
};
|
|
3856
4829
|
|
|
3857
|
-
export { directiveSource, validateSearchPopularIcons, validateNavTargets,
|
|
4830
|
+
export { directiveSource, normalizeFence, QUOTED_ATTR, metaTokens, RESERVED_META_KEYWORDS, isForeignKeyword, isLineRange, validateSearchPopularIcons, validateNavTargets, gitEnv, realPath, gitRepositoryRoot, locateMdxFailure, findFolderMetaFiles, isWithin, scanProject };
|
|
3858
4831
|
|
|
3859
|
-
//# debugId=
|
|
3860
|
-
//# sourceMappingURL=chunk-
|
|
4832
|
+
//# debugId=358E42903E85F0E564756E2164756E21
|
|
4833
|
+
//# sourceMappingURL=chunk-8f8xw4x2.js.map
|