blume 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/dist/cli/{chunk-5cg2rzgf.js → chunk-073rdvgb.js} +19 -48
- package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-073rdvgb.js.map} +4 -5
- 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-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-rekqz4qg.js → chunk-313vnn40.js} +16 -15
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-gp84za0h.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-9xr4dgee.js → chunk-3x6x3pph.js} +29 -16
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-0gz36akr.js → chunk-42t4jxdx.js} +21 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-ta27feev.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-jnwj5kjj.js → chunk-5vjtgs6e.js} +20 -7
- package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-5vjtgs6e.js.map} +3 -3
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-nd1821sk.js → chunk-7ppf1rva.js} +32 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-p804f3pe.js → chunk-8y2kmjv8.js} +6 -6
- package/dist/cli/{chunk-2f7n4qzt.js → chunk-c1y538kd.js} +1 -1
- package/dist/cli/{chunk-1n228xqb.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-y5h4r2y0.js → chunk-cfyg47zg.js} +15 -5
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/{chunk-cmtcnh9f.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-h2a48yhd.js → chunk-e6atmn4h.js} +119 -15
- package/dist/cli/{chunk-h2a48yhd.js.map → chunk-e6atmn4h.js.map} +10 -10
- package/dist/cli/{chunk-5ffywsmb.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-5ffywsmb.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-ntv4qrt7.js → chunk-e9q7a2ak.js} +14 -14
- package/dist/cli/{chunk-xkz5xbtv.js → chunk-ehweg4fr.js} +2366 -1114
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-zf748avv.js → chunk-ep6nd0rn.js} +176 -281
- package/dist/cli/{chunk-zf748avv.js.map → chunk-ep6nd0rn.js.map} +11 -12
- package/dist/cli/{chunk-yxmk4pec.js → chunk-fdrkhpjy.js} +16 -15
- package/dist/cli/{chunk-yxmk4pec.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-5wj2qwn2.js → chunk-ft9vfys6.js} +4 -4
- package/dist/cli/{chunk-4h8z9vv5.js → chunk-gb420vvz.js} +89 -46
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-713mngdq.js → chunk-hebn0tpx.js} +76 -10
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-2n4ps1b3.js → chunk-htrwgabr.js} +40 -22
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-zhvd6adm.js → chunk-hx3xqnk9.js} +13 -13
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-y4ykbark.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/{chunk-py62fewh.js → chunk-mrcccgnh.js} +3 -3
- package/dist/cli/{chunk-2f3ndkpw.js → chunk-n0v7m8ry.js} +3 -3
- package/dist/cli/{chunk-43zcf4gz.js → chunk-ncj7w596.js} +522 -353
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-9geymqns.js → chunk-nq1d20w2.js} +14 -14
- package/dist/cli/{chunk-g2z9x01v.js → chunk-nrns10mj.js} +3 -3
- package/dist/cli/{chunk-e7vvk5ya.js → chunk-sds923fa.js} +3 -3
- package/dist/cli/{chunk-cy5bm0j0.js → chunk-t2m5j4pr.js} +2 -2
- package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-t2m5j4pr.js.map} +1 -1
- package/dist/cli/{chunk-yj17vs39.js → chunk-tdqhyxfz.js} +5 -5
- package/dist/cli/{chunk-q3wxxq39.js → chunk-tphac74f.js} +5 -5
- package/dist/cli/{chunk-6bm0xpgd.js → chunk-w1r4vase.js} +2 -2
- 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/component-markdown.d.ts +2 -0
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/astro/integration.d.ts +5 -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 +30 -3
- 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/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 +119 -0
- 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/normalize.d.ts +20 -1
- 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/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 +20 -1
- 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 +42 -21
- 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/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/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -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 +31 -13
- package/docs/advanced/changelog.mdx +11 -0
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +30 -7
- package/docs/cli/doctor.mdx +5 -1
- 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 +1 -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 +3 -2
- 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 +4 -4
- package/docs/content/sources/contentful.mdx +7 -4
- package/docs/content/sources/custom.mdx +7 -3
- package/docs/content/sources/github-releases.mdx +10 -2
- package/docs/content/sources/index.mdx +3 -3
- package/docs/content/sources/notion.mdx +1 -1
- package/docs/content/sources/obsidian.mdx +1 -1
- package/docs/content/sources/payload.mdx +3 -2
- package/docs/content/sources/remote-mdx.mdx +4 -0
- package/docs/content/sources/sanity.mdx +2 -2
- package/docs/content/sources/strapi.mdx +3 -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 +4 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +1 -1
- package/docs/references/asyncapi.mdx +3 -1
- package/docs/references/graphql.mdx +13 -1
- package/docs/references/openapi.mdx +50 -8
- package/package.json +9 -9
- package/skills/blume-migrate/SKILL.md +1 -1
- package/skills/blume-migrate/references/docusaurus.md +4 -4
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- 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/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/astro/generate.ts +78 -48
- package/src/astro/integration.ts +16 -13
- 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/templates.ts +125 -80
- 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 +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +1 -1
- 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/islands/hooks.ts +27 -1
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/RootLayout.astro +10 -2
- package/src/components/layout/Search.astro +17 -3
- package/src/components/layout/TableOfContents.astro +24 -17
- 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 +24 -5
- 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/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 +31 -3
- 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/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 +21 -2
- 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/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +227 -41
- package/src/core/sources/notion.ts +7 -1
- 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/platforms/cloudflare.ts +13 -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 +113 -16
- 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 +290 -102
- 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/relative-links.ts +3 -2
- 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/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 +3 -1
- 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/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/entry.ts +29 -3
- 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-gp84za0h.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-p804f3pe.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-c1y538kd.js.map} +0 -0
- /package/dist/cli/{chunk-1n228xqb.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-e9q7a2ak.js.map} +0 -0
- /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-hx3xqnk9.js.map} +0 -0
- /package/dist/cli/{chunk-y4ykbark.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-py62fewh.js.map → chunk-mrcccgnh.js.map} +0 -0
- /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-n0v7m8ry.js.map} +0 -0
- /package/dist/cli/{chunk-9geymqns.js.map → chunk-nq1d20w2.js.map} +0 -0
- /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-nrns10mj.js.map} +0 -0
- /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-yj17vs39.js.map → chunk-tdqhyxfz.js.map} +0 -0
- /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-tphac74f.js.map} +0 -0
- /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-w1r4vase.js.map} +0 -0
package/src/cli/commands/eval.ts
CHANGED
|
@@ -104,12 +104,37 @@ const parseFlags = (args: EvalFlags) => {
|
|
|
104
104
|
return { agent: args.agent, threshold, timeoutS };
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
/** A shell word for the rerun command, single-quoted only when it has to be. */
|
|
108
|
+
const shellWord = (value: string): string =>
|
|
109
|
+
/^[\w./:@%+=,-]+$/u.test(value)
|
|
110
|
+
? value
|
|
111
|
+
: `'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The command a `--fix` agent reruns to verify its edits. It carries the run's
|
|
115
|
+
* own flags: a bare `blume eval` would fall back to Codex and `evals.yaml`,
|
|
116
|
+
* grading a different file with a different agent than the run it's fixing.
|
|
117
|
+
*/
|
|
118
|
+
const rerunCommand = (
|
|
119
|
+
args: EvalFlags,
|
|
120
|
+
{ agent, threshold, timeoutS }: ReturnType<typeof parseFlags>
|
|
121
|
+
): string =>
|
|
122
|
+
[
|
|
123
|
+
`blume eval --agent ${agent}`,
|
|
124
|
+
args.file === DEFAULT_FILE ? "" : `--file ${shellWord(args.file)}`,
|
|
125
|
+
args.threshold === undefined ? "" : `--threshold ${threshold}`,
|
|
126
|
+
args.timeout === undefined ? "" : `--timeout ${timeoutS}`,
|
|
127
|
+
]
|
|
128
|
+
.filter((part) => part !== "")
|
|
129
|
+
.join(" ");
|
|
130
|
+
|
|
107
131
|
/** `blume eval --fix`: hand the failing report to the interactive agent. */
|
|
108
132
|
const runFixHandoff = async (
|
|
109
133
|
agent: AgentKind,
|
|
110
134
|
result: EvalResult,
|
|
111
135
|
root: string,
|
|
112
|
-
threshold: number
|
|
136
|
+
threshold: number,
|
|
137
|
+
command: string
|
|
113
138
|
): Promise<void> => {
|
|
114
139
|
const count = result.counts.fail + result.counts.error;
|
|
115
140
|
if (count === 0) {
|
|
@@ -120,7 +145,7 @@ const runFixHandoff = async (
|
|
|
120
145
|
process.stderr.write(
|
|
121
146
|
` Handing ${count} failed question${count === 1 ? "" : "s"} to ${cli.name}…\n\n`
|
|
122
147
|
);
|
|
123
|
-
const code = await launchAgentCode(agent, evalFixPrompt(report));
|
|
148
|
+
const code = await launchAgentCode(agent, evalFixPrompt(report, command));
|
|
124
149
|
if (code !== 0) {
|
|
125
150
|
process.exit(code);
|
|
126
151
|
}
|
|
@@ -184,7 +209,8 @@ export const evalCommand = defineCommand({
|
|
|
184
209
|
meta: commandMeta.eval,
|
|
185
210
|
async run({ args }) {
|
|
186
211
|
const root = process.cwd();
|
|
187
|
-
const
|
|
212
|
+
const flags = parseFlags(args);
|
|
213
|
+
const { agent, threshold, timeoutS } = flags;
|
|
188
214
|
if (args.action === "init") {
|
|
189
215
|
await runInit(agent, args.file);
|
|
190
216
|
return;
|
|
@@ -259,7 +285,13 @@ export const evalCommand = defineCommand({
|
|
|
259
285
|
if (args.fix) {
|
|
260
286
|
// The gate is a CI concern; a handoff run succeeds when the agent
|
|
261
287
|
// session does, not when the docs already passed.
|
|
262
|
-
await runFixHandoff(
|
|
288
|
+
await runFixHandoff(
|
|
289
|
+
agent,
|
|
290
|
+
result,
|
|
291
|
+
root,
|
|
292
|
+
threshold,
|
|
293
|
+
rerunCommand(args, flags)
|
|
294
|
+
);
|
|
263
295
|
return;
|
|
264
296
|
}
|
|
265
297
|
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -272,14 +272,16 @@ export const initCommand = defineCommand({
|
|
|
272
272
|
// which `init` leaves alone; say what it still needs before installing.
|
|
273
273
|
const note = workspaceNote(root, answers, env);
|
|
274
274
|
|
|
275
|
-
// Keep installed dependencies, Blume's generated runtime (`.blume/`),
|
|
276
|
-
// build output (`dist/`)
|
|
277
|
-
//
|
|
278
|
-
//
|
|
275
|
+
// Keep installed dependencies, Blume's generated runtime (`.blume/`),
|
|
276
|
+
// build output (`dist/`), and local secrets (`.env.local`, where every
|
|
277
|
+
// missing-secret warning says to put them) out of version control.
|
|
278
|
+
// Idempotent: creates `.gitignore` when absent and skips entries already
|
|
279
|
+
// present (trailing-slash agnostic).
|
|
279
280
|
const ignored = await ensureGitignore(root, [
|
|
280
281
|
"node_modules/",
|
|
281
282
|
".blume/",
|
|
282
283
|
"dist/",
|
|
284
|
+
".env.local",
|
|
283
285
|
]);
|
|
284
286
|
if (ignored.length > 0) {
|
|
285
287
|
sink.success(`Added ${ignored.join(", ")} to .gitignore`);
|
|
@@ -13,6 +13,7 @@ import { validateLinks } from "../../core/links.ts";
|
|
|
13
13
|
import { buildManifest } from "../../core/manifest.ts";
|
|
14
14
|
import { scanProject } from "../../core/project-graph.ts";
|
|
15
15
|
import type { Diagnostic } from "../../core/types.ts";
|
|
16
|
+
import { parseIgnoreFlag } from "../args.ts";
|
|
16
17
|
import { commandMeta } from "../command-meta.ts";
|
|
17
18
|
import { reportInternalError } from "../internal-error.ts";
|
|
18
19
|
import {
|
|
@@ -28,6 +29,12 @@ export const validateCommand = defineCommand({
|
|
|
28
29
|
description: "Check external (HTTP) links over the network.",
|
|
29
30
|
type: "boolean",
|
|
30
31
|
},
|
|
32
|
+
ignore: {
|
|
33
|
+
description:
|
|
34
|
+
'With --external, skip external links whose URL matches this glob (e.g. "https://example.com/**"). Repeat it for more; internal links are always checked.',
|
|
35
|
+
type: "string",
|
|
36
|
+
valueHint: "glob",
|
|
37
|
+
},
|
|
31
38
|
json: {
|
|
32
39
|
description: "Emit diagnostics as JSON on stdout (for CI/editors).",
|
|
33
40
|
type: "boolean",
|
|
@@ -38,8 +45,9 @@ export const validateCommand = defineCommand({
|
|
|
38
45
|
},
|
|
39
46
|
},
|
|
40
47
|
meta: commandMeta.validate,
|
|
41
|
-
async run({ args }) {
|
|
48
|
+
async run({ args, rawArgs }) {
|
|
42
49
|
const root = process.cwd();
|
|
50
|
+
const ignore = parseIgnoreFlag(rawArgs);
|
|
43
51
|
const diagnostics: Diagnostic[] = [];
|
|
44
52
|
|
|
45
53
|
try {
|
|
@@ -83,6 +91,7 @@ export const validateCommand = defineCommand({
|
|
|
83
91
|
checkExternal: Boolean(args.external),
|
|
84
92
|
extraRoutes,
|
|
85
93
|
i18n: project.config.i18n,
|
|
94
|
+
ignore,
|
|
86
95
|
publicDir: existsSync(publicDir) ? publicDir : null,
|
|
87
96
|
redirects: project.config.redirects,
|
|
88
97
|
}))
|
package/src/cli/init/scaffold.ts
CHANGED
|
@@ -679,7 +679,7 @@ const ESBUILD_APPROVAL = " allowBuilds:\n esbuild: true";
|
|
|
679
679
|
* What the project still needs from a workspace it sits in, when `init` left
|
|
680
680
|
* that workspace's config alone: a place in a pnpm workspace's `packages`
|
|
681
681
|
* (pnpm skips a folder it doesn't list), pnpm's approval for esbuild's build
|
|
682
|
-
* script (pnpm
|
|
682
|
+
* script (pnpm 11 and later fail the install without it), or Yarn Berry's
|
|
683
683
|
* `node_modules` linker. Undefined when the workspace already has it, or the
|
|
684
684
|
* project doesn't sit in one.
|
|
685
685
|
*/
|
|
@@ -699,12 +699,12 @@ export const workspaceNote = (
|
|
|
699
699
|
const entry = ` packages:\n - ${relative(workspace, root)}`;
|
|
700
700
|
return approved
|
|
701
701
|
? `This folder sits inside the pnpm workspace at ${file}, which doesn't list it under packages, so pnpm won't install it. Add it there before installing:\n\n${entry}`
|
|
702
|
-
: `This folder sits inside the pnpm workspace at ${file}, which doesn't list it under packages, so pnpm won't install it. Add it there before installing, and approve esbuild's build script, or pnpm
|
|
702
|
+
: `This folder sits inside the pnpm workspace at ${file}, which doesn't list it under packages, so pnpm won't install it. Add it there before installing, and approve esbuild's build script, or pnpm 11 and later stop the install:\n\n${entry}\n${ESBUILD_APPROVAL}`;
|
|
703
703
|
}
|
|
704
704
|
if (approved) {
|
|
705
705
|
return;
|
|
706
706
|
}
|
|
707
|
-
return `This project joins the pnpm workspace at ${file}. Approve esbuild's build script there before installing, or pnpm
|
|
707
|
+
return `This project joins the pnpm workspace at ${file}. Approve esbuild's build script there before installing, or pnpm 11 and later stop the install:\n\n${ESBUILD_APPROVAL}`;
|
|
708
708
|
}
|
|
709
709
|
if (answers.packageManager !== "yarn" || yarnMajor(env.userAgent) === 1) {
|
|
710
710
|
return;
|
package/src/cli/prepare.ts
CHANGED
|
@@ -59,6 +59,12 @@ export const prepareProject = async (
|
|
|
59
59
|
let project: BlumeProject;
|
|
60
60
|
try {
|
|
61
61
|
project = await scanProject(options.root, {
|
|
62
|
+
// Fail-fast on missing secrets: warn now, not at the first request, and
|
|
63
|
+
// before the sources fetch, so a source that can't authenticate on a
|
|
64
|
+
// fresh build still gets its variable named.
|
|
65
|
+
beforeSources: (config) => {
|
|
66
|
+
reportDiagnostics(checkRequiredSecrets(config), options.root);
|
|
67
|
+
},
|
|
62
68
|
devServerUrl: options.devServerUrl,
|
|
63
69
|
mode: options.mode,
|
|
64
70
|
overrides: options.overrides,
|
|
@@ -138,8 +144,5 @@ export const prepareProject = async (
|
|
|
138
144
|
logger.warn(warning);
|
|
139
145
|
}
|
|
140
146
|
|
|
141
|
-
// Fail-fast on missing runtime secrets: warn now, not at the first request.
|
|
142
|
-
reportDiagnostics(checkRequiredSecrets(project.config), options.root);
|
|
143
|
-
|
|
144
147
|
return project;
|
|
145
148
|
};
|
|
@@ -61,7 +61,7 @@ const adapterSecretOwners = (config: ResolvedConfig): SecretOwner[] => {
|
|
|
61
61
|
* the failure surfaces at `blume dev`/`build` instead of at the first request in
|
|
62
62
|
* production. These are runtime secrets (the endpoint reads them on the server),
|
|
63
63
|
* so this warns rather than hard-fails — the value may live only in the deploy
|
|
64
|
-
* environment. Build-time secrets (search-index sync)
|
|
64
|
+
* environment. Build-time secrets (search-index sync) are checked by the sync.
|
|
65
65
|
*/
|
|
66
66
|
export const checkRequiredSecrets = (config: ResolvedConfig): Diagnostic[] => {
|
|
67
67
|
const diagnostics: Diagnostic[] = [];
|
|
@@ -11,7 +11,10 @@ import { operationModel } from "../openapi/operation-model.ts";
|
|
|
11
11
|
import Playground from "../openapi/Playground.astro";
|
|
12
12
|
import { buildRequest, defaultValues } from "../openapi/request.ts";
|
|
13
13
|
import RequestPanel from "../openapi/RequestPanel.astro";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_SAMPLE_LANGUAGES,
|
|
16
|
+
sampleLanguages,
|
|
17
|
+
} from "../openapi/snippets.ts";
|
|
15
18
|
import {
|
|
16
19
|
endpointBody,
|
|
17
20
|
endpointParameters,
|
|
@@ -46,6 +49,7 @@ const tryIt = spec.playground !== "simple" && api.playground.enabled;
|
|
|
46
49
|
model={model}
|
|
47
50
|
operation={`${spec.method} ${spec.target}`}
|
|
48
51
|
proxy={api.playground.proxy}
|
|
52
|
+
proxyConfig="api"
|
|
49
53
|
slug="api-pages"
|
|
50
54
|
/>
|
|
51
55
|
)
|
|
@@ -53,7 +57,7 @@ const tryIt = spec.playground !== "simple" && api.playground.enabled;
|
|
|
53
57
|
{
|
|
54
58
|
!spec.requestExample && (
|
|
55
59
|
<RequestPanel
|
|
56
|
-
languages={sampleLanguages(
|
|
60
|
+
languages={sampleLanguages(DEFAULT_SAMPLE_LANGUAGES)}
|
|
57
61
|
responses={{}}
|
|
58
62
|
sample={buildRequest(model, defaultValues(model))}
|
|
59
63
|
schemas={{}}
|
|
@@ -43,7 +43,10 @@ export interface ParamFieldProps extends FieldProps {
|
|
|
43
43
|
/** The name, for a field with no location attribute. */
|
|
44
44
|
name?: string;
|
|
45
45
|
path?: string;
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* An example value: the one the playground and request samples start
|
|
48
|
+
* with, not an input's placeholder hint (see `fieldSchema`).
|
|
49
|
+
*/
|
|
47
50
|
placeholder?: string;
|
|
48
51
|
query?: string;
|
|
49
52
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { exampleValue } from "../openapi/helpers.ts";
|
|
1
2
|
import type { ParameterLike, SchemaLike } from "../openapi/helpers.ts";
|
|
2
3
|
import { resolveSecurity } from "../openapi/security.ts";
|
|
3
4
|
import type { OperationSecurity } from "../openapi/security.ts";
|
|
@@ -168,19 +169,42 @@ export const endpointParameters = (fields: EndpointField[]): ParameterLike[] =>
|
|
|
168
169
|
]
|
|
169
170
|
);
|
|
170
171
|
|
|
171
|
-
/**
|
|
172
|
+
/** Whether a field, or one nested in it, has a `default` or `placeholder`. */
|
|
173
|
+
const hasValue = (field: EndpointField): boolean =>
|
|
174
|
+
field.default !== undefined ||
|
|
175
|
+
Boolean(field.placeholder) ||
|
|
176
|
+
(field.fields ?? []).some(hasValue);
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The fields a request starts with: required ones, and ones with a value to
|
|
180
|
+
* send. An optional field with neither is left for the reader to fill,
|
|
181
|
+
* rather than sent as the sampler's `"string"`, `0`, or `true`.
|
|
182
|
+
*/
|
|
183
|
+
const sentFields = (fields: EndpointField[]): EndpointField[] =>
|
|
184
|
+
fields
|
|
185
|
+
.filter((field) => field.required === true || hasValue(field))
|
|
186
|
+
.map((field) => ({ ...field, fields: sentFields(field.fields ?? []) }));
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The body fields as a JSON request body, or `undefined` when there are none.
|
|
190
|
+
* Its example, which the playground and samples start from, holds only the
|
|
191
|
+
* {@link sentFields}.
|
|
192
|
+
*/
|
|
172
193
|
export const endpointBody = (fields: EndpointField[]) => {
|
|
173
194
|
const body = fields.filter((field) => field.location === "body");
|
|
195
|
+
const object = (members: EndpointField[]): SchemaLike =>
|
|
196
|
+
fieldSchema({
|
|
197
|
+
fields: members,
|
|
198
|
+
location: "body",
|
|
199
|
+
name: "",
|
|
200
|
+
type: "object",
|
|
201
|
+
});
|
|
174
202
|
return body.length > 0
|
|
175
203
|
? {
|
|
176
204
|
content: {
|
|
177
205
|
"application/json": {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
location: "body",
|
|
181
|
-
name: "",
|
|
182
|
-
type: "object",
|
|
183
|
-
}),
|
|
206
|
+
example: exampleValue(object(sentFields(body)), {}),
|
|
207
|
+
schema: object(body),
|
|
184
208
|
},
|
|
185
209
|
},
|
|
186
210
|
}
|
|
@@ -55,6 +55,9 @@ export class BlumeMermaid extends HTMLElement {
|
|
|
55
55
|
look: "classic",
|
|
56
56
|
securityLevel: "strict",
|
|
57
57
|
startOnLoad: false,
|
|
58
|
+
// Without this, a diagram that fails to parse leaves Mermaid's own
|
|
59
|
+
// "Syntax error in text" graphic appended to <body>, below the page.
|
|
60
|
+
suppressErrorRendering: true,
|
|
58
61
|
theme: prefersDark() ? "dark" : "default",
|
|
59
62
|
});
|
|
60
63
|
try {
|
|
@@ -71,13 +74,24 @@ export class BlumeMermaid extends HTMLElement {
|
|
|
71
74
|
// oxlint-disable-next-line github/no-inner-html -- Mermaid-generated SVG must be injected as HTML
|
|
72
75
|
output.innerHTML = svg;
|
|
73
76
|
}
|
|
74
|
-
} catch {
|
|
77
|
+
} catch (error) {
|
|
75
78
|
// Localized message stamped on <body> by RootLayout's markup (the
|
|
76
79
|
// data-attribute channel); English fallback when the attribute is
|
|
77
80
|
// missing (a stale snapshot or a custom layout).
|
|
78
81
|
output.textContent =
|
|
79
82
|
document.body.dataset.i18nDiagramError ||
|
|
80
83
|
"Could not render this diagram.";
|
|
84
|
+
// Readers get only the message above; the writer needs Mermaid's
|
|
85
|
+
// parser error (line and expected token) to fix the source. It goes
|
|
86
|
+
// to the console, and `blume dev` also shows it under the message.
|
|
87
|
+
console.error("[blume] Mermaid could not render a diagram:", error);
|
|
88
|
+
if (import.meta.env.DEV) {
|
|
89
|
+
const detail = document.createElement("pre");
|
|
90
|
+
detail.className =
|
|
91
|
+
"mt-2 overflow-x-auto text-muted-foreground text-xs";
|
|
92
|
+
detail.textContent = String(error);
|
|
93
|
+
output.append(detail);
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
96
|
output.removeAttribute("aria-busy");
|
|
83
97
|
};
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
ASK_MAX_MESSAGES,
|
|
5
|
+
ASK_MAX_MESSAGES_CHARS,
|
|
6
|
+
} from "../../ai/ask-limits.ts";
|
|
3
7
|
import { captchaToken } from "../../captcha/client.ts";
|
|
4
8
|
import type { CaptchaSettings } from "../../captcha/schema.ts";
|
|
5
9
|
import type { BlumeClientData } from "../../core/data.ts";
|
|
@@ -264,6 +268,28 @@ const refusalNotice = (
|
|
|
264
268
|
const currentPath = (): string =>
|
|
265
269
|
stripBase(import.meta.env.BASE_URL, window.location.pathname);
|
|
266
270
|
|
|
271
|
+
/**
|
|
272
|
+
* The conversation as the route is sent it: all of it while it fits the
|
|
273
|
+
* route's limits (`ai/ask-limits.ts`), else the latest turns that do, the
|
|
274
|
+
* oldest dropped first, so a long chat keeps working instead of answering
|
|
275
|
+
* `400`. The kept turns open with a question, not an answer cut from its
|
|
276
|
+
* own, and the question being asked always goes, even alone over the limit.
|
|
277
|
+
*/
|
|
278
|
+
const withinAskLimits = (history: AskMessage[]): AskMessage[] => {
|
|
279
|
+
const last = history.length - 1;
|
|
280
|
+
let start = Math.max(0, history.length - ASK_MAX_MESSAGES);
|
|
281
|
+
while (
|
|
282
|
+
start < last &&
|
|
283
|
+
JSON.stringify(history.slice(start)).length > ASK_MAX_MESSAGES_CHARS
|
|
284
|
+
) {
|
|
285
|
+
start += 1;
|
|
286
|
+
}
|
|
287
|
+
while (start < last && history[start]?.role !== "user") {
|
|
288
|
+
start += 1;
|
|
289
|
+
}
|
|
290
|
+
return history.slice(start);
|
|
291
|
+
};
|
|
292
|
+
|
|
267
293
|
/**
|
|
268
294
|
* Stream answers from the assistant endpoint. Mirrors the built-in assistant island so
|
|
269
295
|
* a custom chat UI shares the same grounded, page-aware backend.
|
|
@@ -366,7 +392,7 @@ export const useAssistant = (
|
|
|
366
392
|
}
|
|
367
393
|
const payload: AskPayload = {
|
|
368
394
|
captcha: token,
|
|
369
|
-
messages: history,
|
|
395
|
+
messages: withinAskLimits(history),
|
|
370
396
|
page: { path },
|
|
371
397
|
};
|
|
372
398
|
const response = await fetch(endpoint, {
|
|
@@ -89,6 +89,10 @@ const providers = (openInChat ?? openInChatProviders).map((key) => ({
|
|
|
89
89
|
// keep that separator only while a configured list preserves v0 first.
|
|
90
90
|
const chatSeparator = providers.length > 1 && providers[0]?.key === "v0";
|
|
91
91
|
|
|
92
|
+
// The block renders once, for the right rail. Below `xl` the script moves it
|
|
93
|
+
// into the mobile "On this page" dropdown, and the global styles below the
|
|
94
|
+
// markup restyle it there from that container.
|
|
95
|
+
|
|
92
96
|
// Top-level actions match the ToC links: flat, no background/padding, color
|
|
93
97
|
// shift on hover only.
|
|
94
98
|
const rowClass =
|
|
@@ -98,6 +102,8 @@ const rowClass =
|
|
|
98
102
|
// background).
|
|
99
103
|
const menuRowClass =
|
|
100
104
|
"flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-start text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground";
|
|
105
|
+
const panelClass =
|
|
106
|
+
"absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl";
|
|
101
107
|
---
|
|
102
108
|
|
|
103
109
|
<div
|
|
@@ -130,24 +136,24 @@ const menuRowClass =
|
|
|
130
136
|
<span data-blume-copy-label>{a.copyMarkdown}</span>
|
|
131
137
|
</button>
|
|
132
138
|
|
|
139
|
+
{/* Each chevron turns with its own <details> only: in the mobile dropdown
|
|
140
|
+
these nest inside the outline's <details>, and a `group-open` variant
|
|
141
|
+
would match that open ancestor too. */}
|
|
133
142
|
{
|
|
134
143
|
(exportPdf || exportEpub) && (
|
|
135
|
-
<details class="
|
|
144
|
+
<details class="relative" data-blume-dropdown>
|
|
136
145
|
<summary
|
|
137
146
|
class={`${rowClass} cursor-pointer list-none [&::-webkit-details-marker]:hidden`}
|
|
138
147
|
>
|
|
139
148
|
<Icon name="download" size={16} />
|
|
140
149
|
{a.export}
|
|
141
150
|
<Icon
|
|
142
|
-
class="ms-auto transition-transform
|
|
151
|
+
class="ms-auto transition-transform [details[open]>summary_&]:rotate-180"
|
|
143
152
|
name="chevron-down"
|
|
144
153
|
size={14}
|
|
145
154
|
/>
|
|
146
155
|
</summary>
|
|
147
|
-
<div
|
|
148
|
-
class="absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl"
|
|
149
|
-
data-blume-menu
|
|
150
|
-
>
|
|
156
|
+
<div class={panelClass} data-blume-menu>
|
|
151
157
|
{exportPdf && (
|
|
152
158
|
<button class={menuRowClass} data-blume-export-pdf type="button">
|
|
153
159
|
<Icon name="file" size={16} />
|
|
@@ -169,22 +175,19 @@ const menuRowClass =
|
|
|
169
175
|
|
|
170
176
|
{
|
|
171
177
|
providers.length > 0 && (
|
|
172
|
-
<details class="
|
|
178
|
+
<details class="relative" data-blume-dropdown>
|
|
173
179
|
<summary
|
|
174
180
|
class={`${rowClass} cursor-pointer list-none [&::-webkit-details-marker]:hidden`}
|
|
175
181
|
>
|
|
176
182
|
<Icon name="external-link" size={16} />
|
|
177
183
|
{a.openInChat}
|
|
178
184
|
<Icon
|
|
179
|
-
class="ms-auto transition-transform
|
|
185
|
+
class="ms-auto transition-transform [details[open]>summary_&]:rotate-180"
|
|
180
186
|
name="chevron-down"
|
|
181
187
|
size={14}
|
|
182
188
|
/>
|
|
183
189
|
</summary>
|
|
184
|
-
<div
|
|
185
|
-
class="absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl"
|
|
186
|
-
data-blume-menu
|
|
187
|
-
>
|
|
190
|
+
<div class={panelClass} data-blume-menu>
|
|
188
191
|
{providers.map((provider, index) => (
|
|
189
192
|
<>
|
|
190
193
|
{index === 1 && chatSeparator && (
|
|
@@ -208,22 +211,19 @@ const menuRowClass =
|
|
|
208
211
|
|
|
209
212
|
{
|
|
210
213
|
mcpUrl && (
|
|
211
|
-
<details class="
|
|
214
|
+
<details class="relative" data-blume-dropdown>
|
|
212
215
|
<summary
|
|
213
216
|
class={`${rowClass} cursor-pointer list-none [&::-webkit-details-marker]:hidden`}
|
|
214
217
|
>
|
|
215
218
|
<Icon name="plug" size={16} />
|
|
216
219
|
{a.connectMcp}
|
|
217
220
|
<Icon
|
|
218
|
-
class="ms-auto transition-transform
|
|
221
|
+
class="ms-auto transition-transform [details[open]>summary_&]:rotate-180"
|
|
219
222
|
name="chevron-down"
|
|
220
223
|
size={14}
|
|
221
224
|
/>
|
|
222
225
|
</summary>
|
|
223
|
-
<div
|
|
224
|
-
class="absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl"
|
|
225
|
-
data-blume-menu
|
|
226
|
-
>
|
|
226
|
+
<div class={panelClass} data-blume-menu>
|
|
227
227
|
<button class={menuRowClass} data-mcp-copy-url type="button">
|
|
228
228
|
<Icon name="link" size={16} />
|
|
229
229
|
<span class="flex-1" data-mcp-url-label>
|
|
@@ -259,6 +259,45 @@ const menuRowClass =
|
|
|
259
259
|
}
|
|
260
260
|
</div>
|
|
261
261
|
|
|
262
|
+
<style is:global>
|
|
263
|
+
/* Below `xl` the script moves the block into the mobile "On this page"
|
|
264
|
+
dropdown (see `placeActions`), where it reads like the outline above it:
|
|
265
|
+
under a rule, its rows padded like the outline's links, its groups
|
|
266
|
+
expanding in place under their row instead of floating, and without
|
|
267
|
+
Scroll to top, since that dropdown already sits at the top of the page. */
|
|
268
|
+
[data-blume-page-actions-mount] > [data-blume-page-actions] {
|
|
269
|
+
margin-top: 0;
|
|
270
|
+
border-top: 1px solid var(--blume-border);
|
|
271
|
+
padding: 0.5rem;
|
|
272
|
+
}
|
|
273
|
+
[data-blume-page-actions-mount] [data-blume-scroll-top] {
|
|
274
|
+
display: none;
|
|
275
|
+
}
|
|
276
|
+
[data-blume-page-actions-mount]
|
|
277
|
+
:is([data-blume-page-actions] > :is(a, button), summary) {
|
|
278
|
+
border-radius: 0.375rem;
|
|
279
|
+
padding-inline: 0.5rem;
|
|
280
|
+
}
|
|
281
|
+
[data-blume-page-actions-mount] [data-blume-menu] {
|
|
282
|
+
position: static;
|
|
283
|
+
width: auto;
|
|
284
|
+
min-width: 0;
|
|
285
|
+
max-width: none;
|
|
286
|
+
margin: 0;
|
|
287
|
+
margin-inline-start: 0.5rem;
|
|
288
|
+
border-width: 0;
|
|
289
|
+
border-inline-start-width: 1px;
|
|
290
|
+
border-radius: 0;
|
|
291
|
+
background-color: transparent;
|
|
292
|
+
padding: 0;
|
|
293
|
+
padding-inline-start: 0.5rem;
|
|
294
|
+
box-shadow: none;
|
|
295
|
+
}
|
|
296
|
+
[data-blume-page-actions-mount] [data-blume-menu] > :hover {
|
|
297
|
+
background-color: transparent;
|
|
298
|
+
}
|
|
299
|
+
</style>
|
|
300
|
+
|
|
262
301
|
<script>
|
|
263
302
|
import { copyDeferredText, copyText, flashLabel } from "../copy-feedback.ts";
|
|
264
303
|
import { installDropdownDismiss } from "../dropdown-dismiss.ts";
|
|
@@ -301,13 +340,42 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
301
340
|
// for the EPUB: drop decorative SVGs and interactive controls (which render as
|
|
302
341
|
// broken boxes in a reader), flatten Shiki's variable-colored code to plain
|
|
303
342
|
// monospace text, and reveal panels the tab scripts would otherwise hide.
|
|
304
|
-
|
|
343
|
+
// Links point at the site: a site-relative `/docs/page` means nothing inside
|
|
344
|
+
// the book, so every link but an in-page `#fragment` is made absolute.
|
|
345
|
+
const buildEpubChapterHtml = async (article: Element): Promise<string> => {
|
|
305
346
|
const clone = article.cloneNode(true) as Element;
|
|
306
347
|
for (const el of clone.querySelectorAll(
|
|
307
348
|
"svg, button, select, script, style, link, noscript, template"
|
|
308
349
|
)) {
|
|
309
350
|
el.remove();
|
|
310
351
|
}
|
|
352
|
+
for (const link of clone.querySelectorAll<HTMLAnchorElement>("a[href]")) {
|
|
353
|
+
if (!link.getAttribute("href")?.startsWith("#")) {
|
|
354
|
+
link.setAttribute("href", link.href);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// The generator fetches every image into the book, and one it can't fetch
|
|
358
|
+
// (a cross-origin image served without CORS headers, a 404) fails the
|
|
359
|
+
// whole export. Try each first, and leave out the ones that fail.
|
|
360
|
+
await Promise.all(
|
|
361
|
+
[...clone.querySelectorAll("img")].map(async (img) => {
|
|
362
|
+
if (!img.getAttribute("src")) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const response = await fetch(img.src);
|
|
367
|
+
if (!response.ok) {
|
|
368
|
+
throw new Error(`HTTP ${response.status}`);
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.warn(
|
|
372
|
+
`[blume] EPUB export left out an image it couldn't fetch: ${img.src}`,
|
|
373
|
+
error
|
|
374
|
+
);
|
|
375
|
+
img.remove();
|
|
376
|
+
}
|
|
377
|
+
})
|
|
378
|
+
);
|
|
311
379
|
for (const pre of clone.querySelectorAll("pre")) {
|
|
312
380
|
const code = document.createElement("code");
|
|
313
381
|
code.textContent = pre.textContent ?? "";
|
|
@@ -339,9 +407,11 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
339
407
|
menu.classList.toggle("mb-1", flipUp);
|
|
340
408
|
};
|
|
341
409
|
|
|
410
|
+
// Only the rail's floating dropdowns need placing; a group open in place in
|
|
411
|
+
// the mobile dropdown is not one (see `placeActions`).
|
|
342
412
|
const openDropdown = () =>
|
|
343
413
|
document.querySelector<HTMLDetailsElement>(
|
|
344
|
-
"[data-blume-page-actions] details[open]"
|
|
414
|
+
"[data-blume-page-actions] details[data-blume-dropdown][open]"
|
|
345
415
|
);
|
|
346
416
|
|
|
347
417
|
// rAF-coalesced so a live resize drag re-reads layout once per frame, not
|
|
@@ -362,6 +432,32 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
362
432
|
// dropdown behavior (also the header language switcher and nav selectors).
|
|
363
433
|
installDropdownDismiss();
|
|
364
434
|
|
|
435
|
+
// The page renders its one actions block in the right rail, which only
|
|
436
|
+
// shows at `xl`. Below that the block moves into the mobile "On this page"
|
|
437
|
+
// dropdown, into the empty mount the layout passes it as children, and
|
|
438
|
+
// back at `xl`; a moved node keeps its listeners, so nothing rebinds. In
|
|
439
|
+
// the dropdown its groups expand in place, so they drop out of the shared
|
|
440
|
+
// light-dismiss and the placement here (both keyed on
|
|
441
|
+
// `data-blume-dropdown`) until the block is back in the rail, and the
|
|
442
|
+
// styles above restyle it from the mount.
|
|
443
|
+
const railQuery = window.matchMedia("(min-width: 80rem)");
|
|
444
|
+
const placeActions = () => {
|
|
445
|
+
const root = document.querySelector("[data-blume-page-actions]");
|
|
446
|
+
const rail = railQuery.matches;
|
|
447
|
+
const home = document.querySelector(
|
|
448
|
+
rail ? "[data-blume-toc]" : "[data-blume-page-actions-mount]"
|
|
449
|
+
);
|
|
450
|
+
if (!(root && home) || root.parentElement === home) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
// A group left open would reopen as the other kind of menu.
|
|
454
|
+
for (const details of root.querySelectorAll("details")) {
|
|
455
|
+
details.open = false;
|
|
456
|
+
details.toggleAttribute("data-blume-dropdown", rail);
|
|
457
|
+
}
|
|
458
|
+
home.append(root);
|
|
459
|
+
};
|
|
460
|
+
|
|
365
461
|
// Per-page setup, run on the initial load and again after every
|
|
366
462
|
// client-router swap: the swap rebuilds the actions menu from
|
|
367
463
|
// server-rendered markup, so every handler below binds to fresh elements.
|
|
@@ -384,7 +480,9 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
384
480
|
other.open = false;
|
|
385
481
|
}
|
|
386
482
|
}
|
|
387
|
-
|
|
483
|
+
if (details.hasAttribute("data-blume-dropdown")) {
|
|
484
|
+
placeMenu(details);
|
|
485
|
+
}
|
|
388
486
|
});
|
|
389
487
|
}
|
|
390
488
|
|
|
@@ -537,10 +635,24 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
537
635
|
const epubModule = await loadEpub();
|
|
538
636
|
const epub = epubModule.default?.default ?? epubModule.default;
|
|
539
637
|
// The article HTML already opens with the page's <h1>, so don't let the
|
|
540
|
-
// generator prepend a second chapter-title heading.
|
|
638
|
+
// generator prepend a second chapter-title heading. The book's
|
|
639
|
+
// language is the article's (a fallback page's content locale, else
|
|
640
|
+
// the page's), and the site is its author and publisher; left out,
|
|
641
|
+
// the generator says English and "anonymous". Its defaults are spread
|
|
642
|
+
// under these options, so an unknown value is omitted, not undefined.
|
|
643
|
+
const site = document
|
|
644
|
+
.querySelector('meta[property="og:site_name"]')
|
|
645
|
+
?.getAttribute("content");
|
|
646
|
+
const lang = article.closest("[lang]")?.getAttribute("lang");
|
|
541
647
|
const result = await epub(
|
|
542
|
-
{
|
|
543
|
-
|
|
648
|
+
{
|
|
649
|
+
css: EPUB_STYLES,
|
|
650
|
+
prependChapterTitles: false,
|
|
651
|
+
title,
|
|
652
|
+
...(site && { author: site, publisher: site }),
|
|
653
|
+
...(lang && { lang }),
|
|
654
|
+
},
|
|
655
|
+
[{ content: await buildEpubChapterHtml(article), title }]
|
|
544
656
|
);
|
|
545
657
|
const blob =
|
|
546
658
|
result instanceof Blob
|
|
@@ -572,5 +684,8 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
|
|
|
572
684
|
};
|
|
573
685
|
|
|
574
686
|
initPageActions();
|
|
687
|
+
placeActions();
|
|
575
688
|
document.addEventListener("astro:after-swap", initPageActions);
|
|
689
|
+
document.addEventListener("astro:after-swap", placeActions);
|
|
690
|
+
railQuery.addEventListener("change", placeActions);
|
|
576
691
|
</script>
|