blume 2.0.1 → 2.0.3
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 +147 -0
- package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
- package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
- package/dist/cli/chunk-35d4wj9f.js.map +15 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
- package/dist/cli/chunk-5f86nr5m.js.map +11 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
- package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
- package/dist/cli/chunk-6dsbexzp.js.map +10 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
- package/dist/cli/chunk-acanzt5p.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
- package/dist/cli/chunk-c8chx29p.js.map +36 -0
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
- package/dist/cli/chunk-ey84smr6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
- package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
- package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
- package/dist/cli/chunk-p73c0m7w.js.map +11 -0
- package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
- package/dist/cli/chunk-pehfxfta.js.map +10 -0
- package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
- package/dist/cli/chunk-pv29h0wf.js.map +190 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
- package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
- package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
- package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
- package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
- package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
- package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +162 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +7 -0
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +11 -4
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +4 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +28 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +24 -1
- package/dist/types/core/config-input.d.ts +13 -11
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +9 -1
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +13 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +59 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +38 -13
- package/dist/types/core/sources/normalize.d.ts +23 -2
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +36 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +15 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +31 -15
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +20 -9
- package/docs/08-faq.mdx +10 -3
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +7 -5
- package/docs/cli/audit.mdx +19 -3
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +4 -4
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +42 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +6 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -1
- package/docs/content/index.mdx +8 -4
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +7 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -16
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +2 -1
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +4 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +6 -4
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +8 -4
- package/docs/discoverability/rss.mdx +4 -2
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +6 -4
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +17 -4
- package/src/ai/api-catalog.ts +13 -3
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +22 -8
- package/src/ai/llms.ts +22 -10
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +80 -9
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +99 -15
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/tar.ts +139 -12
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +8 -0
- package/src/analytics/index.ts +7 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +4 -0
- package/src/astro/generate.ts +72 -28
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +139 -57
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +22 -1
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/graph.ts +3 -1
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +13 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/args.ts +32 -0
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +11 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +20 -18
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +18 -18
- package/src/cli/commands/upgrade.ts +11 -0
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +7 -12
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +11 -5
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +58 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +20 -0
- package/src/components/content/base-href.ts +14 -22
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +37 -6
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +42 -21
- package/src/components/islands/webmcp.ts +16 -10
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +23 -10
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +8 -3
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +31 -12
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +28 -13
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +25 -12
- package/src/components/layout/Search.astro +38 -14
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +14 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +23 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +13 -6
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +34 -52
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +100 -12
- package/src/components/openapi/message-composer.ts +14 -1
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +21 -3
- package/src/components/openapi/operation-model.ts +119 -17
- package/src/components/openapi/panel.ts +31 -5
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +110 -23
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +192 -26
- package/src/components/openapi/schema-tree.ts +209 -0
- package/src/components/openapi/snippets.ts +113 -19
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +69 -8
- package/src/core/config-input.ts +13 -11
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +219 -30
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +120 -11
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +44 -5
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +22 -18
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +89 -12
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +272 -83
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +50 -6
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +94 -18
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +28 -20
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +25 -3
- package/src/core/sources/github-releases.ts +131 -11
- package/src/core/sources/lexical.ts +30 -20
- package/src/core/sources/lower.ts +276 -56
- package/src/core/sources/mdx-remote.ts +52 -16
- package/src/core/sources/normalize.ts +456 -119
- package/src/core/sources/notion.ts +77 -34
- package/src/core/sources/obsidian.ts +40 -9
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +85 -49
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +28 -19
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +13 -0
- package/src/core/ui-packs/bg.ts +13 -0
- package/src/core/ui-packs/bn.ts +13 -0
- package/src/core/ui-packs/ca.ts +13 -0
- package/src/core/ui-packs/cs.ts +13 -0
- package/src/core/ui-packs/da.ts +13 -0
- package/src/core/ui-packs/de.ts +13 -0
- package/src/core/ui-packs/el.ts +13 -0
- package/src/core/ui-packs/es.ts +13 -0
- package/src/core/ui-packs/fa.ts +13 -0
- package/src/core/ui-packs/fi.ts +13 -0
- package/src/core/ui-packs/fr.ts +13 -0
- package/src/core/ui-packs/he.ts +13 -0
- package/src/core/ui-packs/hi.ts +13 -0
- package/src/core/ui-packs/hr.ts +13 -0
- package/src/core/ui-packs/hu.ts +13 -0
- package/src/core/ui-packs/id.ts +13 -0
- package/src/core/ui-packs/it.ts +13 -0
- package/src/core/ui-packs/ja.ts +13 -0
- package/src/core/ui-packs/ko.ts +13 -0
- package/src/core/ui-packs/nl.ts +13 -0
- package/src/core/ui-packs/no.ts +13 -0
- package/src/core/ui-packs/pl.ts +13 -0
- package/src/core/ui-packs/pt-br.ts +13 -0
- package/src/core/ui-packs/pt.ts +13 -0
- package/src/core/ui-packs/ro.ts +13 -0
- package/src/core/ui-packs/ru.ts +13 -0
- package/src/core/ui-packs/sk.ts +13 -0
- package/src/core/ui-packs/sr.ts +13 -0
- package/src/core/ui-packs/sv.ts +13 -0
- package/src/core/ui-packs/th.ts +13 -0
- package/src/core/ui-packs/tr.ts +13 -0
- package/src/core/ui-packs/uk.ts +13 -0
- package/src/core/ui-packs/vi.ts +13 -0
- package/src/core/ui-packs/zh-tw.ts +13 -0
- package/src/core/ui-packs/zh.ts +13 -0
- package/src/core/version-cut.ts +69 -17
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +52 -11
- package/src/deploy/cloudflare-negotiation.ts +27 -16
- package/src/deploy/headers.ts +75 -55
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +13 -3
- package/src/deploy/platforms/netlify.ts +83 -5
- package/src/deploy/platforms/node.ts +8 -5
- package/src/deploy/platforms/static.ts +2 -0
- package/src/deploy/platforms/types.ts +15 -0
- package/src/deploy/platforms/vercel.ts +10 -3
- package/src/deploy/redirects.ts +68 -21
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +74 -29
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +13 -9
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +15 -27
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +149 -9
- package/src/og/derive.ts +156 -5
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +99 -27
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +51 -3
- package/src/registry/eject.ts +297 -43
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +89 -27
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/seo/jsonld.ts +7 -3
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +76 -24
- package/src/translate/run.ts +14 -8
- package/src/translate/validate.ts +14 -2
- package/src/translate/work-list.ts +91 -19
- package/src/upgrade/upgrade.ts +36 -4
- package/dist/cli/chunk-27g6wdth.js.map +0 -10
- package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
- package/dist/cli/chunk-6crbhc3x.js.map +0 -14
- package/dist/cli/chunk-6hsn950k.js.map +0 -10
- package/dist/cli/chunk-79jhk4py.js.map +0 -35
- package/dist/cli/chunk-82bbrxdn.js +0 -51
- package/dist/cli/chunk-82bbrxdn.js.map +0 -10
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-ce574jw2.js +0 -23
- package/dist/cli/chunk-ce574jw2.js.map +0 -10
- package/dist/cli/chunk-dh8cwk36.js.map +0 -10
- package/dist/cli/chunk-epjnccmv.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-fs23ddbb.js.map +0 -35
- package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
- package/dist/cli/chunk-jwyddg7y.js.map +0 -15
- package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
- package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
- package/dist/cli/chunk-mb2919y2.js.map +0 -10
- package/dist/cli/chunk-q5163e60.js.map +0 -11
- package/dist/cli/chunk-qkqwkpte.js.map +0 -182
- package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
- package/dist/cli/chunk-s1p84fyh.js.map +0 -10
- package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
- package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
- package/dist/cli/chunk-yw7dm696.js.map +0 -14
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
package/src/search/documents.ts
CHANGED
|
@@ -16,13 +16,19 @@ import { applyAudienceVisibility } from "../ai/visibility.ts";
|
|
|
16
16
|
import type { VisibilityAudience } from "../ai/visibility.ts";
|
|
17
17
|
import matter from "../core/frontmatter.ts";
|
|
18
18
|
import { parseHeadingMarkers } from "../core/heading-markers.ts";
|
|
19
|
+
import { EN_UI, resolveUIStrings } from "../core/i18n-ui.ts";
|
|
19
20
|
import { contentIndexable } from "../core/manifest.ts";
|
|
20
21
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
21
22
|
import { HTML_COMMENT } from "../core/sources/normalize.ts";
|
|
22
23
|
import { readExpandedEntryText } from "../core/sources/read.ts";
|
|
23
24
|
import type { NavNode, PageRecord } from "../core/types.ts";
|
|
24
25
|
import { parseCodeTitle } from "../markdown/code-title.ts";
|
|
25
|
-
import {
|
|
26
|
+
import { directiveSource } from "../markdown/directives.ts";
|
|
27
|
+
import type { DirectiveNode } from "../markdown/directives.ts";
|
|
28
|
+
import {
|
|
29
|
+
MARKDOWN_BODY_FEATURES,
|
|
30
|
+
MDX_BODY_FEATURES,
|
|
31
|
+
} from "../markdown/features.ts";
|
|
26
32
|
import { pageFacets } from "./facets.ts";
|
|
27
33
|
|
|
28
34
|
/** A document indexed by the client-side search providers (Orama, FlexSearch). */
|
|
@@ -93,7 +99,6 @@ const INLINE_PARENTS = new Set([
|
|
|
93
99
|
"strong",
|
|
94
100
|
"subscript",
|
|
95
101
|
"superscript",
|
|
96
|
-
"textDirective",
|
|
97
102
|
]);
|
|
98
103
|
|
|
99
104
|
// Nodes that never render as prose: image alt text was never indexed, MDX
|
|
@@ -119,9 +124,9 @@ interface Walk {
|
|
|
119
124
|
|
|
120
125
|
// Front matter is already off (core/frontmatter.ts) by the time a body gets
|
|
121
126
|
// here, so a body that opens with a `---` divider must read as a thematic
|
|
122
|
-
// break rather than a second front matter block.
|
|
123
|
-
const MD_PARSE = { features:
|
|
124
|
-
const MDX_PARSE = { features:
|
|
127
|
+
// break rather than a second front matter block — as the renderer reads it.
|
|
128
|
+
const MD_PARSE = { features: MARKDOWN_BODY_FEATURES };
|
|
129
|
+
const MDX_PARSE = { features: MDX_BODY_FEATURES };
|
|
125
130
|
|
|
126
131
|
const parseMdx = (markdown: string): Nodes | undefined => {
|
|
127
132
|
try {
|
|
@@ -169,6 +174,25 @@ const collectCode = (
|
|
|
169
174
|
out.push(node.value, " ");
|
|
170
175
|
};
|
|
171
176
|
|
|
177
|
+
/**
|
|
178
|
+
* A text or leaf directive as the page shows it. Blume renders neither (see
|
|
179
|
+
* `markdown/directives.ts`), and prose is full of text that parses as one —
|
|
180
|
+
* `16:9`, `og:image` — so the page shows the source the author wrote, and the
|
|
181
|
+
* index reads the same text rather than dropping the `:name` part.
|
|
182
|
+
*/
|
|
183
|
+
const directiveText = (
|
|
184
|
+
node: Extract<Nodes, { type: "leafDirective" | "textDirective" }>,
|
|
185
|
+
walk: Walk
|
|
186
|
+
): string =>
|
|
187
|
+
directiveSource(
|
|
188
|
+
// SAFETY: DirectiveNode is a structural subset of Satteri's directive
|
|
189
|
+
// nodes — a `name`, optional attributes and children, and a position.
|
|
190
|
+
node as DirectiveNode,
|
|
191
|
+
node.type === "textDirective" ? ":" : "::",
|
|
192
|
+
// The walked tree was parsed from this text, so its offsets index into it.
|
|
193
|
+
walk.downlevel.source
|
|
194
|
+
);
|
|
195
|
+
|
|
172
196
|
// Block boundaries separate words; so does an empty inline element (`<br />`,
|
|
173
197
|
// a self-closing icon), which otherwise fuses its neighbors.
|
|
174
198
|
const separatesWords = (node: Extract<Nodes, { children: unknown }>): boolean =>
|
|
@@ -222,28 +246,17 @@ const collectText = (node: Nodes, out: string[], walk: Walk): void => {
|
|
|
222
246
|
out.push(" ");
|
|
223
247
|
return;
|
|
224
248
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
249
|
+
case "textDirective": {
|
|
250
|
+
out.push(directiveText(node, walk));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
case "leafDirective": {
|
|
254
|
+
out.push(directiveText(node, walk), " ");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
233
257
|
case "heading": {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const inner: string[] = [];
|
|
237
|
-
const kept = trailing ? node.children.slice(0, -1) : node.children;
|
|
238
|
-
for (const child of kept) {
|
|
239
|
-
collectText(child, inner, walk);
|
|
240
|
-
}
|
|
241
|
-
if (trailing) {
|
|
242
|
-
const stripped = parseHeadingMarkers(trailing.value).text;
|
|
243
|
-
const literal = stripped === "" && node.children.length === 1;
|
|
244
|
-
inner.push(literal ? trailing.value : stripped);
|
|
245
|
-
}
|
|
246
|
-
out.push(inner.join("").trimEnd(), " ");
|
|
258
|
+
// oxlint-disable-next-line no-use-before-define -- mutual recursion: a heading's children are walked like any others
|
|
259
|
+
collectHeading(node, out, walk);
|
|
247
260
|
return;
|
|
248
261
|
}
|
|
249
262
|
default: {
|
|
@@ -264,6 +277,35 @@ const collectText = (node: Nodes, out: string[], walk: Walk): void => {
|
|
|
264
277
|
}
|
|
265
278
|
};
|
|
266
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Fold a heading into the accumulator. Trailing heading markers
|
|
282
|
+
* (`[#custom-id]`, `{#custom-id}`, `[!toc]`, `[toc]`) are anchor metadata,
|
|
283
|
+
* not prose — strip them so they never pollute the index. Only a marker that
|
|
284
|
+
* ends the heading's final plain-text child counts, mirroring the renderer: a
|
|
285
|
+
* heading ending in inline code or an image keeps its bracketed text on the
|
|
286
|
+
* page (so it stays searchable), and a heading that is nothing but markers
|
|
287
|
+
* renders them literally.
|
|
288
|
+
*/
|
|
289
|
+
const collectHeading = (
|
|
290
|
+
node: Extract<Nodes, { type: "heading" }>,
|
|
291
|
+
out: string[],
|
|
292
|
+
walk: Walk
|
|
293
|
+
): void => {
|
|
294
|
+
const last = node.children.at(-1);
|
|
295
|
+
const trailing = last?.type === "text" ? last : undefined;
|
|
296
|
+
const inner: string[] = [];
|
|
297
|
+
const kept = trailing ? node.children.slice(0, -1) : node.children;
|
|
298
|
+
for (const child of kept) {
|
|
299
|
+
collectText(child, inner, walk);
|
|
300
|
+
}
|
|
301
|
+
if (trailing) {
|
|
302
|
+
const stripped = parseHeadingMarkers(trailing.value).text;
|
|
303
|
+
const literal = stripped === "" && node.children.length === 1;
|
|
304
|
+
inner.push(literal ? trailing.value : stripped);
|
|
305
|
+
}
|
|
306
|
+
out.push(inner.join("").trimEnd(), " ");
|
|
307
|
+
};
|
|
308
|
+
|
|
267
309
|
/**
|
|
268
310
|
* Reduce Markdown/MDX to plain, searchable text: parse (GFM included) and walk
|
|
269
311
|
* the tree instead of regex-stripping the source, so reference-style links,
|
|
@@ -447,6 +489,26 @@ export const buildSearchDocuments = async (
|
|
|
447
489
|
// otherwise.
|
|
448
490
|
const components = projectComponentSerializers(project);
|
|
449
491
|
|
|
492
|
+
// A page in no sidebar group falls under the "Docs" section, in its own
|
|
493
|
+
// language: the label is a UI string, resolved once per locale.
|
|
494
|
+
const { i18n } = project.config;
|
|
495
|
+
const docsSections = new Map<string, string>();
|
|
496
|
+
const docsSection = (locale: string): string => {
|
|
497
|
+
let label = docsSections.get(locale);
|
|
498
|
+
if (label === undefined) {
|
|
499
|
+
label = (
|
|
500
|
+
i18n
|
|
501
|
+
? resolveUIStrings(locale, {
|
|
502
|
+
defaultLocale: i18n.defaultLocale,
|
|
503
|
+
overrides: i18n.ui,
|
|
504
|
+
})
|
|
505
|
+
: EN_UI
|
|
506
|
+
).search.docs;
|
|
507
|
+
docsSections.set(locale, label);
|
|
508
|
+
}
|
|
509
|
+
return label;
|
|
510
|
+
};
|
|
511
|
+
|
|
450
512
|
return await Promise.all(
|
|
451
513
|
indexable.map(async (route) => {
|
|
452
514
|
const page = pageById.get(route.id);
|
|
@@ -461,7 +523,7 @@ export const buildSearchDocuments = async (
|
|
|
461
523
|
description: page?.description ?? "",
|
|
462
524
|
locale: route.locale,
|
|
463
525
|
route: route.path,
|
|
464
|
-
section: crumb?.section ||
|
|
526
|
+
section: crumb?.section || docsSection(route.locale),
|
|
465
527
|
tags: tags && tags.length > 0 ? tags : undefined,
|
|
466
528
|
title: route.title,
|
|
467
529
|
version: route.version,
|
package/src/search/popular.ts
CHANGED
|
@@ -17,7 +17,8 @@ export interface SearchPopularPage {
|
|
|
17
17
|
* `page.route`). `withBasePath` is idempotent and skips external URLs.
|
|
18
18
|
*
|
|
19
19
|
* `deployment.base` is deliberately *not* applied: routes stay deploy-base-less
|
|
20
|
-
* so `
|
|
20
|
+
* so `Search.astro` can add it when it renders the entry, same as
|
|
21
|
+
* sidebar-derived pages.
|
|
21
22
|
*
|
|
22
23
|
* `icon` stays a name here (like `navigation.featured`): the icon set is a
|
|
23
24
|
* server-only module, so `Search.astro` resolves it to inline SVG at render.
|
|
@@ -7,6 +7,19 @@ import type { SearchRecord } from "../documents.ts";
|
|
|
7
7
|
/** The adapter options the sync reads (the public `apiKey` is never used). */
|
|
8
8
|
export type AlgoliaSyncConfig = Pick<AlgoliaOptions, "appId" | "indexName">;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The record attributes the search dialog filters on (`facetFilters` on
|
|
12
|
+
* `locale:<code>` and `version:<id>`). In Algolia a facet filter on an
|
|
13
|
+
* attribute the index doesn't declare for faceting matches nothing — an i18n
|
|
14
|
+
* or versioned site would get no hits — so the sync declares them,
|
|
15
|
+
* filter-only.
|
|
16
|
+
*/
|
|
17
|
+
const FILTER_ATTRIBUTES = ["locale", "version"] as const;
|
|
18
|
+
|
|
19
|
+
/** A faceting declaration's attribute: `filterOnly(locale)` → `locale`. */
|
|
20
|
+
const facetAttribute = (declaration: string): string =>
|
|
21
|
+
declaration.replace(/^\w+\((?<name>.*)\)$/u, "$<name>");
|
|
22
|
+
|
|
10
23
|
/**
|
|
11
24
|
* Upload the search records to Algolia. Uses the admin key from
|
|
12
25
|
* `ALGOLIA_ADMIN_API_KEY` (never the adapter options, which hold only the
|
|
@@ -14,7 +27,9 @@ export type AlgoliaSyncConfig = Pick<AlgoliaOptions, "appId" | "indexName">;
|
|
|
14
27
|
*
|
|
15
28
|
* Uses `replaceAllObjects`, which atomically replaces the index contents, so
|
|
16
29
|
* pages deleted or renamed since the last sync don't linger as stale search
|
|
17
|
-
* hits that 404 when clicked.
|
|
30
|
+
* hits that 404 when clicked. Then adds `filterOnly(locale)` and
|
|
31
|
+
* `filterOnly(version)` to the index's `attributesForFaceting`, keeping any
|
|
32
|
+
* the site declared itself, so the dialog's locale and version filters match.
|
|
18
33
|
*/
|
|
19
34
|
export const syncAlgolia = async (
|
|
20
35
|
records: SearchRecord[],
|
|
@@ -28,8 +43,27 @@ export const syncAlgolia = async (
|
|
|
28
43
|
// `core/node-require.ts`).
|
|
29
44
|
const { algoliasearch }: typeof AlgoliaSdk = nodeRequire("algoliasearch");
|
|
30
45
|
const client = algoliasearch(config.appId, adminKey);
|
|
46
|
+
const { indexName } = config;
|
|
31
47
|
await client.replaceAllObjects({
|
|
32
|
-
indexName
|
|
48
|
+
indexName,
|
|
33
49
|
objects: records.map((record) => ({ ...record, objectID: record._id })),
|
|
34
50
|
});
|
|
51
|
+
const { attributesForFaceting = [] } = await client.getSettings({
|
|
52
|
+
indexName,
|
|
53
|
+
});
|
|
54
|
+
const declared = new Set(attributesForFaceting.map(facetAttribute));
|
|
55
|
+
const missing = FILTER_ATTRIBUTES.filter((name) => !declared.has(name));
|
|
56
|
+
if (missing.length === 0) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { taskID } = await client.setSettings({
|
|
60
|
+
indexName,
|
|
61
|
+
indexSettings: {
|
|
62
|
+
attributesForFaceting: [
|
|
63
|
+
...attributesForFaceting,
|
|
64
|
+
...missing.map((name) => `filterOnly(${name})`),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
await client.waitForTask({ indexName, taskID });
|
|
35
69
|
};
|
package/src/seo/jsonld.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Crumb } from "../components/layout/nav-utils.ts";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
mountBasePath,
|
|
4
|
+
normalizeBasePath,
|
|
5
|
+
withBasePath,
|
|
6
|
+
} from "../core/base-path.ts";
|
|
3
7
|
|
|
4
8
|
/** A date-ish value carried through frontmatter (string, YAML Date, or unset). */
|
|
5
9
|
type DateInput = string | Date | null;
|
|
@@ -301,7 +305,7 @@ const breadcrumbNode = (
|
|
|
301
305
|
"@type": "BreadcrumbList",
|
|
302
306
|
itemListElement: linked.map((crumb, index) => ({
|
|
303
307
|
"@type": "ListItem",
|
|
304
|
-
item: absolute(base,
|
|
308
|
+
item: absolute(base, mountBasePath(deployBase, crumb.route)),
|
|
305
309
|
name: crumb.label,
|
|
306
310
|
position: index + 1,
|
|
307
311
|
})),
|
|
@@ -323,7 +327,7 @@ export const buildStructuredData = (
|
|
|
323
327
|
// Routes carry `basePath`; a `deployment.base` subdirectory is layered on top
|
|
324
328
|
// so JSON-LD URLs match the served location.
|
|
325
329
|
const deployBase = normalizeBasePath(input.base);
|
|
326
|
-
const pageUrl = absolute(base,
|
|
330
|
+
const pageUrl = absolute(base, mountBasePath(deployBase, input.route));
|
|
327
331
|
const rootUrl = absolute(base, deployBase);
|
|
328
332
|
const graph: JsonLdNode[] = [];
|
|
329
333
|
|
package/src/sources/registry.ts
CHANGED
|
@@ -165,6 +165,11 @@ const sourceEntrySchema = z
|
|
|
165
165
|
.custom<SourceAdapterInput>(
|
|
166
166
|
(value) => isObjectLike(value) && "kind" in value,
|
|
167
167
|
{
|
|
168
|
+
// `z.custom` marks its failure as aborting by default, which makes Zod
|
|
169
|
+
// skip even the `content` refinement that opts in to running after a
|
|
170
|
+
// failed field (the shorthand beside `sources`). The pipe below stops
|
|
171
|
+
// at this entry either way.
|
|
172
|
+
abort: false,
|
|
168
173
|
error: (issue) => {
|
|
169
174
|
const legacy = legacyEntrySchema.safeParse(issue.input);
|
|
170
175
|
return entryHint(legacy.success ? legacy.data.type : undefined);
|
package/src/theme/entry.ts
CHANGED
|
@@ -268,7 +268,7 @@ ${THEME_MAPPING}
|
|
|
268
268
|
@apply block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground;
|
|
269
269
|
}
|
|
270
270
|
@utility blume-nav-drill {
|
|
271
|
-
@apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-
|
|
271
|
+
@apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-start font-medium text-foreground text-sm transition-colors hover:bg-muted;
|
|
272
272
|
}
|
|
273
273
|
@utility blume-nav-summary {
|
|
274
274
|
@apply flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden;
|
|
@@ -884,11 +884,18 @@ pre:has(.line.focused):hover .line:not(.focused) {
|
|
|
884
884
|
}
|
|
885
885
|
|
|
886
886
|
/* Print / "Save as PDF" (the page-actions Export → PDF runs window.print()).
|
|
887
|
-
Strip the surrounding chrome so the printout is just the article.
|
|
887
|
+
Strip the surrounding chrome so the printout is just the article. Scoped to
|
|
888
|
+
the chrome itself, never bare \`header\`/\`aside\`: content renders those too
|
|
889
|
+
(every Callout and Panel is an <aside>, every changelog Update has a
|
|
890
|
+
<header>), and they belong in the printout. \`body > header\` keeps a
|
|
891
|
+
\`layout.Header\` override out as well. */
|
|
888
892
|
@media print {
|
|
889
893
|
[data-blume-banner],
|
|
890
|
-
header,
|
|
891
|
-
|
|
894
|
+
[data-blume-header],
|
|
895
|
+
body > header,
|
|
896
|
+
[data-blume-nav-drawer],
|
|
897
|
+
[data-blume-toc],
|
|
898
|
+
[data-blume-assistant-panel],
|
|
892
899
|
[data-blume-page-actions],
|
|
893
900
|
#blume-content > nav,
|
|
894
901
|
#blume-content > details {
|
package/src/translate/agents.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentKind } from "../audit/agent.ts";
|
|
2
|
-
import { DISALLOWED_TOOLS } from "../eval/agents.ts";
|
|
2
|
+
import { CODEX_LOCKDOWN, DISALLOWED_TOOLS } from "../eval/agents.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* argv builders for the translator role. The subprocess machinery
|
|
@@ -28,6 +28,10 @@ const claudeArgs = (): string[] => [
|
|
|
28
28
|
"1",
|
|
29
29
|
];
|
|
30
30
|
|
|
31
|
+
// `--sandbox read-only` alone still lets codex's shell read any file the user
|
|
32
|
+
// can, and the source page being translated is untrusted input (a remote
|
|
33
|
+
// contributor's prose), so the translator gets the same lockdown as an eval
|
|
34
|
+
// run: no shell, exec, or local-image tool, and no inherited environment.
|
|
31
35
|
const codexArgs = (lastMessagePath: string): string[] => [
|
|
32
36
|
"exec",
|
|
33
37
|
"--skip-git-repo-check",
|
|
@@ -35,6 +39,7 @@ const codexArgs = (lastMessagePath: string): string[] => [
|
|
|
35
39
|
"--ephemeral",
|
|
36
40
|
"--sandbox",
|
|
37
41
|
"read-only",
|
|
42
|
+
...CODEX_LOCKDOWN,
|
|
38
43
|
"--output-last-message",
|
|
39
44
|
lastMessagePath,
|
|
40
45
|
"-",
|
package/src/translate/ledger.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { readFile } from "node:fs/promises";
|
|
|
4
4
|
import { join } from "pathe";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
|
|
7
|
+
import { BlumeError } from "../core/diagnostics.ts";
|
|
7
8
|
import { writeTextAtomic } from "../core/fs-atomic.ts";
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -39,21 +40,34 @@ export const emptyLedger = (): TranslationLedger => ({
|
|
|
39
40
|
* every locale's stamp. sha256-16 like the audit snapshot's content hash.
|
|
40
41
|
* Kept as its own function rather than sharing core's `hashText`: ledger
|
|
41
42
|
* stamps persist in user repos, so this hash must never change shape when an
|
|
42
|
-
* ephemeral cache hash does.
|
|
43
|
+
* ephemeral cache hash does. CRLF line endings are normalized to LF first, so
|
|
44
|
+
* a Windows checkout (`core.autocrlf`) hashes the same as everyone else's —
|
|
45
|
+
* LF text hashes exactly as it always has.
|
|
43
46
|
*/
|
|
44
47
|
export const hashSource = (text: string): string =>
|
|
45
|
-
createHash("sha256")
|
|
48
|
+
createHash("sha256")
|
|
49
|
+
.update(text.replaceAll("\r\n", "\n"))
|
|
50
|
+
.digest("hex")
|
|
51
|
+
.slice(0, 16);
|
|
52
|
+
|
|
53
|
+
/** A git merge-conflict marker line: `<<<<<<< ours`, `=======`, `>>>>>>> theirs`. */
|
|
54
|
+
const CONFLICT_MARKER = /^(?:<{7}|={7}|>{7})(?:[ \r]|$)/mu;
|
|
46
55
|
|
|
47
56
|
/**
|
|
48
57
|
* Read the ledger at `root`, tolerantly: a missing file, unparseable JSON, or
|
|
49
58
|
* an unknown shape/version all resolve to an empty ledger rather than an error
|
|
50
59
|
* (same posture as the dev lock's `parseLock`) — the worst outcome of a
|
|
51
60
|
* corrupt ledger is retranslating files that were already up to date.
|
|
61
|
+
*
|
|
62
|
+
* Unresolved merge-conflict markers are the exception, and an error: read as
|
|
63
|
+
* empty, every existing translation would be adopted as current, so
|
|
64
|
+
* `--check` would pass over translations that are out of date.
|
|
52
65
|
*/
|
|
53
66
|
export const readLedger = async (root: string): Promise<TranslationLedger> => {
|
|
67
|
+
const path = join(root, LEDGER_FILE);
|
|
54
68
|
let raw: string;
|
|
55
69
|
try {
|
|
56
|
-
raw = await readFile(
|
|
70
|
+
raw = await readFile(path, "utf-8");
|
|
57
71
|
} catch {
|
|
58
72
|
return emptyLedger();
|
|
59
73
|
}
|
|
@@ -61,6 +75,15 @@ export const readLedger = async (root: string): Promise<TranslationLedger> => {
|
|
|
61
75
|
try {
|
|
62
76
|
data = JSON.parse(raw);
|
|
63
77
|
} catch {
|
|
78
|
+
if (CONFLICT_MARKER.test(raw)) {
|
|
79
|
+
throw new BlumeError({
|
|
80
|
+
code: "BLUME_TRANSLATE_LEDGER_CONFLICT",
|
|
81
|
+
file: path,
|
|
82
|
+
message: `${LEDGER_FILE} has unresolved merge-conflict markers, so it can't say which translations are current.`,
|
|
83
|
+
severity: "error",
|
|
84
|
+
suggestion: `Resolve the merge conflict in ${LEDGER_FILE}, then rerun.`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
64
87
|
return emptyLedger();
|
|
65
88
|
}
|
|
66
89
|
const parsed = ledgerSchema.safeParse(data);
|
package/src/translate/meta.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
|
|
3
4
|
import { dirname, join, relative } from "pathe";
|
|
4
|
-
import { glob } from "tinyglobby";
|
|
5
5
|
|
|
6
|
+
import { localeTargetPath } from "../core/i18n.ts";
|
|
6
7
|
import { createModuleLoader } from "../core/load-module.ts";
|
|
8
|
+
import { findFolderMetaFiles } from "../core/meta.ts";
|
|
7
9
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
8
10
|
import { folderMetaSchema } from "../core/schema.ts";
|
|
9
|
-
import type { FolderMeta } from "../core/schema.ts";
|
|
11
|
+
import type { FolderMeta, ResolvedI18nConfig } from "../core/schema.ts";
|
|
10
12
|
import type { Diagnostic } from "../core/types.ts";
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -37,7 +39,10 @@ export interface TranslatableMeta {
|
|
|
37
39
|
title: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
/** A folder's meta module names, in the order a lookup prefers them. */
|
|
43
|
+
const META_NAMES = ["meta.ts", "meta.js", "meta.mjs"];
|
|
44
|
+
|
|
45
|
+
const META_FILES = META_NAMES.map((name) => `**/${name}`);
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
48
|
* Whether a loaded meta module default-exports a factory function. Generic so
|
|
@@ -48,19 +53,44 @@ const isFactoryModule = <T>(
|
|
|
48
53
|
): value is T & ((...args: never[]) => FolderMeta) =>
|
|
49
54
|
typeof value === "function";
|
|
50
55
|
|
|
51
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Where a locale's meta module lives. The locale folder is resolved the way
|
|
58
|
+
* pages resolve it (`localeTargetPath`): `folders` are the content root's
|
|
59
|
+
* existing top-level folders, and one naming the locale in another casing
|
|
60
|
+
* (`pt-br/` for a configured `pt-BR`) is the locale's folder already. In that
|
|
61
|
+
* folder, a `meta.js`/`meta.mjs` that already exists is the target — the
|
|
62
|
+
* generated module is plain JavaScript, and writing a `meta.ts` beside it
|
|
63
|
+
* would leave the folder with two meta files. Otherwise it's a new `meta.ts`.
|
|
64
|
+
*/
|
|
52
65
|
export const metaTargetPath = (
|
|
53
66
|
meta: TranslatableMeta,
|
|
54
|
-
locale: string
|
|
55
|
-
|
|
67
|
+
locale: string,
|
|
68
|
+
i18n: ResolvedI18nConfig,
|
|
69
|
+
folders: readonly string[]
|
|
70
|
+
): string => {
|
|
71
|
+
const dir = join(
|
|
72
|
+
meta.contentRoot,
|
|
73
|
+
dirname(
|
|
74
|
+
localeTargetPath(join(meta.dir, "meta.ts"), ".ts", locale, i18n, folders)
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
const existing = META_NAMES.find((name) => existsSync(join(dir, name)));
|
|
78
|
+
return join(dir, existing ?? "meta.ts");
|
|
79
|
+
};
|
|
56
80
|
|
|
57
81
|
/**
|
|
58
82
|
* Discover the default-locale `meta.{ts,js,mjs}` files whose titles a
|
|
59
83
|
* translation run covers. Skips (in order): non-`dir` i18n projects entirely,
|
|
60
|
-
* files
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
84
|
+
* files the scan's own folder-meta discovery never reads either — those
|
|
85
|
+
* matching the source's `exclude` globs (application code) or in folders its
|
|
86
|
+
* `include` globs don't reach (see `findFolderMetaFiles`) — files inside a
|
|
87
|
+
* configured locale directory (those ARE translations), files inside an
|
|
88
|
+
* archived-version snapshot (frozen, with their own translations — the same
|
|
89
|
+
* folders the navigation's meta lookup hoists as versions), modules that fail
|
|
90
|
+
* to load (the scan already errors on those), factory-form modules (a warning
|
|
91
|
+
* — the generator can't re-emit a function), modules that fail meta
|
|
92
|
+
* validation (the scan already errors on those too), and modules with no
|
|
93
|
+
* `title` (nothing to translate).
|
|
64
94
|
*/
|
|
65
95
|
export const discoverTranslatableMeta = async (
|
|
66
96
|
project: BlumeProject
|
|
@@ -74,33 +104,55 @@ export const discoverTranslatableMeta = async (
|
|
|
74
104
|
locale.code === i18n.defaultLocale ? [] : [locale.code.toLowerCase()]
|
|
75
105
|
)
|
|
76
106
|
);
|
|
107
|
+
const versionDirs = new Set(
|
|
108
|
+
project.config.versions?.archived.map((version) => version.id)
|
|
109
|
+
);
|
|
77
110
|
|
|
78
111
|
const load = createModuleLoader();
|
|
79
112
|
const metas: TranslatableMeta[] = [];
|
|
80
113
|
const diagnostics: Diagnostic[] = [];
|
|
81
114
|
|
|
82
115
|
const roots = project.sources.flatMap((source) =>
|
|
83
|
-
source.staged || !source.contentRoot
|
|
116
|
+
source.staged || !source.contentRoot
|
|
117
|
+
? []
|
|
118
|
+
: [
|
|
119
|
+
{
|
|
120
|
+
contentRoot: source.contentRoot,
|
|
121
|
+
exclude: source.exclude,
|
|
122
|
+
include: source.include,
|
|
123
|
+
},
|
|
124
|
+
]
|
|
84
125
|
);
|
|
85
|
-
for (const contentRoot of roots) {
|
|
126
|
+
for (const { contentRoot, exclude, include } of roots) {
|
|
127
|
+
// The scan's own file set (`discoverFolderMeta`): a `meta.ts` under an
|
|
128
|
+
// excluded folder (`src/lib/meta.ts` beside `exclude: ["src/**"]`) is
|
|
129
|
+
// application code, and one in a folder no `include` glob reaches
|
|
130
|
+
// configures no group the build renders.
|
|
86
131
|
// oxlint-disable-next-line no-await-in-loop -- a project has O(1) sources
|
|
87
|
-
const files = await
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
onlyFiles: true,
|
|
92
|
-
});
|
|
132
|
+
const files = await findFolderMetaFiles(
|
|
133
|
+
{ exclude, include, root: contentRoot },
|
|
134
|
+
META_FILES
|
|
135
|
+
);
|
|
93
136
|
for (const file of files.toSorted()) {
|
|
94
137
|
const dir = relative(contentRoot, dirname(file));
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
138
|
+
const [head] = dir.split("/");
|
|
139
|
+
if (
|
|
140
|
+
head &&
|
|
141
|
+
(localeDirs.has(head.toLowerCase()) || versionDirs.has(head))
|
|
142
|
+
) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
let mod: Awaited<ReturnType<typeof load>>;
|
|
146
|
+
try {
|
|
147
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential, ordered discovery
|
|
148
|
+
mod = await load(file);
|
|
149
|
+
} catch {
|
|
150
|
+
// Folder meta that fails to load is the scan's BLUME_META_LOAD_FAILED
|
|
151
|
+
// error, not a crash here; either way there's no title to read.
|
|
97
152
|
continue;
|
|
98
153
|
}
|
|
99
154
|
// oxlint-disable-next-line no-await-in-loop -- sequential, ordered discovery
|
|
100
|
-
const
|
|
101
|
-
readFile(file, "utf-8"),
|
|
102
|
-
load(file),
|
|
103
|
-
]);
|
|
155
|
+
const raw = await readFile(file, "utf-8");
|
|
104
156
|
if (isFactoryModule(mod)) {
|
|
105
157
|
diagnostics.push({
|
|
106
158
|
code: "BLUME_TRANSLATE_META_FACTORY",
|
package/src/translate/run.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
|
|
5
5
|
import pLimit from "p-limit";
|
|
6
6
|
import pMap from "p-map";
|
|
7
|
-
import { join } from "pathe";
|
|
7
|
+
import { dirname, join } from "pathe";
|
|
8
8
|
|
|
9
9
|
import { AGENTS } from "../audit/agent.ts";
|
|
10
10
|
import type { AgentKind } from "../audit/agent.ts";
|
|
@@ -18,6 +18,7 @@ import { DEFAULT_TRANSLATE_TIMEOUT_MS, translateAgentArgs } from "./agents.ts";
|
|
|
18
18
|
import { hashSource, stampLedger } from "./ledger.ts";
|
|
19
19
|
import type { TranslationLedger } from "./ledger.ts";
|
|
20
20
|
import { generateMetaModule } from "./meta.ts";
|
|
21
|
+
import type { TranslatableMeta } from "./meta.ts";
|
|
21
22
|
import { metaPrompt, pagePrompt } from "./prompts.ts";
|
|
22
23
|
import { parseMetaTitles, validateTranslation } from "./validate.ts";
|
|
23
24
|
import type {
|
|
@@ -88,8 +89,13 @@ interface RunContext {
|
|
|
88
89
|
timeoutMs: number;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* A meta file's key in a meta-titles prompt: its directory relative to the
|
|
94
|
+
* project root. The content-root-relative `dir` isn't unique — two
|
|
95
|
+
* filesystem sources can both have a `guides/` folder — and colliding keys
|
|
96
|
+
* would hand both folders one translation.
|
|
97
|
+
*/
|
|
98
|
+
const metaKey = (meta: TranslatableMeta): string => dirname(meta.sourceRel);
|
|
93
99
|
|
|
94
100
|
/**
|
|
95
101
|
* One headless agent call. A missing executable rejects with ENOENT on every
|
|
@@ -140,9 +146,9 @@ const runPageItem = async (
|
|
|
140
146
|
// SAFETY: `targets` maps every configured locale, and work items only carry
|
|
141
147
|
// configured locale codes.
|
|
142
148
|
const target = context.targets.get(item.locale) as LocaleConfig;
|
|
143
|
-
// A
|
|
144
|
-
//
|
|
145
|
-
//
|
|
149
|
+
// A stale item's target is the existing translation's own file, even at a
|
|
150
|
+
// non-canonical name (see `PageWorkItem.targetPath`), so it's the style
|
|
151
|
+
// precedent and the file rewritten; a missing item has nothing to read.
|
|
146
152
|
const previousTranslation = existsSync(item.targetPath)
|
|
147
153
|
? await readFile(item.targetPath, "utf-8")
|
|
148
154
|
: undefined;
|
|
@@ -183,7 +189,7 @@ const runMetaItem = async (
|
|
|
183
189
|
});
|
|
184
190
|
|
|
185
191
|
const titles = Object.fromEntries(
|
|
186
|
-
item.entries.map((entry) => [
|
|
192
|
+
item.entries.map((entry) => [metaKey(entry.meta), entry.meta.title])
|
|
187
193
|
);
|
|
188
194
|
// SAFETY: `targets` maps every configured locale, and work items only carry
|
|
189
195
|
// configured locale codes.
|
|
@@ -199,7 +205,7 @@ const runMetaItem = async (
|
|
|
199
205
|
|
|
200
206
|
const parsed = parseMetaTitles(output.text, Object.keys(titles));
|
|
201
207
|
for (const entry of item.entries) {
|
|
202
|
-
const translated = parsed.titles[
|
|
208
|
+
const translated = parsed.titles[metaKey(entry.meta)];
|
|
203
209
|
if (translated === undefined) {
|
|
204
210
|
continue;
|
|
205
211
|
}
|
|
@@ -32,7 +32,10 @@ interface FrontmatterData {
|
|
|
32
32
|
[key: string]: FrontmatterValue;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// gray-matter strips a leading byte order mark before it looks for the fence,
|
|
36
|
+
// so a BOM-prefixed file still has frontmatter — this test must agree, or the
|
|
37
|
+
// reconstruction below writes the body alone and the metadata is lost.
|
|
38
|
+
const FRONTMATTER_OPEN = /^?---\r?\n/u;
|
|
36
39
|
const FENCE_LINE = /^\s*(?:```|~~~)/u;
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -102,7 +105,16 @@ export const validateTranslation = (
|
|
|
102
105
|
sourceText: string,
|
|
103
106
|
agentText: string
|
|
104
107
|
): ValidationResult => {
|
|
105
|
-
|
|
108
|
+
// Only a fence the agent added is stripped. A source that is itself one
|
|
109
|
+
// code block (a frontmatter-less partial holding a single bash block)
|
|
110
|
+
// starts and ends with its own fence, which the translation must keep — so
|
|
111
|
+
// for such a source the reply loses its outer fence only when it has more
|
|
112
|
+
// fence lines than the source.
|
|
113
|
+
const sourceIsFenced = stripOuterFence(sourceText) !== sourceText.trim();
|
|
114
|
+
const candidate =
|
|
115
|
+
sourceIsFenced && countFenceLines(agentText) <= countFenceLines(sourceText)
|
|
116
|
+
? agentText.trim()
|
|
117
|
+
: stripOuterFence(agentText);
|
|
106
118
|
if (candidate === "") {
|
|
107
119
|
return { ok: false, reason: "agent returned empty output" };
|
|
108
120
|
}
|