blume 2.1.3 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +134 -0
- package/README.md +4 -4
- package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
- package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
- package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
- package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
- package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
- package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
- package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
- package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
- package/dist/cli/chunk-487b95qy.js +329 -0
- package/dist/cli/chunk-487b95qy.js.map +13 -0
- package/dist/cli/chunk-4zxh25hc.js +157 -0
- package/dist/cli/chunk-4zxh25hc.js.map +10 -0
- package/dist/cli/chunk-69rjf786.js +146 -0
- package/dist/cli/chunk-69rjf786.js.map +11 -0
- package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
- package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
- package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
- package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
- package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
- package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
- package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
- package/dist/cli/chunk-b680sv30.js.map +13 -0
- package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
- package/dist/cli/chunk-beb0z983.js.map +10 -0
- package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
- package/dist/cli/chunk-e1n41w9m.js +155 -0
- package/dist/cli/chunk-e1n41w9m.js.map +11 -0
- package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
- package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
- package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
- package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
- package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
- package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
- package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
- package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
- package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
- package/dist/cli/chunk-hktq4j5r.js.map +36 -0
- package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
- package/dist/cli/chunk-hn3g2b89.js.map +10 -0
- package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
- package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
- package/dist/cli/chunk-k45sq16q.js.map +18 -0
- package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
- package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
- package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
- package/dist/cli/chunk-nqna8dzp.js.map +10 -0
- package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
- package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
- package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
- package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
- package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
- package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
- package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
- package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
- package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
- package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
- package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
- package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
- package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
- package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
- package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
- package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
- package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
- package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
- package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
- package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
- package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
- package/dist/cli/index.js +78 -42
- package/dist/cli/index.js.map +7 -6
- package/dist/types/ai/changelog-components.d.ts +18 -0
- package/dist/types/ai/changelog-markdown.d.ts +12 -0
- package/dist/types/ai/openapi-components.d.ts +7 -7
- package/dist/types/ai/operation-details.d.ts +39 -0
- package/dist/types/ai/relative-links.d.ts +3 -1
- package/dist/types/ai/serializers.d.ts +6 -4
- package/dist/types/analytics/head.d.ts +4 -2
- package/dist/types/analytics/script.d.ts +8 -2
- package/dist/types/components/content/youtube.d.ts +20 -0
- package/dist/types/components/layout/nav-utils.d.ts +8 -0
- package/dist/types/components/openapi/example-panels.d.ts +33 -0
- package/dist/types/components/openapi/helpers.d.ts +53 -11
- package/dist/types/components/openapi/snippets.d.ts +7 -1
- package/dist/types/core/alert-diagnostics.d.ts +10 -0
- package/dist/types/core/builtin-tags.d.ts +16 -0
- package/dist/types/core/changelog-list.d.ts +48 -0
- package/dist/types/core/closest-match.d.ts +6 -0
- package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
- package/dist/types/core/code-fences.d.ts +8 -1
- package/dist/types/core/config-input.d.ts +38 -6
- package/dist/types/core/content-assets.d.ts +16 -18
- package/dist/types/core/data.d.ts +6 -2
- package/dist/types/core/diagnostics.d.ts +14 -0
- package/dist/types/core/directive-diagnostics.d.ts +22 -8
- package/dist/types/core/former-routes.d.ts +14 -0
- package/dist/types/core/github.d.ts +5 -4
- package/dist/types/core/heading-markers.d.ts +65 -2
- package/dist/types/core/hidden-pages.d.ts +3 -0
- package/dist/types/core/includes.d.ts +23 -0
- package/dist/types/core/last-modified.d.ts +18 -6
- package/dist/types/core/links.d.ts +42 -2
- package/dist/types/core/load-module.d.ts +19 -4
- package/dist/types/core/manifest.d.ts +14 -1
- package/dist/types/core/markdown-mirrors.d.ts +22 -0
- package/dist/types/core/mcp-clients.d.ts +47 -0
- package/dist/types/core/mdx-elements.d.ts +32 -0
- package/dist/types/core/mdx-syntax.d.ts +100 -0
- package/dist/types/core/meta-diagnostics.d.ts +7 -0
- package/dist/types/core/meta.d.ts +32 -5
- package/dist/types/core/nav-diagnostics.d.ts +16 -2
- package/dist/types/core/navigation.d.ts +13 -0
- package/dist/types/core/ordering-prefix.d.ts +6 -0
- package/dist/types/core/pinned-ids.d.ts +23 -0
- package/dist/types/core/project-graph.d.ts +9 -1
- package/dist/types/core/redirect-patterns.d.ts +18 -0
- package/dist/types/core/relative-files.d.ts +31 -0
- package/dist/types/core/schema.d.ts +322 -11
- package/dist/types/core/sources/normalize.d.ts +101 -9
- package/dist/types/core/sources/types.d.ts +16 -0
- package/dist/types/core/static-files.d.ts +10 -0
- package/dist/types/core/static-host.d.ts +42 -0
- package/dist/types/core/svg-images.d.ts +26 -0
- package/dist/types/core/syntax-diagnostics.d.ts +14 -0
- package/dist/types/core/types.d.ts +42 -1
- package/dist/types/deploy/generated-files.d.ts +41 -0
- package/dist/types/deploy/redirects.d.ts +21 -7
- package/dist/types/markdown/code-title.d.ts +9 -5
- package/dist/types/markdown/directives.d.ts +8 -0
- package/dist/types/markdown/fence-language.d.ts +51 -0
- package/dist/types/markdown/fence-meta.d.ts +5 -0
- package/dist/types/markdown/github-alerts.d.ts +35 -0
- package/dist/types/openapi/asyncapi.d.ts +6 -5
- package/dist/types/openapi/checks.d.ts +10 -5
- package/dist/types/openapi/model.d.ts +55 -8
- package/dist/types/openapi/parse.d.ts +4 -1
- package/dist/types/ratelimit/index.d.ts +2 -0
- package/dist/types/ratelimit/memory.d.ts +1 -1
- package/dist/types/ratelimit/schema.d.ts +40 -0
- package/dist/types/ratelimit/unkey.d.ts +48 -0
- package/dist/types/sources/filesystem.d.ts +14 -4
- package/dist/types/sources/mdx-remote.d.ts +6 -3
- package/dist/types/sources/registry.d.ts +3 -3
- package/dist/types/theme/color.d.ts +33 -0
- package/dist/types/theme/palette.d.ts +122 -0
- package/docs/01-quickstart.mdx +1 -1
- package/docs/02-deployment.mdx +24 -8
- package/docs/04-migrating.mdx +16 -4
- package/docs/advanced/changelog.mdx +27 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +1 -1
- package/docs/cli/index.mdx +4 -2
- package/docs/cli/validate.mdx +10 -7
- package/docs/configuration/analytics.mdx +2 -0
- package/docs/configuration/customization.mdx +3 -1
- package/docs/configuration/index.mdx +7 -6
- package/docs/configuration/rate-limiting.mdx +20 -1
- package/docs/configuration/theming.mdx +5 -1
- package/docs/content/components.mdx +26 -6
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/includes.mdx +6 -2
- package/docs/content/index.mdx +11 -1
- package/docs/content/islands.mdx +2 -0
- package/docs/content/meta.mdx +9 -3
- package/docs/content/navigation.mdx +14 -6
- package/docs/content/sources/obsidian.mdx +1 -1
- package/docs/content/sources/remote-mdx.mdx +3 -1
- package/docs/content/syntax.mdx +76 -8
- package/docs/content/variables.mdx +3 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/json-api.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/mcp.mdx +40 -8
- package/docs/discoverability/open-graph.mdx +1 -1
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/graphql.mdx +2 -0
- package/docs/references/openapi.mdx +45 -7
- package/package.json +4 -4
- package/skills/blume/SKILL.md +2 -2
- package/skills/blume-migrate/SKILL.md +51 -29
- package/skills/blume-migrate/references/docsify.md +262 -0
- package/skills/blume-migrate/references/docus.md +298 -0
- package/skills/blume-migrate/references/docusaurus.md +5 -5
- package/skills/blume-migrate/references/fern.md +327 -0
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/gitbook.md +267 -0
- package/skills/blume-migrate/references/github-wiki.md +259 -0
- package/skills/blume-migrate/references/jekyll.md +253 -0
- package/skills/blume-migrate/references/mdbook.md +256 -0
- package/skills/blume-migrate/references/mintlify.md +5 -5
- package/skills/blume-migrate/references/mkdocs.md +236 -0
- package/skills/blume-migrate/references/monorepo.md +20 -8
- package/skills/blume-migrate/references/nextra.md +5 -5
- package/skills/blume-migrate/references/readme.md +257 -0
- package/skills/blume-migrate/references/redocly.md +198 -0
- package/skills/blume-migrate/references/starlight.md +3 -3
- package/skills/blume-migrate/references/vitepress.md +204 -0
- package/skills/blume-migrate/references/vuepress.md +271 -0
- package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
- package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
- package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
- package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
- package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
- package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
- package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
- package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
- package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
- package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
- package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
- package/src/ai/changelog-components.ts +24 -0
- package/src/ai/changelog-markdown.ts +45 -41
- package/src/ai/component-markdown.ts +22 -9
- package/src/ai/llms.ts +36 -5
- package/src/ai/markdown.ts +2 -2
- package/src/ai/openapi-components.ts +25 -9
- package/src/ai/operation-details.ts +208 -0
- package/src/ai/relative-links.ts +48 -8
- package/src/ai/serializers.ts +7 -6
- package/src/ai/site-skill.ts +6 -2
- package/src/analytics/head.ts +11 -5
- package/src/analytics/script.ts +13 -3
- package/src/astro/generate.ts +47 -11
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +27 -1
- package/src/astro/mdx-source-errors.ts +46 -0
- package/src/astro/templates.ts +93 -189
- package/src/audit/checks/redirects.ts +5 -1
- package/src/audit/redirects.ts +36 -19
- package/src/audit/report.ts +22 -9
- package/src/audit/run.ts +12 -28
- package/src/audit/terms.ts +23 -0
- package/src/audit/url.ts +2 -2
- package/src/cli/build-failure.ts +256 -7
- package/src/cli/command-meta.ts +1 -1
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +12 -3
- package/src/cli/commands/check.ts +13 -2
- package/src/cli/commands/dev.ts +1 -1
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/init.ts +3 -3
- package/src/cli/commands/preview.ts +17 -1
- package/src/cli/commands/validate.ts +22 -10
- package/src/cli/prepare.ts +1 -1
- package/src/cli/preview-pages.ts +161 -0
- package/src/cli/required-secrets.ts +0 -1
- package/src/cli/unknown-flags.ts +1 -42
- package/src/components/code-copy.ts +51 -0
- package/src/components/code-language.ts +75 -0
- package/src/components/content/AccordionItem.astro +16 -1
- package/src/components/content/Callout.astro +11 -1
- package/src/components/content/Changelog.astro +189 -0
- package/src/components/content/CodeBlock.astro +8 -11
- package/src/components/content/Tabs.astro +16 -2
- package/src/components/content/YouTube.astro +6 -4
- package/src/components/content/code-block.ts +33 -0
- package/src/components/content/content-strings.ts +5 -1
- package/src/components/content/inline-markdown.ts +35 -8
- package/src/components/content/youtube.ts +49 -7
- package/src/components/layout/Analytics.astro +5 -1
- package/src/components/layout/NavTree.astro +8 -3
- package/src/components/layout/PageActions.astro +81 -23
- package/src/components/layout/RootLayout.astro +35 -32
- package/src/components/layout/SiteFooter.astro +31 -8
- package/src/components/layout/nav-utils.ts +14 -0
- package/src/components/layout/search/pagefind.ts +2 -1
- package/src/components/openapi/Operation.astro +4 -0
- package/src/components/openapi/PanelTabs.astro +9 -3
- package/src/components/openapi/RequestBody.astro +42 -8
- package/src/components/openapi/RequestPanel.astro +34 -53
- package/src/components/openapi/example-panels.ts +110 -0
- package/src/components/openapi/helpers.ts +195 -20
- package/src/components/openapi/operation-model.ts +63 -26
- package/src/components/openapi/request.ts +13 -0
- package/src/components/openapi/snippets.ts +14 -2
- package/src/core/alert-diagnostics.ts +89 -0
- package/src/core/builtin-tags.ts +76 -0
- package/src/core/changelog-list.ts +87 -0
- package/src/core/closest-match.ts +41 -0
- package/src/core/code-fence-diagnostics.ts +267 -0
- package/src/core/code-fences.ts +43 -7
- package/src/core/component-diagnostics.ts +65 -12
- package/src/core/config-input.ts +39 -5
- package/src/core/config.ts +46 -1
- package/src/core/content-assets.ts +64 -144
- package/src/core/data.ts +10 -3
- package/src/core/diagnostics.ts +36 -3
- package/src/core/directive-diagnostics.ts +265 -47
- package/src/core/former-routes.ts +46 -0
- package/src/core/git-ignored.ts +53 -0
- package/src/core/github.ts +5 -4
- package/src/core/heading-markers.ts +156 -2
- package/src/core/hidden-pages.ts +64 -0
- package/src/core/includes.ts +181 -41
- package/src/core/last-modified.ts +47 -10
- package/src/core/links.ts +438 -83
- package/src/core/load-module.ts +98 -9
- package/src/core/manifest.ts +12 -9
- package/src/core/markdown-mirrors.ts +52 -0
- package/src/core/mcp-clients.ts +87 -0
- package/src/core/mdx-elements.ts +153 -0
- package/src/core/mdx-syntax.ts +377 -0
- package/src/core/meta-diagnostics.ts +134 -0
- package/src/core/meta.ts +154 -55
- package/src/core/nav-diagnostics.ts +41 -4
- package/src/core/navigation.ts +70 -10
- package/src/core/ordering-prefix.ts +22 -2
- package/src/core/pinned-ids.ts +141 -0
- package/src/core/project-graph.ts +221 -29
- package/src/core/redirect-patterns.ts +41 -0
- package/src/core/relative-files.ts +145 -0
- package/src/core/schema.ts +110 -6
- package/src/core/sources/mdx-remote.ts +75 -14
- package/src/core/sources/normalize.ts +885 -179
- package/src/core/sources/notion.ts +5 -4
- package/src/core/sources/obsidian.ts +2 -5
- package/src/core/sources/types.ts +17 -0
- package/src/core/static-files.ts +40 -0
- package/src/core/static-host.ts +125 -0
- package/src/core/svg-images.ts +98 -0
- package/src/core/syntax-diagnostics.ts +491 -0
- package/src/core/types.ts +43 -1
- package/src/deploy/artifacts.ts +2 -28
- package/src/deploy/generated-files.ts +128 -0
- package/src/deploy/platforms/vercel.ts +25 -1
- package/src/deploy/redirects.ts +43 -16
- package/src/deploy/sitemap.ts +2 -1
- package/src/markdown/base-links.ts +87 -6
- package/src/markdown/code-title.ts +29 -13
- package/src/markdown/content-assets.ts +210 -0
- package/src/markdown/directives.ts +69 -5
- package/src/markdown/fence-language.ts +106 -0
- package/src/markdown/fence-meta.ts +42 -0
- package/src/markdown/github-alerts.ts +172 -0
- package/src/markdown/heading-anchors.ts +265 -56
- package/src/markdown/include.ts +30 -3
- package/src/markdown/index.ts +145 -40
- package/src/markdown/prompt-text.ts +95 -0
- package/src/markdown/relative-links.ts +37 -2
- package/src/markdown/route-snapshot.ts +22 -12
- package/src/markdown/svg-fallback.ts +51 -0
- package/src/markdown/title-heading.ts +134 -0
- package/src/migrate/migrate.ts +127 -2
- package/src/og/card.ts +14 -4
- package/src/openapi/asyncapi.ts +14 -6
- package/src/openapi/checks.ts +160 -15
- package/src/openapi/model.ts +172 -33
- package/src/openapi/parse.ts +13 -4
- package/src/openapi/render-mdx.ts +55 -9
- package/src/openapi/source.ts +87 -6
- package/src/ratelimit/index.ts +2 -0
- package/src/ratelimit/memory.ts +1 -1
- package/src/ratelimit/runtime.ts +81 -0
- package/src/ratelimit/schema.ts +2 -0
- package/src/ratelimit/unkey.ts +61 -0
- package/src/registry/eject.ts +0 -1
- package/src/registry/registry.ts +1 -1
- package/src/search/documents.ts +10 -2
- package/src/sources/filesystem.ts +28 -3
- package/src/sources/mdx-remote.ts +7 -15
- package/src/theme/color.ts +14 -0
- package/src/theme/entry.ts +22 -1
- package/src/theme/palette.ts +74 -1
- package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
- package/dist/cli/chunk-0w6hs479.js.map +0 -10
- package/dist/cli/chunk-150jk7km.js +0 -52
- package/dist/cli/chunk-150jk7km.js.map +0 -10
- package/dist/cli/chunk-36bxs9c7.js.map +0 -10
- package/dist/cli/chunk-53jqw8z0.js +0 -89
- package/dist/cli/chunk-53jqw8z0.js.map +0 -10
- package/dist/cli/chunk-60hz265r.js.map +0 -10
- package/dist/cli/chunk-desvm72z.js.map +0 -14
- package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
- package/dist/cli/chunk-es8h0v3j.js +0 -441
- package/dist/cli/chunk-es8h0v3j.js.map +0 -11
- package/dist/cli/chunk-kdpzw38a.js.map +0 -11
- package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
- package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
- package/dist/cli/chunk-twy7psdz.js.map +0 -10
- package/dist/cli/chunk-ydp3065p.js +0 -64
- package/dist/cli/chunk-ydp3065p.js.map +0 -10
- /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
- /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
- /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
- /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
- /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
- /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
- /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
- /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
- /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
- /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
- /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
|
@@ -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,22 +14,28 @@ 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,
|
|
24
30
|
buildNavigation,
|
|
25
31
|
PUBLIC_API_URL,
|
|
32
|
+
isBuiltInMcpClient,
|
|
26
33
|
isPatternPath,
|
|
27
34
|
pathsUnderPattern,
|
|
28
35
|
hasVariables,
|
|
29
36
|
substituteVariables,
|
|
30
37
|
undefinedVariables,
|
|
31
|
-
|
|
38
|
+
BUILT_IN_PAGE_META_KEYS,
|
|
32
39
|
folderMetaSchema,
|
|
33
40
|
versionizeRoute,
|
|
34
41
|
versionsDiagnostics,
|
|
@@ -36,6 +43,10 @@ import {
|
|
|
36
43
|
slugifyPath,
|
|
37
44
|
unloadablePathDiagnostic,
|
|
38
45
|
frontmatterYamlDiagnostic,
|
|
46
|
+
ATTRIBUTE,
|
|
47
|
+
trailingCurlyMarker,
|
|
48
|
+
HTML_COMMENT,
|
|
49
|
+
INLINE_CODE,
|
|
39
50
|
extractHeadings,
|
|
40
51
|
strippedLineOffset,
|
|
41
52
|
isStringValue,
|
|
@@ -45,7 +56,8 @@ import {
|
|
|
45
56
|
normalizeEntry,
|
|
46
57
|
hasIncludeStatements,
|
|
47
58
|
expandIncludes,
|
|
48
|
-
|
|
59
|
+
youtubeEmbedSrc,
|
|
60
|
+
buildManifest,
|
|
49
61
|
nodeRequire,
|
|
50
62
|
escapeMarkdownText,
|
|
51
63
|
guardBlockStart,
|
|
@@ -74,21 +86,24 @@ import {
|
|
|
74
86
|
asObject,
|
|
75
87
|
asNumber,
|
|
76
88
|
objectsIn,
|
|
89
|
+
mirroredPage,
|
|
77
90
|
createModuleLoader,
|
|
78
91
|
resolveProjectContext,
|
|
79
92
|
loadConfig
|
|
80
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-r5nm9w0c.js";
|
|
81
94
|
import {
|
|
82
|
-
getBlumeVersion,
|
|
83
95
|
BlumeError,
|
|
84
|
-
|
|
96
|
+
locatePath,
|
|
97
|
+
diagnosticsFromZod,
|
|
98
|
+
closestMatch
|
|
85
99
|
} from "./index.js";
|
|
86
100
|
import {
|
|
87
101
|
trimChar,
|
|
88
102
|
normalizePath,
|
|
89
103
|
isInternalPath,
|
|
90
104
|
withBasePath,
|
|
91
|
-
mountBasePath
|
|
105
|
+
mountBasePath,
|
|
106
|
+
stripBasePath
|
|
92
107
|
} from "./chunk-zccbjcj0.js";
|
|
93
108
|
import {
|
|
94
109
|
BLUME_IGNORE_DIRS,
|
|
@@ -194,10 +209,22 @@ var validateSearchPopularIcons = (popular) => {
|
|
|
194
209
|
const icons = popular.flatMap((link) => link.icon ? [{ icon: link.icon, where: `popular link "${link.label}"` }] : []);
|
|
195
210
|
return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
|
|
196
211
|
};
|
|
212
|
+
var validateMcpClientIcons = (clients, defaultLocale) => {
|
|
213
|
+
const icons = clients.flatMap((client) => !isBuiltInMcpClient(client) && client.icon ? [
|
|
214
|
+
{
|
|
215
|
+
icon: client.icon,
|
|
216
|
+
where: `MCP client "${resolveLocalizable(client.label, defaultLocale, defaultLocale)}"`
|
|
217
|
+
}
|
|
218
|
+
] : []);
|
|
219
|
+
return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
|
|
220
|
+
};
|
|
197
221
|
var resolvesToPages = (routes, path) => routes.has(path) || [...routes].some((route) => route.startsWith(`${path}/`));
|
|
198
|
-
var validateNavTargets = (navigation, routes) => {
|
|
222
|
+
var validateNavTargets = (navigation, routes, servesFile = () => false) => {
|
|
199
223
|
const targets = [
|
|
200
|
-
...navigation.tabs.map((tab) => ({
|
|
224
|
+
...navigation.tabs.map((tab) => ({
|
|
225
|
+
label: tab.label,
|
|
226
|
+
path: tab.href ?? tab.path
|
|
227
|
+
})),
|
|
201
228
|
...navigation.selectors.flatMap((selector) => selector.items.map((item) => ({ label: item.label, path: item.path }))),
|
|
202
229
|
...navigation.featured.map((link) => ({
|
|
203
230
|
label: link.label,
|
|
@@ -215,7 +242,8 @@ var validateNavTargets = (navigation, routes) => {
|
|
|
215
242
|
if (!isInternalPath(path) || path.startsWith("/#") || seen.has(path)) {
|
|
216
243
|
continue;
|
|
217
244
|
}
|
|
218
|
-
|
|
245
|
+
const bare = path.split(/[?#]/u)[0] ?? path;
|
|
246
|
+
if (!(resolvesToPages(routes, bare) || servesFile(bare))) {
|
|
219
247
|
seen.add(path);
|
|
220
248
|
diagnostics.push({
|
|
221
249
|
code: "BLUME_NAV_MISSING_PAGE",
|
|
@@ -324,133 +352,12 @@ var validateNavStructure = (navigation, pages) => [
|
|
|
324
352
|
...hiddenInSidebarDiagnostics(navigation, pages)
|
|
325
353
|
];
|
|
326
354
|
|
|
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
355
|
// src/core/project-graph.ts
|
|
450
|
-
import {
|
|
356
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
357
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative5 } from "pathe";
|
|
451
358
|
|
|
452
|
-
// src/core/
|
|
453
|
-
import {
|
|
359
|
+
// src/core/alert-diagnostics.ts
|
|
360
|
+
import { markdownToMdast } from "satteri";
|
|
454
361
|
|
|
455
362
|
// src/markdown/directives.ts
|
|
456
363
|
import { toString as mdastToString } from "mdast-util-to-string";
|
|
@@ -498,25 +405,366 @@ var directiveSource = (node, marker, source) => {
|
|
|
498
405
|
return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
|
|
499
406
|
};
|
|
500
407
|
|
|
408
|
+
// src/markdown/github-alerts.ts
|
|
409
|
+
var ALERT_CALLOUTS = new Map([
|
|
410
|
+
["caution", "danger"],
|
|
411
|
+
["important", "note"],
|
|
412
|
+
["note", "note"],
|
|
413
|
+
["tip", "tip"],
|
|
414
|
+
["warning", "warning"]
|
|
415
|
+
]);
|
|
416
|
+
var ALERT_MARKER = /^\[!(?<kind>[a-z]+)\]/iu;
|
|
417
|
+
var alertMarkerOf = (text) => {
|
|
418
|
+
const match = ALERT_MARKER.exec(text);
|
|
419
|
+
const callout = ALERT_CALLOUTS.get(match?.groups?.kind?.toLowerCase() ?? "");
|
|
420
|
+
return match && callout ? { callout, marker: match[0] } : undefined;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/core/alert-diagnostics.ts
|
|
424
|
+
var ALERT_LINE = /^[\t >]*>[\t ]*\[![a-z]+\]/imu;
|
|
425
|
+
var alertFindings = (text) => {
|
|
426
|
+
if (!ALERT_LINE.test(text)) {
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
const tree = markdownToMdast(text, {
|
|
430
|
+
features: MARKDOWN_BODY_FEATURES
|
|
431
|
+
});
|
|
432
|
+
const found = [];
|
|
433
|
+
const walk = (node) => {
|
|
434
|
+
if (node.type === "blockquote") {
|
|
435
|
+
const [paragraph] = node.children;
|
|
436
|
+
const first = paragraph?.type === "paragraph" ? paragraph.children[0] : undefined;
|
|
437
|
+
const escaped = text[first?.position?.start.offset ?? 0] === "\\";
|
|
438
|
+
const alert = first?.type === "text" && !escaped ? alertMarkerOf(first.value) : undefined;
|
|
439
|
+
if (alert !== undefined) {
|
|
440
|
+
found.push({ ...alert, line: node.position?.start.line ?? 1 });
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if ("children" in node) {
|
|
445
|
+
for (const child of node.children) {
|
|
446
|
+
walk(child);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
walk(tree);
|
|
451
|
+
return found;
|
|
452
|
+
};
|
|
453
|
+
var alertDiagnostics = (entry, sourceName) => {
|
|
454
|
+
if (entry.body.format !== "md") {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
458
|
+
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
459
|
+
return alertFindings(entry.expanded?.text ?? entry.body.text).map(({ callout, line, marker }) => {
|
|
460
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
461
|
+
return {
|
|
462
|
+
code: "BLUME_MD_GITHUB_ALERT",
|
|
463
|
+
file: origin?.file ?? page,
|
|
464
|
+
line: origin?.line ?? line + offset,
|
|
465
|
+
message: `\`${marker}\` makes a quote a callout only in .mdx; in this .md page it renders as a quote that shows \`${marker}\` as text.`,
|
|
466
|
+
severity: "warning",
|
|
467
|
+
suggestion: `Rename the page to .mdx to render it as a \`${callout}\` callout, or remove \`${marker}\` to keep a plain quote.`
|
|
468
|
+
};
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// src/core/code-fence-diagnostics.ts
|
|
473
|
+
import { markdownToMdast as markdownToMdast2, mdxToMdast } from "satteri";
|
|
474
|
+
|
|
475
|
+
// src/markdown/fence-language.ts
|
|
476
|
+
import { bundledLanguages } from "shiki/langs";
|
|
477
|
+
var PLAIN_LANGUAGES = new Set([
|
|
478
|
+
"ansi",
|
|
479
|
+
"plain",
|
|
480
|
+
"plaintext",
|
|
481
|
+
"text",
|
|
482
|
+
"txt"
|
|
483
|
+
]);
|
|
484
|
+
var isKnownLanguage = (lang) => PLAIN_LANGUAGES.has(lang) || Object.hasOwn(bundledLanguages, lang);
|
|
485
|
+
var GLUED_RANGE = /^(?<lang>[^{]+)(?<range>\{[^}]*\})$/u;
|
|
486
|
+
var GLUED_TITLE_RANGE = /(?<=^|\s)(?<title>\[[^\]]*\])(?<range>\{[^}]*\})(?=\s|$)/u;
|
|
487
|
+
var normalizeFence = (lang, meta) => {
|
|
488
|
+
if (!lang) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
const glued = GLUED_RANGE.exec(lang)?.groups;
|
|
492
|
+
const id = glued?.lang ?? lang;
|
|
493
|
+
const fenceMeta = (glued?.range ? [glued.range, meta].filter(Boolean).join(" ") : meta)?.replace(GLUED_TITLE_RANGE, "$<title> $<range>") ?? null;
|
|
494
|
+
const lower = id.toLowerCase();
|
|
495
|
+
const language = !isKnownLanguage(id) && isKnownLanguage(lower) ? lower : id;
|
|
496
|
+
return language === lang && fenceMeta === (meta ?? null) ? null : { lang: language, meta: fenceMeta };
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// src/markdown/fence-meta.ts
|
|
500
|
+
var QUOTED_ATTR = /[\w-]+=(?:"[^"]*"|'[^']*')/gu;
|
|
501
|
+
var LINE_RANGE = /\{[^}]*\}/u;
|
|
502
|
+
var META_TOKEN = new RegExp(`${QUOTED_ATTR.source}|${LINE_RANGE.source}|\\S+`, "gu");
|
|
503
|
+
var metaTokens = (meta) => meta?.match(META_TOKEN) ?? [];
|
|
504
|
+
var RESERVED_META_KEYWORDS = new Set([
|
|
505
|
+
"expandable",
|
|
506
|
+
"lineNumbers",
|
|
507
|
+
"ts2js",
|
|
508
|
+
"twoslash",
|
|
509
|
+
"wrap"
|
|
510
|
+
]);
|
|
511
|
+
var FOREIGN_META_KEYWORDS = new Set([
|
|
512
|
+
"lines",
|
|
513
|
+
"wordWrap"
|
|
514
|
+
]);
|
|
515
|
+
var SHOW_LINE_NUMBERS = /^showLineNumbers(?:\{\d*\})?$/u;
|
|
516
|
+
var RUST_META_KEYWORDS = new Set([
|
|
517
|
+
"compile_fail",
|
|
518
|
+
"edition2015",
|
|
519
|
+
"edition2018",
|
|
520
|
+
"edition2021",
|
|
521
|
+
"edition2024",
|
|
522
|
+
"editable",
|
|
523
|
+
"ignore",
|
|
524
|
+
"no_run",
|
|
525
|
+
"noplayground",
|
|
526
|
+
"should_panic"
|
|
527
|
+
]);
|
|
528
|
+
var RUST_LANGUAGES = new Set(["rs", "rust"]);
|
|
529
|
+
var isForeignKeyword = (token, lang) => FOREIGN_META_KEYWORDS.has(token) || SHOW_LINE_NUMBERS.test(token) || RUST_LANGUAGES.has(lang ?? "") && RUST_META_KEYWORDS.has(token);
|
|
530
|
+
var isLineRange = (token) => token.startsWith("{");
|
|
531
|
+
|
|
532
|
+
// src/core/code-fence-diagnostics.ts
|
|
533
|
+
var BLUME_LANGUAGES = new Set([
|
|
534
|
+
"math",
|
|
535
|
+
"package-install"
|
|
536
|
+
]);
|
|
537
|
+
var lineRange = (value) => `{${value.trim().split(/[\s,]+/u).join(",")}}`;
|
|
538
|
+
var FOREIGN_OPTIONS = [
|
|
539
|
+
{
|
|
540
|
+
effect: "the block highlights no lines",
|
|
541
|
+
instead: (value) => `Put the lines in braces after the language instead: \`${lineRange(value)}\`.`,
|
|
542
|
+
pattern: /^hl_lines=["'](?<value>[^"']*)["']$/u
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
effect: "the block shows no line numbers",
|
|
546
|
+
instead: (value) => `Write \`lineNumbers\` after the language instead.${value.trim() === "1" ? "" : " Blume numbers every block from 1."}`,
|
|
547
|
+
pattern: /^linenums=["'](?<value>[^"']*)["']$/u
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
effect: "the block shows no line numbers",
|
|
551
|
+
instead: () => "Write `lineNumbers` after the language instead.",
|
|
552
|
+
pattern: /^showLineNumbers(?:\{\d*\})?$/u
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
effect: "the block shows no line numbers",
|
|
556
|
+
instead: () => 'Write `lineNumbers` after the language instead. To keep the word in the block\'s title, set the title with `title="…"`.',
|
|
557
|
+
pattern: /^lines$/u
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
effect: "the block doesn't wrap its long lines",
|
|
561
|
+
instead: () => "Write `wrap` after the language instead.",
|
|
562
|
+
pattern: /^wordWrap$/u
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
effect: "the block has no title",
|
|
566
|
+
instead: (value) => `Write \`title="${value}"\` instead.`,
|
|
567
|
+
pattern: /^filename=["'](?<value>[^"']*)["']$/u
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
effect: "it does nothing: Blume shows a code block as static text, and never compiles, tests, or runs it",
|
|
571
|
+
instead: () => "Remove it.",
|
|
572
|
+
languages: RUST_LANGUAGES,
|
|
573
|
+
pattern: new RegExp(`^(?:${[...RUST_META_KEYWORDS].join("|")})$`, "u")
|
|
574
|
+
}
|
|
575
|
+
];
|
|
576
|
+
var foreignOption = (token, lang) => {
|
|
577
|
+
for (const option of FOREIGN_OPTIONS) {
|
|
578
|
+
const match = option.pattern.exec(token);
|
|
579
|
+
if (match && (option.languages?.has(lang) ?? true)) {
|
|
580
|
+
return { option, value: match.groups?.value ?? "" };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
};
|
|
585
|
+
var rendersLanguage = (lang) => isKnownLanguage(lang) || BLUME_LANGUAGES.has(lang);
|
|
586
|
+
var fenceParts = (lang, meta) => {
|
|
587
|
+
const fence = normalizeFence(lang, meta) ?? {
|
|
588
|
+
lang: lang ?? "",
|
|
589
|
+
meta: meta ?? null
|
|
590
|
+
};
|
|
591
|
+
return { lang: fence.lang, meta: fence.meta, tokens: metaTokens(fence.meta) };
|
|
592
|
+
};
|
|
593
|
+
var unknownLanguage = (node, line) => {
|
|
594
|
+
const { lang } = fenceParts(node.lang, node.meta);
|
|
595
|
+
if (!lang || rendersLanguage(lang)) {
|
|
596
|
+
return [];
|
|
597
|
+
}
|
|
598
|
+
return [
|
|
599
|
+
{
|
|
600
|
+
code: "BLUME_UNKNOWN_CODE_LANGUAGE",
|
|
601
|
+
line,
|
|
602
|
+
message: `\`${lang}\` isn't a language Blume can highlight, so the code block renders as plain text.`,
|
|
603
|
+
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"`.'
|
|
604
|
+
}
|
|
605
|
+
];
|
|
606
|
+
};
|
|
607
|
+
var foreignOptions = (node, line) => {
|
|
608
|
+
const { lang, tokens } = fenceParts(node.lang, node.meta);
|
|
609
|
+
return [node.lang ?? "", ...tokens].flatMap((token) => {
|
|
610
|
+
const found = foreignOption(token, lang);
|
|
611
|
+
if (!found) {
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
return [
|
|
615
|
+
{
|
|
616
|
+
code: "BLUME_CODE_FENCE_OPTION",
|
|
617
|
+
line,
|
|
618
|
+
message: `\`${token}\` isn't a Blume code block option, so ${found.option.effect}.`,
|
|
619
|
+
suggestion: found.option.instead(found.value)
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
var FENCE_INFO = /(?:`{3,}|~{3,})[\t ]*(?<info>[^`\n]*)/gu;
|
|
625
|
+
var suspectInfo = (info) => {
|
|
626
|
+
const [lang, ...rest] = info.trim().split(/\s+/u);
|
|
627
|
+
if (!lang) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
const parts = fenceParts(lang, rest.join(" ") || null);
|
|
631
|
+
return !rendersLanguage(parts.lang) || [lang, ...parts.tokens].some((token) => foreignOption(token, parts.lang) !== null);
|
|
632
|
+
};
|
|
633
|
+
var parse = (text, format) => {
|
|
634
|
+
try {
|
|
635
|
+
return format === "mdx" ? mdxToMdast(text, { features: MDX_BODY_FEATURES }) : markdownToMdast2(text, { features: MARKDOWN_BODY_FEATURES });
|
|
636
|
+
} catch {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
var fenceFindings = (text, format) => {
|
|
641
|
+
const suspect = [...text.matchAll(FENCE_INFO)].some((match) => suspectInfo(match.groups?.info ?? ""));
|
|
642
|
+
const tree = suspect ? parse(text, format) : null;
|
|
643
|
+
if (!tree) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
const found = [];
|
|
647
|
+
const walk = (node) => {
|
|
648
|
+
if (node.type === "code") {
|
|
649
|
+
const line = node.position?.start.line ?? 1;
|
|
650
|
+
found.push(...unknownLanguage(node, line), ...foreignOptions(node, line));
|
|
651
|
+
}
|
|
652
|
+
if ("children" in node) {
|
|
653
|
+
for (const child of node.children) {
|
|
654
|
+
walk(child);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
walk(tree);
|
|
659
|
+
return found;
|
|
660
|
+
};
|
|
661
|
+
var codeFenceDiagnostics = (entry, sourceName) => {
|
|
662
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
663
|
+
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
664
|
+
return fenceFindings(entry.expanded?.text ?? entry.body.text, entry.body.format).map(({ line, ...finding }) => {
|
|
665
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
666
|
+
return {
|
|
667
|
+
...finding,
|
|
668
|
+
file: origin?.file ?? page,
|
|
669
|
+
line: origin?.line ?? line + offset,
|
|
670
|
+
severity: "warning"
|
|
671
|
+
};
|
|
672
|
+
});
|
|
673
|
+
};
|
|
674
|
+
|
|
501
675
|
// src/core/directive-diagnostics.ts
|
|
676
|
+
import { markdownToMdast as markdownToMdast3, mdxToMdast as mdxToMdast2 } from "satteri";
|
|
502
677
|
var CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
|
|
678
|
+
var FENCE_WITH_TEXT = /^[\t >]*:{3,}(?:[\t ]+\S|[^\s:a-z])/imu;
|
|
679
|
+
var CLOSING_LINE = /^[\t >]*(?<written>(?<fence>:{3,})[\t ]*(?<text>.*?))\s*$/u;
|
|
680
|
+
var OPENING_TEXT = /^[\t >]*(?<written>(?<fence>:{3,})(?<name>[a-z][\w-]*)(?<label>\[[^\]\n]*\])?(?<attributes>\{[^}\n]*\})?[\t ]+(?<text>\S.*?))\s*$/iu;
|
|
681
|
+
var OPENING_TEXT_LINE = /^[\t >]*:{3,}[a-z][\w-]*(?:\[[^\]\n]*\])?(?:\{[^}\n]*\})?[\t ]+\S/imu;
|
|
682
|
+
var SPACED_OPENING = /^[\t >]*(?<written>(?<fence>:{3,})[\t ]+(?<name>[a-z][\w-]*)(?<title>.*?))\s*$/iu;
|
|
683
|
+
var MD_OPENING = /^[\t >]*(?<written>:{3,}(?<name>[a-z][\w-]*))/iu;
|
|
684
|
+
var MD_OPENING_LINE = /^[\t >]*:{3,}[a-z]/imu;
|
|
503
685
|
var codeList = (names) => [...names].map((name) => `\`${name}\``).join(", ");
|
|
504
686
|
var CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
|
|
505
|
-
var
|
|
506
|
-
|
|
507
|
-
|
|
687
|
+
var unknownContainer = (node) => calloutTypeFor(node.name) === null ? [
|
|
688
|
+
{
|
|
689
|
+
code: "BLUME_UNKNOWN_DIRECTIVE",
|
|
690
|
+
line: node.position?.start.line ?? 1,
|
|
691
|
+
message: `\`:::${node.name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
|
|
692
|
+
suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
|
|
693
|
+
}
|
|
694
|
+
] : [];
|
|
695
|
+
var closingText = (node, lines) => {
|
|
696
|
+
const start = node.position?.start.line ?? 1;
|
|
697
|
+
const end = node.position?.end.line ?? start;
|
|
698
|
+
const content = node.children.at(-1)?.position?.end.line ?? start;
|
|
699
|
+
const groups = end > content ? CLOSING_LINE.exec(lines[end - 1] ?? "")?.groups : undefined;
|
|
700
|
+
if (!(groups?.text && groups.fence && groups.written)) {
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
const longer = ":".repeat(groups.fence.length + 1);
|
|
704
|
+
return [
|
|
705
|
+
{
|
|
706
|
+
code: "BLUME_DIRECTIVE_CLOSING_TEXT",
|
|
707
|
+
line: end,
|
|
708
|
+
message: `\`${groups.written}\` closes the \`:::${node.name}\` container above it, so \`${groups.text}\` never reaches the page.`,
|
|
709
|
+
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.`
|
|
710
|
+
}
|
|
711
|
+
];
|
|
712
|
+
};
|
|
713
|
+
var openingText = (node, lines) => {
|
|
714
|
+
const line = node.position?.start.line ?? 1;
|
|
715
|
+
const groups = OPENING_TEXT.exec(lines[line - 1] ?? "")?.groups;
|
|
716
|
+
if (!(groups?.fence && groups.name && groups.text && groups.written) || calloutTypeFor(node.name) === null) {
|
|
717
|
+
return [];
|
|
718
|
+
}
|
|
719
|
+
const opener = `${groups.fence}${groups.name}`;
|
|
720
|
+
return [
|
|
721
|
+
{
|
|
722
|
+
code: "BLUME_DIRECTIVE_OPENING_TEXT",
|
|
723
|
+
line,
|
|
724
|
+
message: `\`${groups.written}\` opens a \`${groups.name}\` callout, but the text after its name, \`${groups.text}\`, never reaches the page.`,
|
|
725
|
+
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}]\`.`
|
|
726
|
+
}
|
|
727
|
+
];
|
|
728
|
+
};
|
|
729
|
+
var spacedOpenings = (node, lines) => {
|
|
730
|
+
const start = node.position?.start.line ?? 1;
|
|
731
|
+
const end = node.position?.end.line ?? start;
|
|
732
|
+
return lines.slice(start - 1, end).flatMap((text, index) => {
|
|
733
|
+
const groups = SPACED_OPENING.exec(text)?.groups;
|
|
734
|
+
if (!(groups?.fence && groups.name && groups.written) || calloutTypeFor(groups.name) === null) {
|
|
735
|
+
return [];
|
|
736
|
+
}
|
|
737
|
+
const opener = `${groups.fence}${groups.name}`;
|
|
738
|
+
const title = groups.title?.trim();
|
|
739
|
+
return [
|
|
740
|
+
{
|
|
741
|
+
code: "BLUME_DIRECTIVE_SPACED_NAME",
|
|
742
|
+
line: start + index,
|
|
743
|
+
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.`,
|
|
744
|
+
suggestion: title ? `Remove the space and put the title in brackets: \`${opener}[${title}]\`.` : `Remove the space: \`${opener}\`.`
|
|
745
|
+
}
|
|
746
|
+
];
|
|
747
|
+
});
|
|
748
|
+
};
|
|
749
|
+
var directiveFindings = (text) => {
|
|
750
|
+
const suspect = FENCE_WITH_TEXT.test(text) || OPENING_TEXT_LINE.test(text) || [...text.matchAll(CONTAINER_OPENING)].some((match) => calloutTypeFor(match.groups?.name ?? "") === null);
|
|
751
|
+
if (!suspect) {
|
|
508
752
|
return [];
|
|
509
753
|
}
|
|
510
754
|
let tree;
|
|
511
755
|
try {
|
|
512
|
-
tree =
|
|
756
|
+
tree = mdxToMdast2(text, { features: MDX_BODY_FEATURES });
|
|
513
757
|
} catch {
|
|
514
758
|
return [];
|
|
515
759
|
}
|
|
760
|
+
const lines = text.split(`
|
|
761
|
+
`);
|
|
516
762
|
const found = [];
|
|
517
763
|
const walk = (node) => {
|
|
518
|
-
if (node.type === "containerDirective"
|
|
519
|
-
found.push(
|
|
764
|
+
if (node.type === "containerDirective") {
|
|
765
|
+
found.push(...unknownContainer(node), ...openingText(node, lines), ...closingText(node, lines));
|
|
766
|
+
} else if (node.type === "paragraph") {
|
|
767
|
+
found.push(...spacedOpenings(node, lines));
|
|
520
768
|
}
|
|
521
769
|
if ("children" in node) {
|
|
522
770
|
for (const child of node.children) {
|
|
@@ -525,27 +773,83 @@ var unknownContainers = (text) => {
|
|
|
525
773
|
}
|
|
526
774
|
};
|
|
527
775
|
walk(tree);
|
|
528
|
-
return found;
|
|
776
|
+
return found.toSorted((a, b) => a.line - b.line);
|
|
777
|
+
};
|
|
778
|
+
var mdOpenings = (node, lines) => {
|
|
779
|
+
const start = node.position?.start.line ?? 1;
|
|
780
|
+
const end = node.position?.end.line ?? start;
|
|
781
|
+
return lines.slice(start - 1, end).flatMap((text, index) => {
|
|
782
|
+
const groups = MD_OPENING.exec(text)?.groups;
|
|
783
|
+
if (!(groups?.name && groups.written)) {
|
|
784
|
+
return [];
|
|
785
|
+
}
|
|
786
|
+
const callout = calloutTypeFor(groups.name);
|
|
787
|
+
return [
|
|
788
|
+
{
|
|
789
|
+
code: "BLUME_MD_DIRECTIVE",
|
|
790
|
+
line: start + index,
|
|
791
|
+
message: `\`${groups.written}\` opens a directive, which Blume renders only in .mdx, so this .md page shows it and its closing \`:::\` as text.`,
|
|
792
|
+
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.`
|
|
793
|
+
}
|
|
794
|
+
];
|
|
795
|
+
});
|
|
529
796
|
};
|
|
530
|
-
var
|
|
531
|
-
if (
|
|
797
|
+
var mdDirectiveFindings = (text) => {
|
|
798
|
+
if (!MD_OPENING_LINE.test(text)) {
|
|
532
799
|
return [];
|
|
533
800
|
}
|
|
801
|
+
const tree = markdownToMdast3(text, {
|
|
802
|
+
features: MARKDOWN_BODY_FEATURES
|
|
803
|
+
});
|
|
804
|
+
const lines = text.split(`
|
|
805
|
+
`);
|
|
806
|
+
const found = [];
|
|
807
|
+
const walk = (node) => {
|
|
808
|
+
if (node.type === "paragraph") {
|
|
809
|
+
found.push(...mdOpenings(node, lines));
|
|
810
|
+
} else if ("children" in node) {
|
|
811
|
+
for (const child of node.children) {
|
|
812
|
+
walk(child);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
walk(tree);
|
|
817
|
+
return found;
|
|
818
|
+
};
|
|
819
|
+
var directiveDiagnostics = (entry, sourceName) => {
|
|
534
820
|
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
535
821
|
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
536
|
-
|
|
822
|
+
const text = entry.expanded?.text ?? entry.body.text;
|
|
823
|
+
const findings = entry.body.format === "mdx" ? directiveFindings(text) : mdDirectiveFindings(text);
|
|
824
|
+
return findings.map(({ line, ...finding }) => {
|
|
537
825
|
const origin = entry.expanded?.origins[line - 1];
|
|
538
826
|
return {
|
|
539
|
-
|
|
827
|
+
...finding,
|
|
540
828
|
file: origin?.file ?? page,
|
|
541
829
|
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.`
|
|
830
|
+
severity: "warning"
|
|
545
831
|
};
|
|
546
832
|
});
|
|
547
833
|
};
|
|
548
834
|
|
|
835
|
+
// src/core/former-routes.ts
|
|
836
|
+
var formerRouteRedirects = (pages, served, config) => {
|
|
837
|
+
const targets = new Map;
|
|
838
|
+
for (const page of pages) {
|
|
839
|
+
if (page.formerRoute !== undefined) {
|
|
840
|
+
targets.set(page.formerRoute, targets.has(page.formerRoute) ? undefined : page.route);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const authored = new Set(config.redirects.map((redirect) => normalizePath(withBasePath(config.basePath, redirect.from))));
|
|
844
|
+
return [...targets].flatMap(([from, to]) => to === undefined || served.has(from) || authored.has(from) ? [] : [
|
|
845
|
+
{
|
|
846
|
+
from: stripBasePath(config.basePath, from),
|
|
847
|
+
status: 301,
|
|
848
|
+
to: stripBasePath(config.basePath, to)
|
|
849
|
+
}
|
|
850
|
+
]);
|
|
851
|
+
};
|
|
852
|
+
|
|
549
853
|
// src/core/graph.ts
|
|
550
854
|
var collectRoutes = (pages) => {
|
|
551
855
|
const routes = new Map;
|
|
@@ -810,13 +1114,33 @@ var gitEnv = () => Object.fromEntries(Object.entries(process.env).filter(([key])
|
|
|
810
1114
|
var resolveLastModifiedConfig = (value) => value === false ? { enabled: false, source: "git" } : { enabled: true, source: value };
|
|
811
1115
|
var parseGitLog = (output) => {
|
|
812
1116
|
const times = new Map;
|
|
1117
|
+
const currentNames = new Map;
|
|
1118
|
+
const renamedAt = new Map;
|
|
813
1119
|
let current = null;
|
|
814
1120
|
for (const line of output.split(`
|
|
815
1121
|
`)) {
|
|
816
1122
|
if (line.startsWith("\x00")) {
|
|
817
1123
|
current = line.slice(1);
|
|
818
|
-
|
|
819
|
-
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
const [status = "", from = "", to] = line.split("\t");
|
|
1127
|
+
const path = to ?? from;
|
|
1128
|
+
if (!(current && path)) {
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
const name = currentNames.get(path) ?? path;
|
|
1132
|
+
if (status.startsWith("R")) {
|
|
1133
|
+
currentNames.set(from, name);
|
|
1134
|
+
if (!renamedAt.has(name)) {
|
|
1135
|
+
renamedAt.set(name, current);
|
|
1136
|
+
}
|
|
1137
|
+
} else if (!times.has(name)) {
|
|
1138
|
+
times.set(name, current);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
for (const [name, date] of renamedAt) {
|
|
1142
|
+
if (!times.has(name)) {
|
|
1143
|
+
times.set(name, date);
|
|
820
1144
|
}
|
|
821
1145
|
}
|
|
822
1146
|
return times;
|
|
@@ -852,7 +1176,8 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths, repositoryRoot) =>
|
|
|
852
1176
|
"core.quotePath=false",
|
|
853
1177
|
"log",
|
|
854
1178
|
"--format=%x00%cI",
|
|
855
|
-
"--name-
|
|
1179
|
+
"--name-status",
|
|
1180
|
+
"-M100%",
|
|
856
1181
|
"--",
|
|
857
1182
|
...contentRoots.map(realPath)
|
|
858
1183
|
], { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 });
|
|
@@ -894,6 +1219,245 @@ var lastModifiedShallowWarning = (root, undatedCount) => {
|
|
|
894
1219
|
];
|
|
895
1220
|
};
|
|
896
1221
|
|
|
1222
|
+
// src/core/mdx-syntax.ts
|
|
1223
|
+
import { readFile } from "node:fs/promises";
|
|
1224
|
+
import { createMdxMdastHandle, dropHandle, mdxToMdast as mdxToMdast3 } from "satteri";
|
|
1225
|
+
var POSITIONED = /^(?<line>\d+):(?<column>\d+): (?<reason>[\s\S]*?)(?: \([\w-]+:[\w-]+\))?$/u;
|
|
1226
|
+
var INNER_POSITION = / \((?<line>\d+):(?<column>\d+)\)/gu;
|
|
1227
|
+
var mdxSyntaxError = (text) => {
|
|
1228
|
+
try {
|
|
1229
|
+
dropHandle(createMdxMdastHandle(text, MDX_BODY_FEATURES, false));
|
|
1230
|
+
return null;
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1233
|
+
const groups = POSITIONED.exec(message)?.groups;
|
|
1234
|
+
return {
|
|
1235
|
+
column: Number(groups?.column ?? 1),
|
|
1236
|
+
line: Number(groups?.line ?? 1),
|
|
1237
|
+
reason: groups?.reason ?? message
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
var sitesIn = (file, lineOffset, origins) => (line, column) => {
|
|
1242
|
+
const origin = origins?.[line - 1];
|
|
1243
|
+
return origin ? { column, file: origin.file, line: origin.line } : { column, file, line: line + lineOffset };
|
|
1244
|
+
};
|
|
1245
|
+
var placedReason = (reason, at, siteOf) => reason.replaceAll(INNER_POSITION, (written, line, column) => {
|
|
1246
|
+
const inner = siteOf(Number(line), Number(column));
|
|
1247
|
+
return inner.file === at.file ? ` (${inner.line}:${column})` : "";
|
|
1248
|
+
});
|
|
1249
|
+
var EXPRESSION_REASON = /could not parse expression/iu;
|
|
1250
|
+
var COMMENT_REASON = /to create a comment in MDX/u;
|
|
1251
|
+
var LINK_REASON = /to create a link in MDX/u;
|
|
1252
|
+
var syntaxSuggestion = (reason) => {
|
|
1253
|
+
if (EXPRESSION_REASON.test(reason)) {
|
|
1254
|
+
return "In .mdx, `{` starts a JavaScript expression. To show a brace as text, escape it (`\\{`) or put the text in inline code.";
|
|
1255
|
+
}
|
|
1256
|
+
if (COMMENT_REASON.test(reason)) {
|
|
1257
|
+
return "Write the comment as `{/* … */}`.";
|
|
1258
|
+
}
|
|
1259
|
+
if (LINK_REASON.test(reason)) {
|
|
1260
|
+
return "Write the link as `[text](https://…)`.";
|
|
1261
|
+
}
|
|
1262
|
+
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.";
|
|
1263
|
+
};
|
|
1264
|
+
var mdxSyntaxDiagnostic = (error, siteOf, options) => {
|
|
1265
|
+
const at = siteOf(error.line, error.column);
|
|
1266
|
+
const reason = placedReason(error.reason, at, siteOf);
|
|
1267
|
+
return {
|
|
1268
|
+
code: "BLUME_MDX_SYNTAX",
|
|
1269
|
+
column: at.column,
|
|
1270
|
+
file: at.file,
|
|
1271
|
+
line: at.line,
|
|
1272
|
+
message: options.includedIn ? `MDX can't parse this file once ${options.includedIn} includes it: ${reason}.` : `MDX can't parse this page: ${reason}.`,
|
|
1273
|
+
severity: options.severity,
|
|
1274
|
+
suggestion: syntaxSuggestion(reason)
|
|
1275
|
+
};
|
|
1276
|
+
};
|
|
1277
|
+
var mdxSyntaxCheck = (entry, sourceName, variables) => {
|
|
1278
|
+
if (entry.body.format !== "mdx") {
|
|
1279
|
+
return { diagnostics: [], unparsable: false };
|
|
1280
|
+
}
|
|
1281
|
+
const siteOf = (origins) => sitesIn(entry.sourcePath ?? `${sourceName}:${entry.ref}`, entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text), origins);
|
|
1282
|
+
const { expanded } = entry;
|
|
1283
|
+
const body = expanded ? substituteVariables(entry.body.text, variables) : entry.body.text;
|
|
1284
|
+
const own = mdxSyntaxError(body);
|
|
1285
|
+
if (own) {
|
|
1286
|
+
return {
|
|
1287
|
+
diagnostics: [mdxSyntaxDiagnostic(own, siteOf(), { severity: "error" })],
|
|
1288
|
+
unparsable: true
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
const spliced = expanded ? mdxSyntaxError(expanded.text) : null;
|
|
1292
|
+
if (!(expanded && spliced)) {
|
|
1293
|
+
return { diagnostics: [], unparsable: false };
|
|
1294
|
+
}
|
|
1295
|
+
return {
|
|
1296
|
+
diagnostics: [
|
|
1297
|
+
mdxSyntaxDiagnostic(spliced, siteOf(expanded.origins), {
|
|
1298
|
+
includedIn: entry.ref,
|
|
1299
|
+
severity: "warning"
|
|
1300
|
+
})
|
|
1301
|
+
],
|
|
1302
|
+
unparsable: false
|
|
1303
|
+
};
|
|
1304
|
+
};
|
|
1305
|
+
var compiledText = async (page) => {
|
|
1306
|
+
const raw = await readFile(page.sourcePath, "utf-8");
|
|
1307
|
+
const body = frontmatter_default(raw).content;
|
|
1308
|
+
const offset = strippedLineOffset(raw, body);
|
|
1309
|
+
const expansion = page.contentRoot && hasIncludeStatements(body) ? await expandIncludes(body, {
|
|
1310
|
+
contentRoot: page.contentRoot,
|
|
1311
|
+
lineOffset: offset,
|
|
1312
|
+
sourcePath: page.sourcePath
|
|
1313
|
+
}) : null;
|
|
1314
|
+
const written = expansion?.text ?? body;
|
|
1315
|
+
return {
|
|
1316
|
+
siteOf: sitesIn(page.sourcePath, offset, expansion?.origins),
|
|
1317
|
+
text: substituteVariables(written, page.variables),
|
|
1318
|
+
written
|
|
1319
|
+
};
|
|
1320
|
+
};
|
|
1321
|
+
var readsName = (expression, name) => new RegExp(String.raw`(?<![\w$.])${name.replaceAll("$", String.raw`\$`)}(?![\w$])`, "u").test(expression.replaceAll("...", " "));
|
|
1322
|
+
var expressionsOf = (node) => {
|
|
1323
|
+
if (node.type === "mdxFlowExpression" || node.type === "mdxTextExpression") {
|
|
1324
|
+
return [node.value];
|
|
1325
|
+
}
|
|
1326
|
+
if (node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") {
|
|
1327
|
+
return node.attributes.flatMap((attribute) => {
|
|
1328
|
+
if (attribute.type === "mdxJsxExpressionAttribute") {
|
|
1329
|
+
return [attribute.value];
|
|
1330
|
+
}
|
|
1331
|
+
return isExpression(attribute.value) ? [attribute.value.value] : [];
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
return [];
|
|
1335
|
+
};
|
|
1336
|
+
var expressionReading = (text, name) => {
|
|
1337
|
+
let tree;
|
|
1338
|
+
try {
|
|
1339
|
+
tree = mdxToMdast3(text, { features: MDX_BODY_FEATURES });
|
|
1340
|
+
} catch {
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
const visit = (node) => {
|
|
1344
|
+
const expression = expressionsOf(node).find((source) => readsName(source, name));
|
|
1345
|
+
if (expression !== undefined) {
|
|
1346
|
+
return { expression: `{${expression.trim()}}`, node };
|
|
1347
|
+
}
|
|
1348
|
+
if ("children" in node) {
|
|
1349
|
+
for (const child of node.children) {
|
|
1350
|
+
const found = visit(child);
|
|
1351
|
+
if (found) {
|
|
1352
|
+
return found;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
};
|
|
1358
|
+
return visit(tree);
|
|
1359
|
+
};
|
|
1360
|
+
var locateMdxFailure = async (page, failure) => {
|
|
1361
|
+
let compiled;
|
|
1362
|
+
try {
|
|
1363
|
+
compiled = await compiledText(page);
|
|
1364
|
+
} catch {
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
const { siteOf, text, written } = compiled;
|
|
1368
|
+
if (failure.kind === "undefined-name") {
|
|
1369
|
+
const found = expressionReading(text, failure.name);
|
|
1370
|
+
const start = found?.node.position?.start;
|
|
1371
|
+
if (!(found && start)) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
const at = written.split(`
|
|
1375
|
+
`)[start.line - 1]?.indexOf(found.expression) ?? -1;
|
|
1376
|
+
return {
|
|
1377
|
+
...siteOf(start.line, at === -1 ? start.column : at + 1),
|
|
1378
|
+
expression: found.expression
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
const error = mdxSyntaxError(text);
|
|
1382
|
+
if (!error) {
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
const diagnostic = mdxSyntaxDiagnostic(error, siteOf, {
|
|
1386
|
+
includedIn: siteOf(error.line).file === page.sourcePath ? undefined : page.name,
|
|
1387
|
+
severity: "error"
|
|
1388
|
+
});
|
|
1389
|
+
return {
|
|
1390
|
+
column: diagnostic.column,
|
|
1391
|
+
diagnostic,
|
|
1392
|
+
file: diagnostic.file ?? page.sourcePath,
|
|
1393
|
+
line: diagnostic.line ?? 1
|
|
1394
|
+
};
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
// src/core/meta-diagnostics.ts
|
|
1398
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1399
|
+
var groupPathOf = (key, versionDirs, localeDirs) => {
|
|
1400
|
+
const parts = key.split("/");
|
|
1401
|
+
if (versionDirs.has(parts[0] ?? "")) {
|
|
1402
|
+
parts.shift();
|
|
1403
|
+
}
|
|
1404
|
+
if (localeDirs.has(parts[0] ?? "")) {
|
|
1405
|
+
parts.shift();
|
|
1406
|
+
}
|
|
1407
|
+
return parts.join("/");
|
|
1408
|
+
};
|
|
1409
|
+
var likelyChild = (entry, children) => {
|
|
1410
|
+
const key = pageKey(entry);
|
|
1411
|
+
return key !== entry && children.has(key) ? key : closestMatch(entry, [...children]);
|
|
1412
|
+
};
|
|
1413
|
+
var SLUG_HINT = "List each child by its slug: the file or folder name without its extension, numeric prefix, or parentheses.";
|
|
1414
|
+
var unknownPageDiagnostics = async (file, pages, group, children) => {
|
|
1415
|
+
const unknown = pages.flatMap((entry, index) => children.has(entry) ? [] : [{ entry, index }]);
|
|
1416
|
+
if (unknown.length === 0) {
|
|
1417
|
+
return [];
|
|
1418
|
+
}
|
|
1419
|
+
const source = await readFile2(file, "utf-8");
|
|
1420
|
+
const where = group ? `"${group}"` : "the content root";
|
|
1421
|
+
return unknown.map(({ entry, index }) => {
|
|
1422
|
+
const position = locatePath(source, ["pages", index]);
|
|
1423
|
+
const match = likelyChild(entry, children);
|
|
1424
|
+
return {
|
|
1425
|
+
code: "BLUME_META_UNKNOWN_PAGE",
|
|
1426
|
+
column: position?.column,
|
|
1427
|
+
file,
|
|
1428
|
+
line: position?.line,
|
|
1429
|
+
message: `pages lists "${entry}", but no page or folder in ${where} has that slug, so the entry orders nothing.`,
|
|
1430
|
+
severity: "warning",
|
|
1431
|
+
suggestion: match ? `Did you mean "${match}"? ${SLUG_HINT}` : SLUG_HINT
|
|
1432
|
+
};
|
|
1433
|
+
});
|
|
1434
|
+
};
|
|
1435
|
+
var unreadDiagnostic = ({ dir, file }, group) => ({
|
|
1436
|
+
code: "BLUME_META_OUTSIDE_INCLUDE",
|
|
1437
|
+
file,
|
|
1438
|
+
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.`,
|
|
1439
|
+
severity: "warning",
|
|
1440
|
+
suggestion: `Add an include glob that reaches the folder, such as "${dir}/**/*.{md,mdx}".`
|
|
1441
|
+
});
|
|
1442
|
+
var folderMetaDiagnostics = async (discovered, pages, options = {}) => {
|
|
1443
|
+
const children = folderChildKeys(pages.map((page) => page.navPath));
|
|
1444
|
+
const versionDirs = new Set(options.versionDirs);
|
|
1445
|
+
const localeDirs = new Set(options.localeDirs);
|
|
1446
|
+
const groupOf = (key) => groupPathOf(key, versionDirs, localeDirs);
|
|
1447
|
+
const listed = await Promise.all([...discovered.meta, ...discovered.shared].flatMap(([key, meta]) => {
|
|
1448
|
+
const file = discovered.files.get(meta);
|
|
1449
|
+
const group = groupOf(key);
|
|
1450
|
+
return file && meta.pages ? [
|
|
1451
|
+
unknownPageDiagnostics(file, meta.pages, group, children.get(group) ?? new Set)
|
|
1452
|
+
] : [];
|
|
1453
|
+
}));
|
|
1454
|
+
const unread = discovered.unread.flatMap((entry) => {
|
|
1455
|
+
const group = groupOf(entry.key);
|
|
1456
|
+
return children.has(group) ? [unreadDiagnostic(entry, group)] : [];
|
|
1457
|
+
});
|
|
1458
|
+
return [...listed.flat(), ...unread];
|
|
1459
|
+
};
|
|
1460
|
+
|
|
897
1461
|
// src/core/meta.ts
|
|
898
1462
|
import { basename, dirname, relative as relative2 } from "pathe";
|
|
899
1463
|
import { glob, isDynamicPattern } from "tinyglobby";
|
|
@@ -921,19 +1485,21 @@ var globBase = (pattern) => {
|
|
|
921
1485
|
};
|
|
922
1486
|
var onPathOf = (dir, base) => dir === base || base === "" || dir === "" || dir.startsWith(`${base}/`) || base.startsWith(`${dir}/`);
|
|
923
1487
|
var withinInclude = (dir, include) => include === undefined || include.some((pattern) => !pattern.startsWith("!") && onPathOf(dir, globBase(pattern)));
|
|
1488
|
+
var globFolderMetaFiles = (source, patterns) => glob([...patterns], {
|
|
1489
|
+
absolute: true,
|
|
1490
|
+
cwd: source.root,
|
|
1491
|
+
ignore: [
|
|
1492
|
+
...source.exclude ?? [],
|
|
1493
|
+
"**/node_modules/**",
|
|
1494
|
+
"**/.blume/**",
|
|
1495
|
+
"**/dist/**"
|
|
1496
|
+
],
|
|
1497
|
+
onlyFiles: true
|
|
1498
|
+
});
|
|
1499
|
+
var isReadMetaFile = (source, file) => withinInclude(relative2(source.root, dirname(file)), source.include);
|
|
924
1500
|
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));
|
|
1501
|
+
const found = await globFolderMetaFiles(source, patterns);
|
|
1502
|
+
return found.filter((file) => isReadMetaFile(source, file));
|
|
937
1503
|
};
|
|
938
1504
|
var metaKeyFor = (prefix, dir) => {
|
|
939
1505
|
const clean = prefix ? trimChar(prefix, "/") : "";
|
|
@@ -949,10 +1515,31 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
949
1515
|
const load = createModuleLoader();
|
|
950
1516
|
const meta = new Map;
|
|
951
1517
|
const shared = new Map;
|
|
1518
|
+
const files = new Map;
|
|
1519
|
+
const unread = [];
|
|
952
1520
|
const diagnostics = [];
|
|
1521
|
+
const keyOf = (source, file) => {
|
|
1522
|
+
const dir = relative2(source.root, dirname(file));
|
|
1523
|
+
const [head, ...tail] = dir.split("/");
|
|
1524
|
+
const version = head && versionDirs.has(head) ? head : "";
|
|
1525
|
+
const afterVersion = version ? tail : [head ?? "", ...tail];
|
|
1526
|
+
const [localeHead, ...localeTail] = afterVersion;
|
|
1527
|
+
const locale = localeHead ? localeDirs.get(localeHead.toLowerCase()) ?? "" : "";
|
|
1528
|
+
const rest = (locale ? localeTail : afterVersion).filter(Boolean).join("/");
|
|
1529
|
+
return [version, locale, metaKeyFor(source.prefix, rest)].filter(Boolean).join("/");
|
|
1530
|
+
};
|
|
953
1531
|
const perSource = await Promise.all(list.map(async (source) => {
|
|
954
|
-
const
|
|
955
|
-
const
|
|
1532
|
+
const found = await globFolderMetaFiles(source, META_FILES);
|
|
1533
|
+
for (const file of found) {
|
|
1534
|
+
if (!isReadMetaFile(source, file)) {
|
|
1535
|
+
unread.push({
|
|
1536
|
+
dir: relative2(source.root, dirname(file)),
|
|
1537
|
+
file,
|
|
1538
|
+
key: keyOf(source, file)
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
const loaded = await Promise.all(found.filter((file) => isReadMetaFile(source, file)).map(async (file) => {
|
|
956
1543
|
try {
|
|
957
1544
|
return {
|
|
958
1545
|
file,
|
|
@@ -967,14 +1554,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
967
1554
|
}));
|
|
968
1555
|
for (const { loaded, source } of perSource) {
|
|
969
1556
|
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("/");
|
|
1557
|
+
const key = keyOf(source, entry.file);
|
|
978
1558
|
if (!entry.ok) {
|
|
979
1559
|
diagnostics.push({
|
|
980
1560
|
code: "BLUME_META_LOAD_FAILED",
|
|
@@ -988,6 +1568,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
988
1568
|
if (result.success) {
|
|
989
1569
|
const target = basename(entry.file).startsWith("meta.$.") ? shared : meta;
|
|
990
1570
|
target.set(key, result.data);
|
|
1571
|
+
files.set(result.data, entry.file);
|
|
991
1572
|
} else {
|
|
992
1573
|
diagnostics.push(...diagnosticsFromZod(result.error, {
|
|
993
1574
|
code: "BLUME_META_INVALID",
|
|
@@ -996,7 +1577,32 @@ var discoverFolderMeta = async (sources, options = {}) => {
|
|
|
996
1577
|
}
|
|
997
1578
|
}
|
|
998
1579
|
}
|
|
999
|
-
|
|
1580
|
+
const read = new Set(perSource.flatMap(({ loaded }) => loaded.map((entry) => entry.file)));
|
|
1581
|
+
return {
|
|
1582
|
+
diagnostics,
|
|
1583
|
+
files,
|
|
1584
|
+
meta,
|
|
1585
|
+
shared,
|
|
1586
|
+
unread: unread.filter((entry) => !read.has(entry.file))
|
|
1587
|
+
};
|
|
1588
|
+
};
|
|
1589
|
+
var withGeneratedFolderMeta = (user, generated, localeDirs = []) => {
|
|
1590
|
+
const locales = new Set(localeDirs);
|
|
1591
|
+
const beneath = (key, meta) => {
|
|
1592
|
+
const [head, ...rest] = key.split("/");
|
|
1593
|
+
const generatedMeta = generated.get(head !== undefined && locales.has(head) ? rest.join("/") : key);
|
|
1594
|
+
return generatedMeta ? { ...generatedMeta, ...meta } : meta;
|
|
1595
|
+
};
|
|
1596
|
+
return {
|
|
1597
|
+
meta: new Map([...user.meta].map(([key, meta]) => [key, beneath(key, meta)])),
|
|
1598
|
+
shared: new Map([
|
|
1599
|
+
...generated,
|
|
1600
|
+
...[...user.shared].map(([key, meta]) => [
|
|
1601
|
+
key,
|
|
1602
|
+
{ ...generated.get(key), ...meta }
|
|
1603
|
+
])
|
|
1604
|
+
])
|
|
1605
|
+
};
|
|
1000
1606
|
};
|
|
1001
1607
|
|
|
1002
1608
|
// src/core/sources/resolve.ts
|
|
@@ -1390,7 +1996,7 @@ var contentfulSource = (options, ctx) => {
|
|
|
1390
1996
|
|
|
1391
1997
|
// src/core/sources/filesystem.ts
|
|
1392
1998
|
import { existsSync, watch as fsWatch } from "node:fs";
|
|
1393
|
-
import { readFile } from "node:fs/promises";
|
|
1999
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1394
2000
|
import { YAMLException } from "js-yaml";
|
|
1395
2001
|
import { extname, isAbsolute, join as join2, relative as relative3, resolve } from "pathe";
|
|
1396
2002
|
import { glob as glob2 } from "tinyglobby";
|
|
@@ -1415,7 +2021,7 @@ var filesystemSource = (options) => {
|
|
|
1415
2021
|
}
|
|
1416
2022
|
}
|
|
1417
2023
|
const read = await Promise.all(loadable.map(async (file) => {
|
|
1418
|
-
const source = await
|
|
2024
|
+
const source = await readFile3(file, "utf-8");
|
|
1419
2025
|
const ext = extname(file).toLowerCase();
|
|
1420
2026
|
const format = ext === ".mdx" ? "mdx" : "md";
|
|
1421
2027
|
let parsed;
|
|
@@ -1474,7 +2080,7 @@ var filesystemSource = (options) => {
|
|
|
1474
2080
|
load,
|
|
1475
2081
|
name: options.name,
|
|
1476
2082
|
prefix: options.prefix,
|
|
1477
|
-
read: (ref) =>
|
|
2083
|
+
read: (ref) => readFile3(join2(contentRoot, ref), "utf-8"),
|
|
1478
2084
|
staged: false,
|
|
1479
2085
|
validate,
|
|
1480
2086
|
watch
|
|
@@ -1735,19 +2341,13 @@ var includeMatcher = (include) => {
|
|
|
1735
2341
|
return (ref) => included(ref) && !excluded(ref);
|
|
1736
2342
|
};
|
|
1737
2343
|
var GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
|
|
2344
|
+
var isGithubUrl = (url) => URL.canParse(url) && GITHUB_HOSTS.has(new URL(url).hostname);
|
|
1738
2345
|
var githubHeaders2 = (url) => {
|
|
1739
2346
|
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}` } : {};
|
|
2347
|
+
return token && isGithubUrl(url) ? { authorization: `Bearer ${token}` } : {};
|
|
1750
2348
|
};
|
|
2349
|
+
var TOKEN_STATUSES = new Set([401, 403, 404]);
|
|
2350
|
+
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
2351
|
var fetchText = async (url, doFetch) => {
|
|
1752
2352
|
try {
|
|
1753
2353
|
const res = await doFetch(url, {
|
|
@@ -1755,7 +2355,7 @@ var fetchText = async (url, doFetch) => {
|
|
|
1755
2355
|
signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
|
|
1756
2356
|
});
|
|
1757
2357
|
if (!res.ok) {
|
|
1758
|
-
throw new Error(
|
|
2358
|
+
throw new Error(statusReason(url, res.status));
|
|
1759
2359
|
}
|
|
1760
2360
|
return await res.text();
|
|
1761
2361
|
} catch (error) {
|
|
@@ -1797,6 +2397,24 @@ var enumerateGithub = async (github, include, doFetch) => {
|
|
|
1797
2397
|
truncated
|
|
1798
2398
|
};
|
|
1799
2399
|
};
|
|
2400
|
+
var ATX_HEADING = /^ {0,3}#/u;
|
|
2401
|
+
var withoutTitleHeading = (page) => {
|
|
2402
|
+
const lines = page.body.split(`
|
|
2403
|
+
`);
|
|
2404
|
+
const start = lines.findIndex((line) => line.trim() !== "");
|
|
2405
|
+
const span = ATX_HEADING.test(lines[start] ?? "") ? 1 : 2;
|
|
2406
|
+
const [heading] = start === -1 ? [] : extractHeadings(lines.slice(start, start + span).join(`
|
|
2407
|
+
`));
|
|
2408
|
+
if (heading?.depth !== 1 || heading.text === "" || page.data.title !== undefined && page.data.title !== heading.text) {
|
|
2409
|
+
return page;
|
|
2410
|
+
}
|
|
2411
|
+
lines.fill("", start, start + span);
|
|
2412
|
+
return {
|
|
2413
|
+
body: lines.join(`
|
|
2414
|
+
`),
|
|
2415
|
+
data: { ...page.data, title: heading.text }
|
|
2416
|
+
};
|
|
2417
|
+
};
|
|
1800
2418
|
var mdxRemoteSource = (options, ctx) => {
|
|
1801
2419
|
const doFetch = options.fetchImpl ?? globalThis.fetch;
|
|
1802
2420
|
const cache = snapshotCache(ctx.cacheDir);
|
|
@@ -1824,12 +2442,16 @@ var mdxRemoteSource = (options, ctx) => {
|
|
|
1824
2442
|
const text = await fetchText(item.fetchUrl, doFetch);
|
|
1825
2443
|
const parsed = frontmatter_default(text);
|
|
1826
2444
|
const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
|
|
2445
|
+
const page = withoutTitleHeading({
|
|
2446
|
+
body: parsed.content,
|
|
2447
|
+
data: parsed.data
|
|
2448
|
+
});
|
|
1827
2449
|
return {
|
|
1828
|
-
body: { format, text:
|
|
1829
|
-
data:
|
|
2450
|
+
body: { format, text: page.body },
|
|
2451
|
+
data: page.data,
|
|
1830
2452
|
editUrl: item.editUrl,
|
|
1831
2453
|
hash: hashText(text),
|
|
1832
|
-
raw: text,
|
|
2454
|
+
raw: text.slice(0, text.length - parsed.content.length) + page.body,
|
|
1833
2455
|
ref: item.ref
|
|
1834
2456
|
};
|
|
1835
2457
|
};
|
|
@@ -2140,7 +2762,7 @@ var FRAME_ESCAPED = /\\(?=[<>])/gu;
|
|
|
2140
2762
|
var YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
|
|
2141
2763
|
var isYouTubeUrl = (url) => {
|
|
2142
2764
|
const host = URL.parse(url)?.hostname ?? "";
|
|
2143
|
-
return YOUTUBE_HOST.test(host) &&
|
|
2765
|
+
return YOUTUBE_HOST.test(host) && youtubeEmbedSrc(url) !== null;
|
|
2144
2766
|
};
|
|
2145
2767
|
var renderVideo = (data) => {
|
|
2146
2768
|
const url = data.external?.url ?? data.file?.url;
|
|
@@ -2425,7 +3047,7 @@ ${nested}`;
|
|
|
2425
3047
|
|
|
2426
3048
|
// src/core/sources/obsidian.ts
|
|
2427
3049
|
import { existsSync as existsSync2, watch as fsWatch2, statSync } from "node:fs";
|
|
2428
|
-
import { readdir as readdir2, readFile as
|
|
3050
|
+
import { readdir as readdir2, readFile as readFile4, stat } from "node:fs/promises";
|
|
2429
3051
|
import { YAMLException as YAMLException2 } from "js-yaml";
|
|
2430
3052
|
import { basename as basename2, isAbsolute as isAbsolute2, join as join5, relative as relative4, resolve as resolve2 } from "pathe";
|
|
2431
3053
|
var MARKDOWN_FILE = /\.md$/iu;
|
|
@@ -2443,7 +3065,6 @@ var OBSIDIAN_NATIVE_KEYS = new Set([
|
|
|
2443
3065
|
"tag",
|
|
2444
3066
|
"tags"
|
|
2445
3067
|
]);
|
|
2446
|
-
var PAGE_META_KEYS = new Set(pageMetaSchema.keyof().options);
|
|
2447
3068
|
var entrySlugFor = (navPath) => slugifyPath(navPath.replace(MARKDOWN_FILE, ""));
|
|
2448
3069
|
var noTargets = () => ({
|
|
2449
3070
|
ambiguous: [],
|
|
@@ -2726,7 +3347,7 @@ var noteToEntry = (pair, index, options, unresolved) => {
|
|
|
2726
3347
|
const { note } = pair;
|
|
2727
3348
|
const title = titleFor(note.rel, note.data.title);
|
|
2728
3349
|
const keep = keptKeysFor(note.data, options);
|
|
2729
|
-
const data = Object.fromEntries(Object.entries(note.data).filter(([key]) => !OBSIDIAN_NATIVE_KEYS.has(key) && (
|
|
3350
|
+
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
3351
|
const merged = title === undefined ? data : { ...data, title };
|
|
2731
3352
|
const body = note.content.trim();
|
|
2732
3353
|
const text = transformBody(body, index, pair.self, unresolved);
|
|
@@ -2748,7 +3369,7 @@ var noteToEntry = (pair, index, options, unresolved) => {
|
|
|
2748
3369
|
var readNote = async (vaultDir, rel, options) => {
|
|
2749
3370
|
const absPath = join5(vaultDir, rel);
|
|
2750
3371
|
try {
|
|
2751
|
-
const file = await
|
|
3372
|
+
const file = await readFile4(absPath, "utf-8");
|
|
2752
3373
|
const { content, data } = frontmatter_default(file);
|
|
2753
3374
|
return {
|
|
2754
3375
|
absPath,
|
|
@@ -2877,7 +3498,7 @@ var obsidianSource = (options, ctx) => {
|
|
|
2877
3498
|
suggestion: "Reference notes by their vault-relative path."
|
|
2878
3499
|
});
|
|
2879
3500
|
}
|
|
2880
|
-
return await
|
|
3501
|
+
return await readFile4(absPath, "utf-8");
|
|
2881
3502
|
};
|
|
2882
3503
|
const validate = () => {
|
|
2883
3504
|
const stats = statSync(vaultDir, { throwIfNoEntry: false });
|
|
@@ -3593,6 +4214,284 @@ var resolveSources = (config, context, runtime) => {
|
|
|
3593
4214
|
return sources;
|
|
3594
4215
|
};
|
|
3595
4216
|
|
|
4217
|
+
// src/core/svg-images.ts
|
|
4218
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
4219
|
+
import { imageMetadata } from "astro/assets/utils";
|
|
4220
|
+
import { dirname as dirname2, extname as extname3 } from "pathe";
|
|
4221
|
+
var isUnmeasurableSvg = async (path) => {
|
|
4222
|
+
if (extname3(path).toLowerCase() !== ".svg") {
|
|
4223
|
+
return false;
|
|
4224
|
+
}
|
|
4225
|
+
let data;
|
|
4226
|
+
try {
|
|
4227
|
+
data = await readFile5(path);
|
|
4228
|
+
} catch {
|
|
4229
|
+
return false;
|
|
4230
|
+
}
|
|
4231
|
+
try {
|
|
4232
|
+
await imageMetadata(data, path);
|
|
4233
|
+
return false;
|
|
4234
|
+
} catch {
|
|
4235
|
+
return true;
|
|
4236
|
+
}
|
|
4237
|
+
};
|
|
4238
|
+
var unmeasurableSvgDiagnostics = async (pages) => {
|
|
4239
|
+
const seen = new Set;
|
|
4240
|
+
const embeds = pages.flatMap((page) => {
|
|
4241
|
+
const { sourcePath } = page;
|
|
4242
|
+
if (!sourcePath) {
|
|
4243
|
+
return [];
|
|
4244
|
+
}
|
|
4245
|
+
return page.links.flatMap((link) => {
|
|
4246
|
+
const file = link.file ?? sourcePath;
|
|
4247
|
+
const key = `${file}:${link.line}:${link.column}`;
|
|
4248
|
+
if (!link.image || seen.has(key)) {
|
|
4249
|
+
return [];
|
|
4250
|
+
}
|
|
4251
|
+
seen.add(key);
|
|
4252
|
+
const abs = resolveRelativeImage(dirname2(sourcePath), link.target);
|
|
4253
|
+
return abs ? [{ abs, file, link }] : [];
|
|
4254
|
+
});
|
|
4255
|
+
});
|
|
4256
|
+
const found = await Promise.all(embeds.map(async (embed) => await isUnmeasurableSvg(embed.abs) ? [embed] : []));
|
|
4257
|
+
return found.flat().map(({ file, link }) => ({
|
|
4258
|
+
code: "BLUME_SVG_UNOPTIMIZED",
|
|
4259
|
+
column: link.column,
|
|
4260
|
+
file,
|
|
4261
|
+
line: link.line,
|
|
4262
|
+
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.`,
|
|
4263
|
+
severity: "warning",
|
|
4264
|
+
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."
|
|
4265
|
+
}));
|
|
4266
|
+
};
|
|
4267
|
+
|
|
4268
|
+
// src/core/syntax-diagnostics.ts
|
|
4269
|
+
import { slug } from "github-slugger";
|
|
4270
|
+
import { markdownToMdast as markdownToMdast4 } from "satteri";
|
|
4271
|
+
var UNESCAPED = String.raw`(?<=(?:^|[^\\])(?:\\\\)*)`;
|
|
4272
|
+
var TEMPLATE_TAG = new RegExp(String.raw`${UNESCAPED}\{%[^\n]*?%\}`, "gu");
|
|
4273
|
+
var TEMPLATE_OUTPUT = new RegExp(String.raw`${UNESCAPED}\{\{(?<expression>[^\n{}]*)\}\}`, "gu");
|
|
4274
|
+
var VARIABLE_REFERENCE = /^\s*[\w-]+\s*$/u;
|
|
4275
|
+
var WIKILINK2 = /(?<![!\\[])\[\[(?<inner>[^[\]\n]+)\]\](?![([:])/gu;
|
|
4276
|
+
var MDC_BLOCK = /^[\t ]*(?<written>::(?<name>[a-z][\w-]*)(?:\[[^\]\n]*\])?(?:\{[^}\n]*\})?)[\t ]*$/gimu;
|
|
4277
|
+
var MDC_INLINE = /(?<![\w:\\])(?<written>:{1,2}(?<name>[a-z][\w-]*)(?:\[[^\]\n]*\](?:\{[^}\n]*\})?|\{[^}\n]*\}))/giu;
|
|
4278
|
+
var ATTRIBUTE_LIST = new RegExp(String.raw`(?<![=\\])\{:?[\t ]*${ATTRIBUTE}(?:[\t ]+${ATTRIBUTE})*[\t ]*\}`, "gu");
|
|
4279
|
+
var DIRECTIVE_ATTRIBUTES = /:+[a-z][\w-]*(?:\[[^\]\n]*\])?\{[^}\n]*\}/giu;
|
|
4280
|
+
var ATX_HEADING2 = /^ {0,3}#{1,6}[\t ]/u;
|
|
4281
|
+
var SETEXT_UNDERLINE = /^ {0,3}(?:=+|-+)[\t ]*$/u;
|
|
4282
|
+
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");
|
|
4283
|
+
var SUSPECT_WIKILINK = /\[\[/u;
|
|
4284
|
+
var SUSPECT_MDC = /(?:^|[^\w:]):{1,2}[a-z][\w-]*[[{]|^[\t ]*::[a-z]/imu;
|
|
4285
|
+
var MDX_COMMENT = /\{\s*\/\*[\s\S]*?\*\/\s*\}/gu;
|
|
4286
|
+
var ANGLE_DESTINATION = /(?<=\]\([\t ]*|^ {0,3}\[[^\]\n]+\]:[\t ]*)<[^<>\n]*>/gmu;
|
|
4287
|
+
var blank = (text) => text.replaceAll(/[^\n]/gu, " ");
|
|
4288
|
+
var maskMarkdown = (text) => {
|
|
4289
|
+
const ranges = [];
|
|
4290
|
+
const walk = (node) => {
|
|
4291
|
+
const start = node.position?.start.offset ?? 0;
|
|
4292
|
+
const end = node.position?.end.offset ?? start;
|
|
4293
|
+
if (node.type === "code" || node.type === "inlineCode" || node.type === "html" && node.value.startsWith("<!--")) {
|
|
4294
|
+
ranges.push([start, end]);
|
|
4295
|
+
} else if ("children" in node) {
|
|
4296
|
+
for (const child of node.children) {
|
|
4297
|
+
walk(child);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
};
|
|
4301
|
+
walk(markdownToMdast4(text, {
|
|
4302
|
+
features: { ...MARKDOWN_BODY_FEATURES, gfm: true }
|
|
4303
|
+
}));
|
|
4304
|
+
let masked = "";
|
|
4305
|
+
let cursor = 0;
|
|
4306
|
+
for (const [start, end] of ranges) {
|
|
4307
|
+
masked += text.slice(cursor, start) + blank(text.slice(start, end));
|
|
4308
|
+
cursor = end;
|
|
4309
|
+
}
|
|
4310
|
+
return masked + text.slice(cursor);
|
|
4311
|
+
};
|
|
4312
|
+
var maskMdx = (text) => {
|
|
4313
|
+
let fence = null;
|
|
4314
|
+
return text.split(`
|
|
4315
|
+
`).map((line) => {
|
|
4316
|
+
const next = nextFenceState(line, fence);
|
|
4317
|
+
const inFence = fence !== null || next !== null;
|
|
4318
|
+
fence = next;
|
|
4319
|
+
return inFence ? blank(line) : line.replaceAll(INLINE_CODE, blank);
|
|
4320
|
+
}).join(`
|
|
4321
|
+
`).replaceAll(MDX_COMMENT, blank).replaceAll(HTML_COMMENT, blank).replaceAll(ANGLE_DESTINATION, blank);
|
|
4322
|
+
};
|
|
4323
|
+
var lineStartsOf = (text) => {
|
|
4324
|
+
const starts = [0];
|
|
4325
|
+
for (let index = text.indexOf(`
|
|
4326
|
+
`);index !== -1; index = text.indexOf(`
|
|
4327
|
+
`, index + 1)) {
|
|
4328
|
+
starts.push(index + 1);
|
|
4329
|
+
}
|
|
4330
|
+
return starts;
|
|
4331
|
+
};
|
|
4332
|
+
var siteOf = (starts, at) => {
|
|
4333
|
+
const line = starts.findLastIndex((start) => start <= at);
|
|
4334
|
+
return { column: at - (starts[line] ?? 0) + 1, line: line + 1 };
|
|
4335
|
+
};
|
|
4336
|
+
var templateFindings = ({ starts, text }) => [
|
|
4337
|
+
...[...text.matchAll(TEMPLATE_TAG)].map((match) => ({
|
|
4338
|
+
...siteOf(starts, match.index),
|
|
4339
|
+
code: "BLUME_TEMPLATE_TAG",
|
|
4340
|
+
message: `\`${match[0]}\` is a Liquid or Markdoc tag, which Blume doesn't run, so the page shows it as written.`,
|
|
4341
|
+
suggestion: "Rewrite what it produced as Markdown or a Blume component, or remove it. To show it as text, put it in inline code."
|
|
4342
|
+
})),
|
|
4343
|
+
...[...text.matchAll(TEMPLATE_OUTPUT)].filter((match) => !VARIABLE_REFERENCE.test(match.groups?.expression ?? "")).map((match) => ({
|
|
4344
|
+
...siteOf(starts, match.index),
|
|
4345
|
+
code: "BLUME_TEMPLATE_TAG",
|
|
4346
|
+
message: `\`${match[0]}\` is a Liquid output tag, which Blume doesn't fill in, so the page shows it as written.`,
|
|
4347
|
+
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."
|
|
4348
|
+
}))
|
|
4349
|
+
];
|
|
4350
|
+
var wikiKey = (name) => name.trim().toLowerCase().replaceAll(/[\s_-]+/gu, "-");
|
|
4351
|
+
var indexPageNames = (pages) => {
|
|
4352
|
+
const index = new Map;
|
|
4353
|
+
for (const page of pages) {
|
|
4354
|
+
const stem = (page.source.ref.split("/").at(-1) ?? "").replace(/\.mdx?$/iu, "");
|
|
4355
|
+
for (const key of new Set([wikiKey(stem), wikiKey(page.title)])) {
|
|
4356
|
+
const named = index.get(key);
|
|
4357
|
+
if (named) {
|
|
4358
|
+
named.push(page);
|
|
4359
|
+
} else {
|
|
4360
|
+
index.set(key, [page]);
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
return index;
|
|
4365
|
+
};
|
|
4366
|
+
var partRoute = (text, pageRoute) => {
|
|
4367
|
+
const [name = "", heading] = text.split("#", 2);
|
|
4368
|
+
const route = pageRoute(name);
|
|
4369
|
+
return route && heading ? `${route}#${slug(heading)}` : route;
|
|
4370
|
+
};
|
|
4371
|
+
var wikilinkFindings = ({ pageRoute, starts, text }) => [...text.matchAll(WIKILINK2)].flatMap((match) => {
|
|
4372
|
+
const parts = (match.groups?.inner ?? "").split("|").map((part) => ({
|
|
4373
|
+
route: partRoute(part, pageRoute),
|
|
4374
|
+
text: part.trim()
|
|
4375
|
+
}));
|
|
4376
|
+
const target = parts.find((part) => part.route !== undefined);
|
|
4377
|
+
if (!target && parts.length === 1) {
|
|
4378
|
+
return [];
|
|
4379
|
+
}
|
|
4380
|
+
const label = parts.find((part) => part !== target) ?? target;
|
|
4381
|
+
return [
|
|
4382
|
+
{
|
|
4383
|
+
...siteOf(starts, match.index),
|
|
4384
|
+
code: "BLUME_WIKILINK_UNSUPPORTED",
|
|
4385
|
+
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}` : ""}.`,
|
|
4386
|
+
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)`."
|
|
4387
|
+
}
|
|
4388
|
+
];
|
|
4389
|
+
});
|
|
4390
|
+
var mdcFindings = ({ starts, text }) => {
|
|
4391
|
+
const blocks = [...text.matchAll(MDC_BLOCK)].map((match) => ({
|
|
4392
|
+
at: match.index + match[0].indexOf("::"),
|
|
4393
|
+
written: match.groups?.written
|
|
4394
|
+
}));
|
|
4395
|
+
const opened = new Set(blocks.map(({ at }) => at));
|
|
4396
|
+
return [
|
|
4397
|
+
...blocks.map(({ at, written }) => ({
|
|
4398
|
+
...siteOf(starts, at),
|
|
4399
|
+
code: "BLUME_MDC_SYNTAX",
|
|
4400
|
+
message: `\`${written}\` opens a Nuxt Content (MDC) block component, which Blume doesn't render, so the page shows its \`::\` lines as text.`,
|
|
4401
|
+
suggestion: "Rewrite the block as the matching Blume component (`<Callout>`, `<Card>`, `<Tabs>`, …) or a `:::` callout."
|
|
4402
|
+
})),
|
|
4403
|
+
...[...text.matchAll(MDC_INLINE)].filter((match) => !opened.has(match.index)).map((match) => ({
|
|
4404
|
+
...siteOf(starts, match.index),
|
|
4405
|
+
code: "BLUME_MDC_SYNTAX",
|
|
4406
|
+
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.`,
|
|
4407
|
+
suggestion: "Rewrite it as the matching Blume component (`<Badge>`, `<Icon>`, …) or as plain text."
|
|
4408
|
+
}))
|
|
4409
|
+
];
|
|
4410
|
+
};
|
|
4411
|
+
var isHeadingMarker = (lines, site, written, format) => {
|
|
4412
|
+
const line = lines[site.line - 1] ?? "";
|
|
4413
|
+
const heading = ATX_HEADING2.test(line) || SETEXT_UNDERLINE.test(lines[site.line] ?? "");
|
|
4414
|
+
const marker = trailingCurlyMarker(line);
|
|
4415
|
+
return heading && marker?.marker === written && marker.index === site.column - 1 && (format === "md" || !marker.attributes);
|
|
4416
|
+
};
|
|
4417
|
+
var ATTRIBUTE_LIST_PROBLEMS = {
|
|
4418
|
+
md: {
|
|
4419
|
+
code: "BLUME_MD_ATTRIBUTE_LIST",
|
|
4420
|
+
effect: "which Blume doesn't read, so the page shows it as written",
|
|
4421
|
+
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.'
|
|
4422
|
+
},
|
|
4423
|
+
mdx: {
|
|
4424
|
+
code: "BLUME_MDX_ATTRIBUTE_LIST",
|
|
4425
|
+
effect: "which MDX reads as a JavaScript expression, so the page fails to build",
|
|
4426
|
+
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: `\\{…\\}`.'
|
|
4427
|
+
}
|
|
4428
|
+
};
|
|
4429
|
+
var attributeListFindings = (format) => ({ starts, text }) => {
|
|
4430
|
+
const { code, effect, suggestion } = ATTRIBUTE_LIST_PROBLEMS[format];
|
|
4431
|
+
const scanned = text.replaceAll(DIRECTIVE_ATTRIBUTES, blank).replaceAll(TEMPLATE_OUTPUT, blank);
|
|
4432
|
+
const lines = text.split(`
|
|
4433
|
+
`);
|
|
4434
|
+
return [...scanned.matchAll(ATTRIBUTE_LIST)].map((match) => ({
|
|
4435
|
+
site: siteOf(starts, match.index),
|
|
4436
|
+
written: match[0]
|
|
4437
|
+
})).filter(({ site, written }) => !isHeadingMarker(lines, site, written, format)).map(({ site, written }) => ({
|
|
4438
|
+
...site,
|
|
4439
|
+
code,
|
|
4440
|
+
message: `\`${written}\` is an attribute list, ${effect}.`,
|
|
4441
|
+
suggestion
|
|
4442
|
+
}));
|
|
4443
|
+
};
|
|
4444
|
+
var SUSPECT_ATTRIBUTE_LIST = /\{:?[\t ]*(?:[#.][A-Za-z_-]|[\w-]+=)/u;
|
|
4445
|
+
var voidElementFindings = ({ starts, text }) => [...text.matchAll(VOID_ELEMENT)].filter((match) => !text.slice(match.index + match[0].length).trimStart().startsWith(`</${match.groups?.tag}>`)).map((match) => ({
|
|
4446
|
+
...siteOf(starts, match.index),
|
|
4447
|
+
code: "BLUME_MDX_UNCLOSED_ELEMENT",
|
|
4448
|
+
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.`,
|
|
4449
|
+
suggestion: `Close it: \`<${match.groups?.tag} … />\`.`
|
|
4450
|
+
}));
|
|
4451
|
+
var CHECKS = {
|
|
4452
|
+
md: [
|
|
4453
|
+
{ find: templateFindings, suspect: /\{[%{]/u },
|
|
4454
|
+
{ find: wikilinkFindings, suspect: SUSPECT_WIKILINK },
|
|
4455
|
+
{ find: mdcFindings, suspect: SUSPECT_MDC },
|
|
4456
|
+
{ find: attributeListFindings("md"), suspect: SUSPECT_ATTRIBUTE_LIST }
|
|
4457
|
+
],
|
|
4458
|
+
mdx: [
|
|
4459
|
+
{ find: wikilinkFindings, suspect: SUSPECT_WIKILINK },
|
|
4460
|
+
{ find: mdcFindings, suspect: SUSPECT_MDC },
|
|
4461
|
+
{ find: attributeListFindings("mdx"), suspect: SUSPECT_ATTRIBUTE_LIST },
|
|
4462
|
+
{
|
|
4463
|
+
find: voidElementFindings,
|
|
4464
|
+
suspect: /<(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)[\s/>]/u
|
|
4465
|
+
}
|
|
4466
|
+
]
|
|
4467
|
+
};
|
|
4468
|
+
var syntaxFindings = (text, format, pageRoute) => {
|
|
4469
|
+
const checks = CHECKS[format].filter((check) => check.suspect.test(text));
|
|
4470
|
+
if (checks.length === 0) {
|
|
4471
|
+
return [];
|
|
4472
|
+
}
|
|
4473
|
+
const masked = format === "mdx" ? maskMdx(text) : maskMarkdown(text);
|
|
4474
|
+
const scan = { pageRoute, starts: lineStartsOf(masked), text: masked };
|
|
4475
|
+
return checks.flatMap((check) => check.find(scan)).toSorted((a, b) => a.line - b.line || a.column - b.column);
|
|
4476
|
+
};
|
|
4477
|
+
var syntaxDiagnostics = (entry, sourceName, pages = new Map, locale = "") => {
|
|
4478
|
+
const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
|
|
4479
|
+
const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
4480
|
+
const pageRoute = (name) => {
|
|
4481
|
+
const named = pages.get(wikiKey(name));
|
|
4482
|
+
return (named?.find((other) => other.locale === locale) ?? named?.[0])?.route;
|
|
4483
|
+
};
|
|
4484
|
+
return syntaxFindings(entry.expanded?.text ?? entry.body.text, entry.body.format, pageRoute).map(({ line, ...finding }) => {
|
|
4485
|
+
const origin = entry.expanded?.origins[line - 1];
|
|
4486
|
+
return {
|
|
4487
|
+
...finding,
|
|
4488
|
+
file: origin?.file ?? page,
|
|
4489
|
+
line: origin?.line ?? line + offset,
|
|
4490
|
+
severity: "warning"
|
|
4491
|
+
};
|
|
4492
|
+
});
|
|
4493
|
+
};
|
|
4494
|
+
|
|
3596
4495
|
// src/core/project-graph.ts
|
|
3597
4496
|
var isWithin = (dir, path) => {
|
|
3598
4497
|
const rel = relative5(dir, path);
|
|
@@ -3631,16 +4530,25 @@ var entryIdDiagnostics = (pages, collectionBase) => {
|
|
|
3631
4530
|
}
|
|
3632
4531
|
return diagnostics;
|
|
3633
4532
|
};
|
|
3634
|
-
var normalizeLoadedEntries = (loaded, config) => {
|
|
4533
|
+
var normalizeLoadedEntries = (loaded, config, dropUnparsable) => {
|
|
3635
4534
|
const frontmatterExtend = Object.keys(config.frontmatter.extend).length > 0 ? config.frontmatter.extend : undefined;
|
|
3636
4535
|
const declaredTypes = Object.entries(config.content.types).filter(([, type]) => Object.keys(type.frontmatter).length > 0);
|
|
3637
4536
|
const typeFrontmatter = declaredTypes.length > 0 ? Object.fromEntries(declaredTypes.map(([name, type]) => [name, type.frontmatter])) : undefined;
|
|
3638
4537
|
const pages = [];
|
|
3639
4538
|
const allDiagnostics = [];
|
|
4539
|
+
const syntaxErrors = [];
|
|
4540
|
+
const unparsable = [];
|
|
4541
|
+
const published = [];
|
|
3640
4542
|
let droppedPages = 0;
|
|
3641
4543
|
for (const { source, entries, diagnostics } of loaded) {
|
|
3642
4544
|
allDiagnostics.push(...diagnostics);
|
|
3643
4545
|
for (const entry of entries) {
|
|
4546
|
+
const syntax = mdxSyntaxCheck(entry, source.name, config.variables);
|
|
4547
|
+
syntaxErrors.push(...syntax.diagnostics);
|
|
4548
|
+
const drop = syntax.unparsable && dropUnparsable;
|
|
4549
|
+
if (drop && entry.sourcePath && !source.staged) {
|
|
4550
|
+
unparsable.push(entry.sourcePath);
|
|
4551
|
+
}
|
|
3644
4552
|
const normalized = normalizeEntry(entry, {
|
|
3645
4553
|
basePath: config.basePath,
|
|
3646
4554
|
defaultType: config.content.defaultType,
|
|
@@ -3656,17 +4564,27 @@ var normalizeLoadedEntries = (loaded, config) => {
|
|
|
3656
4564
|
typeFrontmatter,
|
|
3657
4565
|
versions: config.versions
|
|
3658
4566
|
});
|
|
3659
|
-
|
|
4567
|
+
const [page] = normalized.pages;
|
|
4568
|
+
if (page ? drop : normalized.diagnostics.length > 0) {
|
|
3660
4569
|
droppedPages += 1;
|
|
3661
4570
|
}
|
|
3662
|
-
|
|
4571
|
+
if (!drop) {
|
|
4572
|
+
pages.push(...normalized.pages);
|
|
4573
|
+
}
|
|
3663
4574
|
allDiagnostics.push(...normalized.diagnostics);
|
|
3664
|
-
if (
|
|
3665
|
-
allDiagnostics.push(...
|
|
4575
|
+
if (page) {
|
|
4576
|
+
allDiagnostics.push(...directiveDiagnostics(entry, source.name), ...codeFenceDiagnostics(entry, source.name), ...alertDiagnostics(entry, source.name));
|
|
4577
|
+
published.push({ entry, locale: page.locale, source: source.name });
|
|
3666
4578
|
}
|
|
3667
4579
|
}
|
|
3668
4580
|
}
|
|
3669
|
-
|
|
4581
|
+
const names = indexPageNames(pages);
|
|
4582
|
+
for (const { entry, locale, source } of published) {
|
|
4583
|
+
allDiagnostics.push(...syntaxDiagnostics(entry, source, names, locale));
|
|
4584
|
+
}
|
|
4585
|
+
const covered = (diagnostic) => allDiagnostics.some((other) => other.file === diagnostic.file && other.line === diagnostic.line && (other.severity === "error" || other.severity === "warning" && diagnostic.severity === "warning"));
|
|
4586
|
+
allDiagnostics.push(...syntaxErrors.filter((error) => !covered(error)));
|
|
4587
|
+
return { diagnostics: allDiagnostics, droppedPages, pages, unparsable };
|
|
3670
4588
|
};
|
|
3671
4589
|
var gitContentRoots = (sources, gitRoot) => {
|
|
3672
4590
|
if (gitRoot === null) {
|
|
@@ -3701,19 +4619,31 @@ var substituteEntryVariables = (entry, variables) => {
|
|
|
3701
4619
|
return diagnostics;
|
|
3702
4620
|
};
|
|
3703
4621
|
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) => {
|
|
4622
|
+
var redirectPageDiagnostics = (config, pages, mirrored, configFile) => config.redirects.flatMap((redirect) => {
|
|
3705
4623
|
const from = withBasePath(config.basePath, redirect.from);
|
|
3706
4624
|
if (!isPatternPath(redirect.from)) {
|
|
3707
4625
|
const page = normalizePath(from);
|
|
3708
|
-
|
|
4626
|
+
if (pages.includes(page)) {
|
|
4627
|
+
return [
|
|
4628
|
+
{
|
|
4629
|
+
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
4630
|
+
file: configFile ?? undefined,
|
|
4631
|
+
message: `The redirect from ${redirect.from} is also the page ${page}, which never publishes: its URL redirects to ${redirect.to} instead.`,
|
|
4632
|
+
severity: "warning",
|
|
4633
|
+
suggestion: "If the page moved, delete it or give it a new slug; otherwise remove the redirect."
|
|
4634
|
+
}
|
|
4635
|
+
];
|
|
4636
|
+
}
|
|
4637
|
+
const owner = mirroredPage(mirrored, page);
|
|
4638
|
+
return owner === undefined ? [] : [
|
|
3709
4639
|
{
|
|
3710
4640
|
code: "BLUME_REDIRECT_MATCHES_PAGE",
|
|
3711
4641
|
file: configFile ?? undefined,
|
|
3712
|
-
message: `The redirect from ${redirect.from} is also the page ${
|
|
4642
|
+
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
4643
|
severity: "warning",
|
|
3714
|
-
suggestion: "
|
|
4644
|
+
suggestion: "Remove the redirect: that URL serves the page's Markdown to agents."
|
|
3715
4645
|
}
|
|
3716
|
-
]
|
|
4646
|
+
];
|
|
3717
4647
|
}
|
|
3718
4648
|
const matched = pathsUnderPattern(from, pages);
|
|
3719
4649
|
const [first] = matched;
|
|
@@ -3731,6 +4661,43 @@ var redirectPageDiagnostics = (config, pages, configFile) => config.redirects.fl
|
|
|
3731
4661
|
}
|
|
3732
4662
|
];
|
|
3733
4663
|
});
|
|
4664
|
+
var withSourceRedirects = (config, loaded, pages) => {
|
|
4665
|
+
const requested = loaded.flatMap(({ redirects }) => redirects ?? []);
|
|
4666
|
+
if (requested.length === 0) {
|
|
4667
|
+
return config;
|
|
4668
|
+
}
|
|
4669
|
+
const served = (route) => normalizePath(withBasePath(config.basePath, route));
|
|
4670
|
+
const prefixes = [
|
|
4671
|
+
"",
|
|
4672
|
+
...config.i18n?.locales.map((locale) => `/${locale.code}`) ?? []
|
|
4673
|
+
];
|
|
4674
|
+
const taken = new Set(config.redirects.map((redirect) => served(redirect.from)));
|
|
4675
|
+
const added = [];
|
|
4676
|
+
for (const redirect of requested) {
|
|
4677
|
+
for (const prefix of prefixes) {
|
|
4678
|
+
const from = `${prefix}${redirect.from}`;
|
|
4679
|
+
const to = `${prefix}${redirect.to}`;
|
|
4680
|
+
if (!pages.has(served(to)) || pages.has(served(from)) || taken.has(served(from))) {
|
|
4681
|
+
continue;
|
|
4682
|
+
}
|
|
4683
|
+
taken.add(served(from));
|
|
4684
|
+
added.push({ from, status: 301, to });
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return added.length > 0 ? { ...config, redirects: [...config.redirects, ...added] } : config;
|
|
4688
|
+
};
|
|
4689
|
+
var publicOpenApiDiagnostics = (config, root) => {
|
|
4690
|
+
const file = join8(root, "public", "openapi.json");
|
|
4691
|
+
return config.agents.api && existsSync3(file) ? [
|
|
4692
|
+
{
|
|
4693
|
+
code: "BLUME_PUBLIC_OPENAPI_JSON",
|
|
4694
|
+
file,
|
|
4695
|
+
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.",
|
|
4696
|
+
severity: "warning",
|
|
4697
|
+
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."
|
|
4698
|
+
}
|
|
4699
|
+
] : [];
|
|
4700
|
+
};
|
|
3734
4701
|
var bannerLinkHref = (banner) => banner === undefined || isBannerShorthand(banner) ? undefined : banner.link?.href;
|
|
3735
4702
|
var validatedSources = (config, context, runtime, beforeSources) => {
|
|
3736
4703
|
beforeSources?.(config);
|
|
@@ -3786,15 +4753,17 @@ var scanProject = async (root, options = {}) => {
|
|
|
3786
4753
|
});
|
|
3787
4754
|
}));
|
|
3788
4755
|
const variableDiagnostics = substituteLoadedVariables(loaded, config.variables);
|
|
3789
|
-
const
|
|
3790
|
-
...loaded.flatMap(({ folderMeta: sourceMeta }) => Object.entries(sourceMeta ?? {})),
|
|
3791
|
-
...folderMeta.shared
|
|
3792
|
-
]);
|
|
4756
|
+
const mergedFolderMeta = withGeneratedFolderMeta(folderMeta, new Map(loaded.flatMap(({ folderMeta: sourceMeta }) => Object.entries(sourceMeta ?? {}))), localeDirs);
|
|
3793
4757
|
const {
|
|
3794
4758
|
diagnostics: contentDiagnostics,
|
|
3795
4759
|
droppedPages,
|
|
3796
|
-
pages: allPages
|
|
3797
|
-
|
|
4760
|
+
pages: allPages,
|
|
4761
|
+
unparsable
|
|
4762
|
+
} = normalizeLoadedEntries(loaded, config, mode === "build");
|
|
4763
|
+
const metaDiagnostics = await folderMetaDiagnostics(folderMeta, allPages, {
|
|
4764
|
+
localeDirs,
|
|
4765
|
+
versionDirs: config.versions?.archived.map((version) => version.id)
|
|
4766
|
+
});
|
|
3798
4767
|
const pages = mode === "build" && !preview ? allPages.filter((page) => !page.meta.draft) : allPages;
|
|
3799
4768
|
const lastModified = resolveLastModifiedConfig(config.lastModified);
|
|
3800
4769
|
const lastModifiedWarnings = [];
|
|
@@ -3821,40 +4790,54 @@ var scanProject = async (root, options = {}) => {
|
|
|
3821
4790
|
basePath: config.basePath,
|
|
3822
4791
|
brandHref: logoHref(config.logo),
|
|
3823
4792
|
extraRoutes,
|
|
3824
|
-
folderMeta:
|
|
4793
|
+
folderMeta: mergedFolderMeta.meta,
|
|
3825
4794
|
i18n: config.i18n,
|
|
3826
4795
|
navigation: config.navigation,
|
|
3827
|
-
sharedFolderMeta,
|
|
4796
|
+
sharedFolderMeta: mergedFolderMeta.shared,
|
|
3828
4797
|
versions: config.versions
|
|
3829
4798
|
});
|
|
3830
4799
|
const manifest = buildManifest({ config, context, graph });
|
|
3831
4800
|
const i18nWarnings = config.i18n ? i18nDiagnostics(pages, config.i18n, config.versions) : [];
|
|
4801
|
+
const svgWarnings = await unmeasurableSvgDiagnostics(pages);
|
|
3832
4802
|
const versionWarnings = config.versions ? versionsDiagnostics(pages, config.versions) : [];
|
|
4803
|
+
const servedPaths = [
|
|
4804
|
+
...manifest.routes.map((route) => route.path),
|
|
4805
|
+
...extraRoutes
|
|
4806
|
+
];
|
|
4807
|
+
const redirects = [
|
|
4808
|
+
...config.redirects,
|
|
4809
|
+
...formerRouteRedirects(pages, new Set(servedPaths), config)
|
|
4810
|
+
];
|
|
3833
4811
|
return {
|
|
3834
|
-
config,
|
|
4812
|
+
config: withSourceRedirects(redirects.length === config.redirects.length ? config : { ...config, redirects }, loaded, new Set([...routeSetFor(manifest.routes), ...extraRoutes])),
|
|
3835
4813
|
context,
|
|
3836
4814
|
diagnostics: [
|
|
4815
|
+
...configResult.diagnostics,
|
|
3837
4816
|
...contentDiagnostics,
|
|
3838
4817
|
...includeDiagnostics,
|
|
3839
4818
|
...variableDiagnostics,
|
|
3840
4819
|
...folderMeta.diagnostics,
|
|
4820
|
+
...metaDiagnostics,
|
|
3841
4821
|
...entryIdDiagnostics(pages, resolveDocsCollection(config, context.root).base),
|
|
3842
4822
|
...graph.diagnostics,
|
|
3843
4823
|
...i18nWarnings,
|
|
3844
4824
|
...versionWarnings,
|
|
3845
4825
|
...lastModifiedWarnings,
|
|
3846
|
-
...
|
|
4826
|
+
...svgWarnings,
|
|
4827
|
+
...publicOpenApiDiagnostics(config, context.root),
|
|
4828
|
+
...redirectPageDiagnostics(config, servedPaths, routeSetFor(manifest.routes), context.configFile)
|
|
3847
4829
|
],
|
|
3848
4830
|
droppedPages,
|
|
3849
4831
|
graph,
|
|
3850
4832
|
manifest,
|
|
3851
4833
|
mode,
|
|
3852
4834
|
sources,
|
|
3853
|
-
themeFontsConfigured: configResult.themeFontsConfigured
|
|
4835
|
+
themeFontsConfigured: configResult.themeFontsConfigured,
|
|
4836
|
+
unparsable
|
|
3854
4837
|
};
|
|
3855
4838
|
};
|
|
3856
4839
|
|
|
3857
|
-
export { directiveSource, validateSearchPopularIcons, validateNavTargets,
|
|
4840
|
+
export { directiveSource, normalizeFence, QUOTED_ATTR, metaTokens, RESERVED_META_KEYWORDS, isForeignKeyword, isLineRange, validateSearchPopularIcons, validateMcpClientIcons, validateNavTargets, gitEnv, realPath, gitRepositoryRoot, locateMdxFailure, findFolderMetaFiles, isWithin, scanProject };
|
|
3858
4841
|
|
|
3859
|
-
//# debugId=
|
|
3860
|
-
//# sourceMappingURL=chunk-
|
|
4842
|
+
//# debugId=76845D03AD74A31664756E2164756E21
|
|
4843
|
+
//# sourceMappingURL=chunk-zxd5jrgx.js.map
|