blume 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +133 -0
- package/README.md +4 -4
- package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
- package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
- package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
- package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
- package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
- package/dist/cli/chunk-2nr29y95.js.map +13 -0
- package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
- package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
- package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
- package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
- package/dist/cli/chunk-79bzzkx4.js.map +10 -0
- package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
- package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
- package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
- package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
- package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
- package/dist/cli/chunk-96snx8yy.js +329 -0
- package/dist/cli/chunk-96snx8yy.js.map +13 -0
- package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
- package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
- package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
- package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
- package/dist/cli/chunk-as09pafp.js.map +18 -0
- package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
- package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
- package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
- package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
- package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
- package/dist/cli/chunk-cn3dy3hz.js +146 -0
- package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
- package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
- package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
- package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
- package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
- package/dist/cli/chunk-dpcahcrb.js.map +10 -0
- package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
- package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
- package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
- package/dist/cli/chunk-enk76qd5.js.map +10 -0
- package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
- package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
- package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
- package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
- package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
- package/dist/cli/chunk-gprwej7h.js.map +11 -0
- package/dist/cli/chunk-kkrteb15.js +157 -0
- package/dist/cli/chunk-kkrteb15.js.map +10 -0
- package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
- package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
- package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
- package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
- package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
- package/dist/cli/chunk-rkqae7kh.js.map +10 -0
- package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
- package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
- package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
- package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
- package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
- package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
- package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
- package/dist/cli/chunk-wehv26ry.js +155 -0
- package/dist/cli/chunk-wehv26ry.js.map +11 -0
- package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
- package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
- package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
- package/dist/cli/index.js +78 -42
- package/dist/cli/index.js.map +7 -6
- package/dist/types/ai/changelog-components.d.ts +18 -0
- package/dist/types/ai/changelog-markdown.d.ts +12 -0
- package/dist/types/ai/openapi-components.d.ts +7 -7
- package/dist/types/ai/operation-details.d.ts +39 -0
- package/dist/types/ai/relative-links.d.ts +3 -1
- package/dist/types/ai/serializers.d.ts +6 -4
- package/dist/types/analytics/head.d.ts +4 -2
- package/dist/types/analytics/script.d.ts +8 -2
- package/dist/types/components/content/youtube.d.ts +20 -0
- package/dist/types/components/layout/nav-utils.d.ts +8 -0
- package/dist/types/components/openapi/example-panels.d.ts +33 -0
- package/dist/types/components/openapi/helpers.d.ts +53 -11
- package/dist/types/components/openapi/snippets.d.ts +7 -1
- package/dist/types/core/alert-diagnostics.d.ts +10 -0
- package/dist/types/core/builtin-tags.d.ts +16 -0
- package/dist/types/core/changelog-list.d.ts +48 -0
- package/dist/types/core/closest-match.d.ts +6 -0
- package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
- package/dist/types/core/code-fences.d.ts +8 -1
- package/dist/types/core/config-input.d.ts +1 -1
- package/dist/types/core/content-assets.d.ts +16 -18
- package/dist/types/core/diagnostics.d.ts +14 -0
- package/dist/types/core/directive-diagnostics.d.ts +22 -8
- package/dist/types/core/former-routes.d.ts +14 -0
- package/dist/types/core/github.d.ts +5 -4
- package/dist/types/core/heading-markers.d.ts +65 -2
- package/dist/types/core/hidden-pages.d.ts +3 -0
- package/dist/types/core/includes.d.ts +23 -0
- package/dist/types/core/last-modified.d.ts +18 -6
- package/dist/types/core/links.d.ts +42 -2
- package/dist/types/core/load-module.d.ts +19 -4
- package/dist/types/core/manifest.d.ts +14 -1
- package/dist/types/core/markdown-mirrors.d.ts +22 -0
- package/dist/types/core/mdx-elements.d.ts +32 -0
- package/dist/types/core/mdx-syntax.d.ts +100 -0
- package/dist/types/core/meta-diagnostics.d.ts +7 -0
- package/dist/types/core/meta.d.ts +32 -5
- package/dist/types/core/nav-diagnostics.d.ts +9 -2
- package/dist/types/core/navigation.d.ts +13 -0
- package/dist/types/core/ordering-prefix.d.ts +6 -0
- package/dist/types/core/pinned-ids.d.ts +23 -0
- package/dist/types/core/project-graph.d.ts +9 -1
- package/dist/types/core/redirect-patterns.d.ts +18 -0
- package/dist/types/core/relative-files.d.ts +31 -0
- package/dist/types/core/schema.d.ts +267 -7
- package/dist/types/core/sources/normalize.d.ts +101 -9
- package/dist/types/core/sources/types.d.ts +16 -0
- package/dist/types/core/static-files.d.ts +10 -0
- package/dist/types/core/static-host.d.ts +42 -0
- package/dist/types/core/svg-images.d.ts +26 -0
- package/dist/types/core/syntax-diagnostics.d.ts +14 -0
- package/dist/types/core/types.d.ts +42 -1
- package/dist/types/deploy/generated-files.d.ts +41 -0
- package/dist/types/deploy/redirects.d.ts +21 -7
- package/dist/types/markdown/code-title.d.ts +9 -5
- package/dist/types/markdown/directives.d.ts +8 -0
- package/dist/types/markdown/fence-language.d.ts +51 -0
- package/dist/types/markdown/fence-meta.d.ts +5 -0
- package/dist/types/markdown/github-alerts.d.ts +35 -0
- package/dist/types/openapi/asyncapi.d.ts +6 -5
- package/dist/types/openapi/checks.d.ts +10 -5
- package/dist/types/openapi/model.d.ts +55 -8
- package/dist/types/openapi/parse.d.ts +4 -1
- package/dist/types/sources/filesystem.d.ts +14 -4
- package/dist/types/sources/mdx-remote.d.ts +6 -3
- package/dist/types/sources/registry.d.ts +3 -3
- package/dist/types/theme/color.d.ts +33 -0
- package/dist/types/theme/palette.d.ts +122 -0
- package/docs/01-quickstart.mdx +1 -1
- package/docs/02-deployment.mdx +23 -8
- package/docs/04-migrating.mdx +16 -4
- package/docs/advanced/changelog.mdx +27 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +1 -1
- package/docs/cli/index.mdx +4 -2
- package/docs/cli/validate.mdx +10 -7
- package/docs/configuration/analytics.mdx +2 -0
- package/docs/configuration/customization.mdx +3 -1
- package/docs/configuration/index.mdx +2 -2
- package/docs/configuration/theming.mdx +5 -1
- package/docs/content/components.mdx +26 -6
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/includes.mdx +6 -2
- package/docs/content/index.mdx +11 -1
- package/docs/content/islands.mdx +2 -0
- package/docs/content/meta.mdx +9 -3
- package/docs/content/navigation.mdx +14 -6
- package/docs/content/sources/obsidian.mdx +1 -1
- package/docs/content/sources/remote-mdx.mdx +3 -1
- package/docs/content/syntax.mdx +76 -8
- package/docs/content/variables.mdx +3 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/json-api.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +1 -1
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/graphql.mdx +2 -0
- package/docs/references/openapi.mdx +45 -7
- package/package.json +10 -9
- package/skills/blume/SKILL.md +2 -2
- package/skills/blume-migrate/SKILL.md +49 -27
- package/skills/blume-migrate/references/docsify.md +262 -0
- package/skills/blume-migrate/references/docus.md +298 -0
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/fern.md +327 -0
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/gitbook.md +267 -0
- package/skills/blume-migrate/references/github-wiki.md +259 -0
- package/skills/blume-migrate/references/jekyll.md +253 -0
- package/skills/blume-migrate/references/mdbook.md +256 -0
- package/skills/blume-migrate/references/mintlify.md +5 -5
- package/skills/blume-migrate/references/mkdocs.md +236 -0
- package/skills/blume-migrate/references/monorepo.md +20 -8
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/readme.md +257 -0
- package/skills/blume-migrate/references/redocly.md +198 -0
- package/skills/blume-migrate/references/starlight.md +3 -3
- package/skills/blume-migrate/references/vitepress.md +204 -0
- package/skills/blume-migrate/references/vuepress.md +271 -0
- package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
- package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
- package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
- package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
- package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
- package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
- package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
- package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
- package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
- package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
- package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
- package/src/ai/changelog-components.ts +24 -0
- package/src/ai/changelog-markdown.ts +45 -41
- package/src/ai/component-markdown.ts +22 -9
- package/src/ai/llms.ts +36 -5
- package/src/ai/markdown.ts +2 -2
- package/src/ai/openapi-components.ts +25 -9
- package/src/ai/operation-details.ts +208 -0
- package/src/ai/relative-links.ts +48 -8
- package/src/ai/serializers.ts +7 -6
- package/src/ai/site-skill.ts +6 -2
- package/src/analytics/head.ts +11 -5
- package/src/analytics/script.ts +13 -3
- package/src/astro/generate.ts +32 -6
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +27 -1
- package/src/astro/mdx-source-errors.ts +46 -0
- package/src/astro/templates.ts +89 -185
- package/src/audit/checks/redirects.ts +5 -1
- package/src/audit/redirects.ts +36 -19
- package/src/audit/report.ts +22 -9
- package/src/audit/run.ts +12 -28
- package/src/audit/terms.ts +23 -0
- package/src/audit/url.ts +2 -2
- package/src/cli/build-failure.ts +256 -7
- package/src/cli/command-meta.ts +1 -1
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +12 -3
- package/src/cli/commands/check.ts +13 -2
- package/src/cli/commands/dev.ts +1 -1
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/init.ts +3 -3
- package/src/cli/commands/preview.ts +17 -1
- package/src/cli/commands/validate.ts +22 -10
- package/src/cli/prepare.ts +1 -1
- package/src/cli/preview-pages.ts +161 -0
- package/src/cli/unknown-flags.ts +1 -42
- package/src/components/code-copy.ts +51 -0
- package/src/components/code-language.ts +75 -0
- package/src/components/content/AccordionItem.astro +16 -1
- package/src/components/content/Callout.astro +11 -1
- package/src/components/content/Changelog.astro +189 -0
- package/src/components/content/CodeBlock.astro +8 -11
- package/src/components/content/Tabs.astro +16 -2
- package/src/components/content/YouTube.astro +6 -4
- package/src/components/content/code-block.ts +33 -0
- package/src/components/content/content-strings.ts +5 -1
- package/src/components/content/inline-markdown.ts +35 -8
- package/src/components/content/youtube.ts +49 -7
- package/src/components/layout/Analytics.astro +5 -1
- package/src/components/layout/NavTree.astro +8 -3
- package/src/components/layout/RootLayout.astro +18 -31
- package/src/components/layout/nav-utils.ts +14 -0
- package/src/components/layout/search/pagefind.ts +2 -1
- package/src/components/openapi/Operation.astro +4 -0
- package/src/components/openapi/PanelTabs.astro +9 -3
- package/src/components/openapi/RequestBody.astro +42 -8
- package/src/components/openapi/RequestPanel.astro +34 -53
- package/src/components/openapi/example-panels.ts +110 -0
- package/src/components/openapi/helpers.ts +195 -20
- package/src/components/openapi/operation-model.ts +63 -26
- package/src/components/openapi/request.ts +13 -0
- package/src/components/openapi/snippets.ts +14 -2
- package/src/core/alert-diagnostics.ts +89 -0
- package/src/core/builtin-tags.ts +76 -0
- package/src/core/changelog-list.ts +87 -0
- package/src/core/closest-match.ts +41 -0
- package/src/core/code-fence-diagnostics.ts +267 -0
- package/src/core/code-fences.ts +43 -7
- package/src/core/component-diagnostics.ts +65 -12
- package/src/core/config-input.ts +1 -1
- package/src/core/config.ts +46 -1
- package/src/core/content-assets.ts +64 -144
- package/src/core/diagnostics.ts +36 -3
- package/src/core/directive-diagnostics.ts +265 -47
- package/src/core/former-routes.ts +46 -0
- package/src/core/git-ignored.ts +53 -0
- package/src/core/github.ts +5 -4
- package/src/core/heading-markers.ts +156 -2
- package/src/core/hidden-pages.ts +64 -0
- package/src/core/includes.ts +181 -41
- package/src/core/last-modified.ts +47 -10
- package/src/core/links.ts +438 -83
- package/src/core/load-module.ts +98 -9
- package/src/core/manifest.ts +12 -9
- package/src/core/markdown-mirrors.ts +52 -0
- package/src/core/mdx-elements.ts +153 -0
- package/src/core/mdx-syntax.ts +377 -0
- package/src/core/meta-diagnostics.ts +134 -0
- package/src/core/meta.ts +154 -55
- package/src/core/nav-diagnostics.ts +16 -4
- package/src/core/navigation.ts +70 -10
- package/src/core/ordering-prefix.ts +22 -2
- package/src/core/pinned-ids.ts +141 -0
- package/src/core/project-graph.ts +221 -29
- package/src/core/redirect-patterns.ts +41 -0
- package/src/core/relative-files.ts +145 -0
- package/src/core/schema.ts +17 -3
- package/src/core/sources/mdx-remote.ts +75 -14
- package/src/core/sources/normalize.ts +885 -179
- package/src/core/sources/notion.ts +5 -4
- package/src/core/sources/obsidian.ts +2 -5
- package/src/core/sources/types.ts +17 -0
- package/src/core/static-files.ts +40 -0
- package/src/core/static-host.ts +125 -0
- package/src/core/svg-images.ts +98 -0
- package/src/core/syntax-diagnostics.ts +491 -0
- package/src/core/types.ts +43 -1
- package/src/deploy/artifacts.ts +2 -28
- package/src/deploy/generated-files.ts +128 -0
- package/src/deploy/platforms/vercel.ts +25 -1
- package/src/deploy/redirects.ts +43 -16
- package/src/deploy/sitemap.ts +2 -1
- package/src/markdown/base-links.ts +87 -6
- package/src/markdown/code-title.ts +29 -13
- package/src/markdown/content-assets.ts +210 -0
- package/src/markdown/directives.ts +69 -5
- package/src/markdown/fence-language.ts +106 -0
- package/src/markdown/fence-meta.ts +42 -0
- package/src/markdown/github-alerts.ts +172 -0
- package/src/markdown/heading-anchors.ts +265 -56
- package/src/markdown/include.ts +30 -3
- package/src/markdown/index.ts +145 -40
- package/src/markdown/prompt-text.ts +95 -0
- package/src/markdown/relative-links.ts +37 -2
- package/src/markdown/route-snapshot.ts +22 -12
- package/src/markdown/svg-fallback.ts +51 -0
- package/src/markdown/title-heading.ts +134 -0
- package/src/migrate/migrate.ts +127 -2
- package/src/og/card.ts +14 -4
- package/src/openapi/asyncapi.ts +14 -6
- package/src/openapi/checks.ts +160 -15
- package/src/openapi/model.ts +172 -33
- package/src/openapi/parse.ts +13 -4
- package/src/openapi/render-mdx.ts +55 -9
- package/src/openapi/source.ts +87 -6
- package/src/registry/eject.ts +0 -1
- package/src/search/documents.ts +10 -2
- package/src/sources/filesystem.ts +28 -3
- package/src/sources/mdx-remote.ts +7 -15
- package/src/theme/color.ts +14 -0
- package/src/theme/entry.ts +22 -1
- package/src/theme/palette.ts +74 -1
- package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
- package/dist/cli/chunk-0w6hs479.js.map +0 -10
- package/dist/cli/chunk-150jk7km.js +0 -52
- package/dist/cli/chunk-150jk7km.js.map +0 -10
- package/dist/cli/chunk-36bxs9c7.js.map +0 -10
- package/dist/cli/chunk-53jqw8z0.js +0 -89
- package/dist/cli/chunk-53jqw8z0.js.map +0 -10
- package/dist/cli/chunk-60hz265r.js.map +0 -10
- package/dist/cli/chunk-desvm72z.js.map +0 -14
- package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
- package/dist/cli/chunk-es8h0v3j.js +0 -441
- package/dist/cli/chunk-es8h0v3j.js.map +0 -11
- package/dist/cli/chunk-kdpzw38a.js.map +0 -11
- package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
- package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
- package/dist/cli/chunk-twy7psdz.js.map +0 -10
- package/dist/cli/chunk-ydp3065p.js +0 -64
- package/dist/cli/chunk-ydp3065p.js.map +0 -10
- /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
- /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
- /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
- /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
- /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
- /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
- /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
- /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
- /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
- /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
- /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
- /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
- /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
package/src/openapi/model.ts
CHANGED
|
@@ -6,8 +6,9 @@ import type {
|
|
|
6
6
|
AsyncApiDocument,
|
|
7
7
|
AsyncApiSpecValue,
|
|
8
8
|
} from "./asyncapi.ts";
|
|
9
|
-
// Type-only, so the import can't cycle at runtime (
|
|
10
|
-
//
|
|
9
|
+
// Type-only, so the import can't cycle at runtime (checks.ts and graphql.ts
|
|
10
|
+
// import from here).
|
|
11
|
+
import type { SpecIssue } from "./checks.ts";
|
|
11
12
|
import type { GraphqlDocument, GraphqlMember } from "./graphql.ts";
|
|
12
13
|
import type { ReferenceKind } from "./references.ts";
|
|
13
14
|
import { slugify } from "./references.ts";
|
|
@@ -294,6 +295,12 @@ const isText = <Value>(value: Value): value is Value & (string | number) =>
|
|
|
294
295
|
const specText = <Value>(value: Value): string | undefined =>
|
|
295
296
|
isText(value) ? String(value) : undefined;
|
|
296
297
|
|
|
298
|
+
/**
|
|
299
|
+
* A tag's slug before collisions are resolved, its word boundaries hyphenated
|
|
300
|
+
* the way an operation id's are (`InboxesThreads` -> `inboxes-threads`).
|
|
301
|
+
*/
|
|
302
|
+
const wordSlug = (name: string): string => slugify(splitIdentifier(name));
|
|
303
|
+
|
|
297
304
|
/**
|
|
298
305
|
* Assign each distinct tag name a unique slug. `slugify` can collapse
|
|
299
306
|
* different names onto one value — any two punctuation-only tags (`!!!`,
|
|
@@ -301,9 +308,13 @@ const specText = <Value>(value: Value): string | undefined =>
|
|
|
301
308
|
* silently merges the tags' routes, sidebar groups, and overview sections.
|
|
302
309
|
* Collisions gain `-2`, `-3`, … in first-seen order. Shared with the AsyncAPI
|
|
303
310
|
* extractor (`asyncapi.ts`), whose untagged fallback groups are channel
|
|
304
|
-
* addresses.
|
|
311
|
+
* addresses. `slug` is the rule before collisions: the collector also runs
|
|
312
|
+
* the one earlier releases used (`slugify` alone, so `InboxesThreads` ->
|
|
313
|
+
* `inboxesthreads`) to redirect the routes they gave.
|
|
305
314
|
*/
|
|
306
|
-
export const tagSlugger = (
|
|
315
|
+
export const tagSlugger = (
|
|
316
|
+
slug: (name: string) => string = wordSlug
|
|
317
|
+
): ((name: string) => string) => {
|
|
307
318
|
const assigned = new Map<string, string>();
|
|
308
319
|
const taken = new Set<string>();
|
|
309
320
|
return (name) => {
|
|
@@ -311,17 +322,30 @@ export const tagSlugger = (): ((name: string) => string) => {
|
|
|
311
322
|
if (existing) {
|
|
312
323
|
return existing;
|
|
313
324
|
}
|
|
314
|
-
const base =
|
|
315
|
-
let
|
|
316
|
-
for (let suffix = 2; taken.has(
|
|
317
|
-
|
|
325
|
+
const base = slug(name) || "operations";
|
|
326
|
+
let unique = base;
|
|
327
|
+
for (let suffix = 2; taken.has(unique); suffix += 1) {
|
|
328
|
+
unique = `${base}-${suffix}`;
|
|
318
329
|
}
|
|
319
|
-
taken.add(
|
|
320
|
-
assigned.set(name,
|
|
321
|
-
return
|
|
330
|
+
taken.add(unique);
|
|
331
|
+
assigned.set(name, unique);
|
|
332
|
+
return unique;
|
|
322
333
|
};
|
|
323
334
|
};
|
|
324
335
|
|
|
336
|
+
/**
|
|
337
|
+
* How a warning names an operation: `GET /pets`, or a webhook by its name
|
|
338
|
+
* (`Webhook "newPet" (POST)`), since a webhook has no path.
|
|
339
|
+
*/
|
|
340
|
+
export const operationLabel = (
|
|
341
|
+
name: string,
|
|
342
|
+
method: string,
|
|
343
|
+
webhook: boolean
|
|
344
|
+
): string =>
|
|
345
|
+
webhook
|
|
346
|
+
? `Webhook "${name}" (${method.toUpperCase()})`
|
|
347
|
+
: `${method.toUpperCase()} ${name}`;
|
|
348
|
+
|
|
325
349
|
/** One entry of a document's `paths` or `webhooks` map. */
|
|
326
350
|
type PathItemEntry =
|
|
327
351
|
| NonNullable<ApiDocument["paths"]>[string]
|
|
@@ -330,15 +354,39 @@ type PathItemEntry =
|
|
|
330
354
|
/** An operation before the collector assigns its unique key and route. */
|
|
331
355
|
type CollectedOperation = Omit<ApiOperationRef, "route" | "tagSlug">;
|
|
332
356
|
|
|
357
|
+
/** What a spec's `tags` list says about one tag. */
|
|
358
|
+
export interface ApiTagMeta {
|
|
359
|
+
description: string;
|
|
360
|
+
/**
|
|
361
|
+
* The tag's label, in place of its name, from Redocly's `x-displayName`
|
|
362
|
+
* extension. Only the label changes: the tag's slug, and so its routes,
|
|
363
|
+
* still come from its name.
|
|
364
|
+
*/
|
|
365
|
+
displayName?: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* A route an earlier release gave an operation's page, and the route the
|
|
370
|
+
* page has now: the source turns each into a redirect, so links to the old
|
|
371
|
+
* URL keep working.
|
|
372
|
+
*/
|
|
373
|
+
export interface MovedRoute {
|
|
374
|
+
from: string;
|
|
375
|
+
to: string;
|
|
376
|
+
}
|
|
377
|
+
|
|
333
378
|
/** The flattened output both extractors produce. */
|
|
334
379
|
export interface CollectedOperations {
|
|
335
380
|
operations: ApiOperationRef[];
|
|
336
381
|
tags: ApiTagRef[];
|
|
382
|
+
/** Operation routes that moved from what earlier slug rules gave them. */
|
|
383
|
+
moved: MovedRoute[];
|
|
337
384
|
}
|
|
338
385
|
|
|
339
386
|
/** The collector handle: feed operations in, read the flattened output out. */
|
|
340
387
|
export interface OperationCollector {
|
|
341
|
-
|
|
388
|
+
/** Add an operation, returning it with its key and route assigned. */
|
|
389
|
+
add: (entry: CollectedOperation) => ApiOperationRef;
|
|
342
390
|
finish: () => CollectedOperations;
|
|
343
391
|
}
|
|
344
392
|
|
|
@@ -353,18 +401,34 @@ export interface ExtractedOperations extends CollectedOperations {
|
|
|
353
401
|
* (a repeated key gains its method/action as a suffix), and the shared route
|
|
354
402
|
* template — so URL shape and slug rules can never drift between the two spec
|
|
355
403
|
* kinds.
|
|
404
|
+
*
|
|
405
|
+
* A key is unique across the spec, since it names the operation in
|
|
406
|
+
* `<Operation id>`. Its page's URL only has to be unique within its tag's
|
|
407
|
+
* folder, so the last route segment de-duplicates per tag: `list` in two
|
|
408
|
+
* tags is `/pets/list` and `/stores/list`, not `/stores/list-get`. Earlier
|
|
409
|
+
* releases de-duplicated the route like the key, and slugged tags without
|
|
410
|
+
* splitting their words; each route those rules gave differently is
|
|
411
|
+
* reported in `moved`.
|
|
356
412
|
*/
|
|
357
413
|
export const operationCollector = (
|
|
358
414
|
baseRoute: string,
|
|
359
|
-
tagMeta: ReadonlyMap<string,
|
|
415
|
+
tagMeta: ReadonlyMap<string, ApiTagMeta>
|
|
360
416
|
): OperationCollector => {
|
|
361
417
|
const operations: ApiOperationRef[] = [];
|
|
418
|
+
const moved: MovedRoute[] = [];
|
|
362
419
|
const tagOrder: string[] = [];
|
|
363
420
|
const tagsSeen = new Set<string>();
|
|
364
421
|
const seen = new Set<string>();
|
|
422
|
+
// Tag slug -> the route segments its operations took.
|
|
423
|
+
const segments = new Map<string, Set<string>>();
|
|
365
424
|
const slugForTag = tagSlugger();
|
|
425
|
+
const legacySlugForTag = tagSlugger(slugify);
|
|
426
|
+
// A root-mounted reference (`route: "/"`) must not emit `//tag/key`.
|
|
427
|
+
const base = baseRoute === "/" ? "" : baseRoute;
|
|
428
|
+
const label = (name: string): string =>
|
|
429
|
+
tagMeta.get(name)?.displayName ?? name;
|
|
366
430
|
|
|
367
|
-
const add = (entry: CollectedOperation):
|
|
431
|
+
const add = (entry: CollectedOperation): ApiOperationRef => {
|
|
368
432
|
const tagSlug = slugForTag(entry.tag);
|
|
369
433
|
if (!tagsSeen.has(entry.tag)) {
|
|
370
434
|
tagsSeen.add(entry.tag);
|
|
@@ -375,13 +439,27 @@ export const operationCollector = (
|
|
|
375
439
|
key = `${key}-${entry.method}`;
|
|
376
440
|
}
|
|
377
441
|
seen.add(key);
|
|
378
|
-
|
|
442
|
+
const taken = segments.get(tagSlug) ?? new Set<string>();
|
|
443
|
+
segments.set(tagSlug, taken);
|
|
444
|
+
let segment = entry.key;
|
|
445
|
+
while (taken.has(segment)) {
|
|
446
|
+
segment = `${segment}-${entry.method}`;
|
|
447
|
+
}
|
|
448
|
+
taken.add(segment);
|
|
449
|
+
const route = `${base}/${tagSlug}/${segment}`;
|
|
450
|
+
const legacy = `${base}/${legacySlugForTag(entry.tag)}/${key}`;
|
|
451
|
+
if (legacy !== route) {
|
|
452
|
+
moved.push({ from: legacy, to: route });
|
|
453
|
+
}
|
|
454
|
+
const operation: ApiOperationRef = {
|
|
379
455
|
...entry,
|
|
380
456
|
key,
|
|
381
|
-
|
|
382
|
-
|
|
457
|
+
route,
|
|
458
|
+
tag: label(entry.tag),
|
|
383
459
|
tagSlug,
|
|
384
|
-
}
|
|
460
|
+
};
|
|
461
|
+
operations.push(operation);
|
|
462
|
+
return operation;
|
|
385
463
|
};
|
|
386
464
|
|
|
387
465
|
// Declared tags in the order the spec's `tags` list gives them, then any
|
|
@@ -395,12 +473,13 @@ export const operationCollector = (
|
|
|
395
473
|
};
|
|
396
474
|
|
|
397
475
|
const finish = (): CollectedOperations => ({
|
|
476
|
+
moved,
|
|
398
477
|
operations,
|
|
399
478
|
tags: tagOrder
|
|
400
479
|
.toSorted((a, b) => rank(a) - rank(b))
|
|
401
480
|
.map((name) => ({
|
|
402
|
-
description: tagMeta.get(name) ?? "",
|
|
403
|
-
name,
|
|
481
|
+
description: tagMeta.get(name)?.description ?? "",
|
|
482
|
+
name: label(name),
|
|
404
483
|
// The same slugger instance, so every tag resolves to the slug its
|
|
405
484
|
// operations were routed under.
|
|
406
485
|
slug: slugForTag(name),
|
|
@@ -410,25 +489,52 @@ export const operationCollector = (
|
|
|
410
489
|
return { add, finish };
|
|
411
490
|
};
|
|
412
491
|
|
|
492
|
+
/** {@link ExtractedOperations} plus the spec mistakes extraction noticed. */
|
|
493
|
+
export interface ExtractedApiOperations extends ExtractedOperations {
|
|
494
|
+
issues: SpecIssue[];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** A spec's `tags` as the collector reads them: name -> description and label. */
|
|
498
|
+
const declaredTags = (document: ApiDocument): Map<string, ApiTagMeta> =>
|
|
499
|
+
new Map(
|
|
500
|
+
(document.tags ?? []).flatMap((tag): [string, ApiTagMeta][] => {
|
|
501
|
+
const name = specText(tag.name);
|
|
502
|
+
if (name === undefined) {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
const meta: ApiTagMeta = { description: tag.description ?? "" };
|
|
506
|
+
const displayName = specText(tag["x-displayName"]);
|
|
507
|
+
if (displayName) {
|
|
508
|
+
meta.displayName = displayName;
|
|
509
|
+
}
|
|
510
|
+
return [[name, meta]];
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
|
|
413
514
|
/**
|
|
414
515
|
* Flatten a 3.1 document into a route-mapped operation list and its ordered
|
|
415
516
|
* tags, webhooks included. Operations inherit the first tag they declare; keys
|
|
416
|
-
* are de-duplicated so a repeated `operationId` still yields distinct routes
|
|
417
|
-
* `warnings` reports anything skipped (a
|
|
418
|
-
* operations aren't silent.
|
|
517
|
+
* are de-duplicated so a repeated `operationId` still yields distinct routes,
|
|
518
|
+
* with an issue naming both operations. `warnings` reports anything skipped (a
|
|
519
|
+
* `$ref` path item), so missing operations aren't silent.
|
|
520
|
+
*
|
|
521
|
+
* Operations come out in the spec's order: paths as listed, and each path's
|
|
522
|
+
* methods as written. They're keyed in the method order earlier releases
|
|
523
|
+
* walked (`HTTP_METHODS`), so a repeated id resolves to the same key, and
|
|
524
|
+
* the same route, it always did.
|
|
419
525
|
*/
|
|
420
526
|
export const extractOperations = (
|
|
421
527
|
document: ApiDocument,
|
|
422
528
|
baseRoute: string
|
|
423
|
-
):
|
|
529
|
+
): ExtractedApiOperations => {
|
|
424
530
|
const warnings: string[] = [];
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
);
|
|
431
|
-
|
|
531
|
+
const issues: SpecIssue[] = [];
|
|
532
|
+
const collector = operationCollector(baseRoute, declaredTags(document));
|
|
533
|
+
// Each operation's place in the spec, and the first operation to declare
|
|
534
|
+
// each operationId.
|
|
535
|
+
const position = new Map<ApiOperationRef, number>();
|
|
536
|
+
const firstWithId = new Map<string, string>();
|
|
537
|
+
let cursor = 0;
|
|
432
538
|
|
|
433
539
|
// A path item's operations, filed under `name`: a path for `paths`, the
|
|
434
540
|
// webhook's key for `webhooks`.
|
|
@@ -449,6 +555,9 @@ export const extractOperations = (
|
|
|
449
555
|
);
|
|
450
556
|
return;
|
|
451
557
|
}
|
|
558
|
+
const written = Object.keys(item);
|
|
559
|
+
const offset = cursor;
|
|
560
|
+
cursor += written.length;
|
|
452
561
|
for (const method of HTTP_METHODS) {
|
|
453
562
|
const operation = item[method];
|
|
454
563
|
if (!isOperation(operation)) {
|
|
@@ -473,7 +582,28 @@ export const extractOperations = (
|
|
|
473
582
|
specText(operation.tags?.[0]) ??
|
|
474
583
|
(webhook ? UNTAGGED_WEBHOOKS : UNTAGGED),
|
|
475
584
|
};
|
|
476
|
-
collector.add(
|
|
585
|
+
const added = collector.add(
|
|
586
|
+
webhook ? { ...entry, webhook: true } : entry
|
|
587
|
+
);
|
|
588
|
+
position.set(added, offset + written.indexOf(method));
|
|
589
|
+
if (operationId === undefined) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const label = operationLabel(name, method, webhook);
|
|
593
|
+
const first = firstWithId.get(operationId);
|
|
594
|
+
if (first === undefined) {
|
|
595
|
+
firstWithId.set(operationId, label);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
// The key always gains a suffix; the URL only when the two share a tag.
|
|
599
|
+
const url = added.route.endsWith(`/${entry.key}`)
|
|
600
|
+
? ""
|
|
601
|
+
: ` and serves its page at ${added.route}`;
|
|
602
|
+
issues.push({
|
|
603
|
+
code: "BLUME_OPENAPI_DUPLICATE_OPERATION_ID",
|
|
604
|
+
message: `${first} and ${label} share the operationId "${operationId}", which must be unique in a spec, so Blume names the second one "${added.key}" in \`<Operation id>\`${url}.`,
|
|
605
|
+
suggestion: `Give each operation its own operationId.`,
|
|
606
|
+
});
|
|
477
607
|
}
|
|
478
608
|
};
|
|
479
609
|
|
|
@@ -486,7 +616,16 @@ export const extractOperations = (
|
|
|
486
616
|
addPathItem(name, item, true);
|
|
487
617
|
}
|
|
488
618
|
|
|
489
|
-
|
|
619
|
+
const collected = collector.finish();
|
|
620
|
+
return {
|
|
621
|
+
...collected,
|
|
622
|
+
issues,
|
|
623
|
+
// `position` holds every operation the collector added.
|
|
624
|
+
operations: collected.operations.toSorted(
|
|
625
|
+
(a, b) => (position.get(a) ?? 0) - (position.get(b) ?? 0)
|
|
626
|
+
),
|
|
627
|
+
warnings,
|
|
628
|
+
};
|
|
490
629
|
};
|
|
491
630
|
|
|
492
631
|
/** Resolve the operation object for a ref out of its (OpenAPI) document. */
|
package/src/openapi/parse.ts
CHANGED
|
@@ -15,11 +15,11 @@ import { nodeRequire } from "../core/node-require.ts";
|
|
|
15
15
|
import { hashText } from "../core/sources/cache.ts";
|
|
16
16
|
import type { AsyncApiDocument } from "./asyncapi.ts";
|
|
17
17
|
import { normalizeAsyncApiDocument } from "./asyncapi.ts";
|
|
18
|
-
import {
|
|
18
|
+
import { nullableIssues } from "./checks.ts";
|
|
19
19
|
import type { SpecIssue } from "./checks.ts";
|
|
20
20
|
import { buildGraphqlDocument } from "./graphql-build.ts";
|
|
21
21
|
import type { GraphqlDocument } from "./graphql.ts";
|
|
22
|
-
import { HTTP_METHODS } from "./model.ts";
|
|
22
|
+
import { HTTP_METHODS, operationLabel } from "./model.ts";
|
|
23
23
|
import type { ApiDocument } from "./model.ts";
|
|
24
24
|
import { applyOverlay, isJsonObject, overlayDocument } from "./overlay.ts";
|
|
25
25
|
import { SpecDependencyError } from "./spec-dependency-error.ts";
|
|
@@ -63,7 +63,10 @@ const PROXY_ENV_VARS = [
|
|
|
63
63
|
|
|
64
64
|
export interface ParsedSpec {
|
|
65
65
|
document: ApiDocument;
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Issues only the spec as written shows: `nullable` in a 3.1 spec, and
|
|
68
|
+
* `x-codeSamples` entries whose `$ref` couldn't be inlined.
|
|
69
|
+
*/
|
|
67
70
|
issues: SpecIssue[];
|
|
68
71
|
warnings: string[];
|
|
69
72
|
}
|
|
@@ -494,6 +497,8 @@ export const parseSpec = async (
|
|
|
494
497
|
root,
|
|
495
498
|
options
|
|
496
499
|
);
|
|
500
|
+
// Checked as written: upgrading a 3.0 spec rewrites its `nullable`s.
|
|
501
|
+
const nullable = isJsonObject(normalized) ? nullableIssues(normalized) : [];
|
|
497
502
|
const { specification } = upgrade(normalized);
|
|
498
503
|
// Reject a non-mapping here so the renderer never sees a non-document.
|
|
499
504
|
if (!isApiDocument(specification)) {
|
|
@@ -502,7 +507,11 @@ export const parseSpec = async (
|
|
|
502
507
|
);
|
|
503
508
|
}
|
|
504
509
|
const issues = await inlineSampleRefs(specification, spec, root, options);
|
|
505
|
-
return {
|
|
510
|
+
return {
|
|
511
|
+
document: specification,
|
|
512
|
+
issues: [...nullable, ...issues],
|
|
513
|
+
warnings,
|
|
514
|
+
};
|
|
506
515
|
};
|
|
507
516
|
|
|
508
517
|
export interface ParsedAsyncApiSpec {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { List, Nodes, Paragraph, Root } from "mdast";
|
|
1
|
+
import type { Heading, List, Nodes, Paragraph, Root } from "mdast";
|
|
2
2
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
3
3
|
import { toString as mdastToString } from "mdast-util-to-string";
|
|
4
4
|
import stringWidth from "string-width";
|
|
@@ -161,9 +161,55 @@ const closeOpenFence = (text: string): string => {
|
|
|
161
161
|
: `${text}\n${fence}`;
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
-
/**
|
|
165
|
-
const
|
|
166
|
-
|
|
164
|
+
/** The deepest heading Markdown can write. */
|
|
165
|
+
const MAX_HEADING_DEPTH = 6;
|
|
166
|
+
|
|
167
|
+
/** A line break inside a heading's text, with any quote or list indentation. */
|
|
168
|
+
const HEADING_BREAK = /[ \t]*\r?\n[\s>]*/gu;
|
|
169
|
+
|
|
170
|
+
/** Every heading in a Markdown tree, in document order. */
|
|
171
|
+
const headingsOf = (node: Nodes): Heading[] => {
|
|
172
|
+
if (node.type === "heading") {
|
|
173
|
+
return [node];
|
|
174
|
+
}
|
|
175
|
+
return "children" in node ? node.children.flatMap(headingsOf) : [];
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Spec prose with every heading `by` levels deeper, written as an ATX
|
|
180
|
+
* heading (`#` × depth, capped at 6). A spec's `info.description` is often a
|
|
181
|
+
* whole document under `# Introduction` and `# Authentication` headings, so
|
|
182
|
+
* on a page whose title is already its `<h1>` it would add more: the
|
|
183
|
+
* overview's description and an operation's sit under the title (`#` becomes
|
|
184
|
+
* `##`), and a tag's under its `##` section (`#` becomes `###`). Headings are
|
|
185
|
+
* found in the same `<`-masked parse as `mdxSafe`'s, since the emitted MDX
|
|
186
|
+
* has no HTML blocks to hide one in; a setext heading's lines join into one.
|
|
187
|
+
*/
|
|
188
|
+
const demoteHeadings = (text: string, by: number): string => {
|
|
189
|
+
let out = "";
|
|
190
|
+
let cursor = 0;
|
|
191
|
+
for (const heading of headingsOf(
|
|
192
|
+
fromMarkdown(text.replaceAll("<", HTML_MASK))
|
|
193
|
+
)) {
|
|
194
|
+
// fromMarkdown always stamps positions; 0 is an unreachable guard.
|
|
195
|
+
const start = heading.position?.start.offset ?? 0;
|
|
196
|
+
const from = heading.children[0]?.position?.start.offset ?? start;
|
|
197
|
+
const to = heading.children.at(-1)?.position?.end.offset ?? from;
|
|
198
|
+
const depth = Math.min(heading.depth + by, MAX_HEADING_DEPTH);
|
|
199
|
+
out += `${text.slice(cursor, start)}${"#".repeat(depth)} ${text
|
|
200
|
+
.slice(from, to)
|
|
201
|
+
.replace(HEADING_BREAK, " ")}`.trimEnd();
|
|
202
|
+
cursor = heading.position?.end.offset ?? start;
|
|
203
|
+
}
|
|
204
|
+
return out + text.slice(cursor);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Spec prose as MDX that is safe to follow with a component, its headings
|
|
209
|
+
* demoted `by` levels under the page's own (see {@link demoteHeadings}).
|
|
210
|
+
*/
|
|
211
|
+
const descriptionMdx = (text: string, by: number): string =>
|
|
212
|
+
mdxSafe(closeOpenFence(demoteHeadings(text.trim(), by)));
|
|
167
213
|
|
|
168
214
|
/**
|
|
169
215
|
* Frontmatter emitted for one operation or overview page. Boolean flags are
|
|
@@ -349,7 +395,7 @@ const operationDescription = (
|
|
|
349
395
|
/** Prepend a markdown description (if any) above a component invocation. */
|
|
350
396
|
const withDescription = (description: string, component: string): string =>
|
|
351
397
|
description.trim()
|
|
352
|
-
? `${descriptionMdx(description)}\n\n${component}`
|
|
398
|
+
? `${descriptionMdx(description, 1)}\n\n${component}`
|
|
353
399
|
: component;
|
|
354
400
|
|
|
355
401
|
export const operationMdx = (
|
|
@@ -397,9 +443,9 @@ export const operationMdx = (
|
|
|
397
443
|
if (reference?.noindex) {
|
|
398
444
|
seo.noindex = true;
|
|
399
445
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
};
|
|
446
|
+
// An operation without a summary is labeled as its page is titled, by its
|
|
447
|
+
// method and path: the path alone repeats for `GET /pets` and `POST /pets`.
|
|
448
|
+
const sidebar: RenderedPageData["sidebar"] = { label: title };
|
|
403
449
|
// GraphQL operation kinds badge like HTTP methods, but a type page's kind
|
|
404
450
|
// already heads its sidebar group ("Objects", "Enums", …) — an `OBJECT`
|
|
405
451
|
// badge on every row would only repeat it, so type pages get none. The
|
|
@@ -468,7 +514,7 @@ export const overviewMdx = (
|
|
|
468
514
|
continue;
|
|
469
515
|
}
|
|
470
516
|
const description = tag.description.trim()
|
|
471
|
-
? [descriptionMdx(tag.description)]
|
|
517
|
+
? [descriptionMdx(tag.description, 2)]
|
|
472
518
|
: [];
|
|
473
519
|
tagSections.push(
|
|
474
520
|
[
|
package/src/openapi/source.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SAMPLE_LANGUAGE_IDS,
|
|
3
|
+
unknownSampleLanguages,
|
|
4
|
+
} from "../components/openapi/snippets.ts";
|
|
1
5
|
import { withBasePath } from "../core/base-path.ts";
|
|
2
6
|
import matter from "../core/frontmatter.ts";
|
|
7
|
+
import { stripOrderingPrefix } from "../core/ordering-prefix.ts";
|
|
3
8
|
import type { FolderMeta } from "../core/schema.ts";
|
|
4
9
|
import { hashText } from "../core/sources/cache.ts";
|
|
5
10
|
import type {
|
|
@@ -7,6 +12,7 @@ import type {
|
|
|
7
12
|
SourceContext,
|
|
8
13
|
SourceEntry,
|
|
9
14
|
SourceLoadResult,
|
|
15
|
+
SourceRedirect,
|
|
10
16
|
} from "../core/sources/types.ts";
|
|
11
17
|
import type { Diagnostic } from "../core/types.ts";
|
|
12
18
|
import { extractAsyncApiOperations } from "./asyncapi.ts";
|
|
@@ -21,6 +27,7 @@ import type {
|
|
|
21
27
|
ApiOperationRef,
|
|
22
28
|
ApiSpecData,
|
|
23
29
|
ApiTagRef,
|
|
30
|
+
MovedRoute,
|
|
24
31
|
OpenApiData,
|
|
25
32
|
} from "./model.ts";
|
|
26
33
|
import {
|
|
@@ -136,6 +143,41 @@ const failureSuggestion = (
|
|
|
136
143
|
return "Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY.";
|
|
137
144
|
};
|
|
138
145
|
|
|
146
|
+
/** The adapter that configures each kind, as a warning names it. */
|
|
147
|
+
const ADAPTER_NAMES = {
|
|
148
|
+
asyncapi: "asyncapi()",
|
|
149
|
+
graphql: "graphql()",
|
|
150
|
+
openapi: "openapi()",
|
|
151
|
+
} satisfies Record<BlumeReferenceSource["kind"], string>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A warning for the `codeSamples` ids Blume generates no sample for, which
|
|
155
|
+
* the operation pages would otherwise leave out without a sign. AsyncAPI's
|
|
156
|
+
* ids name tools, matched against each operation's protocol binding, so only
|
|
157
|
+
* the HTTP kinds are checked here.
|
|
158
|
+
*/
|
|
159
|
+
const unknownLanguageDiagnostics = (
|
|
160
|
+
reference: BlumeReferenceSource
|
|
161
|
+
): Diagnostic[] => {
|
|
162
|
+
if (reference.kind === "asyncapi") {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const unknown = unknownSampleLanguages(reference.display.codeSamples);
|
|
166
|
+
if (unknown.length === 0) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
const names = unknown.map((id) => `"${id}"`).join(", ");
|
|
170
|
+
const one = unknown.length === 1;
|
|
171
|
+
return [
|
|
172
|
+
{
|
|
173
|
+
code: `${CODE_PREFIXES[reference.kind]}_UNKNOWN_CODE_SAMPLE`,
|
|
174
|
+
message: `\`codeSamples\` in ${ADAPTER_NAMES[reference.kind]} lists ${names}, which ${one ? "isn't a language" : "aren't languages"} Blume generates samples in, so ${one ? "it's" : "they're"} left out of every operation page.`,
|
|
175
|
+
severity: "warning",
|
|
176
|
+
suggestion: `Use the ids Blume generates (${SAMPLE_LANGUAGE_IDS.join(", ")}) or an alias the reference docs list, or show your own samples with \`x-codeSamples\` in the spec.`,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
};
|
|
180
|
+
|
|
139
181
|
const toEntry = (rendered: RenderedPage, ref: string): SourceEntry => {
|
|
140
182
|
const raw = matter.stringify(`${rendered.body}\n`, rendered.data);
|
|
141
183
|
return {
|
|
@@ -185,17 +227,31 @@ const specEntries = (
|
|
|
185
227
|
* not "Github V2"). Keys are the directories under the reference route, the
|
|
186
228
|
* same group paths `meta.ts` files use, so user-authored meta still
|
|
187
229
|
* overrides these.
|
|
230
|
+
*
|
|
231
|
+
* Each group lists its operation pages in `operations` order, the spec's own
|
|
232
|
+
* (see `extractOperations`), as the overview does, rather than letting the
|
|
233
|
+
* sidebar sort them by label. A page's nav key is its file name without an
|
|
234
|
+
* ordering prefix, as `meta.ts` `pages` entries are.
|
|
188
235
|
*/
|
|
189
236
|
const tagFolderMeta = (
|
|
190
237
|
spec: ApiSpecData,
|
|
191
238
|
tags: { slug: string; name: string }[],
|
|
239
|
+
operations: ApiOperationRef[],
|
|
192
240
|
groupLabel: string | undefined
|
|
193
241
|
): Record<string, FolderMeta> => {
|
|
194
242
|
const base = routeToRef(spec.route);
|
|
195
243
|
const meta: Record<string, FolderMeta> = Object.fromEntries(
|
|
196
244
|
tags.map((tag, order) => [
|
|
197
245
|
base ? `${base}/${tag.slug}` : tag.slug,
|
|
198
|
-
{
|
|
246
|
+
{
|
|
247
|
+
order,
|
|
248
|
+
pages: operations
|
|
249
|
+
.filter((operation) => operation.tagSlug === tag.slug)
|
|
250
|
+
.map((operation) =>
|
|
251
|
+
stripOrderingPrefix(operation.route.split("/").at(-1) ?? "")
|
|
252
|
+
),
|
|
253
|
+
title: tag.name,
|
|
254
|
+
},
|
|
199
255
|
])
|
|
200
256
|
);
|
|
201
257
|
// A root-mounted reference has no group of its own to name.
|
|
@@ -211,6 +267,8 @@ interface LoadedSpec {
|
|
|
211
267
|
entries: SourceEntry[];
|
|
212
268
|
/** Sidebar-group labels for the spec's tag directories. */
|
|
213
269
|
folderMeta: Record<string, FolderMeta>;
|
|
270
|
+
/** Redirects from the routes earlier releases gave its operations. */
|
|
271
|
+
redirects: SourceRedirect[];
|
|
214
272
|
/** Non-fatal notes from the load (e.g. an offline cache fallback). */
|
|
215
273
|
diagnostics: Diagnostic[];
|
|
216
274
|
}
|
|
@@ -221,6 +279,8 @@ interface ParsedReference {
|
|
|
221
279
|
warnings: string[];
|
|
222
280
|
operations: ApiOperationRef[];
|
|
223
281
|
tags: ApiTagRef[];
|
|
282
|
+
/** Operation routes earlier releases gave elsewhere (see `operationCollector`). */
|
|
283
|
+
moved: MovedRoute[];
|
|
224
284
|
extractWarnings: string[];
|
|
225
285
|
/** Spec mistakes that would render silently wrong (OpenAPI only). */
|
|
226
286
|
issues?: SpecIssue[];
|
|
@@ -241,6 +301,7 @@ const parseReference = async (
|
|
|
241
301
|
return {
|
|
242
302
|
document,
|
|
243
303
|
extractWarnings: extracted.warnings,
|
|
304
|
+
moved: extracted.moved,
|
|
244
305
|
operations: extracted.operations,
|
|
245
306
|
tags: extracted.tags,
|
|
246
307
|
warnings,
|
|
@@ -256,6 +317,7 @@ const parseReference = async (
|
|
|
256
317
|
return {
|
|
257
318
|
document,
|
|
258
319
|
extractWarnings: extracted.warnings,
|
|
320
|
+
moved: extracted.moved,
|
|
259
321
|
operations: extracted.operations,
|
|
260
322
|
tags: extracted.tags,
|
|
261
323
|
warnings,
|
|
@@ -270,7 +332,8 @@ const parseReference = async (
|
|
|
270
332
|
return {
|
|
271
333
|
document,
|
|
272
334
|
extractWarnings: extracted.warnings,
|
|
273
|
-
issues: [...issues, ...specIssues(document)],
|
|
335
|
+
issues: [...issues, ...specIssues(document), ...extracted.issues],
|
|
336
|
+
moved: extracted.moved,
|
|
274
337
|
operations: extracted.operations,
|
|
275
338
|
tags: extracted.tags,
|
|
276
339
|
warnings,
|
|
@@ -291,8 +354,15 @@ export const openApiSource = (
|
|
|
291
354
|
const kindLabel = KIND_LABELS[reference.kind];
|
|
292
355
|
const codePrefix = CODE_PREFIXES[reference.kind];
|
|
293
356
|
try {
|
|
294
|
-
const {
|
|
295
|
-
|
|
357
|
+
const {
|
|
358
|
+
document,
|
|
359
|
+
warnings,
|
|
360
|
+
operations,
|
|
361
|
+
tags,
|
|
362
|
+
moved,
|
|
363
|
+
extractWarnings,
|
|
364
|
+
issues,
|
|
365
|
+
} = await parseReference(reference, ctx);
|
|
296
366
|
const info = document.info ?? { title: reference.label, version: "" };
|
|
297
367
|
// The playground proxy resolves here, not client-side: `true` selects
|
|
298
368
|
// the built-in `/_api-proxy` route (mounted under the site `basePath`,
|
|
@@ -375,7 +445,8 @@ export const openApiSource = (
|
|
|
375
445
|
: []),
|
|
376
446
|
],
|
|
377
447
|
entries: specEntries(spec, operations, reference),
|
|
378
|
-
folderMeta: tagFolderMeta(spec, tags, reference.groupLabel),
|
|
448
|
+
folderMeta: tagFolderMeta(spec, tags, operations, reference.groupLabel),
|
|
449
|
+
redirects: moved,
|
|
379
450
|
slug: reference.slug,
|
|
380
451
|
spec,
|
|
381
452
|
};
|
|
@@ -408,8 +479,17 @@ export const openApiSource = (
|
|
|
408
479
|
severity: "warning" as const,
|
|
409
480
|
}))
|
|
410
481
|
);
|
|
482
|
+
// Display options belong to the adapter, which every one of its sources
|
|
483
|
+
// repeats: one warning per distinct message.
|
|
484
|
+
const languageWarnings = new Map(
|
|
485
|
+
references
|
|
486
|
+
.flatMap(unknownLanguageDiagnostics)
|
|
487
|
+
.map((diagnostic) => [diagnostic.message, diagnostic])
|
|
488
|
+
);
|
|
489
|
+
diagnostics.push(...languageWarnings.values());
|
|
411
490
|
const data: OpenApiData = {};
|
|
412
491
|
const folderMeta: Record<string, FolderMeta> = {};
|
|
492
|
+
const redirects: SourceRedirect[] = [];
|
|
413
493
|
for (const result of results) {
|
|
414
494
|
if ("severity" in result) {
|
|
415
495
|
diagnostics.push(result);
|
|
@@ -418,10 +498,11 @@ export const openApiSource = (
|
|
|
418
498
|
data[result.slug] = result.spec;
|
|
419
499
|
entries.push(...result.entries);
|
|
420
500
|
Object.assign(folderMeta, result.folderMeta);
|
|
501
|
+
redirects.push(...result.redirects);
|
|
421
502
|
diagnostics.push(...result.diagnostics);
|
|
422
503
|
}
|
|
423
504
|
parsed = data;
|
|
424
|
-
return { diagnostics, entries, folderMeta };
|
|
505
|
+
return { diagnostics, entries, folderMeta, redirects };
|
|
425
506
|
};
|
|
426
507
|
|
|
427
508
|
return {
|