blume 2.0.3 → 2.1.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 -0
- package/CHANGELOG.md +48 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
- package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
- package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
- package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
- package/dist/cli/chunk-6bm0xpgd.js +18 -0
- package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
- package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
- package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
- package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
- package/dist/cli/chunk-q3wxxq39.js +126 -0
- package/dist/cli/chunk-q3wxxq39.js.map +11 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
- package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
- package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +26 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +22 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +8 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/components/content/api-field.d.ts +59 -0
- package/dist/types/components/content/api-page.d.ts +95 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/helpers.d.ts +137 -0
- package/dist/types/components/openapi/security.d.ts +85 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/config-input.d.ts +187 -20
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/redirect-patterns.d.ts +144 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +551 -56
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +39 -11
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/model.d.ts +12 -6
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +13 -0
- package/dist/types/openapi/references.d.ts +14 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +111 -0
- package/dist/types/ratelimit/upstash.d.ts +21 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +13 -10
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +45 -5
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +60 -36
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +4 -0
- package/dist/types/seo/metatags.d.ts +20 -0
- package/docs/02-deployment.mdx +41 -5
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +21 -1
- package/docs/cli/index.mdx +1 -0
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +4 -4
- package/docs/configuration/index.mdx +63 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +69 -0
- package/docs/configuration/search.mdx +29 -1
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +106 -0
- package/docs/content/frontmatter.mdx +76 -0
- package/docs/content/i18n.mdx +38 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +28 -6
- package/docs/content/sources/contentful.mdx +32 -0
- package/docs/content/sources/custom.mdx +39 -0
- package/docs/content/sources/github-releases.mdx +31 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +30 -0
- package/docs/content/sources/remote-mdx.mdx +40 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +31 -0
- package/docs/content/syntax.mdx +67 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +102 -3
- package/docs/references/scalar.mdx +3 -1
- package/package.json +36 -1
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +5 -5
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +73 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/component-markdown.ts +75 -0
- package/src/ai/cors.ts +2 -0
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/openapi-components.ts +9 -3
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +132 -18
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +35 -0
- package/src/astro/module-types.ts +6 -0
- package/src/astro/templates.ts +283 -30
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +19 -0
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +5 -12
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +2 -0
- package/src/audit/types.ts +2 -1
- package/src/blume-modules.d.ts +7 -0
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/doctor.ts +22 -16
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +62 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +100 -0
- package/src/components/content/api-page.ts +224 -0
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +110 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +308 -45
- package/src/components/layout/Search.astro +31 -17
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/analytics.ts +98 -0
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/types.ts +20 -0
- package/src/components/layout/search/typesense.ts +5 -1
- package/src/components/openapi/Operation.astro +161 -13
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +869 -8
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +194 -20
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +3 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/includes.ts +20 -3
- package/src/core/links.ts +16 -2
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +111 -5
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +318 -64
- package/src/core/sources/normalize.ts +2 -1
- package/src/core/sources/read.ts +8 -3
- package/src/core/types.ts +1 -1
- package/src/core/ui-packs/ar.ts +43 -0
- package/src/core/ui-packs/bg.ts +45 -0
- package/src/core/ui-packs/bn.ts +43 -0
- package/src/core/ui-packs/ca.ts +46 -0
- package/src/core/ui-packs/cs.ts +44 -0
- package/src/core/ui-packs/da.ts +45 -0
- package/src/core/ui-packs/de.ts +46 -0
- package/src/core/ui-packs/el.ts +45 -0
- package/src/core/ui-packs/es.ts +46 -0
- package/src/core/ui-packs/fa.ts +45 -0
- package/src/core/ui-packs/fi.ts +45 -0
- package/src/core/ui-packs/fr.ts +46 -0
- package/src/core/ui-packs/he.ts +43 -0
- package/src/core/ui-packs/hi.ts +44 -0
- package/src/core/ui-packs/hr.ts +45 -0
- package/src/core/ui-packs/hu.ts +44 -0
- package/src/core/ui-packs/id.ts +45 -0
- package/src/core/ui-packs/it.ts +45 -0
- package/src/core/ui-packs/ja.ts +45 -0
- package/src/core/ui-packs/ko.ts +44 -0
- package/src/core/ui-packs/nl.ts +46 -0
- package/src/core/ui-packs/no.ts +44 -0
- package/src/core/ui-packs/pl.ts +45 -0
- package/src/core/ui-packs/pt-br.ts +46 -0
- package/src/core/ui-packs/pt.ts +45 -0
- package/src/core/ui-packs/ro.ts +45 -0
- package/src/core/ui-packs/ru.ts +45 -0
- package/src/core/ui-packs/sk.ts +44 -0
- package/src/core/ui-packs/sr.ts +45 -0
- package/src/core/ui-packs/sv.ts +44 -0
- package/src/core/ui-packs/th.ts +43 -0
- package/src/core/ui-packs/tr.ts +45 -0
- package/src/core/ui-packs/uk.ts +45 -0
- package/src/core/ui-packs/vi.ts +45 -0
- package/src/core/ui-packs/zh-tw.ts +43 -0
- package/src/core/ui-packs/zh.ts +43 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/artifacts.ts +77 -8
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +32 -0
- package/src/deploy/platforms/netlify.ts +33 -1
- package/src/deploy/platforms/vercel.ts +37 -2
- package/src/deploy/redirects.ts +91 -24
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/model.ts +57 -24
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +1 -1
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +229 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +30 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +5 -2
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +51 -7
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +20 -4
- package/src/registry/registry.ts +7 -0
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/sync/algolia.ts +15 -4
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +5 -0
- package/src/seo/metatags.ts +67 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +56 -3
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +8 -2
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
- /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
- /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
- /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
|
@@ -29,8 +29,15 @@ import {
|
|
|
29
29
|
blumeDependencyNames,
|
|
30
30
|
functionBundleVerdict,
|
|
31
31
|
} from "../function-bundle.ts";
|
|
32
|
-
import {
|
|
33
|
-
|
|
32
|
+
import {
|
|
33
|
+
buildVercelConfig,
|
|
34
|
+
platformRedirects,
|
|
35
|
+
vercelPatternRoutes,
|
|
36
|
+
} from "../redirects.ts";
|
|
37
|
+
import {
|
|
38
|
+
injectNegotiationRoutes,
|
|
39
|
+
injectRedirectRoutes,
|
|
40
|
+
} from "../vercel-negotiation.ts";
|
|
34
41
|
import { adapterRoot, toSiteUrl } from "./paths.ts";
|
|
35
42
|
import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
|
|
36
43
|
|
|
@@ -141,6 +148,33 @@ export const emitVercelNegotiation = async (
|
|
|
141
148
|
);
|
|
142
149
|
};
|
|
143
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Route the pattern redirects (`/beta/:slug*`) in the adapter's Build Output
|
|
153
|
+
* config: Astro's `redirects` carry only the exact ones, since it can't
|
|
154
|
+
* prerender a pattern's pages, so the adapter never sees these.
|
|
155
|
+
*/
|
|
156
|
+
export const emitVercelPatternRedirects = async (
|
|
157
|
+
project: BlumeProject,
|
|
158
|
+
log: BuildLog
|
|
159
|
+
): Promise<void> => {
|
|
160
|
+
const routes = vercelPatternRoutes(platformRedirects(project));
|
|
161
|
+
const configPath = join(buildOutputDir(project.context), "config.json");
|
|
162
|
+
if (routes.length === 0 || !existsSync(configPath)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const injected = injectRedirectRoutes(
|
|
166
|
+
await readFile(configPath, "utf-8"),
|
|
167
|
+
routes
|
|
168
|
+
);
|
|
169
|
+
if (injected === null) {
|
|
170
|
+
log.warn(
|
|
171
|
+
"Could not route the pattern redirects in .vercel/output/config.json, so paths they cover answer 404."
|
|
172
|
+
);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
await writeFile(configPath, injected, "utf-8");
|
|
176
|
+
};
|
|
177
|
+
|
|
144
178
|
/**
|
|
145
179
|
* Vercel. A server build's Build Output tree lands at `.vercel/output` — at
|
|
146
180
|
* the project root, because the adapter is handed that root up front: its
|
|
@@ -180,6 +214,7 @@ export const vercelPlatform: DeployPlatform = {
|
|
|
180
214
|
// config is a deploy artifact and stays untouched.
|
|
181
215
|
if (!isolated) {
|
|
182
216
|
await emitVercelNegotiation(project, log);
|
|
217
|
+
await emitVercelPatternRedirects(project, log);
|
|
183
218
|
}
|
|
184
219
|
return true;
|
|
185
220
|
},
|
package/src/deploy/redirects.ts
CHANGED
|
@@ -7,16 +7,28 @@ import {
|
|
|
7
7
|
import { routeSetFor, servesRoute } from "../core/locale-links.ts";
|
|
8
8
|
import type { RouteSet } from "../core/locale-links.ts";
|
|
9
9
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
10
|
+
import {
|
|
11
|
+
compileRedirects,
|
|
12
|
+
exactFirst,
|
|
13
|
+
expandRedirect,
|
|
14
|
+
underscoreRedirect,
|
|
15
|
+
vercelRedirect,
|
|
16
|
+
} from "../core/redirect-patterns.ts";
|
|
17
|
+
import type { RedirectRule } from "../core/redirect-patterns.ts";
|
|
10
18
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
11
19
|
import { escapeVercelSource } from "./headers.ts";
|
|
12
20
|
import type { VercelHeader } from "./headers.ts";
|
|
21
|
+
import type { VercelRoute } from "./vercel-negotiation.ts";
|
|
13
22
|
|
|
14
23
|
/**
|
|
15
24
|
* Platform redirect files for a static build. Astro already emits redirect HTML
|
|
16
25
|
* (meta-refresh) pages for static output, but that's a soft client redirect.
|
|
17
26
|
* These give the host a real HTTP 3xx: Netlify/Cloudflare read `_redirects`,
|
|
18
27
|
* Vercel reads `vercel.json`, and `blume-redirects.json` is a structured
|
|
19
|
-
* manifest for anything else (Apache/nginx rules, an edge worker).
|
|
28
|
+
* manifest for anything else (Apache/nginx rules, an edge worker). A pattern
|
|
29
|
+
* redirect (`/beta/:slug*`) gets no redirect page, since Astro can't
|
|
30
|
+
* prerender one for paths it doesn't know; each host file carries it in that
|
|
31
|
+
* host's syntax (see `core/redirect-patterns.ts`).
|
|
20
32
|
*/
|
|
21
33
|
|
|
22
34
|
type Redirect = ResolvedConfig["redirects"][number];
|
|
@@ -99,37 +111,57 @@ export const applyBaseToPlatformRedirects = (
|
|
|
99
111
|
|
|
100
112
|
/**
|
|
101
113
|
* The configured redirects as the host platform matches them, via
|
|
102
|
-
* {@link applyBaseToPlatformRedirects}
|
|
103
|
-
*
|
|
104
|
-
*
|
|
114
|
+
* {@link applyBaseToPlatformRedirects}, exact paths ahead of patterns: hosts
|
|
115
|
+
* try rules in order, and an exact redirect wins over a pattern that also
|
|
116
|
+
* covers its path. The one basing every consumer must share: the emitted
|
|
117
|
+
* redirect files and the server wrappers all compare these paths against
|
|
118
|
+
* real served URLs.
|
|
105
119
|
*/
|
|
106
120
|
export const platformRedirects = (project: {
|
|
107
121
|
config: ResolvedConfig;
|
|
108
122
|
manifest: Pick<BlumeProject["manifest"], "routes">;
|
|
109
123
|
}): Redirect[] => {
|
|
110
124
|
const { config } = project;
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
return exactFirst(
|
|
126
|
+
applyBaseToPlatformRedirects(
|
|
127
|
+
config.redirects,
|
|
128
|
+
config.basePath,
|
|
129
|
+
config.deployment.options.base ?? "",
|
|
130
|
+
routeSetFor(project.manifest.routes)
|
|
131
|
+
)
|
|
116
132
|
);
|
|
117
133
|
};
|
|
118
134
|
|
|
119
135
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
* A redirect as the entries a host file lists: itself when its path is
|
|
137
|
+
* exact, else the rules its pattern expands to, in the host's syntax.
|
|
138
|
+
*/
|
|
139
|
+
const hostEntries = <Entry>(
|
|
140
|
+
redirect: Redirect,
|
|
141
|
+
exact: (redirect: Redirect) => Entry,
|
|
142
|
+
pattern: (rule: RedirectRule) => Entry
|
|
143
|
+
): Entry[] => {
|
|
144
|
+
const rules = expandRedirect(redirect);
|
|
145
|
+
return rules.length > 0 ? rules.map(pattern) : [exact(redirect)];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line,
|
|
150
|
+
* a pattern as `:placeholder`s and a `*` splat the destination reads as
|
|
151
|
+
* `:splat`. `force` appends Netlify's `!` to each status (`301!`), so the rule
|
|
152
|
+
* wins over the redirect page Astro writes at `from`; Cloudflare, which always
|
|
153
|
+
* applies its rules first, rejects a line carrying it.
|
|
124
154
|
*/
|
|
125
155
|
export const buildNetlifyRedirects = (
|
|
126
156
|
redirects: Redirect[],
|
|
127
157
|
force = false
|
|
128
158
|
): string =>
|
|
129
159
|
`${redirects
|
|
130
|
-
.
|
|
131
|
-
(redirect) =>
|
|
132
|
-
|
|
160
|
+
.flatMap((redirect) =>
|
|
161
|
+
hostEntries(redirect, (exact) => exact, underscoreRedirect).map(
|
|
162
|
+
(entry) =>
|
|
163
|
+
`${entry.from} ${entry.to} ${redirect.status}${force ? "!" : ""}`
|
|
164
|
+
)
|
|
133
165
|
)
|
|
134
166
|
.join("\n")}\n`;
|
|
135
167
|
|
|
@@ -145,19 +177,25 @@ interface VercelConfig {
|
|
|
145
177
|
* alternative to the boolean `permanent`) so the configured code ships exactly:
|
|
146
178
|
* `permanent` would silently coerce a 301 to 308 and a 302 to 307, diverging
|
|
147
179
|
* from the `_redirects` file, which preserves exact codes. A `source` is a
|
|
148
|
-
* `path-to-regexp` pattern, so each
|
|
149
|
-
* `/c++-guide` fails the whole config and `/faq(old)` never
|
|
180
|
+
* `path-to-regexp` pattern, so each literal run of a `from` path is escaped:
|
|
181
|
+
* unescaped, `/c++-guide` fails the whole config and `/faq(old)` never
|
|
182
|
+
* matches. A pattern's captures are `path-to-regexp` params.
|
|
150
183
|
*/
|
|
151
184
|
export const buildVercelConfig = (
|
|
152
185
|
redirects: Redirect[],
|
|
153
186
|
headers: readonly VercelHeader[] = []
|
|
154
187
|
): string => {
|
|
155
188
|
const config: VercelConfig = {
|
|
156
|
-
redirects: redirects.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
redirects: redirects.flatMap((redirect) =>
|
|
190
|
+
hostEntries(
|
|
191
|
+
redirect,
|
|
192
|
+
(exact) => ({
|
|
193
|
+
destination: exact.to,
|
|
194
|
+
source: escapeVercelSource(exact.from),
|
|
195
|
+
}),
|
|
196
|
+
(rule) => vercelRedirect(rule, escapeVercelSource)
|
|
197
|
+
).map((entry) => ({ ...entry, statusCode: redirect.status }))
|
|
198
|
+
),
|
|
161
199
|
};
|
|
162
200
|
if (headers.length > 0) {
|
|
163
201
|
config.headers = headers;
|
|
@@ -165,7 +203,36 @@ export const buildVercelConfig = (
|
|
|
165
203
|
return `${JSON.stringify(config, null, 2)}\n`;
|
|
166
204
|
};
|
|
167
205
|
|
|
168
|
-
/**
|
|
206
|
+
/**
|
|
207
|
+
* The pattern redirects as `_redirects` entries, for the `redirects` of a
|
|
208
|
+
* Netlify server build's Frameworks API config (see
|
|
209
|
+
* {@link buildNetlifyRedirects} for the syntax).
|
|
210
|
+
*/
|
|
211
|
+
export const netlifyPatternRedirects = (
|
|
212
|
+
redirects: Redirect[]
|
|
213
|
+
): { from: string; status: number; to: string }[] =>
|
|
214
|
+
redirects.flatMap(expandRedirect).map((rule) => ({
|
|
215
|
+
...underscoreRedirect(rule),
|
|
216
|
+
status: rule.status,
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The pattern redirects as Build Output API routes, for a Vercel server
|
|
221
|
+
* build: the adapter routes only the exact redirects Astro's config carries.
|
|
222
|
+
* Each matches the served path, a trailing slash optional, and fills its
|
|
223
|
+
* `Location` from numbered captures, as the adapter's own redirect routes do.
|
|
224
|
+
*/
|
|
225
|
+
export const vercelPatternRoutes = (redirects: Redirect[]): VercelRoute[] =>
|
|
226
|
+
compileRedirects(redirects).map(([src, location, status]) => ({
|
|
227
|
+
headers: { Location: location },
|
|
228
|
+
src,
|
|
229
|
+
status,
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Structured manifest for hosts that need manual wiring. A pattern stays as
|
|
234
|
+
* configured (`/beta/:slug*`), for the rule it becomes to be written by hand.
|
|
235
|
+
*/
|
|
169
236
|
export const buildRedirectManifest = (redirects: Redirect[]): string =>
|
|
170
237
|
`${JSON.stringify(
|
|
171
238
|
redirects.map((redirect) => ({
|
|
@@ -399,3 +399,39 @@ export const injectNegotiationRoutes = (
|
|
|
399
399
|
config.routes = routes;
|
|
400
400
|
return `${JSON.stringify(config, null, "\t")}\n`;
|
|
401
401
|
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Splice routes for the pattern redirects (see `vercelPatternRoutes`) into a
|
|
405
|
+
* Build Output `config.json`, in the main phase just ahead of
|
|
406
|
+
* `handle: "filesystem"`: after the exact redirects the adapter puts first,
|
|
407
|
+
* so an exact path still wins. A second pass replaces its own routes. Returns
|
|
408
|
+
* the updated JSON text, or `null` when there is nowhere safe to splice: an
|
|
409
|
+
* unparsable config, no `routes` array, or no `handle: "filesystem"` marker.
|
|
410
|
+
*/
|
|
411
|
+
export const injectRedirectRoutes = (
|
|
412
|
+
configText: string,
|
|
413
|
+
redirectRoutes: readonly VercelRoute[]
|
|
414
|
+
): string | null => {
|
|
415
|
+
let config: { routes?: VercelRoute[] };
|
|
416
|
+
try {
|
|
417
|
+
config = JSON.parse(configText);
|
|
418
|
+
} catch {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
if (!Array.isArray(config.routes)) {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
const ours = new Set(redirectRoutes.map((route) => route.src));
|
|
425
|
+
const routes = config.routes.filter(
|
|
426
|
+
(route) => !(ours.has(route.src) && isString(route.headers?.Location))
|
|
427
|
+
);
|
|
428
|
+
const filesystemIndex = routes.findIndex(
|
|
429
|
+
(route) => route.handle === "filesystem"
|
|
430
|
+
);
|
|
431
|
+
if (filesystemIndex === -1) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
routes.splice(filesystemIndex, 0, ...redirectRoutes);
|
|
435
|
+
config.routes = routes;
|
|
436
|
+
return `${JSON.stringify(config, null, "\t")}\n`;
|
|
437
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { EvaluatedValue } from "../ai/component-markdown.ts";
|
|
2
|
+
import { readStaticExpression } from "../ai/static-expression.ts";
|
|
3
|
+
import { PARAM_LOCATIONS } from "../components/content/api-field.ts";
|
|
4
|
+
import type { FieldValue } from "../components/content/api-field.ts";
|
|
5
|
+
import {
|
|
6
|
+
AUTH_METHODS,
|
|
7
|
+
PLAYGROUND_MODES,
|
|
8
|
+
parseApiEndpoint,
|
|
9
|
+
} from "../components/content/api-page.ts";
|
|
10
|
+
import type {
|
|
11
|
+
EndpointField,
|
|
12
|
+
EndpointSpec,
|
|
13
|
+
} from "../components/content/api-page.ts";
|
|
14
|
+
import { jsxAttribute, jsxFlowElement } from "./mdast.ts";
|
|
15
|
+
import type { MdastNode, MdastValue } from "./mdast.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The right-hand column of a hand-written API page, as Mintlify renders one.
|
|
19
|
+
* Top-level `<RequestExample>` and `<ResponseExample>` move into an
|
|
20
|
+
* `<ApiRail>` at the end of the document, request examples first, and the
|
|
21
|
+
* render is flagged so the layout makes room for the column (see
|
|
22
|
+
* `ApiRail.astro`). Below `xl` the rail follows the page's content, and the
|
|
23
|
+
* examples are ordinary code groups there. An example nested in another
|
|
24
|
+
* component stays where it is written.
|
|
25
|
+
*
|
|
26
|
+
* A page with `api` frontmatter (`POST /v1/users`) also gets its endpoint: the
|
|
27
|
+
* method and path at the top of the page (`<ApiEndpoint>`), and at the head of
|
|
28
|
+
* the rail a Try it panel and request samples (`<ApiPlayground>`) built from
|
|
29
|
+
* the page's `<ParamField>`s, which the plugin collects here, nested body
|
|
30
|
+
* fields included (see `components/content/api-page.ts`).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** The render-frontmatter key that tells the layout a page has a rail. */
|
|
34
|
+
export const API_RAIL_KEY = "blumeApiRail";
|
|
35
|
+
|
|
36
|
+
/** The components that move to the rail, in the order they stack there. */
|
|
37
|
+
const RAIL_COMPONENTS = ["RequestExample", "ResponseExample"];
|
|
38
|
+
|
|
39
|
+
/** The root, as the plugin's `after` hook receives it. */
|
|
40
|
+
interface RailRoot {
|
|
41
|
+
children: MdastNode[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The slice of Satteri's visitor context the plugin uses. */
|
|
45
|
+
export interface ApiRailContext {
|
|
46
|
+
appendChild: (node: RailRoot, child: MdastNode) => void;
|
|
47
|
+
data?: { astro?: { frontmatter?: Record<string, EvaluatedValue> } };
|
|
48
|
+
insertChildAt: (node: RailRoot, index: number, child: MdastNode) => void;
|
|
49
|
+
removeNode: (node: MdastNode) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A detached copy of a node Satteri materialized lazily: its own fields,
|
|
54
|
+
* children included, without the arena handles that tie it to its old place.
|
|
55
|
+
* Appending the original instead would move an empty shell.
|
|
56
|
+
*/
|
|
57
|
+
const detach = (node: MdastNode): MdastNode =>
|
|
58
|
+
JSON.parse(
|
|
59
|
+
JSON.stringify(node, (key: string, value: MdastValue) =>
|
|
60
|
+
key === "_id" || key === "_resolver" ? undefined : value
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const isName = <Value>(value: Value): value is Value & string =>
|
|
65
|
+
typeof value === "string";
|
|
66
|
+
|
|
67
|
+
const railIndex = (node: MdastNode): number =>
|
|
68
|
+
node.type === "mdxJsxFlowElement" && isName(node.name)
|
|
69
|
+
? RAIL_COMPONENTS.indexOf(node.name)
|
|
70
|
+
: -1;
|
|
71
|
+
|
|
72
|
+
/** An element's name, when it is a JSX element. */
|
|
73
|
+
const elementName = (node: MdastNode): string | undefined =>
|
|
74
|
+
node.type === "mdxJsxFlowElement" && isName(node.name)
|
|
75
|
+
? node.name
|
|
76
|
+
: undefined;
|
|
77
|
+
|
|
78
|
+
const isNode = (value: MdastValue): value is MdastNode =>
|
|
79
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
+
|
|
81
|
+
/** A node's children, when it has any. */
|
|
82
|
+
const childrenOf = (node: MdastNode): MdastNode[] =>
|
|
83
|
+
Array.isArray(node.children) ? node.children.filter(isNode) : [];
|
|
84
|
+
|
|
85
|
+
/** One JSX attribute, as the parser leaves it. */
|
|
86
|
+
interface JsxAttribute {
|
|
87
|
+
name?: string;
|
|
88
|
+
type: string;
|
|
89
|
+
value?: string | { value: string } | null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const isAttribute = (value: MdastValue): value is JsxAttribute & MdastNode =>
|
|
93
|
+
isNode(value) && value.type === "mdxJsxAttribute";
|
|
94
|
+
|
|
95
|
+
/** An element's attributes, name to value. */
|
|
96
|
+
interface AttributeValues {
|
|
97
|
+
[name: string]: EvaluatedValue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* An element's attributes as values: text, shorthand flags as `true`, and
|
|
102
|
+
* expressions read as literal data (see `ai/static-expression.ts`); one that
|
|
103
|
+
* isn't literal is left out.
|
|
104
|
+
*/
|
|
105
|
+
const attributeValues = (
|
|
106
|
+
node: MdastNode,
|
|
107
|
+
frontmatter: Record<string, EvaluatedValue> | undefined
|
|
108
|
+
): AttributeValues => {
|
|
109
|
+
const values: AttributeValues = {};
|
|
110
|
+
const attributes = Array.isArray(node.attributes) ? node.attributes : [];
|
|
111
|
+
for (const attribute of attributes.filter(isAttribute)) {
|
|
112
|
+
const { name, value } = attribute;
|
|
113
|
+
if (!name) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (value === null || value === undefined) {
|
|
117
|
+
values[name] = true;
|
|
118
|
+
} else if (isName(value)) {
|
|
119
|
+
values[name] = value;
|
|
120
|
+
} else {
|
|
121
|
+
const read = readStaticExpression(value.value, frontmatter);
|
|
122
|
+
if (read.ok) {
|
|
123
|
+
values[name] = read.value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return values;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const isFlag = (value: EvaluatedValue): boolean =>
|
|
131
|
+
value === true || value === "true";
|
|
132
|
+
|
|
133
|
+
/** A `<ParamField>`'s values as an endpoint field, or `null` with no location. */
|
|
134
|
+
const endpointField = (values: AttributeValues): EndpointField | null => {
|
|
135
|
+
const location = PARAM_LOCATIONS.find((key) => isName(values[key]));
|
|
136
|
+
const name = location ? values[location] : undefined;
|
|
137
|
+
if (!(location && isName(name))) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const field: EndpointField = { location, name };
|
|
141
|
+
if (isName(values.type)) {
|
|
142
|
+
field.type = values.type;
|
|
143
|
+
}
|
|
144
|
+
if (isName(values.placeholder)) {
|
|
145
|
+
field.placeholder = values.placeholder;
|
|
146
|
+
}
|
|
147
|
+
if (isFlag(values.required)) {
|
|
148
|
+
field.required = true;
|
|
149
|
+
}
|
|
150
|
+
if (isFlag(values.deprecated)) {
|
|
151
|
+
field.deprecated = true;
|
|
152
|
+
}
|
|
153
|
+
if (values.default !== undefined) {
|
|
154
|
+
// SAFETY: an attribute's literal data is JSON-shaped; a frontmatter
|
|
155
|
+
// date only arrives through `frontmatter.*`, which a default never reads.
|
|
156
|
+
field.default = values.default as FieldValue;
|
|
157
|
+
}
|
|
158
|
+
return field;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The `<ParamField>`s under `nodes`, in document order. A field's own nested
|
|
163
|
+
* fields (in its `<Expandable>`) become its `fields`, the properties of the
|
|
164
|
+
* object it describes.
|
|
165
|
+
*/
|
|
166
|
+
const collectFields = (
|
|
167
|
+
nodes: MdastNode[],
|
|
168
|
+
frontmatter: Record<string, EvaluatedValue> | undefined
|
|
169
|
+
): EndpointField[] =>
|
|
170
|
+
nodes.flatMap((node) => {
|
|
171
|
+
const nested = collectFields(childrenOf(node), frontmatter);
|
|
172
|
+
if (elementName(node) !== "ParamField") {
|
|
173
|
+
return nested;
|
|
174
|
+
}
|
|
175
|
+
const field = endpointField(attributeValues(node, frontmatter));
|
|
176
|
+
if (!field) {
|
|
177
|
+
return nested;
|
|
178
|
+
}
|
|
179
|
+
return nested.length > 0 ? [{ ...field, fields: nested }] : [field];
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/** The page's endpoint, when its `api` frontmatter names one. */
|
|
183
|
+
const endpointSpec = (
|
|
184
|
+
root: RailRoot,
|
|
185
|
+
frontmatter: Record<string, EvaluatedValue> | undefined,
|
|
186
|
+
requestExample: boolean
|
|
187
|
+
): EndpointSpec | null => {
|
|
188
|
+
const api = frontmatter?.api;
|
|
189
|
+
const endpoint = isName(api) ? parseApiEndpoint(api) : null;
|
|
190
|
+
if (!endpoint) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const spec: EndpointSpec = {
|
|
194
|
+
fields: collectFields(root.children, frontmatter),
|
|
195
|
+
method: endpoint.method,
|
|
196
|
+
requestExample,
|
|
197
|
+
target: endpoint.target,
|
|
198
|
+
};
|
|
199
|
+
const authMethod = AUTH_METHODS.find(
|
|
200
|
+
(method) => method === frontmatter?.authMethod
|
|
201
|
+
);
|
|
202
|
+
if (authMethod) {
|
|
203
|
+
spec.authMethod = authMethod;
|
|
204
|
+
}
|
|
205
|
+
const playground = PLAYGROUND_MODES.find(
|
|
206
|
+
(mode) => mode === frontmatter?.playground
|
|
207
|
+
);
|
|
208
|
+
if (playground) {
|
|
209
|
+
spec.playground = playground;
|
|
210
|
+
}
|
|
211
|
+
return spec;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** Where the endpoint header goes: after a front matter block, if any. */
|
|
215
|
+
const headerIndex = (root: RailRoot): number =>
|
|
216
|
+
root.children.findIndex(
|
|
217
|
+
(node) => node.type !== "yaml" && node.type !== "toml"
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
export const apiRailPlugin = () => ({
|
|
221
|
+
after(root: RailRoot, ctx: ApiRailContext) {
|
|
222
|
+
const frontmatter = ctx.data?.astro?.frontmatter;
|
|
223
|
+
const examples = root.children.filter((node) => railIndex(node) !== -1);
|
|
224
|
+
const spec = endpointSpec(
|
|
225
|
+
root,
|
|
226
|
+
frontmatter,
|
|
227
|
+
examples.some((node) => railIndex(node) === 0)
|
|
228
|
+
);
|
|
229
|
+
const specAttribute = spec
|
|
230
|
+
? [jsxAttribute("spec", JSON.stringify(spec))]
|
|
231
|
+
: [];
|
|
232
|
+
const playground =
|
|
233
|
+
spec && spec.playground !== "none"
|
|
234
|
+
? [jsxFlowElement("ApiPlayground", specAttribute, [])]
|
|
235
|
+
: [];
|
|
236
|
+
for (const example of examples) {
|
|
237
|
+
ctx.removeNode(example);
|
|
238
|
+
}
|
|
239
|
+
if (spec) {
|
|
240
|
+
ctx.insertChildAt(
|
|
241
|
+
root,
|
|
242
|
+
Math.max(headerIndex(root), 0),
|
|
243
|
+
jsxFlowElement("ApiEndpoint", specAttribute, [])
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const rail = [
|
|
247
|
+
...playground,
|
|
248
|
+
...examples.toSorted((a, b) => railIndex(a) - railIndex(b)).map(detach),
|
|
249
|
+
];
|
|
250
|
+
if (rail.length === 0) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
ctx.appendChild(root, jsxFlowElement("ApiRail", [], rail));
|
|
254
|
+
if (frontmatter) {
|
|
255
|
+
frontmatter[API_RAIL_KEY] = true;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
name: "blume-api-rail",
|
|
259
|
+
});
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* language label.
|
|
8
8
|
* - the `lineNumbers` keyword (```ts file.ts lineNumbers) becomes
|
|
9
9
|
* `data-line-numbers`; the theme renders a counter-driven line-number gutter.
|
|
10
|
+
* - the `wrap` keyword becomes `data-wrap`: this block's long lines wrap
|
|
11
|
+
* instead of scrolling, as `markdown.code.wrap` does for every block.
|
|
12
|
+
* - the `expandable` keyword becomes `data-expandable` on a block of
|
|
13
|
+
* {@link EXPANDABLE_MIN_LINES} lines or more: the theme shows its first
|
|
14
|
+
* {@link EXPANDABLE_VISIBLE_LINES} lines, and the page script adds a toggle
|
|
15
|
+
* that shows the rest in full. A shorter block renders as it is.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
import {
|
|
@@ -16,9 +22,20 @@ import {
|
|
|
16
22
|
RESERVED_META_KEYWORDS,
|
|
17
23
|
} from "./fence-meta.ts";
|
|
18
24
|
|
|
25
|
+
/** The lines a collapsed `expandable` block shows. */
|
|
26
|
+
export const EXPANDABLE_VISIBLE_LINES = 10;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The lines a block needs before `expandable` collapses it: a few more than it
|
|
30
|
+
* shows, so the toggle never hides a line or two behind a click.
|
|
31
|
+
*/
|
|
32
|
+
export const EXPANDABLE_MIN_LINES = 16;
|
|
33
|
+
|
|
19
34
|
/** The slice of Shiki's transformer `this` context Blume reads. */
|
|
20
35
|
interface CodeMetaContext {
|
|
21
36
|
options: { meta?: { __raw?: string } };
|
|
37
|
+
/** The block's code, as highlighted. */
|
|
38
|
+
source?: string;
|
|
22
39
|
}
|
|
23
40
|
|
|
24
41
|
/** The `<pre>` hast node a Shiki `pre` hook receives. */
|
|
@@ -33,12 +50,6 @@ export interface CodeTitleTransformer {
|
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
const TITLE_ATTR = /(?:^|\s)title=(?:"(?<dq>[^"]*)"|'(?<sq>[^']*)')/u;
|
|
36
|
-
const LINE_NUMBERS = /(?:^|\s)lineNumbers(?=\s|$)/u;
|
|
37
|
-
|
|
38
|
-
// Quoted attrs are blanked before keyword/bare-token scans so a quoted value
|
|
39
|
-
// can't leak tokens (`title="enable lineNumbers later"`).
|
|
40
|
-
const withoutQuotedAttrs = (raw: string): string =>
|
|
41
|
-
raw.replace(QUOTED_ATTR, " ");
|
|
42
53
|
|
|
43
54
|
// The first bare token is the title (```ts blume.config.ts): a non-empty token
|
|
44
55
|
// that isn't a Shiki line range (`{1,3-5}`), a `key=value` attr, or a reserved
|
|
@@ -71,8 +82,14 @@ export const parseCodeTitle = (raw: string | undefined): string | undefined => {
|
|
|
71
82
|
return metaTokens(raw).find(isTitleToken);
|
|
72
83
|
};
|
|
73
84
|
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
// The shared tokenizer keeps a quoted attr whole, so a keyword inside a
|
|
86
|
+
// quoted value (`title="enable lineNumbers later"`) is never a token of its own.
|
|
87
|
+
const hasKeyword = (raw: string | undefined, keyword: string): boolean =>
|
|
88
|
+
metaTokens(raw).includes(keyword);
|
|
89
|
+
|
|
90
|
+
/** How many lines a block's code runs to, a trailing newline aside. */
|
|
91
|
+
export const lineCount = (source = ""): number =>
|
|
92
|
+
source === "" ? 0 : source.replace(/\r?\n$/u, "").split(/\r?\n/u).length;
|
|
76
93
|
|
|
77
94
|
/** Build the transformer. Runs after Shiki's built-in `data-language` hook. */
|
|
78
95
|
export const codeTitleTransformer = (): CodeTitleTransformer => ({
|
|
@@ -83,8 +100,17 @@ export const codeTitleTransformer = (): CodeTitleTransformer => ({
|
|
|
83
100
|
if (title) {
|
|
84
101
|
node.properties.dataTitle = title;
|
|
85
102
|
}
|
|
86
|
-
if (
|
|
103
|
+
if (hasKeyword(raw, "lineNumbers")) {
|
|
87
104
|
node.properties.dataLineNumbers = true;
|
|
88
105
|
}
|
|
106
|
+
if (hasKeyword(raw, "wrap")) {
|
|
107
|
+
node.properties.dataWrap = true;
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
hasKeyword(raw, "expandable") &&
|
|
111
|
+
lineCount(this.source) >= EXPANDABLE_MIN_LINES
|
|
112
|
+
) {
|
|
113
|
+
node.properties.dataExpandable = true;
|
|
114
|
+
}
|
|
89
115
|
},
|
|
90
116
|
});
|
package/src/markdown/features.ts
CHANGED
|
@@ -11,8 +11,9 @@ export const MARKDOWN_FEATURES = {
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The `.mdx` feature set: Markdown's plus `:::` directives (→ `<Callout>`) and
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* math — `singleDollarTextMath: false` keeps a bare `$` (currency, shell,
|
|
15
|
+
* code) as literal text and only parses `$$…$$`, as a block on its own lines
|
|
16
|
+
* or inline within a sentence.
|
|
16
17
|
*/
|
|
17
18
|
export const MDX_FEATURES = {
|
|
18
19
|
...MARKDOWN_FEATURES,
|
|
@@ -41,9 +41,11 @@ export const metaTokens = (meta: string | null | undefined): string[] =>
|
|
|
41
41
|
* a block title.
|
|
42
42
|
*/
|
|
43
43
|
export const RESERVED_META_KEYWORDS: ReadonlySet<string> = new Set([
|
|
44
|
+
"expandable",
|
|
44
45
|
"lineNumbers",
|
|
45
46
|
"ts2js",
|
|
46
47
|
"twoslash",
|
|
48
|
+
"wrap",
|
|
47
49
|
]);
|
|
48
50
|
|
|
49
51
|
/** A Shiki `{1,3-5}` line-range token. */
|
package/src/markdown/include.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
hasIncludeStatements,
|
|
10
10
|
matchIncludeStatement,
|
|
11
11
|
} from "../core/includes.ts";
|
|
12
|
+
import { substituteVariables } from "../core/variables.ts";
|
|
13
|
+
import type { ContentVariables } from "../core/variables.ts";
|
|
12
14
|
import type { MdastNode, MdastValue } from "./mdast.ts";
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -83,6 +85,8 @@ const errorBlock = (message: string): string =>
|
|
|
83
85
|
export interface IncludePluginOptions {
|
|
84
86
|
/** The docs content root; bounds include resolution when set. */
|
|
85
87
|
contentRoot?: string;
|
|
88
|
+
/** Content variables, substituted in each included file before it's parsed. */
|
|
89
|
+
variables?: ContentVariables;
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
/** A plain string attribute value; expression values don't carry a path. */
|
|
@@ -104,6 +108,10 @@ const statementFromJsx = (
|
|
|
104
108
|
if (attr.name === "meta" && attr.value) {
|
|
105
109
|
attributes.meta = attr.value;
|
|
106
110
|
}
|
|
111
|
+
// Every other string attribute is a prop, as in the string-level parser.
|
|
112
|
+
if (attr.name && attr.name !== "lang" && attr.name !== "meta") {
|
|
113
|
+
attributes.props = { ...attributes.props, [attr.name]: attr.value };
|
|
114
|
+
}
|
|
107
115
|
}
|
|
108
116
|
return { attributes, target: ctx.textContent(node).trim() };
|
|
109
117
|
};
|
|
@@ -147,7 +155,9 @@ export const includePlugin = (options: IncludePluginOptions = {}) => {
|
|
|
147
155
|
for (const nested of expanded.errors) {
|
|
148
156
|
ctx.report({ message: nested.message, node, severity: "warning" });
|
|
149
157
|
}
|
|
150
|
-
|
|
158
|
+
// Substituted here, before the spliced text is parsed: in an MDX page it
|
|
159
|
+
// parses as MDX, where `{{name}}` would be a JavaScript expression.
|
|
160
|
+
return substituteVariables(expanded.text, options.variables);
|
|
151
161
|
};
|
|
152
162
|
|
|
153
163
|
/**
|