blume 2.1.0 → 2.1.2
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/CHANGELOG.md +110 -0
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
- package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
- package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
- package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
- package/dist/cli/chunk-124fvmzq.js.map +220 -0
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
- package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
- package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
- package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
- package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
- package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
- package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
- package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
- package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
- package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
- package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
- package/dist/cli/chunk-60hz265r.js.map +10 -0
- package/dist/cli/chunk-74wfhv79.js +65 -0
- package/dist/cli/chunk-74wfhv79.js.map +10 -0
- package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
- package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
- package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
- package/dist/cli/chunk-ave1wcbr.js.map +14 -0
- package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
- package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
- package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
- package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
- package/dist/cli/chunk-ckrcm08h.js +324 -0
- package/dist/cli/chunk-ckrcm08h.js.map +14 -0
- package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
- package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
- package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
- package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
- package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
- package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
- package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
- package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
- package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
- package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
- package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
- package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
- package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
- package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
- package/dist/cli/chunk-j1x92mx9.js.map +10 -0
- package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
- package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
- package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
- package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
- package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
- package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
- package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
- package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
- package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
- package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
- package/dist/cli/chunk-twy7psdz.js.map +10 -0
- package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
- package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
- package/dist/cli/index.js +24 -19
- package/dist/cli/index.js.map +3 -3
- package/dist/types/ai/ask-context.d.ts +6 -2
- package/dist/types/ai/ask.d.ts +24 -5
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/analytics/clics.d.ts +37 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +18 -0
- package/dist/types/astro/integration.d.ts +11 -4
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +4 -1
- package/dist/types/components/content/api-page.d.ts +6 -1
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +22 -1
- package/dist/types/components/openapi/security.d.ts +10 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +37 -6
- package/dist/types/core/data.d.ts +4 -0
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/powered-by.d.ts +12 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +11 -3
- package/dist/types/core/schema.d.ts +228 -3
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +10 -2
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/notion.d.ts +8 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +17 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
- package/dist/types/deploy/node-headers.d.ts +12 -4
- package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +21 -2
- package/dist/types/deploy/redirects.d.ts +17 -1
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
- package/dist/types/narration/build.d.ts +34 -12
- package/dist/types/narration/provider.d.ts +96 -8
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +15 -4
- package/dist/types/openapi/parse.d.ts +3 -0
- package/dist/types/openapi/references.d.ts +16 -0
- package/dist/types/openapi/sentence.d.ts +11 -1
- package/dist/types/ratelimit/index.d.ts +1 -1
- package/dist/types/ratelimit/schema.d.ts +8 -0
- package/dist/types/ratelimit/upstash.d.ts +39 -14
- package/dist/types/reference/graphql.d.ts +76 -1
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +2 -2
- package/dist/types/reference/options.d.ts +37 -1
- package/dist/types/reference/schema.d.ts +84 -0
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/orama-index.d.ts +9 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/jsonld.d.ts +11 -6
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/notion.d.ts +8 -0
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +63 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +59 -13
- package/docs/03-upgrading.mdx +2 -1
- package/docs/04-migrating.mdx +9 -9
- package/docs/08-faq.mdx +12 -12
- package/docs/advanced/changelog.mdx +30 -2
- package/docs/advanced/custom-pages.mdx +1 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +30 -7
- package/docs/cli/doctor.mdx +6 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +19 -1
- package/docs/configuration/assistant.mdx +6 -2
- package/docs/configuration/customization.mdx +4 -2
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +4 -2
- package/docs/configuration/narration.mdx +43 -6
- package/docs/configuration/rate-limiting.mdx +23 -2
- package/docs/configuration/search.mdx +37 -14
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -3
- package/docs/content/i18n.mdx +2 -2
- package/docs/content/includes.mdx +1 -1
- package/docs/content/navigation.mdx +8 -6
- package/docs/content/sources/contentful.mdx +9 -4
- package/docs/content/sources/custom.mdx +7 -3
- package/docs/content/sources/github-releases.mdx +12 -2
- package/docs/content/sources/index.mdx +3 -3
- package/docs/content/sources/notion.mdx +17 -2
- package/docs/content/sources/obsidian.mdx +3 -1
- package/docs/content/sources/payload.mdx +5 -2
- package/docs/content/sources/remote-mdx.mdx +6 -0
- package/docs/content/sources/sanity.mdx +4 -2
- package/docs/content/sources/strapi.mdx +5 -2
- package/docs/content/syntax.mdx +2 -2
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +6 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/discoverability/structured-data.mdx +8 -6
- package/docs/references/api-pages.mdx +1 -1
- package/docs/references/asyncapi.mdx +5 -1
- package/docs/references/graphql.mdx +15 -1
- package/docs/references/openapi.mdx +64 -8
- package/package.json +9 -9
- package/skills/blume/SKILL.md +1 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
- package/skills/blume-migrate/references/docusaurus.md +4 -4
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/monorepo.md +5 -5
- package/skills/blume-migrate/references/nextra.md +8 -5
- package/skills/blume-migrate/references/starlight.md +9 -7
- package/src/ai/ask-context.ts +33 -3
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask.ts +24 -6
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +1 -1
- package/src/ai/cors.ts +12 -3
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +28 -4
- package/src/ai/relative-links.ts +4 -38
- package/src/analytics/clics.ts +58 -0
- package/src/analytics/head.ts +5 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +91 -56
- package/src/astro/integration.ts +72 -22
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +3 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/render-deps.ts +10 -5
- package/src/astro/runtime-deps.ts +23 -2
- package/src/astro/templates.ts +129 -81
- package/src/audit/catalog.ts +19 -3
- package/src/audit/checks/links.ts +14 -1
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/run.ts +3 -0
- package/src/audit/types.ts +2 -0
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +45 -12
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/init/scaffold.ts +9 -6
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +3 -2
- package/src/components/content/ApiPlayground.astro +6 -2
- package/src/components/content/api-field.ts +4 -1
- package/src/components/content/api-page.ts +31 -7
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +7 -4
- package/src/components/islands/hooks.ts +27 -1
- package/src/components/layout/NavTree.astro +49 -5
- package/src/components/layout/NavTreeScript.astro +18 -0
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageLayout.astro +13 -3
- package/src/components/layout/RootLayout.astro +27 -14
- package/src/components/layout/Search.astro +17 -3
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/analytics-client.ts +8 -8
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +42 -14
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +12 -0
- package/src/components/layout/search/typesense.ts +18 -6
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +24 -1
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +19 -2
- package/src/components/openapi/Responses.astro +1 -0
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +61 -5
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +77 -58
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +10 -0
- package/src/components/openapi/snippets.ts +5 -3
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/powered-by-middleware.ts +33 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +42 -6
- package/src/core/data.ts +2 -0
- package/src/core/diagnostics.ts +5 -0
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/includes.ts +1 -1
- package/src/core/links.ts +47 -17
- package/src/core/powered-by.ts +12 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +59 -18
- package/src/core/redirect-patterns.ts +36 -7
- package/src/core/schema.ts +26 -6
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/lower.ts +19 -3
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +227 -41
- package/src/core/sources/notion.ts +34 -10
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +18 -0
- package/src/core/ui-packs/ar.ts +1 -0
- package/src/core/ui-packs/bg.ts +1 -0
- package/src/core/ui-packs/bn.ts +1 -0
- package/src/core/ui-packs/ca.ts +1 -0
- package/src/core/ui-packs/cs.ts +1 -0
- package/src/core/ui-packs/da.ts +1 -0
- package/src/core/ui-packs/de.ts +1 -0
- package/src/core/ui-packs/el.ts +1 -0
- package/src/core/ui-packs/es.ts +1 -0
- package/src/core/ui-packs/fa.ts +1 -0
- package/src/core/ui-packs/fi.ts +1 -0
- package/src/core/ui-packs/fr.ts +1 -0
- package/src/core/ui-packs/he.ts +1 -0
- package/src/core/ui-packs/hi.ts +1 -0
- package/src/core/ui-packs/hr.ts +1 -0
- package/src/core/ui-packs/hu.ts +1 -0
- package/src/core/ui-packs/id.ts +1 -0
- package/src/core/ui-packs/it.ts +1 -0
- package/src/core/ui-packs/ja.ts +1 -0
- package/src/core/ui-packs/ko.ts +1 -0
- package/src/core/ui-packs/nl.ts +1 -0
- package/src/core/ui-packs/no.ts +1 -0
- package/src/core/ui-packs/pl.ts +1 -0
- package/src/core/ui-packs/pt-br.ts +1 -0
- package/src/core/ui-packs/pt.ts +1 -0
- package/src/core/ui-packs/ro.ts +1 -0
- package/src/core/ui-packs/ru.ts +1 -0
- package/src/core/ui-packs/sk.ts +1 -0
- package/src/core/ui-packs/sr.ts +1 -0
- package/src/core/ui-packs/sv.ts +1 -0
- package/src/core/ui-packs/th.ts +1 -0
- package/src/core/ui-packs/tr.ts +1 -0
- package/src/core/ui-packs/uk.ts +1 -0
- package/src/core/ui-packs/vi.ts +1 -0
- package/src/core/ui-packs/zh-tw.ts +1 -0
- package/src/core/ui-packs/zh.ts +1 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +2 -1
- package/src/deploy/cloudflare-negotiation.ts +36 -7
- package/src/deploy/headers.ts +15 -1
- package/src/deploy/node-headers.ts +32 -20
- package/src/deploy/platforms/cloudflare.ts +14 -9
- package/src/deploy/platforms/netlify.ts +2 -1
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +115 -17
- package/src/deploy/redirects.ts +66 -6
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +325 -103
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/base-links.ts +15 -14
- package/src/markdown/package-commands.ts +9 -8
- package/src/markdown/relative-links.ts +3 -2
- package/src/narration/build.ts +218 -40
- package/src/narration/provider.ts +89 -9
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +15 -3
- package/src/openapi/parse.ts +127 -2
- package/src/openapi/references.ts +43 -3
- package/src/openapi/render-mdx.ts +68 -8
- package/src/openapi/sentence.ts +29 -2
- package/src/openapi/source.ts +39 -14
- package/src/ratelimit/index.ts +1 -1
- package/src/ratelimit/runtime.ts +37 -19
- package/src/ratelimit/upstash.ts +40 -10
- package/src/reference/graphql.ts +15 -1
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +2 -2
- package/src/reference/options.ts +29 -1
- package/src/registry/eject.ts +8 -2
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/orama-index.ts +163 -31
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +99 -9
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/typesense.ts +72 -27
- package/src/seo/jsonld.ts +38 -20
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/notion.ts +7 -0
- package/src/sources/shared.ts +3 -0
- package/src/theme/color.ts +9 -10
- package/src/theme/entry.ts +43 -6
- package/src/translate/validate.ts +23 -1
- package/dist/cli/chunk-0gz36akr.js.map +0 -12
- package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
- package/dist/cli/chunk-43zcf4gz.js.map +0 -36
- package/dist/cli/chunk-4ejxbzee.js +0 -39
- package/dist/cli/chunk-4ejxbzee.js.map +0 -10
- package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
- package/dist/cli/chunk-713mngdq.js.map +0 -14
- package/dist/cli/chunk-9xr4dgee.js.map +0 -10
- package/dist/cli/chunk-amrb6hgh.js +0 -145
- package/dist/cli/chunk-amrb6hgh.js.map +0 -11
- package/dist/cli/chunk-nd1821sk.js.map +0 -10
- package/dist/cli/chunk-rekqz4qg.js.map +0 -10
- package/dist/cli/chunk-ta27feev.js.map +0 -10
- package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
- package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
- package/dist/cli/chunk-zf748avv.js.map +0 -37
- /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
- /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
- /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
- /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
- /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
- /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
- /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
- /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
- /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
- /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
- /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
- /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
- /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
- /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
- /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
- /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
- /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { isAbsolute, join } from "pathe";
|
|
2
|
+
|
|
1
3
|
import { normalizeRoute, withBasePath } from "../core/base-path.ts";
|
|
2
4
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
3
5
|
import { trimChar } from "../core/trim.ts";
|
|
6
|
+
import type { GraphqlAuthOptions } from "../reference/options.ts";
|
|
4
7
|
import type { ScalarOptions } from "../reference/scalar.ts";
|
|
5
8
|
import type { ResolvedReferenceAdapter } from "../reference/schema.ts";
|
|
6
9
|
|
|
@@ -54,6 +57,12 @@ export interface ReferenceSource {
|
|
|
54
57
|
*/
|
|
55
58
|
basePath: string;
|
|
56
59
|
label: string;
|
|
60
|
+
/**
|
|
61
|
+
* The source's own `label`, when it sets one: it names the sidebar group
|
|
62
|
+
* of the source's route. An unlabeled source's group keeps the name its
|
|
63
|
+
* route gives it.
|
|
64
|
+
*/
|
|
65
|
+
groupLabel?: string;
|
|
57
66
|
/** Whether generated pages are included in llms.txt/llms-full.txt. */
|
|
58
67
|
includeInLlms: boolean;
|
|
59
68
|
/** Whether generated pages are included in site search. */
|
|
@@ -77,6 +86,8 @@ export interface ReferenceSource {
|
|
|
77
86
|
* (GraphQL only — a schema, unlike an OpenAPI document, names no server).
|
|
78
87
|
*/
|
|
79
88
|
endpoint?: string;
|
|
89
|
+
/** How the GraphQL endpoint authenticates (GraphQL only). */
|
|
90
|
+
auth?: GraphqlAuthOptions;
|
|
80
91
|
/**
|
|
81
92
|
* The `scalar()` adapter's own options (`scalar` kind only): `theme` plus
|
|
82
93
|
* any Scalar config forwarded verbatim to `<ScalarComponent>`, which takes
|
|
@@ -181,6 +192,7 @@ const scalarOptionsOf = (
|
|
|
181
192
|
|
|
182
193
|
/** A source row with every kind's fields reconciled onto one shape. */
|
|
183
194
|
interface SourceRow {
|
|
195
|
+
auth?: GraphqlAuthOptions;
|
|
184
196
|
endpoint?: string;
|
|
185
197
|
includeInLlms: boolean;
|
|
186
198
|
includeInSearch: boolean;
|
|
@@ -194,9 +206,9 @@ interface SourceRow {
|
|
|
194
206
|
|
|
195
207
|
/**
|
|
196
208
|
* One row per source, with the kind-specific fields already reconciled: a
|
|
197
|
-
* GraphQL source's `endpoint`
|
|
198
|
-
* common single-schema case pairs
|
|
199
|
-
*
|
|
209
|
+
* GraphQL source's `endpoint` and `auth` fall back to the adapter-wide
|
|
210
|
+
* defaults (the common single-schema case pairs them with the `spec`
|
|
211
|
+
* shorthand); the other kinds have neither. A Scalar source carries only `noindex` of
|
|
200
212
|
* the per-source controls — the embed sits outside search and llms.txt, so
|
|
201
213
|
* the other two read as off.
|
|
202
214
|
*/
|
|
@@ -204,6 +216,7 @@ const sourceRowsOf = (adapter: ResolvedReferenceAdapter): SourceRow[] => {
|
|
|
204
216
|
if (adapter.kind === "graphql") {
|
|
205
217
|
return adapter.options.sources.map((source) => ({
|
|
206
218
|
...source,
|
|
219
|
+
auth: source.auth ?? adapter.options.auth,
|
|
207
220
|
endpoint: source.endpoint ?? adapter.options.endpoint,
|
|
208
221
|
}));
|
|
209
222
|
}
|
|
@@ -258,9 +271,15 @@ const referencesFor = (
|
|
|
258
271
|
if (adapter.kind === "scalar") {
|
|
259
272
|
reference.scalar = scalarOptionsOf(adapter.options);
|
|
260
273
|
}
|
|
274
|
+
if (source.label !== undefined) {
|
|
275
|
+
reference.groupLabel = source.label;
|
|
276
|
+
}
|
|
261
277
|
if (source.endpoint !== undefined) {
|
|
262
278
|
reference.endpoint = source.endpoint;
|
|
263
279
|
}
|
|
280
|
+
if (source.auth !== undefined) {
|
|
281
|
+
reference.auth = source.auth;
|
|
282
|
+
}
|
|
264
283
|
if (source.overlays !== undefined) {
|
|
265
284
|
reference.overlays = source.overlays;
|
|
266
285
|
}
|
|
@@ -348,6 +367,27 @@ export const blumeReferences = (
|
|
|
348
367
|
return result;
|
|
349
368
|
};
|
|
350
369
|
|
|
370
|
+
/** A spec or overlay the parser fetches rather than reads from disk. */
|
|
371
|
+
const REMOTE_SPEC = /^https?:\/\//u;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The local files every reference reads, as absolute paths: each spec and
|
|
375
|
+
* overlay that isn't an `http(s)` URL, resolved from the project root the way
|
|
376
|
+
* the parser resolves them. `blume dev` watches these, so an edit to a spec
|
|
377
|
+
* regenerates its reference like an edit to a page does.
|
|
378
|
+
*/
|
|
379
|
+
export const referenceSpecFiles = (
|
|
380
|
+
config: ResolvedConfig,
|
|
381
|
+
root: string
|
|
382
|
+
): string[] => [
|
|
383
|
+
...new Set(
|
|
384
|
+
resolveReferences(config)
|
|
385
|
+
.flatMap((ref) => [ref.spec, ...(ref.overlays ?? [])])
|
|
386
|
+
.filter((path) => !REMOTE_SPEC.test(path))
|
|
387
|
+
.map((path) => (isAbsolute(path) ? path : join(root, path)))
|
|
388
|
+
),
|
|
389
|
+
];
|
|
390
|
+
|
|
351
391
|
/** Whether any reference is Scalar-rendered (gates the Scalar pages). */
|
|
352
392
|
export const hasScalarReferences = (config: ResolvedConfig): boolean =>
|
|
353
393
|
resolveReferences(config).some((ref) => ref.kind === "scalar");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Nodes, Root } from "mdast";
|
|
1
|
+
import type { 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";
|
|
@@ -10,7 +10,7 @@ import type { GraphqlMember } from "./graphql.ts";
|
|
|
10
10
|
import { isGraphqlOperationKind } from "./graphql.ts";
|
|
11
11
|
import type { ApiOperationRef, ApiSpecData } from "./model.ts";
|
|
12
12
|
import type { ReferenceSource } from "./references.ts";
|
|
13
|
-
import { asSentence } from "./sentence.ts";
|
|
13
|
+
import { asSentence, closeSentence, endsInLeadIn } from "./sentence.ts";
|
|
14
14
|
import { operationSignature } from "./signature.ts";
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -193,6 +193,51 @@ export interface RenderedPage {
|
|
|
193
193
|
const META_DESCRIPTION_MAX = 160;
|
|
194
194
|
const WHITESPACE = /\s+/gu;
|
|
195
195
|
const TRAILING_WORD = /\s+\S*$/u;
|
|
196
|
+
// A list item's own closing mark, dropped where the items join into one line.
|
|
197
|
+
const ITEM_END = /[.;。;]$/u;
|
|
198
|
+
|
|
199
|
+
/** Markdown as single-line plain text. */
|
|
200
|
+
const flatten = (node: Nodes): string =>
|
|
201
|
+
mdastToString(node).replace(WHITESPACE, " ").trim();
|
|
202
|
+
|
|
203
|
+
/** The innermost last node of `node`: the one its text ends in. */
|
|
204
|
+
const lastLeaf = (node: Nodes): Nodes => {
|
|
205
|
+
const last = "children" in node ? node.children.at(-1) : undefined;
|
|
206
|
+
return last ? lastLeaf(last) : node;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Whether a paragraph ends in a lead-in colon. Only a colon in the prose
|
|
211
|
+
* counts: one closing inline code (`tenant:`) is part of the literal.
|
|
212
|
+
*/
|
|
213
|
+
const leadsIn = (paragraph: Paragraph): boolean => {
|
|
214
|
+
const leaf = lastLeaf(paragraph);
|
|
215
|
+
return leaf.type === "text" && endsInLeadIn(leaf.value);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/** A list as one line: each item's first paragraph, joined by semicolons. */
|
|
219
|
+
const listProse = (list: List): string =>
|
|
220
|
+
list.children
|
|
221
|
+
.map((item) => {
|
|
222
|
+
const paragraph = item.children.find(
|
|
223
|
+
(child) => child.type === "paragraph"
|
|
224
|
+
);
|
|
225
|
+
if (!paragraph) {
|
|
226
|
+
return "";
|
|
227
|
+
}
|
|
228
|
+
const text = flatten(paragraph);
|
|
229
|
+
// A closing mark in inline code (`SELECT 1;`) is part of the literal.
|
|
230
|
+
if (lastLeaf(paragraph).type !== "text") {
|
|
231
|
+
return text;
|
|
232
|
+
}
|
|
233
|
+
// A nested list's lead-in ("Modes:") closes like any other.
|
|
234
|
+
return (leadsIn(paragraph) ? asSentence(text) : text).replace(
|
|
235
|
+
ITEM_END,
|
|
236
|
+
""
|
|
237
|
+
);
|
|
238
|
+
})
|
|
239
|
+
.filter(Boolean)
|
|
240
|
+
.join("; ");
|
|
196
241
|
|
|
197
242
|
/**
|
|
198
243
|
* Flatten markdown prose to its first paragraph as single-line plain text,
|
|
@@ -200,12 +245,25 @@ const TRAILING_WORD = /\s+\S*$/u;
|
|
|
200
245
|
* was lossy on literal prose — `snake_case` → `snakecase`, `C#` → `C` — and
|
|
201
246
|
* these strings ship as `seo.description` meta tags. A description with no
|
|
202
247
|
* paragraph (say, only a heading or list) falls back to its first block.
|
|
248
|
+
*
|
|
249
|
+
* A first paragraph that leads in to a list ("Supports two modes:") is cut
|
|
250
|
+
* from what it introduces, so the list's items follow it on the same line. A
|
|
251
|
+
* lead-in to anything else, such as a code block, is closed as a sentence.
|
|
203
252
|
*/
|
|
204
253
|
const plainProse = (markdown: string): string => {
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
254
|
+
const { children } = fromMarkdown(markdown);
|
|
255
|
+
const index = children.findIndex((node) => node.type === "paragraph");
|
|
256
|
+
const paragraph = children[index];
|
|
257
|
+
if (paragraph?.type !== "paragraph") {
|
|
258
|
+
return children[0] ? flatten(children[0]) : "";
|
|
259
|
+
}
|
|
260
|
+
const text = flatten(paragraph);
|
|
261
|
+
if (!leadsIn(paragraph)) {
|
|
262
|
+
return text;
|
|
263
|
+
}
|
|
264
|
+
const next = children[index + 1];
|
|
265
|
+
const items = next?.type === "list" ? listProse(next) : "";
|
|
266
|
+
return items ? `${text} ${items}` : asSentence(text);
|
|
209
267
|
};
|
|
210
268
|
|
|
211
269
|
/** Cap `text` at `max` display columns, cutting on a word boundary. */
|
|
@@ -274,14 +332,16 @@ const operationDescription = (
|
|
|
274
332
|
subject = `${operation.method.toUpperCase()} ${operation.path} ${operation.webhook ? "webhook" : "endpoint"}`;
|
|
275
333
|
}
|
|
276
334
|
const suffix = `Reference for the ${subject} in the ${apiPhrase(spec)}.`;
|
|
277
|
-
// Room for the joining space and the period `
|
|
335
|
+
// Room for the joining space and the period `closeSentence` may add, so a
|
|
278
336
|
// title-like summary ("Get a flag") ends before the suffix begins.
|
|
279
337
|
const prose = clip(
|
|
280
338
|
plainProse(operation.description || operation.summary),
|
|
281
339
|
META_DESCRIPTION_MAX - stringWidth(suffix) - 2
|
|
282
340
|
);
|
|
341
|
+
// `plainProse` has already closed a lead-in colon, so one still at the end
|
|
342
|
+
// is a literal (`tenant:`) and keeps its period after it.
|
|
283
343
|
return clip(
|
|
284
|
-
[
|
|
344
|
+
[closeSentence(prose), suffix].filter(Boolean).join(" "),
|
|
285
345
|
META_DESCRIPTION_MAX
|
|
286
346
|
);
|
|
287
347
|
};
|
package/src/openapi/sentence.ts
CHANGED
|
@@ -4,11 +4,38 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const TERMINAL_PUNCTUATION = /[.!?…。!?]["'”’)\]]*$/u;
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* A colon closing the fragment introduces a list or block it was cut from
|
|
9
|
+
* ("Supports two modes:" before its bullets). The ASCII or full-width colon,
|
|
10
|
+
* with the space French typesetting puts before it ("Deux modes :").
|
|
11
|
+
*/
|
|
12
|
+
const LEAD_IN_COLON = /\s*[::]\s*$/u;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A trailing colon that belongs to a literal rather than the prose: an emoji
|
|
16
|
+
* shortcode (`:rocket:`) or a `Foo::` scope.
|
|
17
|
+
*/
|
|
18
|
+
const LITERAL_COLON = /(?:[::]{2}|:[\w+-]+:)\s*$/u;
|
|
19
|
+
|
|
20
|
+
/** Whether `text` ends in a colon that leads in to what follows it. */
|
|
21
|
+
export const endsInLeadIn = (text: string): boolean =>
|
|
22
|
+
LEAD_IN_COLON.test(text) && !LITERAL_COLON.test(text);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Close prose with a period unless it already ends a sentence. A colon at the
|
|
26
|
+
* end is kept and gains the period: use this when the caller has already told
|
|
27
|
+
* a lead-in apart from a literal (`tenant:` in inline code). Empty text stays
|
|
28
|
+
* empty.
|
|
29
|
+
*/
|
|
30
|
+
export const closeSentence = (text: string): string =>
|
|
31
|
+
text === "" || TERMINAL_PUNCTUATION.test(text) ? text : `${text}.`;
|
|
32
|
+
|
|
7
33
|
/**
|
|
8
34
|
* Close a fragment of spec prose as a sentence. OpenAPI `summary` values are
|
|
9
35
|
* usually title-like ("Get a flag", no period), so prose that runs straight
|
|
10
36
|
* into the next generated sentence ("Get a flag Reference for …") needs one.
|
|
11
|
-
*
|
|
37
|
+
* A lead-in colon becomes the period rather than gaining one after it, and
|
|
38
|
+
* prose that already ended before it ("e.g.:") keeps its own mark.
|
|
12
39
|
*/
|
|
13
40
|
export const asSentence = (text: string): string =>
|
|
14
|
-
|
|
41
|
+
closeSentence(endsInLeadIn(text) ? text.replace(LEAD_IN_COLON, "") : text);
|
package/src/openapi/source.ts
CHANGED
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
import type { Diagnostic } from "../core/types.ts";
|
|
12
12
|
import { extractAsyncApiOperations } from "./asyncapi.ts";
|
|
13
13
|
import type { AsyncApiDocument } from "./asyncapi.ts";
|
|
14
|
+
import { specIssues } from "./checks.ts";
|
|
15
|
+
import type { SpecIssue } from "./checks.ts";
|
|
14
16
|
import { extractGraphqlOperations } from "./graphql.ts";
|
|
15
17
|
import type { GraphqlDocument } from "./graphql.ts";
|
|
16
18
|
import { extractOperations } from "./model.ts";
|
|
@@ -173,24 +175,34 @@ const specEntries = (
|
|
|
173
175
|
};
|
|
174
176
|
|
|
175
177
|
/**
|
|
176
|
-
* Label each tag's sidebar group with the spec's own tag name
|
|
177
|
-
*
|
|
178
|
+
* Label each tag's sidebar group with the spec's own tag name, and order the
|
|
179
|
+
* groups as the overview does: the spec's declared tag order, then first use
|
|
180
|
+
* (see `operationCollector`), not alphabetically. The group label is
|
|
181
|
+
* otherwise re-humanized from the tag's route slug (split on hyphens,
|
|
178
182
|
* title-cased), which mangles authored casing and symbols — `OAuth2` →
|
|
179
|
-
* "Oauth2", `Größe` → "Größe" only by luck of the slug.
|
|
180
|
-
*
|
|
181
|
-
*
|
|
183
|
+
* "Oauth2", `Größe` → "Größe" only by luck of the slug. A source that names
|
|
184
|
+
* its own `label` gets it on its route's group the same way (`GitHub (v2)`,
|
|
185
|
+
* not "Github V2"). Keys are the directories under the reference route, the
|
|
186
|
+
* same group paths `meta.ts` files use, so user-authored meta still
|
|
187
|
+
* overrides these.
|
|
182
188
|
*/
|
|
183
189
|
const tagFolderMeta = (
|
|
184
190
|
spec: ApiSpecData,
|
|
185
|
-
tags: { slug: string; name: string }[]
|
|
191
|
+
tags: { slug: string; name: string }[],
|
|
192
|
+
groupLabel: string | undefined
|
|
186
193
|
): Record<string, FolderMeta> => {
|
|
187
194
|
const base = routeToRef(spec.route);
|
|
188
|
-
|
|
189
|
-
tags.map((tag) => [
|
|
195
|
+
const meta: Record<string, FolderMeta> = Object.fromEntries(
|
|
196
|
+
tags.map((tag, order) => [
|
|
190
197
|
base ? `${base}/${tag.slug}` : tag.slug,
|
|
191
|
-
{ title: tag.name },
|
|
198
|
+
{ order, title: tag.name },
|
|
192
199
|
])
|
|
193
200
|
);
|
|
201
|
+
// A root-mounted reference has no group of its own to name.
|
|
202
|
+
if (groupLabel !== undefined && base) {
|
|
203
|
+
meta[base] = { title: groupLabel };
|
|
204
|
+
}
|
|
205
|
+
return meta;
|
|
194
206
|
};
|
|
195
207
|
|
|
196
208
|
interface LoadedSpec {
|
|
@@ -210,6 +222,8 @@ interface ParsedReference {
|
|
|
210
222
|
operations: ApiOperationRef[];
|
|
211
223
|
tags: ApiTagRef[];
|
|
212
224
|
extractWarnings: string[];
|
|
225
|
+
/** Spec mistakes that would render silently wrong (OpenAPI only). */
|
|
226
|
+
issues?: SpecIssue[];
|
|
213
227
|
}
|
|
214
228
|
|
|
215
229
|
const parseReference = async (
|
|
@@ -247,7 +261,7 @@ const parseReference = async (
|
|
|
247
261
|
warnings,
|
|
248
262
|
};
|
|
249
263
|
}
|
|
250
|
-
const { document, warnings } = await parseSpec(
|
|
264
|
+
const { document, issues, warnings } = await parseSpec(
|
|
251
265
|
reference.spec,
|
|
252
266
|
ctx.projectRoot,
|
|
253
267
|
{ ...options, overlays: reference.overlays }
|
|
@@ -256,6 +270,7 @@ const parseReference = async (
|
|
|
256
270
|
return {
|
|
257
271
|
document,
|
|
258
272
|
extractWarnings: extracted.warnings,
|
|
273
|
+
issues: [...issues, ...specIssues(document)],
|
|
259
274
|
operations: extracted.operations,
|
|
260
275
|
tags: extracted.tags,
|
|
261
276
|
warnings,
|
|
@@ -276,7 +291,7 @@ export const openApiSource = (
|
|
|
276
291
|
const kindLabel = KIND_LABELS[reference.kind];
|
|
277
292
|
const codePrefix = CODE_PREFIXES[reference.kind];
|
|
278
293
|
try {
|
|
279
|
-
const { document, warnings, operations, tags, extractWarnings } =
|
|
294
|
+
const { document, warnings, operations, tags, extractWarnings, issues } =
|
|
280
295
|
await parseReference(reference, ctx);
|
|
281
296
|
const info = document.info ?? { title: reference.label, version: "" };
|
|
282
297
|
// The playground proxy resolves here, not client-side: `true` selects
|
|
@@ -318,11 +333,15 @@ export const openApiSource = (
|
|
|
318
333
|
title: info.title ?? reference.label,
|
|
319
334
|
version: info.version ?? "",
|
|
320
335
|
};
|
|
321
|
-
// Only GraphQL references carry a live endpoint (a schema
|
|
322
|
-
// server); assigned separately so the
|
|
336
|
+
// Only GraphQL references carry a live endpoint and its auth (a schema
|
|
337
|
+
// names no server); assigned separately so the keys stay absent
|
|
338
|
+
// otherwise.
|
|
323
339
|
if (reference.endpoint !== undefined) {
|
|
324
340
|
spec.endpoint = reference.endpoint;
|
|
325
341
|
}
|
|
342
|
+
if (reference.auth !== undefined) {
|
|
343
|
+
spec.auth = reference.auth;
|
|
344
|
+
}
|
|
326
345
|
return {
|
|
327
346
|
diagnostics: [
|
|
328
347
|
...warnings.map((message) => ({
|
|
@@ -335,6 +354,12 @@ export const openApiSource = (
|
|
|
335
354
|
message: `In ${kindLabel} spec "${reference.spec}": ${message}`,
|
|
336
355
|
severity: "warning" as const,
|
|
337
356
|
})),
|
|
357
|
+
...(issues ?? []).map((issue) => ({
|
|
358
|
+
code: issue.code,
|
|
359
|
+
message: `In ${kindLabel} spec "${reference.spec}": ${issue.message}`,
|
|
360
|
+
severity: "warning" as const,
|
|
361
|
+
suggestion: issue.suggestion,
|
|
362
|
+
})),
|
|
338
363
|
// A document with no operations (say, a config file that happens to
|
|
339
364
|
// parse as YAML) would otherwise build a nav tab onto an empty
|
|
340
365
|
// reference with no hint why.
|
|
@@ -350,7 +375,7 @@ export const openApiSource = (
|
|
|
350
375
|
: []),
|
|
351
376
|
],
|
|
352
377
|
entries: specEntries(spec, operations, reference),
|
|
353
|
-
folderMeta: tagFolderMeta(spec, tags),
|
|
378
|
+
folderMeta: tagFolderMeta(spec, tags, reference.groupLabel),
|
|
354
379
|
slug: reference.slug,
|
|
355
380
|
spec,
|
|
356
381
|
};
|
package/src/ratelimit/index.ts
CHANGED
|
@@ -27,4 +27,4 @@ export { memory } from "./memory.ts";
|
|
|
27
27
|
export type { MemoryAdapter, RateLimitOptions } from "./memory.ts";
|
|
28
28
|
export type { RateLimitAdapter } from "./schema.ts";
|
|
29
29
|
export { upstash } from "./upstash.ts";
|
|
30
|
-
export type { UpstashAdapter } from "./upstash.ts";
|
|
30
|
+
export type { UpstashAdapter, UpstashOptions } from "./upstash.ts";
|
package/src/ratelimit/runtime.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type { ClientContext } from "../core/client-address.ts";
|
|
|
16
16
|
import { RATE_LIMIT_BINDING } from "./cloudflare.ts";
|
|
17
17
|
import { DEFAULT_REQUESTS, DEFAULT_WINDOW } from "./memory.ts";
|
|
18
18
|
import type { RateLimitAdapter } from "./schema.ts";
|
|
19
|
-
import {
|
|
19
|
+
import { upstashSecrets } from "./upstash.ts";
|
|
20
20
|
|
|
21
21
|
/** What a limiter says about one request. */
|
|
22
22
|
export interface RateLimitResult {
|
|
@@ -86,16 +86,32 @@ export const memoryLimiter = (
|
|
|
86
86
|
};
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Counts the request and reads what's left of the window, run by Redis as
|
|
91
|
+
* one step. A key without an expiry — the one `INCR` just created, or one a
|
|
92
|
+
* failed call left behind — gets the window as its expiry, so a count can't
|
|
93
|
+
* outlive its window and lock a reader out of the route for good. Separate
|
|
94
|
+
* commands, even pipelined, let the key expire between them, and the `INCR`
|
|
95
|
+
* after that created a key that never expired.
|
|
96
|
+
*/
|
|
97
|
+
export const UPSTASH_COUNT_SCRIPT = `local count = redis.call("INCR", KEYS[1])
|
|
98
|
+
local ttl = redis.call("TTL", KEYS[1])
|
|
99
|
+
if ttl < 0 then
|
|
100
|
+
redis.call("EXPIRE", KEYS[1], ARGV[1])
|
|
101
|
+
ttl = tonumber(ARGV[1])
|
|
102
|
+
end
|
|
103
|
+
return {count, ttl}`;
|
|
104
|
+
|
|
105
|
+
/** Upstash's REST reply to one command. */
|
|
90
106
|
interface UpstashReply {
|
|
91
107
|
error?: string;
|
|
92
|
-
result?: number |
|
|
108
|
+
result?: (number | null)[] | null;
|
|
93
109
|
}
|
|
94
110
|
|
|
95
111
|
/**
|
|
96
|
-
* Count in Upstash Redis over its REST API: one
|
|
97
|
-
*
|
|
98
|
-
* the
|
|
112
|
+
* Count in Upstash Redis over its REST API: one `EVAL` of
|
|
113
|
+
* {@link UPSTASH_COUNT_SCRIPT} counts the request, opens the window when it's
|
|
114
|
+
* the first, and reads what's left of it.
|
|
99
115
|
*/
|
|
100
116
|
export const upstashLimiter = (
|
|
101
117
|
requests: number,
|
|
@@ -104,13 +120,15 @@ export const upstashLimiter = (
|
|
|
104
120
|
token: string,
|
|
105
121
|
fetchImpl: typeof fetch = fetch
|
|
106
122
|
): Limiter => {
|
|
107
|
-
const url =
|
|
123
|
+
const url = endpoint.replace(/\/+$/u, "");
|
|
108
124
|
return async (key) => {
|
|
109
125
|
const response = await fetchImpl(url, {
|
|
110
126
|
body: JSON.stringify([
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
"EVAL",
|
|
128
|
+
UPSTASH_COUNT_SCRIPT,
|
|
129
|
+
"1",
|
|
130
|
+
key,
|
|
131
|
+
String(window),
|
|
114
132
|
]),
|
|
115
133
|
headers: {
|
|
116
134
|
authorization: `Bearer ${token}`,
|
|
@@ -121,11 +139,12 @@ export const upstashLimiter = (
|
|
|
121
139
|
if (!response.ok) {
|
|
122
140
|
throw new Error(`Upstash answered ${response.status}.`);
|
|
123
141
|
}
|
|
124
|
-
// SAFETY: Upstash's
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
const
|
|
142
|
+
// SAFETY: Upstash's REST API answers a command with `{ result }`, here
|
|
143
|
+
// the script's `{count, ttl}`; a missing or failed reply reads as `NaN`
|
|
144
|
+
// below.
|
|
145
|
+
const reply = (await response.json()) as UpstashReply;
|
|
146
|
+
const count = Number(reply.result?.[0] ?? Number.NaN);
|
|
147
|
+
const ttl = Number(reply.result?.[1]);
|
|
129
148
|
if (Number.isNaN(count)) {
|
|
130
149
|
throw new TypeError("Upstash sent no count.");
|
|
131
150
|
}
|
|
@@ -175,14 +194,13 @@ export const createLimiter = (
|
|
|
175
194
|
);
|
|
176
195
|
}
|
|
177
196
|
if (adapter.kind === "upstash") {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
);
|
|
197
|
+
const secrets = upstashSecrets(adapter.options);
|
|
198
|
+
const [endpoint, token] = secrets.map((name) => runtime.secret?.(name));
|
|
181
199
|
if (endpoint && token) {
|
|
182
200
|
return upstashLimiter(requests, window, endpoint, token, runtime.fetch);
|
|
183
201
|
}
|
|
184
202
|
console.warn(
|
|
185
|
-
`Rate limiting counts in memory: set ${
|
|
203
|
+
`Rate limiting counts in memory: set ${secrets.join(" and ")} to share the count through Upstash.`
|
|
186
204
|
);
|
|
187
205
|
}
|
|
188
206
|
return memoryLimiter(requests, window, runtime.now);
|
package/src/ratelimit/upstash.ts
CHANGED
|
@@ -1,30 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
1
3
|
import type { AdapterDescriptor } from "../core/adapter.ts";
|
|
2
4
|
import { adapterDescriptorSchema } from "../core/adapter.ts";
|
|
3
5
|
import { rateLimitOptionsSchema } from "./memory.ts";
|
|
4
6
|
import type { RateLimitOptions } from "./memory.ts";
|
|
5
7
|
|
|
6
|
-
/** The env
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/** The env var holding the Upstash Redis REST endpoint, unless set. */
|
|
9
|
+
const UPSTASH_URL_ENV = "UPSTASH_REDIS_REST_URL";
|
|
10
|
+
|
|
11
|
+
/** The env var holding the endpoint's token, unless set. */
|
|
12
|
+
const UPSTASH_TOKEN_ENV = "UPSTASH_REDIS_REST_TOKEN";
|
|
13
|
+
|
|
14
|
+
/** Options for {@link upstash}. */
|
|
15
|
+
export interface UpstashOptions extends RateLimitOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Name of the env var holding the REST token. Defaults to
|
|
18
|
+
* `UPSTASH_REDIS_REST_TOKEN`; a database added from Vercel's Marketplace
|
|
19
|
+
* sets `KV_REST_API_TOKEN`.
|
|
20
|
+
*/
|
|
21
|
+
tokenEnv?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Name of the env var holding the REST endpoint. Defaults to
|
|
24
|
+
* `UPSTASH_REDIS_REST_URL`; a database added from Vercel's Marketplace
|
|
25
|
+
* sets `KV_REST_API_URL`.
|
|
26
|
+
*/
|
|
27
|
+
urlEnv?: string;
|
|
28
|
+
}
|
|
11
29
|
|
|
12
|
-
export
|
|
30
|
+
export const upstashOptionsSchema = rateLimitOptionsSchema.extend({
|
|
31
|
+
tokenEnv: z.string().min(1).optional(),
|
|
32
|
+
urlEnv: z.string().min(1).optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type UpstashAdapter = AdapterDescriptor<"upstash", UpstashOptions>;
|
|
13
36
|
|
|
14
37
|
export const upstashAdapterSchema = adapterDescriptorSchema(
|
|
15
38
|
"upstash",
|
|
16
|
-
|
|
39
|
+
upstashOptionsSchema
|
|
17
40
|
);
|
|
18
41
|
|
|
42
|
+
/** The env vars the endpoint and its token are read from, in that order. */
|
|
43
|
+
export const upstashSecrets = (options: UpstashOptions): [string, string] => [
|
|
44
|
+
options.urlEnv ?? UPSTASH_URL_ENV,
|
|
45
|
+
options.tokenEnv ?? UPSTASH_TOKEN_ENV,
|
|
46
|
+
];
|
|
47
|
+
|
|
19
48
|
/**
|
|
20
49
|
* Keeps the count in Upstash Redis, which every instance on every host shares,
|
|
21
50
|
* so the limit holds exactly. It talks to Upstash's REST API, so there is
|
|
22
51
|
* nothing to install; the endpoint and token come from
|
|
23
|
-
* `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN
|
|
52
|
+
* `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`, or the env vars
|
|
53
|
+
* `urlEnv` and `tokenEnv` name.
|
|
24
54
|
*/
|
|
25
|
-
export const upstash = (options:
|
|
55
|
+
export const upstash = (options: UpstashOptions = {}): UpstashAdapter => ({
|
|
26
56
|
kind: "upstash",
|
|
27
57
|
options,
|
|
28
|
-
requiredSecrets:
|
|
58
|
+
requiredSecrets: upstashSecrets(options),
|
|
29
59
|
runtimeDeps: [],
|
|
30
60
|
});
|
package/src/reference/graphql.ts
CHANGED
|
@@ -3,16 +3,28 @@ import { z } from "zod";
|
|
|
3
3
|
import type { AdapterDescriptor } from "../core/adapter.ts";
|
|
4
4
|
import { adapterDescriptorSchema } from "../core/adapter.ts";
|
|
5
5
|
import {
|
|
6
|
+
graphqlAuthSchema,
|
|
6
7
|
graphqlSourceSchema,
|
|
7
8
|
liftSpec,
|
|
8
9
|
hasSources,
|
|
9
10
|
missingSourcesIssue,
|
|
10
11
|
sharedOptions,
|
|
11
12
|
} from "./options.ts";
|
|
12
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
GraphqlAuthOptions,
|
|
15
|
+
GraphqlSourceOptions,
|
|
16
|
+
PlaygroundOptions,
|
|
17
|
+
} from "./options.ts";
|
|
13
18
|
|
|
14
19
|
/** Options for {@link graphql}. */
|
|
15
20
|
export interface GraphqlOptions {
|
|
21
|
+
/**
|
|
22
|
+
* How the endpoint authenticates, like the site's `api.auth`: `bearer`,
|
|
23
|
+
* `basic`, or `key` (sent in the `name` header, `x-api-key` by default).
|
|
24
|
+
* The Try it panel gets a field for the credential and the code samples
|
|
25
|
+
* send a placeholder. Applies to every source; a per-source `auth` wins.
|
|
26
|
+
*/
|
|
27
|
+
auth?: GraphqlAuthOptions;
|
|
16
28
|
/**
|
|
17
29
|
* Code-sample languages shown per operation, in order. Defaults to
|
|
18
30
|
* `["curl", "js", "python"]`; `false` shows none.
|
|
@@ -52,6 +64,8 @@ export const graphqlOptionsSchema = z
|
|
|
52
64
|
codeSamples: ["curl", "js", "python"],
|
|
53
65
|
route: "/graphql",
|
|
54
66
|
}),
|
|
67
|
+
/** Default endpoint auth for every source (per-source `auth` wins). */
|
|
68
|
+
auth: graphqlAuthSchema.optional(),
|
|
55
69
|
/** Default live endpoint URL for every source (per-source `endpoint` wins). */
|
|
56
70
|
endpoint: z.string().optional(),
|
|
57
71
|
/** One or more schemas; each renders on its own route by default. */
|
package/src/reference/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type { GraphqlAdapter, GraphqlOptions } from "./graphql.ts";
|
|
|
27
27
|
export { openapi } from "./openapi.ts";
|
|
28
28
|
export type { OpenApiAdapter, OpenApiOptions } from "./openapi.ts";
|
|
29
29
|
export type {
|
|
30
|
+
GraphqlAuthOptions,
|
|
30
31
|
GraphqlSourceOptions,
|
|
31
32
|
PlaygroundOptions,
|
|
32
33
|
ReferenceSourceOptions,
|
package/src/reference/openapi.ts
CHANGED
|
@@ -18,8 +18,8 @@ import type { OpenApiSourceOptions, PlaygroundOptions } from "./options.ts";
|
|
|
18
18
|
export interface OpenApiOptions {
|
|
19
19
|
/**
|
|
20
20
|
* Code-sample languages generated per operation (Blume renderer), in
|
|
21
|
-
* order. Defaults to `["curl", "js", "python"]`; `false`
|
|
22
|
-
* only the spec's own `x-codeSamples` show.
|
|
21
|
+
* order. Defaults to `["curl", "js", "python"]`; `false` (or an empty list)
|
|
22
|
+
* generates none, so only the spec's own `x-codeSamples` show.
|
|
23
23
|
*/
|
|
24
24
|
codeSamples?: string[] | false;
|
|
25
25
|
/** Start nested schema rows expanded rather than collapsed (Blume renderer). Defaults to `false`. */
|