blume 2.0.2 → 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 +65 -0
- package/dist/cli/{chunk-6k4ftwze.js → chunk-1d7ve1dm.js} +1 -1
- package/dist/cli/{chunk-zp79m0ts.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-2hsdwb9n.js} +19 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-2hsdwb9n.js.map} +1 -1
- package/dist/cli/{chunk-g698a744.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-35d4wj9f.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-35d4wj9f.js.map} +3 -3
- package/dist/cli/{chunk-mqc662a6.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-3em5wd2y.js} +21 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5f86nr5m.js} +15 -15
- package/dist/cli/{chunk-hr8ne106.js → chunk-5m5nmvyq.js} +68 -43
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-5xvm6tfj.js} +14 -14
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-6dsbexzp.js} +14 -14
- package/dist/cli/{chunk-00gs3wqs.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-j85scx15.js → chunk-9t7a85s3.js} +130 -44
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-nfcyttvj.js → chunk-acanzt5p.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-acanzt5p.js.map} +1 -1
- package/dist/cli/{chunk-wdrt2k2v.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-xh43dwgw.js → chunk-c8chx29p.js} +42 -29
- package/dist/cli/{chunk-xh43dwgw.js.map → chunk-c8chx29p.js.map} +9 -9
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-crgn1q09.js} +21 -12
- 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-4e9b9ra6.js → chunk-ey84smr6.js} +3 -3
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-g4hq16wv.js} +13 -13
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-hm3vjy5s.js} +64 -45
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-5r8g91qn.js → chunk-j85vccga.js} +306 -107
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-7vtckvaw.js → chunk-p73c0m7w.js} +14 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-p73c0m7w.js.map} +1 -1
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-pehfxfta.js} +3 -3
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-pv29h0wf.js} +1223 -978
- package/dist/cli/{chunk-v6ya5kcb.js.map → chunk-pv29h0wf.js.map} +47 -46
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6dtt0zfn.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-r9rcc4w7.js} +7 -7
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-3yce002v.js → chunk-vg9r4eb9.js} +6 -6
- package/dist/cli/{chunk-3yce002v.js.map → chunk-vg9r4eb9.js.map} +4 -4
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-wrr3j9w9.js} +16 -17
- package/dist/cli/{chunk-hqp2ajnh.js.map → chunk-wrr3j9w9.js.map} +7 -7
- package/dist/cli/{chunk-ddndchfr.js → chunk-yfyb25rh.js} +38 -26
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +20 -18
- package/dist/cli/index.js.map +3 -3
- 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 +9 -7
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/core/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +9 -7
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/meta.d.ts +8 -0
- package/dist/types/core/schema.d.ts +7 -0
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -0
- package/dist/types/deploy/platforms/types.d.ts +8 -0
- package/dist/types/deploy/redirects.d.ts +24 -13
- 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/docs/02-deployment.mdx +4 -2
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/cli/audit.mdx +17 -1
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +20 -1
- package/docs/configuration/customization.mdx +2 -0
- package/docs/configuration/search.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +2 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +3 -1
- package/docs/content/sources.mdx +1 -5
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +2 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +1 -1
- package/docs/references/openapi.mdx +5 -3
- package/package.json +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +7 -2
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/link-headers.ts +14 -5
- package/src/ai/llms.ts +18 -7
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/tar.ts +139 -12
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +1 -1
- 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 +81 -37
- package/src/audit/checks/links.ts +8 -1
- package/src/audit/graph.ts +3 -1
- package/src/audit/run.ts +12 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/cli/args.ts +32 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/Badge.astro +5 -3
- package/src/components/content/Component.astro +2 -2
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -0
- package/src/components/content/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- package/src/components/islands/assistant.tsx +21 -4
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +35 -19
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +2 -2
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/NavSelector.astro +2 -2
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +7 -6
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/RootLayout.astro +5 -5
- package/src/components/layout/Search.astro +33 -13
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -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/Operation.astro +2 -0
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestPanel.astro +5 -2
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/snippets.ts +85 -6
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +62 -16
- package/src/core/config-input.ts +9 -7
- package/src/core/diagnostics.ts +2 -0
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n.ts +31 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/locale-links.ts +5 -1
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +28 -8
- package/src/core/project-graph.ts +27 -5
- package/src/core/schema.ts +24 -4
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +325 -90
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +27 -6
- package/src/deploy/cloudflare-negotiation.ts +4 -12
- package/src/deploy/headers.ts +8 -4
- package/src/deploy/node-headers.ts +1 -1
- package/src/deploy/platforms/cloudflare.ts +12 -3
- package/src/deploy/platforms/netlify.ts +1 -0
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +1 -0
- package/src/deploy/redirects.ts +54 -18
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +7 -6
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -4
- package/src/og/card.ts +129 -5
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +22 -14
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +41 -2
- package/src/registry/eject.ts +181 -24
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +67 -26
- package/src/search/popular.ts +2 -1
- package/src/seo/jsonld.ts +7 -3
- package/src/translate/meta.ts +68 -24
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +4 -1
- package/src/translate/work-list.ts +56 -17
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- /package/dist/cli/{chunk-6k4ftwze.js.map → chunk-1d7ve1dm.js.map} +0 -0
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-mqc662a6.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5f86nr5m.js.map} +0 -0
- /package/dist/cli/{chunk-91ws1n6j.js.map → chunk-5xvm6tfj.js.map} +0 -0
- /package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-6dsbexzp.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-ey84smr6.js.map} +0 -0
- /package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-g4hq16wv.js.map} +0 -0
- /package/dist/cli/{chunk-qkb5a8sa.js.map → chunk-pehfxfta.js.map} +0 -0
- /package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-r9rcc4w7.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-tzmab476.js.map} +0 -0
package/src/registry/eject.ts
CHANGED
|
@@ -3,10 +3,13 @@ import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
|
|
4
4
|
import { join, relative } from "pathe";
|
|
5
5
|
|
|
6
|
+
import { OPENAPI_PATH } from "../ai/api/paths.ts";
|
|
7
|
+
import { buildApiSpec } from "../ai/api/spec.ts";
|
|
6
8
|
import { buildAskData } from "../ai/ask-data.ts";
|
|
7
9
|
import { resolveAskBackend } from "../ai/ask.ts";
|
|
8
10
|
import { buildRawMarkdown } from "../ai/markdown.ts";
|
|
9
11
|
import { buildMcpData } from "../ai/mcp/data.ts";
|
|
12
|
+
import type { McpData } from "../ai/mcp/data.ts";
|
|
10
13
|
import { buildMcpDiscovery, buildMcpServerCard } from "../ai/mcp/discovery.ts";
|
|
11
14
|
import { planComponentSlots } from "../astro/component-slots.ts";
|
|
12
15
|
import {
|
|
@@ -31,6 +34,11 @@ import {
|
|
|
31
34
|
import { discoverIslands } from "../astro/islands.ts";
|
|
32
35
|
import { customOgRoutes, discoverPages, routeIsTaken } from "../astro/pages.ts";
|
|
33
36
|
import {
|
|
37
|
+
apiNavigationTemplate,
|
|
38
|
+
apiNotFoundTemplate,
|
|
39
|
+
apiPagesIndexTemplate,
|
|
40
|
+
apiPageTemplate,
|
|
41
|
+
apiSearchTemplate,
|
|
34
42
|
askComponentTemplate,
|
|
35
43
|
askEndpointTemplate,
|
|
36
44
|
astroConfigTemplate,
|
|
@@ -46,6 +54,8 @@ import {
|
|
|
46
54
|
mcpEndpointTemplate,
|
|
47
55
|
mcpPageFile,
|
|
48
56
|
mixedbreadSearchEndpointTemplate,
|
|
57
|
+
notFoundJsonTemplate,
|
|
58
|
+
notFoundMarkdownTemplate,
|
|
49
59
|
notFoundPageTemplate,
|
|
50
60
|
ogEndpointTemplate,
|
|
51
61
|
playgroundProxyTemplate,
|
|
@@ -68,6 +78,7 @@ import {
|
|
|
68
78
|
} from "../core/sources/collection.ts";
|
|
69
79
|
import type { ProjectContext } from "../core/types.ts";
|
|
70
80
|
import { buildRssFeeds, renderRssFeed } from "../deploy/rss.ts";
|
|
81
|
+
import { resolveOgFonts } from "../og/derive.ts";
|
|
71
82
|
import type { OpenApiData } from "../openapi/model.ts";
|
|
72
83
|
import {
|
|
73
84
|
hasScalarReferences,
|
|
@@ -80,6 +91,7 @@ import {
|
|
|
80
91
|
examplesEntryTemplate,
|
|
81
92
|
tailwindEntryTemplate,
|
|
82
93
|
} from "../theme/entry.ts";
|
|
94
|
+
import { fontLocaleCodes } from "../theme/fonts.ts";
|
|
83
95
|
import { buildThemeCss } from "../theme/palette.ts";
|
|
84
96
|
import { rebaseSourceDirectives } from "../theme/sources.ts";
|
|
85
97
|
import { twoslashCss } from "../theme/twoslash.ts";
|
|
@@ -238,21 +250,33 @@ const playgroundProxyFiles = (
|
|
|
238
250
|
: [];
|
|
239
251
|
|
|
240
252
|
/**
|
|
241
|
-
* The
|
|
242
|
-
*
|
|
243
|
-
* is
|
|
253
|
+
* The agent data snapshot (`blume:mcp-data`) behind the MCP server and the
|
|
254
|
+
* JSON docs API, mirroring `publishAgentData` in generate.ts: built when
|
|
255
|
+
* either is on, null when neither needs it.
|
|
244
256
|
*/
|
|
245
|
-
const
|
|
257
|
+
const ejectAgentData = async (
|
|
258
|
+
project: BlumeProject,
|
|
259
|
+
userPages: { pattern: string }[]
|
|
260
|
+
): Promise<McpData | null> =>
|
|
261
|
+
hostsMcp(project, userPages) || project.config.agents.api
|
|
262
|
+
? await buildMcpData(project)
|
|
263
|
+
: null;
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The MCP server endpoint and `.well-known` discovery documents, mirroring
|
|
267
|
+
* `writeMcpFiles` in generate.ts. Empty when the server is disabled or its
|
|
268
|
+
* route is already owned by a page.
|
|
269
|
+
*/
|
|
270
|
+
const mcpFiles = (
|
|
246
271
|
project: BlumeProject,
|
|
247
272
|
userPages: { pattern: string }[],
|
|
248
273
|
srcDir: string,
|
|
249
|
-
|
|
250
|
-
):
|
|
251
|
-
if (!hostsMcp(project, userPages)) {
|
|
274
|
+
data: McpData | null
|
|
275
|
+
): { content: string; path: string }[] => {
|
|
276
|
+
if (!(data && hostsMcp(project, userPages))) {
|
|
252
277
|
return [];
|
|
253
278
|
}
|
|
254
279
|
const { route } = project.config.agents.mcp;
|
|
255
|
-
const data = await buildMcpData(project);
|
|
256
280
|
const discoveryInput = {
|
|
257
281
|
base: data.base,
|
|
258
282
|
name: data.name,
|
|
@@ -261,10 +285,6 @@ const mcpFiles = async (
|
|
|
261
285
|
version: data.version,
|
|
262
286
|
};
|
|
263
287
|
return [
|
|
264
|
-
{
|
|
265
|
-
content: `${JSON.stringify(data)}\n`,
|
|
266
|
-
path: join(genDir, "mcp-data.json"),
|
|
267
|
-
},
|
|
268
288
|
{
|
|
269
289
|
content: mcpEndpointTemplate(),
|
|
270
290
|
path: join(srcDir, "pages", mcpPageFile(route)),
|
|
@@ -280,6 +300,87 @@ const mcpFiles = async (
|
|
|
280
300
|
];
|
|
281
301
|
};
|
|
282
302
|
|
|
303
|
+
/**
|
|
304
|
+
* The JSON docs API, mirroring `planApi` and `writeApiFiles` in generate.ts:
|
|
305
|
+
* the prerendered page index, per-page documents, and navigation; on server
|
|
306
|
+
* output the search endpoint and, unless a user page or a content page already
|
|
307
|
+
* lives under `/api/`, the JSON 404 catch-all; and `/openapi.json`, unless a
|
|
308
|
+
* `public/openapi.json` or a page owns that route. The ejected `llms.txt`,
|
|
309
|
+
* homepage `Link` header, and 404 page advertise these routes, so the ejected
|
|
310
|
+
* app serves them. Empty when `agents.api` is off.
|
|
311
|
+
*/
|
|
312
|
+
const apiFiles = (
|
|
313
|
+
project: BlumeProject,
|
|
314
|
+
userPages: { pattern: string }[],
|
|
315
|
+
root: string,
|
|
316
|
+
srcDir: string,
|
|
317
|
+
data: McpData | null
|
|
318
|
+
): { content: string; path: string }[] => {
|
|
319
|
+
const { config } = project;
|
|
320
|
+
if (!(data && config.agents.api)) {
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
const server = config.deployment.options.output === "server";
|
|
324
|
+
const apiDir = join(srcDir, "pages", "api");
|
|
325
|
+
const files = [
|
|
326
|
+
{
|
|
327
|
+
content: apiPagesIndexTemplate(),
|
|
328
|
+
path: join(apiDir, "docs", "pages.json.ts"),
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
content: apiPageTemplate(),
|
|
332
|
+
path: join(apiDir, "docs", "pages", "[...route].json.ts"),
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
content: apiNavigationTemplate(),
|
|
336
|
+
path: join(apiDir, "docs", "navigation.json.ts"),
|
|
337
|
+
},
|
|
338
|
+
];
|
|
339
|
+
if (server) {
|
|
340
|
+
files.push({
|
|
341
|
+
content: apiSearchTemplate(),
|
|
342
|
+
path: join(apiDir, "docs", "search.ts"),
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
const apiOwned =
|
|
346
|
+
userPages.some((page) => page.pattern.startsWith("/api/[")) ||
|
|
347
|
+
project.graph.pages.some(
|
|
348
|
+
(page) => page.route === "/api" || page.route.startsWith("/api/")
|
|
349
|
+
);
|
|
350
|
+
if (server && !apiOwned) {
|
|
351
|
+
files.push({
|
|
352
|
+
content: apiNotFoundTemplate({ base: data.base, site: data.site }),
|
|
353
|
+
path: join(apiDir, "[...path].ts"),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
if (
|
|
357
|
+
!(
|
|
358
|
+
routeIsTaken(userPages, project.graph.pages, OPENAPI_PATH) ||
|
|
359
|
+
existsSync(join(root, "public", "openapi.json"))
|
|
360
|
+
)
|
|
361
|
+
) {
|
|
362
|
+
files.push({
|
|
363
|
+
content: staticJsonEndpointTemplate(
|
|
364
|
+
buildApiSpec({
|
|
365
|
+
agentReadability: config.agents.agentReadability,
|
|
366
|
+
base: data.base,
|
|
367
|
+
description: config.description,
|
|
368
|
+
llmsTxt: config.agents.llmsTxt.enabled,
|
|
369
|
+
mcpRoute: hostsMcp(project, userPages)
|
|
370
|
+
? config.agents.mcp.route
|
|
371
|
+
: null,
|
|
372
|
+
name: config.title,
|
|
373
|
+
search: server,
|
|
374
|
+
site: data.site,
|
|
375
|
+
version: data.version,
|
|
376
|
+
})
|
|
377
|
+
),
|
|
378
|
+
path: join(srcDir, "pages", "openapi.json.ts"),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return files;
|
|
382
|
+
};
|
|
383
|
+
|
|
283
384
|
/**
|
|
284
385
|
* The `/changelog` index page, mirroring `shouldGenerateChangelog` in
|
|
285
386
|
* generate.ts: emitted when `type: changelog` entries or a release-backed
|
|
@@ -346,6 +447,25 @@ const contentAssetFiles = async (
|
|
|
346
447
|
];
|
|
347
448
|
};
|
|
348
449
|
|
|
450
|
+
/**
|
|
451
|
+
* The partial → including-pages map with every path relative to the project
|
|
452
|
+
* root, like every other path in the ejected app, so it holds in any checkout.
|
|
453
|
+
* Its readers (`includeHmrPlugin`, `withIncludeRefresh`) resolve each path
|
|
454
|
+
* against the app's root, which after eject is the project itself.
|
|
455
|
+
*/
|
|
456
|
+
const portableIncludeGraph = (
|
|
457
|
+
project: BlumeProject,
|
|
458
|
+
root: string
|
|
459
|
+
): Record<string, string[]> =>
|
|
460
|
+
Object.fromEntries(
|
|
461
|
+
Object.entries(buildIncludeGraph(project.graph.pages)).map(
|
|
462
|
+
([partial, includers]) => [
|
|
463
|
+
toPosix(relative(root, partial)),
|
|
464
|
+
includers.map((page) => toPosix(relative(root, page))),
|
|
465
|
+
]
|
|
466
|
+
)
|
|
467
|
+
);
|
|
468
|
+
|
|
349
469
|
/** Contents of the configured `examples.css`, or `""` when unset/absent. */
|
|
350
470
|
/**
|
|
351
471
|
* Read a user stylesheet that eject inlines into a generated entry under
|
|
@@ -655,7 +775,7 @@ export const eject = async (
|
|
|
655
775
|
// hot update's read would silently no-op and partial edits would serve
|
|
656
776
|
// stale pages. A snapshot like the rest of `src/generated`: the ejected
|
|
657
777
|
// app owns (and may regenerate or prune) it.
|
|
658
|
-
content: `${JSON.stringify(
|
|
778
|
+
content: `${JSON.stringify(portableIncludeGraph(project, root))}\n`,
|
|
659
779
|
path: join(genDir, "includes.json"),
|
|
660
780
|
},
|
|
661
781
|
{
|
|
@@ -684,32 +804,69 @@ export const eject = async (
|
|
|
684
804
|
});
|
|
685
805
|
|
|
686
806
|
if (config.seo.og.enabled) {
|
|
807
|
+
// The default script fallbacks ride along (Google family names, nothing
|
|
808
|
+
// machine-specific), so a non-Latin card still renders after eject. An
|
|
809
|
+
// explicit seo.og.fonts replaces them, and isn't carried over.
|
|
810
|
+
const { fallbacks, families } = resolveOgFonts(
|
|
811
|
+
{
|
|
812
|
+
locales: fontLocaleCodes(config.i18n),
|
|
813
|
+
ogFonts: config.seo.og.fonts,
|
|
814
|
+
themeFonts: config.theme.fonts,
|
|
815
|
+
themeFontsConfigured: false,
|
|
816
|
+
},
|
|
817
|
+
root
|
|
818
|
+
);
|
|
687
819
|
files.push({
|
|
688
820
|
content: ogEndpointTemplate(
|
|
689
821
|
customOgRoutes(pages, config.title, config.seo.og.titles),
|
|
690
|
-
{
|
|
822
|
+
{
|
|
823
|
+
fallbacks,
|
|
824
|
+
families,
|
|
825
|
+
pageDescriptions: config.seo.og.description !== false,
|
|
826
|
+
},
|
|
691
827
|
changelog.length > 0
|
|
692
828
|
),
|
|
693
829
|
path: join(srcDir, "pages", "og", "[...slug].png.ts"),
|
|
694
830
|
});
|
|
695
831
|
}
|
|
696
832
|
|
|
697
|
-
// The hosted MCP server and the playground's built-in
|
|
698
|
-
// the generated runtime (`[]` when each is off).
|
|
833
|
+
// The hosted MCP server, the JSON docs API, and the playground's built-in
|
|
834
|
+
// proxy, mirrored from the generated runtime (`[]` when each is off). The
|
|
835
|
+
// server and the API share one agent data snapshot.
|
|
836
|
+
const agentData = await ejectAgentData(project, pages);
|
|
837
|
+
if (agentData) {
|
|
838
|
+
files.push({
|
|
839
|
+
content: `${JSON.stringify(agentData)}\n`,
|
|
840
|
+
path: join(genDir, "mcp-data.json"),
|
|
841
|
+
});
|
|
842
|
+
}
|
|
699
843
|
files.push(
|
|
700
|
-
...
|
|
844
|
+
...mcpFiles(project, pages, srcDir, agentData),
|
|
845
|
+
...apiFiles(project, pages, root, srcDir, agentData),
|
|
701
846
|
...changelog,
|
|
702
847
|
...playgroundProxyFiles(config, openApiData, srcDir)
|
|
703
848
|
);
|
|
704
849
|
|
|
705
|
-
// Default 404 page
|
|
706
|
-
//
|
|
707
|
-
//
|
|
850
|
+
// Default 404 page and its Markdown (`/404.md`) and JSON (`/404.json`)
|
|
851
|
+
// twins, mirroring `writeNotFoundPage` in generate.ts: all three are skipped
|
|
852
|
+
// when the project already owns `/404` (a custom `pages/404.astro` or a
|
|
853
|
+
// `404.md` content page). The ejected project owns the files afterwards and
|
|
854
|
+
// can edit or remove them.
|
|
708
855
|
if (!routeIsTaken(pages, project.graph.pages, "/404")) {
|
|
709
|
-
files.push(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
856
|
+
files.push(
|
|
857
|
+
{
|
|
858
|
+
content: notFoundPageTemplate(),
|
|
859
|
+
path: join(srcDir, "pages", "404.astro"),
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
content: notFoundMarkdownTemplate(),
|
|
863
|
+
path: join(srcDir, "pages", "404.md.ts"),
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
content: notFoundJsonTemplate(),
|
|
867
|
+
path: join(srcDir, "pages", "404.json.ts"),
|
|
868
|
+
}
|
|
869
|
+
);
|
|
713
870
|
}
|
|
714
871
|
|
|
715
872
|
// The client-feature loaders behind the `blume:features` alias, and the
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SearchAdapterKind } from "./registry.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether each adapter's client can filter results to one docs version. The
|
|
5
|
+
* source-built indexes (Orama, FlexSearch) store every document's `version`,
|
|
6
|
+
* and the Algolia and Typesense syncs upload it as a facet their clients
|
|
7
|
+
* filter on. Pagefind indexes the built HTML with no version metadata, the
|
|
8
|
+
* Orama Cloud sync uploads no `version`, and the Mixedbread endpoint takes no
|
|
9
|
+
* filter, so those always search every version. Keyed by every kind so a new
|
|
10
|
+
* adapter has to decide.
|
|
11
|
+
*/
|
|
12
|
+
const SCOPES_VERSIONS = {
|
|
13
|
+
algolia: true,
|
|
14
|
+
flexsearch: true,
|
|
15
|
+
mixedbread: false,
|
|
16
|
+
none: false,
|
|
17
|
+
orama: true,
|
|
18
|
+
"orama-cloud": false,
|
|
19
|
+
pagefind: false,
|
|
20
|
+
typesense: true,
|
|
21
|
+
} satisfies Record<SearchAdapterKind, boolean>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether the configured search adapter scopes results to the viewed docs
|
|
25
|
+
* version. The search dialog offers its "All versions" toggle only when it
|
|
26
|
+
* does: on the others the toggle would change nothing, and every query
|
|
27
|
+
* already spans all versions.
|
|
28
|
+
*/
|
|
29
|
+
export const scopesVersions = (kind: SearchAdapterKind): boolean =>
|
|
30
|
+
SCOPES_VERSIONS[kind];
|
package/src/search/documents.ts
CHANGED
|
@@ -23,7 +23,12 @@ import { HTML_COMMENT } from "../core/sources/normalize.ts";
|
|
|
23
23
|
import { readExpandedEntryText } from "../core/sources/read.ts";
|
|
24
24
|
import type { NavNode, PageRecord } from "../core/types.ts";
|
|
25
25
|
import { parseCodeTitle } from "../markdown/code-title.ts";
|
|
26
|
-
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";
|
|
27
32
|
import { pageFacets } from "./facets.ts";
|
|
28
33
|
|
|
29
34
|
/** A document indexed by the client-side search providers (Orama, FlexSearch). */
|
|
@@ -94,7 +99,6 @@ const INLINE_PARENTS = new Set([
|
|
|
94
99
|
"strong",
|
|
95
100
|
"subscript",
|
|
96
101
|
"superscript",
|
|
97
|
-
"textDirective",
|
|
98
102
|
]);
|
|
99
103
|
|
|
100
104
|
// Nodes that never render as prose: image alt text was never indexed, MDX
|
|
@@ -120,9 +124,9 @@ interface Walk {
|
|
|
120
124
|
|
|
121
125
|
// Front matter is already off (core/frontmatter.ts) by the time a body gets
|
|
122
126
|
// here, so a body that opens with a `---` divider must read as a thematic
|
|
123
|
-
// break rather than a second front matter block.
|
|
124
|
-
const MD_PARSE = { features:
|
|
125
|
-
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 };
|
|
126
130
|
|
|
127
131
|
const parseMdx = (markdown: string): Nodes | undefined => {
|
|
128
132
|
try {
|
|
@@ -170,6 +174,25 @@ const collectCode = (
|
|
|
170
174
|
out.push(node.value, " ");
|
|
171
175
|
};
|
|
172
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
|
+
|
|
173
196
|
// Block boundaries separate words; so does an empty inline element (`<br />`,
|
|
174
197
|
// a self-closing icon), which otherwise fuses its neighbors.
|
|
175
198
|
const separatesWords = (node: Extract<Nodes, { children: unknown }>): boolean =>
|
|
@@ -223,28 +246,17 @@ const collectText = (node: Nodes, out: string[], walk: Walk): void => {
|
|
|
223
246
|
out.push(" ");
|
|
224
247
|
return;
|
|
225
248
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
249
|
+
case "textDirective": {
|
|
250
|
+
out.push(directiveText(node, walk));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
case "leafDirective": {
|
|
254
|
+
out.push(directiveText(node, walk), " ");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
234
257
|
case "heading": {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const inner: string[] = [];
|
|
238
|
-
const kept = trailing ? node.children.slice(0, -1) : node.children;
|
|
239
|
-
for (const child of kept) {
|
|
240
|
-
collectText(child, inner, walk);
|
|
241
|
-
}
|
|
242
|
-
if (trailing) {
|
|
243
|
-
const stripped = parseHeadingMarkers(trailing.value).text;
|
|
244
|
-
const literal = stripped === "" && node.children.length === 1;
|
|
245
|
-
inner.push(literal ? trailing.value : stripped);
|
|
246
|
-
}
|
|
247
|
-
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);
|
|
248
260
|
return;
|
|
249
261
|
}
|
|
250
262
|
default: {
|
|
@@ -265,6 +277,35 @@ const collectText = (node: Nodes, out: string[], walk: Walk): void => {
|
|
|
265
277
|
}
|
|
266
278
|
};
|
|
267
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
|
+
|
|
268
309
|
/**
|
|
269
310
|
* Reduce Markdown/MDX to plain, searchable text: parse (GFM included) and walk
|
|
270
311
|
* the tree instead of regex-stripping the source, so reference-style links,
|
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.
|
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/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,21 +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
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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).
|
|
66
94
|
*/
|
|
67
95
|
export const discoverTranslatableMeta = async (
|
|
68
96
|
project: BlumeProject
|
|
@@ -85,16 +113,26 @@ export const discoverTranslatableMeta = async (
|
|
|
85
113
|
const diagnostics: Diagnostic[] = [];
|
|
86
114
|
|
|
87
115
|
const roots = project.sources.flatMap((source) =>
|
|
88
|
-
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
|
+
]
|
|
89
125
|
);
|
|
90
|
-
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.
|
|
91
131
|
// oxlint-disable-next-line no-await-in-loop -- a project has O(1) sources
|
|
92
|
-
const files = await
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
onlyFiles: true,
|
|
97
|
-
});
|
|
132
|
+
const files = await findFolderMetaFiles(
|
|
133
|
+
{ exclude, include, root: contentRoot },
|
|
134
|
+
META_FILES
|
|
135
|
+
);
|
|
98
136
|
for (const file of files.toSorted()) {
|
|
99
137
|
const dir = relative(contentRoot, dirname(file));
|
|
100
138
|
const [head] = dir.split("/");
|
|
@@ -104,11 +142,17 @@ export const discoverTranslatableMeta = async (
|
|
|
104
142
|
) {
|
|
105
143
|
continue;
|
|
106
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.
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
107
154
|
// oxlint-disable-next-line no-await-in-loop -- sequential, ordered discovery
|
|
108
|
-
const
|
|
109
|
-
readFile(file, "utf-8"),
|
|
110
|
-
load(file),
|
|
111
|
-
]);
|
|
155
|
+
const raw = await readFile(file, "utf-8");
|
|
112
156
|
if (isFactoryModule(mod)) {
|
|
113
157
|
diagnostics.push({
|
|
114
158
|
code: "BLUME_TRANSLATE_META_FACTORY",
|
package/src/translate/run.ts
CHANGED
|
@@ -146,9 +146,9 @@ const runPageItem = async (
|
|
|
146
146
|
// SAFETY: `targets` maps every configured locale, and work items only carry
|
|
147
147
|
// configured locale codes.
|
|
148
148
|
const target = context.targets.get(item.locale) as LocaleConfig;
|
|
149
|
-
// A
|
|
150
|
-
//
|
|
151
|
-
//
|
|
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.
|
|
152
152
|
const previousTranslation = existsSync(item.targetPath)
|
|
153
153
|
? await readFile(item.targetPath, "utf-8")
|
|
154
154
|
: undefined;
|