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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The heading a page's title came from. A page without a frontmatter `title`
|
|
3
|
+
* takes the text of its first `#` heading as its title (`deriveTitle` in
|
|
4
|
+
* `core/sources/normalize.ts`), and the page template renders that title as
|
|
5
|
+
* the page's `<h1>`, so a page that opens with the heading showed it twice,
|
|
6
|
+
* one above the other. This Satteri hast plugin drops that opening `<h1>`
|
|
7
|
+
* from such a page. Only a Markdown heading with nothing rendered before it
|
|
8
|
+
* counts: a raw HTML or JSX `<h1>` isn't the scan's heading, and one after
|
|
9
|
+
* other content stays where it is. A page in a mode without page chrome
|
|
10
|
+
* (`custom`, `frame`) renders no title, so its heading stays too.
|
|
11
|
+
*
|
|
12
|
+
* It runs after `heading-anchors`, so every other heading keeps the id the
|
|
13
|
+
* scan gave it (`setup`, `setup-1`): only the title's own element goes. Its
|
|
14
|
+
* id doesn't: the anchor index lists it, and links to `#setup` still land,
|
|
15
|
+
* because the dropped heading's id goes out through the render's front matter
|
|
16
|
+
* under {@link TITLE_ID_KEY}, which the page template sets on its `<h1>`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { pageModeLayout } from "../core/page-modes.ts";
|
|
20
|
+
import type { PageMode } from "../core/page-modes.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Front matter key carrying the id of the heading this plugin dropped out of
|
|
24
|
+
* a render. The page template reads it back via `remarkPluginFrontmatter` and
|
|
25
|
+
* gives the title `<h1>` it renders in the heading's place that id.
|
|
26
|
+
*/
|
|
27
|
+
export const TITLE_ID_KEY = "blumeTitleId";
|
|
28
|
+
|
|
29
|
+
/** A minimal hast node (avoids a hast type dependency). */
|
|
30
|
+
interface HastNode {
|
|
31
|
+
children?: HastNode[];
|
|
32
|
+
/** An element's properties: a heading's `id` is the slug `heading-anchors` set. */
|
|
33
|
+
properties?: { id?: string };
|
|
34
|
+
type: string;
|
|
35
|
+
value?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The front matter keys this plugin reads and writes. */
|
|
39
|
+
interface TitleFrontmatter {
|
|
40
|
+
[TITLE_ID_KEY]?: string;
|
|
41
|
+
mode?: PageMode;
|
|
42
|
+
title?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The slice of Satteri's hast visitor context this plugin reads. */
|
|
46
|
+
interface TitleHeadingContext {
|
|
47
|
+
data?: { astro?: { frontmatter?: TitleFrontmatter } };
|
|
48
|
+
indexOf: (node: HastNode) => number | undefined;
|
|
49
|
+
parent: (node: HastNode) => HastNode | undefined;
|
|
50
|
+
removeNode: (node: HastNode) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A Satteri hast plugin, typed structurally to avoid a Satteri dep. */
|
|
54
|
+
export interface TitleHeadingPlugin {
|
|
55
|
+
before: (root: HastNode, ctx: TitleHeadingContext) => void;
|
|
56
|
+
name: string;
|
|
57
|
+
element: {
|
|
58
|
+
filter: string[];
|
|
59
|
+
visit: (node: HastNode, ctx: TitleHeadingContext) => void;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// An MDX expression that is only a comment, `{/* … */}`, and raw HTML that is
|
|
64
|
+
// only a comment, `<!-- … -->`.
|
|
65
|
+
const COMMENT_EXPRESSION = /^\s*\/\*[\s\S]*\*\/\s*$/u;
|
|
66
|
+
const HTML_COMMENT = /^\s*<!--[\s\S]*-->\s*$/u;
|
|
67
|
+
|
|
68
|
+
/** Whether a node renders nothing: whitespace, a comment, or an MDX import. */
|
|
69
|
+
const rendersNothing = (node: HastNode): boolean => {
|
|
70
|
+
switch (node.type) {
|
|
71
|
+
case "comment":
|
|
72
|
+
case "mdxjsEsm": {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
case "text": {
|
|
76
|
+
return (node.value ?? "").trim() === "";
|
|
77
|
+
}
|
|
78
|
+
case "mdxFlowExpression": {
|
|
79
|
+
return COMMENT_EXPRESSION.test(node.value ?? "");
|
|
80
|
+
}
|
|
81
|
+
case "raw": {
|
|
82
|
+
return HTML_COMMENT.test(node.value ?? "");
|
|
83
|
+
}
|
|
84
|
+
default: {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// The renders whose first `<h1>` has been seen. The plugin instance is shared
|
|
91
|
+
// across every page; the render-scoped `astro` data object is a stable,
|
|
92
|
+
// unique key for one render (as in `heading-anchors`).
|
|
93
|
+
const seen = new WeakSet<object>();
|
|
94
|
+
|
|
95
|
+
export const titleHeadingPlugin = (): TitleHeadingPlugin => ({
|
|
96
|
+
// A render of an entry Astro rendered before can get the front matter
|
|
97
|
+
// object that render wrote to, so a heading dropped then leaves no id.
|
|
98
|
+
before(_root, ctx) {
|
|
99
|
+
const frontmatter = ctx.data?.astro?.frontmatter;
|
|
100
|
+
if (frontmatter) {
|
|
101
|
+
Reflect.deleteProperty(frontmatter, TITLE_ID_KEY);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
element: {
|
|
105
|
+
filter: ["h1"],
|
|
106
|
+
visit(node, ctx) {
|
|
107
|
+
const astro = ctx.data?.astro;
|
|
108
|
+
const frontmatter = astro?.frontmatter;
|
|
109
|
+
if (
|
|
110
|
+
!(astro && frontmatter) ||
|
|
111
|
+
frontmatter.title ||
|
|
112
|
+
node.type !== "element" ||
|
|
113
|
+
seen.has(astro)
|
|
114
|
+
) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
seen.add(astro);
|
|
118
|
+
const parent = ctx.parent(node);
|
|
119
|
+
const before = parent?.children?.slice(0, ctx.indexOf(node) ?? 0) ?? [];
|
|
120
|
+
if (
|
|
121
|
+
parent?.type === "root" &&
|
|
122
|
+
before.every(rendersNothing) &&
|
|
123
|
+
pageModeLayout(frontmatter.mode).chrome
|
|
124
|
+
) {
|
|
125
|
+
const id = node.properties?.id;
|
|
126
|
+
if (id) {
|
|
127
|
+
frontmatter[TITLE_ID_KEY] = id;
|
|
128
|
+
}
|
|
129
|
+
ctx.removeNode(node);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
name: "blume:title-heading",
|
|
134
|
+
});
|
package/src/migrate/migrate.ts
CHANGED
|
@@ -19,17 +19,41 @@ export const MIGRATE_SOURCES = [
|
|
|
19
19
|
"docusaurus",
|
|
20
20
|
"starlight",
|
|
21
21
|
"nextra",
|
|
22
|
+
"vitepress",
|
|
23
|
+
"vuepress",
|
|
24
|
+
"docus",
|
|
25
|
+
"mkdocs",
|
|
26
|
+
"mdbook",
|
|
27
|
+
"fern",
|
|
28
|
+
"gitbook",
|
|
29
|
+
"redocly",
|
|
30
|
+
"readme",
|
|
31
|
+
"docsify",
|
|
32
|
+
"jekyll",
|
|
33
|
+
"github-wiki",
|
|
22
34
|
] as const;
|
|
23
35
|
|
|
24
36
|
export type MigrateSourceId = (typeof MIGRATE_SOURCES)[number];
|
|
25
37
|
|
|
26
38
|
/** Display names, for messages and the prompt. */
|
|
27
39
|
export const MIGRATE_SOURCE_NAMES: Record<MigrateSourceId, string> = {
|
|
40
|
+
docsify: "Docsify",
|
|
41
|
+
docus: "Docus",
|
|
28
42
|
docusaurus: "Docusaurus",
|
|
43
|
+
fern: "Fern",
|
|
29
44
|
fumadocs: "Fumadocs",
|
|
45
|
+
gitbook: "GitBook",
|
|
46
|
+
"github-wiki": "GitHub Wiki",
|
|
47
|
+
jekyll: "Jekyll",
|
|
48
|
+
mdbook: "mdBook",
|
|
30
49
|
mintlify: "Mintlify",
|
|
50
|
+
mkdocs: "MkDocs",
|
|
31
51
|
nextra: "Nextra",
|
|
52
|
+
readme: "ReadMe",
|
|
53
|
+
redocly: "Redocly",
|
|
32
54
|
starlight: "Starlight",
|
|
55
|
+
vitepress: "VitePress",
|
|
56
|
+
vuepress: "VuePress",
|
|
33
57
|
};
|
|
34
58
|
|
|
35
59
|
/** The skill's directory inside the published package. */
|
|
@@ -72,7 +96,9 @@ const dependencyNames = async (root: string): Promise<Set<string>> => {
|
|
|
72
96
|
|
|
73
97
|
/**
|
|
74
98
|
* Each source's tell, in the order the skill's workflow checks them: a config
|
|
75
|
-
* file the framework owns, or a dependency only it installs.
|
|
99
|
+
* file the framework owns, or a dependency only it installs. A GitHub wiki
|
|
100
|
+
* clone has neither (its tell is the `.wiki.git` remote), so `github-wiki` is
|
|
101
|
+
* named-only.
|
|
76
102
|
*/
|
|
77
103
|
const SIGNALS: {
|
|
78
104
|
deps?: string[];
|
|
@@ -96,6 +122,93 @@ const SIGNALS: {
|
|
|
96
122
|
},
|
|
97
123
|
{ deps: ["nextra"], source: "nextra" },
|
|
98
124
|
{ deps: ["@astrojs/starlight"], source: "starlight" },
|
|
125
|
+
// The config usually sits in docs/.vitepress, often with the dependency in
|
|
126
|
+
// docs/package.json rather than the root one.
|
|
127
|
+
{
|
|
128
|
+
deps: ["vitepress"],
|
|
129
|
+
files: [".vitepress", "docs/.vitepress"].flatMap((dir) =>
|
|
130
|
+
["ts", "mts", "js", "mjs"].map((ext) => `${dir}/config.${ext}`)
|
|
131
|
+
),
|
|
132
|
+
source: "vitepress",
|
|
133
|
+
},
|
|
134
|
+
// v1 also reads YAML and TOML configs; v2 also reads vuepress.config.* from
|
|
135
|
+
// the directory it runs in. vuepress-vite and vuepress-webpack were the v2
|
|
136
|
+
// beta wrappers.
|
|
137
|
+
{
|
|
138
|
+
deps: ["vuepress", "vuepress-vite", "vuepress-webpack"],
|
|
139
|
+
files: [
|
|
140
|
+
...[".vuepress", "docs/.vuepress"].flatMap((dir) =>
|
|
141
|
+
["ts", "js", "mjs", "yml", "toml"].map((ext) => `${dir}/config.${ext}`)
|
|
142
|
+
),
|
|
143
|
+
"vuepress.config.ts",
|
|
144
|
+
"vuepress.config.js",
|
|
145
|
+
"vuepress.config.mjs",
|
|
146
|
+
],
|
|
147
|
+
source: "vuepress",
|
|
148
|
+
},
|
|
149
|
+
// The Nuxt docs theme (not Docusaurus); 1.x shipped as @nuxt-themes/docus.
|
|
150
|
+
{ deps: ["docus", "@nuxt-themes/docus"], source: "docus" },
|
|
151
|
+
// MkDocs and the tools that build its projects (Zensical, ProperDocs). They
|
|
152
|
+
// come after the JavaScript frameworks, so a stale mkdocs.yml doesn't win.
|
|
153
|
+
{
|
|
154
|
+
files: [
|
|
155
|
+
"mkdocs.yml",
|
|
156
|
+
"mkdocs.yaml",
|
|
157
|
+
"zensical.toml",
|
|
158
|
+
"properdocs.yml",
|
|
159
|
+
"properdocs.yaml",
|
|
160
|
+
],
|
|
161
|
+
source: "mkdocs",
|
|
162
|
+
},
|
|
163
|
+
// A code repo usually keeps its book in a subfolder.
|
|
164
|
+
{
|
|
165
|
+
files: ["book.toml", "docs/book.toml", "book/book.toml"],
|
|
166
|
+
source: "mdbook",
|
|
167
|
+
},
|
|
168
|
+
// Not fern.config.json alone: plenty of repos run Fern only to generate SDKs.
|
|
169
|
+
{ files: ["fern/docs.yml"], source: "fern" },
|
|
170
|
+
// Not SUMMARY.md (mdBook and HonKit books have one too) or book.json (too
|
|
171
|
+
// generic a name).
|
|
172
|
+
{
|
|
173
|
+
deps: ["honkit", "gitbook-cli"],
|
|
174
|
+
files: ["gitbook-docs.yaml", ".gitbook.yaml", ".gitbook.yml"],
|
|
175
|
+
source: "gitbook",
|
|
176
|
+
},
|
|
177
|
+
// After the config-file sources, and never on redocly.yaml alone: that file
|
|
178
|
+
// is just as often Redocly CLI lint config in a repo built with another
|
|
179
|
+
// framework.
|
|
180
|
+
{
|
|
181
|
+
deps: [
|
|
182
|
+
"@redocly/realm",
|
|
183
|
+
"@redocly/redoc",
|
|
184
|
+
"@redocly/revel",
|
|
185
|
+
"@redocly/reef",
|
|
186
|
+
"@redocly/redoc-revel",
|
|
187
|
+
"@redocly/redoc-reef",
|
|
188
|
+
"@redocly/revel-reef",
|
|
189
|
+
],
|
|
190
|
+
files: ["sidebars.yaml"],
|
|
191
|
+
source: "redocly",
|
|
192
|
+
},
|
|
193
|
+
// ReadMe's bi-directional Git sync; repos that only push with the rdme CLI
|
|
194
|
+
// leave no tell, so those are named-only.
|
|
195
|
+
{
|
|
196
|
+
files: [
|
|
197
|
+
"docs/_order.yaml",
|
|
198
|
+
"reference/_order.yaml",
|
|
199
|
+
"recipes/_order.yaml",
|
|
200
|
+
"custom_pages/_order.yaml",
|
|
201
|
+
],
|
|
202
|
+
source: "readme",
|
|
203
|
+
},
|
|
204
|
+
// A Docsify site is an index.html that loads the library from a CDN, so only
|
|
205
|
+
// the ones with a manifest give themselves away. Not _sidebar.md: on a
|
|
206
|
+
// case-insensitive filesystem it also matches a GitHub wiki's _Sidebar.md.
|
|
207
|
+
{ deps: ["docsify-cli", "docsify"], source: "docsify" },
|
|
208
|
+
// Last: _config.yml is a generic name (Hexo and Jupyter Book use it too), so
|
|
209
|
+
// every more specific tell wins first, and the reference says how to spot
|
|
210
|
+
// the others.
|
|
211
|
+
{ files: ["_config.yml", "docs/_config.yml"], source: "jekyll" },
|
|
99
212
|
];
|
|
100
213
|
|
|
101
214
|
/**
|
|
@@ -120,6 +233,15 @@ export const detectMigrateSource = async (
|
|
|
120
233
|
return null;
|
|
121
234
|
};
|
|
122
235
|
|
|
236
|
+
/**
|
|
237
|
+
* The prompt's URL rule, where a source can't follow the default: a GitHub
|
|
238
|
+
* wiki's old URLs stay on github.com, where no redirect can reach them.
|
|
239
|
+
*/
|
|
240
|
+
const URL_RULES: Partial<Record<MigrateSourceId, string>> = {
|
|
241
|
+
"github-wiki":
|
|
242
|
+
"Keep every content page. The old wiki URLs stay on github.com and can't be redirected, so map them with the reference's route table and link stubs instead: add no `redirects`, and never push to the wiki; prepare the stubs for the user.",
|
|
243
|
+
};
|
|
244
|
+
|
|
123
245
|
/**
|
|
124
246
|
* The handoff prompt: follow the skill, where its mappings for this source
|
|
125
247
|
* are, and the few rules worth restating — the target version, and that no
|
|
@@ -139,11 +261,14 @@ export const migratePrompt = (options: {
|
|
|
139
261
|
const sourceLine = source
|
|
140
262
|
? `The source is ${MIGRATE_SOURCE_NAMES[source]}${evidence}. Its exact mappings are in ${join(skillDir, "references", `${source}.md`)}.`
|
|
141
263
|
: `The source framework wasn't detected, so inventory the repo first. If it's ${MIGRATE_SOURCES.map((id) => MIGRATE_SOURCE_NAMES[id]).join(", ")}, read that framework's file in ${join(skillDir, "references")}; otherwise follow the skill's mental model directly.`;
|
|
264
|
+
const urlRule =
|
|
265
|
+
(source && URL_RULES[source]) ??
|
|
266
|
+
"Keep every page, and add a redirect for any URL that moves.";
|
|
142
267
|
return `Migrate this documentation project to Blume ${version}.
|
|
143
268
|
|
|
144
269
|
Follow the blume-migrate skill: read ${join(skillDir, "SKILL.md")} first and work through its migration workflow. Wherever the skill says \`<skill>\`, it means ${skillDir}.
|
|
145
270
|
|
|
146
271
|
${sourceLine}
|
|
147
272
|
|
|
148
|
-
Add \`blume@^${version}\` as the docs package's dependency.
|
|
273
|
+
Add \`blume@^${version}\` as the docs package's dependency. ${urlRule} Report everything you drop or approximate, and run the skill's verification steps before you finish.`;
|
|
149
274
|
};
|
package/src/og/card.ts
CHANGED
|
@@ -52,9 +52,10 @@ export type OgFont =
|
|
|
52
52
|
export type OgGoogleFont = Exclude<OgFont, OgLocalFont>;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Which loaded family each card role renders in
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* Which loaded family each card role renders in, by plain family name (the
|
|
56
|
+
* card quotes it for the renderer). Takumi still falls back across every
|
|
57
|
+
* loaded font per glyph, so a family that misses a script degrades to the
|
|
58
|
+
* rest of the chain instead of tofu.
|
|
58
59
|
*/
|
|
59
60
|
export interface OgFontFamilies {
|
|
60
61
|
/** Family for the description and footer text. */
|
|
@@ -423,9 +424,18 @@ const titleSize = (title: string): number => {
|
|
|
423
424
|
return 76;
|
|
424
425
|
};
|
|
425
426
|
|
|
427
|
+
/**
|
|
428
|
+
* `family` as a quoted CSS string. Takumi parses `fontFamily` with the CSS
|
|
429
|
+
* grammar, where an unquoted family must be a run of identifiers, so a name
|
|
430
|
+
* with a token that starts with a digit ("Source Sans 3", "3270") fails the
|
|
431
|
+
* render. A quoted string accepts any name, escaped per CSS.
|
|
432
|
+
*/
|
|
433
|
+
const cssFamilyName = (family: string): string =>
|
|
434
|
+
`"${family.replaceAll(/["\\]/gu, "\\$&")}"`;
|
|
435
|
+
|
|
426
436
|
/** A spreadable `fontFamily` style, empty when no family is configured. */
|
|
427
437
|
const familyStyle = (family?: string): { fontFamily?: string } =>
|
|
428
|
-
family ? { fontFamily: family } : {};
|
|
438
|
+
family ? { fontFamily: cssFamilyName(family) } : {};
|
|
429
439
|
|
|
430
440
|
/** Render a 1200x630 Open Graph card to a PNG buffer. */
|
|
431
441
|
export const renderOgImage = async (
|
package/src/openapi/asyncapi.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ApiOperationRef,
|
|
3
|
+
ApiTagMeta,
|
|
4
|
+
CollectedOperations,
|
|
5
|
+
} from "./model.ts";
|
|
2
6
|
import { operationCollector, operationKey } from "./model.ts";
|
|
3
7
|
|
|
4
8
|
/**
|
|
@@ -285,10 +289,11 @@ const operationSite = (
|
|
|
285
289
|
return { action, address: channelAddress(channelId, channel), channelId };
|
|
286
290
|
};
|
|
287
291
|
|
|
288
|
-
/**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
+
/**
|
|
293
|
+
* The route-mapped operations, ordered tags, moved routes, and skip warnings
|
|
294
|
+
* of one document.
|
|
295
|
+
*/
|
|
296
|
+
export interface AsyncApiOperationCatalog extends CollectedOperations {
|
|
292
297
|
warnings: string[];
|
|
293
298
|
}
|
|
294
299
|
|
|
@@ -315,7 +320,10 @@ export const extractAsyncApiOperations = (
|
|
|
315
320
|
(tag): tag is { name: string; description?: string } =>
|
|
316
321
|
typeof tag?.name === "string"
|
|
317
322
|
)
|
|
318
|
-
.map((tag): [string,
|
|
323
|
+
.map((tag): [string, ApiTagMeta] => [
|
|
324
|
+
tag.name,
|
|
325
|
+
{ description: tag.description ?? "" },
|
|
326
|
+
])
|
|
319
327
|
);
|
|
320
328
|
const collector = operationCollector(baseRoute, tagMeta);
|
|
321
329
|
const channels = document.channels ?? {};
|
package/src/openapi/checks.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { OperationObject } from "@scalar/openapi-types/3.2";
|
|
2
2
|
|
|
3
|
+
import type { JsonValue } from "../core/adapter.ts";
|
|
4
|
+
import type { AsyncApiSpecValue } from "./asyncapi.ts";
|
|
3
5
|
import type { ApiDocument } from "./model.ts";
|
|
4
|
-
import { HTTP_METHODS } from "./model.ts";
|
|
6
|
+
import { HTTP_METHODS, operationLabel, withServerDefaults } from "./model.ts";
|
|
7
|
+
import { isJsonObject } from "./overlay.ts";
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Targeted checks for spec mistakes that would otherwise render silently
|
|
@@ -32,19 +35,6 @@ const NAMED_OPERATIONS = 3;
|
|
|
32
35
|
/** One entry of a document's `paths` or `webhooks` map. */
|
|
33
36
|
type PathItem = NonNullable<ApiDocument["paths"]>[string];
|
|
34
37
|
|
|
35
|
-
/**
|
|
36
|
-
* How a warning names an operation: `GET /pets`, or a webhook by its name
|
|
37
|
-
* (`Webhook "newPet" (POST)`), since a webhook has no path.
|
|
38
|
-
*/
|
|
39
|
-
export const operationLabel = (
|
|
40
|
-
name: string,
|
|
41
|
-
method: string,
|
|
42
|
-
webhook: boolean
|
|
43
|
-
): string =>
|
|
44
|
-
webhook
|
|
45
|
-
? `Webhook "${name}" (${method.toUpperCase()})`
|
|
46
|
-
: `${method.toUpperCase()} ${name}`;
|
|
47
|
-
|
|
48
38
|
/** One entry of a `parameters` list: a parameter, or a `$ref` to one. */
|
|
49
39
|
type ParameterEntry = NonNullable<OperationObject["parameters"]>[number];
|
|
50
40
|
|
|
@@ -209,6 +199,66 @@ const xWebhooksIssues = (document: ApiDocument): SpecIssue[] =>
|
|
|
209
199
|
]
|
|
210
200
|
: [];
|
|
211
201
|
|
|
202
|
+
/**
|
|
203
|
+
* A loopback or unspecified host (`localhost`, `127.0.0.1`, `0.0.0.0`,
|
|
204
|
+
* `[::1]`): one only the machine that wrote the spec can reach.
|
|
205
|
+
*/
|
|
206
|
+
const LOCAL_HOST =
|
|
207
|
+
/^(?:localhost|.+\.localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[::1?\])$/u;
|
|
208
|
+
|
|
209
|
+
/** One `servers` entry, as a hand-written spec may have written it. */
|
|
210
|
+
type ServerEntry = NonNullable<ApiDocument["servers"]>[number];
|
|
211
|
+
|
|
212
|
+
/** A server's URL, variables at their defaults, when its host is local. */
|
|
213
|
+
const localServerUrl = (server: ServerEntry): string | undefined => {
|
|
214
|
+
if (!isName(server.url)) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
// SAFETY: a parsed spec is JSON, and `withServerDefaults` checks the shape
|
|
218
|
+
// of the `variables` map it reads.
|
|
219
|
+
const variables = server.variables as AsyncApiSpecValue;
|
|
220
|
+
const url = withServerDefaults(server.url, variables);
|
|
221
|
+
try {
|
|
222
|
+
return LOCAL_HOST.test(new URL(url).hostname) ? url : undefined;
|
|
223
|
+
} catch {
|
|
224
|
+
// A relative URL (`/v1`) or an unresolved variable names no host.
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Servers the spec, a path, or an operation declares at a local address,
|
|
231
|
+
* which a framework exporting its spec from a dev machine writes (Laravel's
|
|
232
|
+
* Scramble writes `http://localhost/api` from an `.env`-less CI run). The
|
|
233
|
+
* reference publishes it as the server Try it sends to and the code samples
|
|
234
|
+
* call, which no reader can reach. One warning per URL.
|
|
235
|
+
*/
|
|
236
|
+
const localServerIssues = (document: ApiDocument): SpecIssue[] => {
|
|
237
|
+
const lists = [
|
|
238
|
+
document.servers,
|
|
239
|
+
...Object.values(document.paths ?? {}).flatMap((item) =>
|
|
240
|
+
isObject(item) && !("$ref" in item)
|
|
241
|
+
? [
|
|
242
|
+
item.servers,
|
|
243
|
+
...HTTP_METHODS.map((method) => {
|
|
244
|
+
const operation = item[method];
|
|
245
|
+
return isObject(operation) ? operation.servers : undefined;
|
|
246
|
+
}),
|
|
247
|
+
]
|
|
248
|
+
: []
|
|
249
|
+
),
|
|
250
|
+
];
|
|
251
|
+
const urls = new Set(
|
|
252
|
+
lists.flatMap((servers) => listOf(servers).map(localServerUrl))
|
|
253
|
+
);
|
|
254
|
+
return [...urls].filter(isName).map((url) => ({
|
|
255
|
+
code: "BLUME_OPENAPI_LOCAL_SERVER",
|
|
256
|
+
message: `The spec's server ${url} is a local address, so the reference publishes it as the server Try it sends requests to and the code samples call, which readers can't reach.`,
|
|
257
|
+
suggestion:
|
|
258
|
+
"List the API's public URL in the spec's `servers`, or replace it with an overlay (an action with `target: $.servers[0]` and `update: { url: \"https://api.example.com\" }`).",
|
|
259
|
+
}));
|
|
260
|
+
};
|
|
261
|
+
|
|
212
262
|
/**
|
|
213
263
|
* Check a parsed document for the mistakes above, and for the OpenAPI 3.2
|
|
214
264
|
* features Blume doesn't render yet. `$ref` path items are skipped: the
|
|
@@ -269,5 +319,100 @@ export const specIssues = (document: ApiDocument): SpecIssue[] => {
|
|
|
269
319
|
for (const [name, item] of Object.entries(document.webhooks ?? {})) {
|
|
270
320
|
visit(name, item, true);
|
|
271
321
|
}
|
|
272
|
-
return [
|
|
322
|
+
return [
|
|
323
|
+
...issues,
|
|
324
|
+
...securitySchemeIssues(document, requirers),
|
|
325
|
+
...localServerIssues(document),
|
|
326
|
+
];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/** The OpenAPI versions that have no `nullable` keyword: 3.1 and later. */
|
|
330
|
+
const NO_NULLABLE = /^3\.[1-9]/u;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Keys whose values are data rather than schemas, so a `nullable` inside
|
|
334
|
+
* one belongs to an example. Extensions (`x-…`) are skipped too.
|
|
335
|
+
*/
|
|
336
|
+
const DATA_KEYS = new Set(["const", "default", "enum", "example", "examples"]);
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Keys whose values map names to objects: the names aren't keywords, so a
|
|
340
|
+
* property named `nullable` isn't the keyword, and a `default` response
|
|
341
|
+
* isn't a default value.
|
|
342
|
+
*/
|
|
343
|
+
const NAME_MAPS = new Set([
|
|
344
|
+
"$defs",
|
|
345
|
+
"definitions",
|
|
346
|
+
"dependentSchemas",
|
|
347
|
+
"patternProperties",
|
|
348
|
+
"properties",
|
|
349
|
+
"responses",
|
|
350
|
+
"schemas",
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
/** A JSON Pointer reference token, `~` and `/` escaped (RFC 6901). */
|
|
354
|
+
const pointerToken = (key: string): string =>
|
|
355
|
+
key.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
356
|
+
|
|
357
|
+
const isBoolean = (value: JsonValue): value is boolean =>
|
|
358
|
+
typeof value === "boolean";
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Where `node` sets `nullable` to a boolean, as JSON Pointers from
|
|
362
|
+
* `pointer`. `names` says `node`'s keys are names (see {@link NAME_MAPS}).
|
|
363
|
+
*/
|
|
364
|
+
const nullablePointers = (
|
|
365
|
+
node: JsonValue,
|
|
366
|
+
pointer: string,
|
|
367
|
+
names: boolean
|
|
368
|
+
): string[] => {
|
|
369
|
+
if (Array.isArray(node)) {
|
|
370
|
+
return node.flatMap((item, index) =>
|
|
371
|
+
nullablePointers(item, `${pointer}/${index}`, false)
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (!isJsonObject(node)) {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
return Object.entries(node).flatMap(([key, value]) => {
|
|
378
|
+
const at = `${pointer}/${pointerToken(key)}`;
|
|
379
|
+
if (names) {
|
|
380
|
+
return nullablePointers(value, at, false);
|
|
381
|
+
}
|
|
382
|
+
if (key === "nullable" && isBoolean(value)) {
|
|
383
|
+
return [at];
|
|
384
|
+
}
|
|
385
|
+
return DATA_KEYS.has(key) || key.startsWith("x-")
|
|
386
|
+
? []
|
|
387
|
+
: nullablePointers(value, at, NAME_MAPS.has(key));
|
|
388
|
+
});
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* A spec written as OpenAPI 3.1 or later that still uses `nullable`, which
|
|
393
|
+
* 3.1 removed for `type: [T, "null"]`. Read as written (before Blume
|
|
394
|
+
* upgrades a 3.0 spec, which rewrites `nullable` itself), since that's the
|
|
395
|
+
* document the author edits. Blume still renders those values as nullable,
|
|
396
|
+
* but validators and SDK generators that follow 3.1 read them as never
|
|
397
|
+
* null. One warning per spec, naming the first few places.
|
|
398
|
+
*/
|
|
399
|
+
export const nullableIssues = (document: JsonValue): SpecIssue[] => {
|
|
400
|
+
const version =
|
|
401
|
+
isJsonObject(document) && isName(document.openapi) ? document.openapi : "";
|
|
402
|
+
if (!NO_NULLABLE.test(version)) {
|
|
403
|
+
return [];
|
|
404
|
+
}
|
|
405
|
+
const pointers = nullablePointers(document, "#", false);
|
|
406
|
+
if (pointers.length === 0) {
|
|
407
|
+
return [];
|
|
408
|
+
}
|
|
409
|
+
const more = pointers.length - NAMED_OPERATIONS;
|
|
410
|
+
return [
|
|
411
|
+
{
|
|
412
|
+
code: "BLUME_OPENAPI_NULLABLE",
|
|
413
|
+
message: `The spec is OpenAPI ${version}, which removed \`nullable\`, but sets it at ${pointers.slice(0, NAMED_OPERATIONS).join(", ")}${more > 0 ? ` and ${more} more` : ""}. Blume still shows those values as nullable, but tools that follow ${version}, like validators and SDK generators, read them as never null.`,
|
|
414
|
+
suggestion:
|
|
415
|
+
'Add "null" to the schema\'s `type` instead (`type: [string, "null"]`), or beside a `$ref`, use `anyOf` with `{ type: "null" }`. If the spec is really 3.0, declare `openapi: 3.0.3`, and Blume upgrades it.',
|
|
416
|
+
},
|
|
417
|
+
];
|
|
273
418
|
};
|