blume 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/blume.mjs +18 -0
- package/dist/cli/index.js +12696 -0
- package/dist/cli/index.js.map +145 -0
- package/dist/types/core/bridge.d.ts +24 -0
- package/dist/types/core/config.d.ts +35 -0
- package/dist/types/core/data.d.ts +129 -0
- package/dist/types/core/define-components.d.ts +27 -0
- package/dist/types/core/define-meta.d.ts +16 -0
- package/dist/types/core/deployment-env.d.ts +9 -0
- package/dist/types/core/diagnostics.d.ts +17 -0
- package/dist/types/core/i18n-ui.d.ts +500 -0
- package/dist/types/core/load-module.d.ts +7 -0
- package/dist/types/core/package-root.d.ts +17 -0
- package/dist/types/core/project.d.ts +9 -0
- package/dist/types/core/schema.d.ts +3451 -0
- package/dist/types/core/sources/types.d.ts +107 -0
- package/dist/types/core/types.d.ts +245 -0
- package/dist/types/core/ui-packs/ar.d.ts +3 -0
- package/dist/types/core/ui-packs/bg.d.ts +3 -0
- package/dist/types/core/ui-packs/bn.d.ts +3 -0
- package/dist/types/core/ui-packs/ca.d.ts +3 -0
- package/dist/types/core/ui-packs/cs.d.ts +3 -0
- package/dist/types/core/ui-packs/da.d.ts +3 -0
- package/dist/types/core/ui-packs/de.d.ts +3 -0
- package/dist/types/core/ui-packs/el.d.ts +3 -0
- package/dist/types/core/ui-packs/es.d.ts +3 -0
- package/dist/types/core/ui-packs/fa.d.ts +3 -0
- package/dist/types/core/ui-packs/fi.d.ts +3 -0
- package/dist/types/core/ui-packs/fr.d.ts +3 -0
- package/dist/types/core/ui-packs/he.d.ts +3 -0
- package/dist/types/core/ui-packs/hi.d.ts +3 -0
- package/dist/types/core/ui-packs/hr.d.ts +3 -0
- package/dist/types/core/ui-packs/hu.d.ts +3 -0
- package/dist/types/core/ui-packs/id.d.ts +3 -0
- package/dist/types/core/ui-packs/index.d.ts +13 -0
- package/dist/types/core/ui-packs/it.d.ts +3 -0
- package/dist/types/core/ui-packs/ja.d.ts +3 -0
- package/dist/types/core/ui-packs/ko.d.ts +3 -0
- package/dist/types/core/ui-packs/nl.d.ts +3 -0
- package/dist/types/core/ui-packs/no.d.ts +3 -0
- package/dist/types/core/ui-packs/pl.d.ts +3 -0
- package/dist/types/core/ui-packs/pt-br.d.ts +3 -0
- package/dist/types/core/ui-packs/pt.d.ts +3 -0
- package/dist/types/core/ui-packs/ro.d.ts +3 -0
- package/dist/types/core/ui-packs/ru.d.ts +3 -0
- package/dist/types/core/ui-packs/sk.d.ts +3 -0
- package/dist/types/core/ui-packs/sr.d.ts +3 -0
- package/dist/types/core/ui-packs/sv.d.ts +3 -0
- package/dist/types/core/ui-packs/th.d.ts +3 -0
- package/dist/types/core/ui-packs/tr.d.ts +3 -0
- package/dist/types/core/ui-packs/uk.d.ts +3 -0
- package/dist/types/core/ui-packs/vi.d.ts +3 -0
- package/dist/types/core/ui-packs/zh-tw.d.ts +3 -0
- package/dist/types/core/ui-packs/zh.d.ts +3 -0
- package/dist/types/core/version.d.ts +8 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/migrate/mintlify/config.d.ts +2 -0
- package/dist/types/migrate/mintlify/i18n.d.ts +7 -0
- package/dist/types/theme/fonts.d.ts +163 -0
- package/docs/01-quickstart.mdx +99 -0
- package/docs/02-deployment.mdx +129 -0
- package/docs/advanced/api-reference.mdx +114 -0
- package/docs/advanced/blog.mdx +121 -0
- package/docs/advanced/changelog.mdx +113 -0
- package/docs/advanced/custom-pages.mdx +268 -0
- package/docs/advanced/meta.ts +7 -0
- package/docs/changelog/v0-1-0.mdx +12 -0
- package/docs/changelog/v0-2-0.mdx +16 -0
- package/docs/configuration/ai.mdx +228 -0
- package/docs/configuration/analytics.mdx +98 -0
- package/docs/configuration/customization.mdx +91 -0
- package/docs/configuration/export.mdx +70 -0
- package/docs/configuration/index.mdx +290 -0
- package/docs/configuration/meta.ts +15 -0
- package/docs/configuration/search.mdx +172 -0
- package/docs/configuration/seo.mdx +196 -0
- package/docs/configuration/theming.mdx +178 -0
- package/docs/content/components.mdx +651 -0
- package/docs/content/i18n.mdx +205 -0
- package/docs/content/index.mdx +161 -0
- package/docs/content/islands.mdx +94 -0
- package/docs/content/meta.mdx +119 -0
- package/docs/content/meta.ts +15 -0
- package/docs/content/navigation.mdx +168 -0
- package/docs/content/sources.mdx +216 -0
- package/docs/content/syntax.mdx +445 -0
- package/docs/index.mdx +112 -0
- package/docs/reference/cli.mdx +43 -0
- package/docs/reference/frontmatter.mdx +74 -0
- package/docs/reference/meta.ts +7 -0
- package/package.json +150 -6
- package/src/ai/ask.ts +93 -0
- package/src/ai/llms.ts +64 -0
- package/src/ai/markdown.ts +31 -0
- package/src/ai/mcp/data.ts +74 -0
- package/src/ai/mcp/discovery.ts +49 -0
- package/src/ai/mcp/server.ts +225 -0
- package/src/ai/mcp/tools.ts +47 -0
- package/src/assets/icon.png +0 -0
- package/src/astro/examples.ts +104 -0
- package/src/astro/generate.ts +1006 -0
- package/src/astro/index.ts +4 -0
- package/src/astro/integration.ts +74 -0
- package/src/astro/islands.ts +131 -0
- package/src/astro/markdown-negotiation.ts +68 -0
- package/src/astro/pages.ts +87 -0
- package/src/astro/templates.ts +1326 -0
- package/src/cli/commands/add.ts +81 -0
- package/src/cli/commands/build.ts +103 -0
- package/src/cli/commands/dev.ts +108 -0
- package/src/cli/commands/doctor.ts +74 -0
- package/src/cli/commands/eject.ts +57 -0
- package/src/cli/commands/init.ts +98 -0
- package/src/cli/commands/migrate.ts +39 -0
- package/src/cli/commands/preview.ts +39 -0
- package/src/cli/commands/sync.ts +52 -0
- package/src/cli/commands/validate.ts +61 -0
- package/src/cli/index.ts +35 -0
- package/src/cli/log.ts +37 -0
- package/src/cli/prepare.ts +80 -0
- package/src/components/Icon.astro +99 -0
- package/src/components/content/Accordion.astro +8 -0
- package/src/components/content/AccordionItem.astro +121 -0
- package/src/components/content/AutoTypeTable.astro +51 -0
- package/src/components/content/Badge.astro +124 -0
- package/src/components/content/Callout.astro +73 -0
- package/src/components/content/Card.astro +104 -0
- package/src/components/content/CardGroup.astro +14 -0
- package/src/components/content/CodeBlock.astro +28 -0
- package/src/components/content/CodeGroup.astro +13 -0
- package/src/components/content/Color.astro +15 -0
- package/src/components/content/ColorItem.astro +87 -0
- package/src/components/content/ColorRow.astro +10 -0
- package/src/components/content/Column.astro +6 -0
- package/src/components/content/Columns.astro +9 -0
- package/src/components/content/Component.astro +65 -0
- package/src/components/content/Diff.astro +44 -0
- package/src/components/content/Expandable.astro +11 -0
- package/src/components/content/FileTree.astro +8 -0
- package/src/components/content/Frame.astro +70 -0
- package/src/components/content/GithubInfo.astro +110 -0
- package/src/components/content/Math.astro +24 -0
- package/src/components/content/Panel.astro +20 -0
- package/src/components/content/Prompt.astro +129 -0
- package/src/components/content/Step.astro +34 -0
- package/src/components/content/Steps.astro +20 -0
- package/src/components/content/Tab.astro +46 -0
- package/src/components/content/Tabs.astro +273 -0
- package/src/components/content/Tile.astro +42 -0
- package/src/components/content/Tooltip.astro +68 -0
- package/src/components/content/Tree.astro +300 -0
- package/src/components/content/TreeFile.astro +15 -0
- package/src/components/content/TreeFolder.astro +62 -0
- package/src/components/content/TypeTable.astro +106 -0
- package/src/components/content/Update.astro +66 -0
- package/src/components/content/Visibility.astro +12 -0
- package/src/components/content/Warning.astro +9 -0
- package/src/components/content/auto-type-table.ts +141 -0
- package/src/components/content/diff.ts +95 -0
- package/src/components/content/github-info.ts +79 -0
- package/src/components/content/mermaid-element.ts +68 -0
- package/src/components/github-mark.ts +9 -0
- package/src/components/index.ts +14 -0
- package/src/components/islands/AskAI.astro +12 -0
- package/src/components/islands/ask-ai.tsx +156 -0
- package/src/components/layout/Analytics.astro +63 -0
- package/src/components/layout/Banner.astro +50 -0
- package/src/components/layout/Breadcrumbs.astro +31 -0
- package/src/components/layout/Favicon.astro +22 -0
- package/src/components/layout/Fonts.astro +14 -0
- package/src/components/layout/Header.astro +188 -0
- package/src/components/layout/LanguageSwitcher.astro +56 -0
- package/src/components/layout/NavTree.astro +462 -0
- package/src/components/layout/PageActions.astro +438 -0
- package/src/components/layout/PageFeedback.astro +58 -0
- package/src/components/layout/PageLayout.astro +173 -0
- package/src/components/layout/Pagination.astro +56 -0
- package/src/components/layout/ReferenceLayout.astro +107 -0
- package/src/components/layout/RootLayout.astro +537 -0
- package/src/components/layout/Search.astro +608 -0
- package/src/components/layout/TableOfContents.astro +68 -0
- package/src/components/layout/analytics-client.ts +38 -0
- package/src/components/layout/head-scripts.ts +19 -0
- package/src/components/layout/nav-utils.ts +87 -0
- package/src/components/layout/overrides.ts +32 -0
- package/src/components/layout/search/algolia.ts +43 -0
- package/src/components/layout/search/endpoint.ts +22 -0
- package/src/components/layout/search/flexsearch.ts +52 -0
- package/src/components/layout/search/orama-cloud.ts +41 -0
- package/src/components/layout/search/orama.ts +26 -0
- package/src/components/layout/search/pagefind.ts +43 -0
- package/src/components/layout/search/types.ts +163 -0
- package/src/components/layout/search/typesense.ts +60 -0
- package/src/components/layout/toc-element.ts +108 -0
- package/src/core/bridge.ts +92 -0
- package/src/core/config.ts +112 -0
- package/src/core/content.ts +50 -0
- package/src/core/data.ts +128 -0
- package/src/core/define-components.ts +34 -0
- package/src/core/define-meta.ts +20 -0
- package/src/core/deployment-env.ts +73 -0
- package/src/core/diagnostics.ts +104 -0
- package/src/core/frontmatter.ts +43 -0
- package/src/core/graph.ts +128 -0
- package/src/core/i18n-ui.ts +171 -0
- package/src/core/i18n.ts +169 -0
- package/src/core/last-modified.ts +88 -0
- package/src/core/links.ts +352 -0
- package/src/core/load-module.ts +15 -0
- package/src/core/manifest.ts +126 -0
- package/src/core/meta.ts +97 -0
- package/src/core/navigation.ts +392 -0
- package/src/core/package-root.ts +37 -0
- package/src/core/project-graph.ts +153 -0
- package/src/core/project.ts +56 -0
- package/src/core/schema.ts +1057 -0
- package/src/core/server-features.ts +23 -0
- package/src/core/sources/assets.ts +77 -0
- package/src/core/sources/cache.ts +122 -0
- package/src/core/sources/filesystem.ts +99 -0
- package/src/core/sources/mdx-remote.ts +215 -0
- package/src/core/sources/mintlify.ts +161 -0
- package/src/core/sources/normalize.ts +243 -0
- package/src/core/sources/notion.ts +440 -0
- package/src/core/sources/portable-text.ts +143 -0
- package/src/core/sources/read.ts +36 -0
- package/src/core/sources/resolve.ts +158 -0
- package/src/core/sources/sanity.ts +218 -0
- package/src/core/sources/types.ts +105 -0
- package/src/core/tsconfig-aliases.ts +201 -0
- package/src/core/types.ts +261 -0
- package/src/core/ui-packs/ar.ts +47 -0
- package/src/core/ui-packs/bg.ts +47 -0
- package/src/core/ui-packs/bn.ts +47 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +47 -0
- package/src/core/ui-packs/da.ts +47 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +47 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +47 -0
- package/src/core/ui-packs/fi.ts +47 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +47 -0
- package/src/core/ui-packs/hi.ts +47 -0
- package/src/core/ui-packs/hr.ts +47 -0
- package/src/core/ui-packs/hu.ts +47 -0
- package/src/core/ui-packs/id.ts +47 -0
- package/src/core/ui-packs/index.ts +87 -0
- package/src/core/ui-packs/it.ts +47 -0
- package/src/core/ui-packs/ja.ts +47 -0
- package/src/core/ui-packs/ko.ts +47 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +47 -0
- package/src/core/ui-packs/pl.ts +47 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +47 -0
- package/src/core/ui-packs/ro.ts +47 -0
- package/src/core/ui-packs/ru.ts +47 -0
- package/src/core/ui-packs/sk.ts +47 -0
- package/src/core/ui-packs/sr.ts +47 -0
- package/src/core/ui-packs/sv.ts +47 -0
- package/src/core/ui-packs/th.ts +47 -0
- package/src/core/ui-packs/tr.ts +47 -0
- package/src/core/ui-packs/uk.ts +47 -0
- package/src/core/ui-packs/vi.ts +47 -0
- package/src/core/ui-packs/zh-tw.ts +47 -0
- package/src/core/ui-packs/zh.ts +47 -0
- package/src/core/version.ts +23 -0
- package/src/deploy/robots.ts +20 -0
- package/src/deploy/rss.ts +128 -0
- package/src/deploy/sitemap.ts +28 -0
- package/src/index.ts +39 -0
- package/src/markdown/code-title.ts +71 -0
- package/src/markdown/directives.ts +83 -0
- package/src/markdown/heading-anchors.ts +137 -0
- package/src/markdown/index.ts +228 -0
- package/src/markdown/inline-code.ts +108 -0
- package/src/markdown/language-icon.ts +172 -0
- package/src/markdown/math.ts +32 -0
- package/src/markdown/mdast.ts +48 -0
- package/src/markdown/mermaid.ts +37 -0
- package/src/markdown/package-commands.ts +159 -0
- package/src/markdown/package-install.ts +40 -0
- package/src/migrate/fumadocs/config.ts +155 -0
- package/src/migrate/fumadocs/content.ts +365 -0
- package/src/migrate/fumadocs/frontmatter.ts +18 -0
- package/src/migrate/fumadocs/groups.ts +230 -0
- package/src/migrate/fumadocs/index.ts +337 -0
- package/src/migrate/fumadocs/meta.ts +244 -0
- package/src/migrate/migrate.ts +53 -0
- package/src/migrate/mintlify/config.ts +1040 -0
- package/src/migrate/mintlify/content.ts +98 -0
- package/src/migrate/mintlify/frontmatter.ts +126 -0
- package/src/migrate/mintlify/i18n.ts +51 -0
- package/src/migrate/mintlify/icons.ts +128 -0
- package/src/migrate/mintlify/index.ts +266 -0
- package/src/migrate/mintlify/snippets.ts +306 -0
- package/src/migrate/mintlify/transform.ts +80 -0
- package/src/migrate/nextra/content.ts +46 -0
- package/src/migrate/nextra/frontmatter.ts +40 -0
- package/src/migrate/nextra/index.ts +374 -0
- package/src/migrate/nextra/meta.ts +266 -0
- package/src/migrate/shared.ts +720 -0
- package/src/migrate/starlight/config.ts +459 -0
- package/src/migrate/starlight/content.ts +78 -0
- package/src/migrate/starlight/frontmatter.ts +111 -0
- package/src/migrate/starlight/i18n.ts +54 -0
- package/src/migrate/starlight/index.ts +131 -0
- package/src/og/card.ts +92 -0
- package/src/og/index.ts +2 -0
- package/src/openapi/scalar.ts +246 -0
- package/src/registry/eject.ts +310 -0
- package/src/registry/registry.ts +100 -0
- package/src/registry/rewrite-imports.ts +39 -0
- package/src/runtime/index.ts +14 -0
- package/src/search/build.ts +23 -0
- package/src/search/documents.ts +164 -0
- package/src/search/orama-index.ts +66 -0
- package/src/search/providers.ts +91 -0
- package/src/search/sync/algolia.ts +30 -0
- package/src/search/sync/index.ts +50 -0
- package/src/search/sync/orama-cloud.ts +40 -0
- package/src/search/sync/typesense.ts +65 -0
- package/src/seo/jsonld.ts +113 -0
- package/src/theme/entry.ts +637 -0
- package/src/theme/fonts.ts +198 -0
- package/src/theme/icons.ts +184 -0
- package/src/theme/palette.ts +143 -0
- package/src/theme/twoslash.ts +81 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import { join, relative } from "pathe";
|
|
5
|
+
|
|
6
|
+
import { resolveAskBackend } from "../ai/ask.ts";
|
|
7
|
+
import { buildRawMarkdown } from "../ai/markdown.ts";
|
|
8
|
+
import { discoverExamples } from "../astro/examples.ts";
|
|
9
|
+
import {
|
|
10
|
+
buildRuntimeData,
|
|
11
|
+
collectStaged,
|
|
12
|
+
detectNeedsReact,
|
|
13
|
+
} from "../astro/generate.ts";
|
|
14
|
+
import { discoverIslands } from "../astro/islands.ts";
|
|
15
|
+
import { customOgRoutes, discoverPages } from "../astro/pages.ts";
|
|
16
|
+
import {
|
|
17
|
+
askEndpointTemplate,
|
|
18
|
+
astroConfigTemplate,
|
|
19
|
+
catchAllPageTemplate,
|
|
20
|
+
contentConfigTemplate,
|
|
21
|
+
envTemplate,
|
|
22
|
+
exampleMapTemplate,
|
|
23
|
+
exampleWrapperTemplate,
|
|
24
|
+
exampleSlug,
|
|
25
|
+
islandMapTemplate,
|
|
26
|
+
islandWrapperTemplate,
|
|
27
|
+
mixedbreadSearchEndpointTemplate,
|
|
28
|
+
ogEndpointTemplate,
|
|
29
|
+
rawMarkdownEndpointTemplate,
|
|
30
|
+
rssEndpointTemplate,
|
|
31
|
+
runtimeTsconfigTemplate,
|
|
32
|
+
searchClientTemplate,
|
|
33
|
+
searchEndpointTemplate,
|
|
34
|
+
userComponentsTemplate,
|
|
35
|
+
} from "../astro/templates.ts";
|
|
36
|
+
import { scanProject } from "../core/project-graph.ts";
|
|
37
|
+
import type { ProjectContext } from "../core/types.ts";
|
|
38
|
+
import { buildRssFeeds, renderRssFeed } from "../deploy/rss.ts";
|
|
39
|
+
import { buildReferenceFiles, hasReferences } from "../openapi/scalar.ts";
|
|
40
|
+
import { buildSearchDocuments } from "../search/documents.ts";
|
|
41
|
+
import { servesStaticIndex } from "../search/providers.ts";
|
|
42
|
+
import { tailwindEntryTemplate } from "../theme/entry.ts";
|
|
43
|
+
import { buildThemeCss } from "../theme/palette.ts";
|
|
44
|
+
import { twoslashCss } from "../theme/twoslash.ts";
|
|
45
|
+
|
|
46
|
+
const POSIX = (path: string): string => path.split("\\").join("/");
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Promote the generated runtime into the project as an owned Astro app. After
|
|
50
|
+
* eject the project has a normal `astro.config.mjs` and `src/`, the `blume` CLI
|
|
51
|
+
* is no longer required, and the `blume` package remains importable.
|
|
52
|
+
*
|
|
53
|
+
* Returns the list of written files.
|
|
54
|
+
*/
|
|
55
|
+
export const eject = async (root: string): Promise<string[]> => {
|
|
56
|
+
const project = await scanProject(root, { mode: "build" });
|
|
57
|
+
const { context, config } = project;
|
|
58
|
+
|
|
59
|
+
const srcDir = join(root, "src");
|
|
60
|
+
const genDir = join(srcDir, "generated");
|
|
61
|
+
const askEnabled = config.ai.ask?.enabled ?? false;
|
|
62
|
+
const exportPdf = config.export.pdf;
|
|
63
|
+
const exportEpub = config.export.epub;
|
|
64
|
+
|
|
65
|
+
const [pages, needsReactRaw, userTheme, rawMarkdown, islands, examples] =
|
|
66
|
+
await Promise.all([
|
|
67
|
+
context.pagesRoot
|
|
68
|
+
? discoverPages(context.pagesRoot)
|
|
69
|
+
: Promise.resolve([]),
|
|
70
|
+
detectNeedsReact(root),
|
|
71
|
+
context.themeFile
|
|
72
|
+
? readFile(context.themeFile, "utf-8")
|
|
73
|
+
: Promise.resolve(""),
|
|
74
|
+
buildRawMarkdown(project),
|
|
75
|
+
discoverIslands(root),
|
|
76
|
+
discoverExamples(root),
|
|
77
|
+
]);
|
|
78
|
+
// Island/example frameworks drive which Astro renderers the ejected config
|
|
79
|
+
// wires in; React also switches on for project `.tsx`/`.jsx` and Ask AI.
|
|
80
|
+
const frameworks = new Set<string>([
|
|
81
|
+
...islands.islands.map((island) => island.framework),
|
|
82
|
+
...examples.examples.map((example) => example.framework),
|
|
83
|
+
]);
|
|
84
|
+
const needsReact = needsReactRaw || askEnabled || frameworks.has("react");
|
|
85
|
+
const needsVue = frameworks.has("vue");
|
|
86
|
+
const needsSvelte = frameworks.has("svelte");
|
|
87
|
+
|
|
88
|
+
// A project-relative context so generated files use portable paths.
|
|
89
|
+
const relContext: ProjectContext = {
|
|
90
|
+
...context,
|
|
91
|
+
contentRoot: POSIX(relative(root, context.contentRoot)),
|
|
92
|
+
outDir: ".",
|
|
93
|
+
root: ".",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const componentsImport = context.componentsFile
|
|
97
|
+
? `../../${POSIX(relative(root, context.componentsFile))}`
|
|
98
|
+
: null;
|
|
99
|
+
const relPages = pages.map((page) => ({
|
|
100
|
+
entrypoint: POSIX(relative(root, page.entrypoint)),
|
|
101
|
+
pattern: page.pattern,
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
// Non-filesystem sources eject their materialized MDX into `<root>/blume-staged`
|
|
105
|
+
// (a dedicated dir so it never clashes with a content root literally named
|
|
106
|
+
// `content`; the relative `staged` collection points there).
|
|
107
|
+
const staged = collectStaged(project);
|
|
108
|
+
const hasStaged = staged.size > 0;
|
|
109
|
+
const stagedDir = "blume-staged";
|
|
110
|
+
|
|
111
|
+
const files: { path: string; content: string }[] = [
|
|
112
|
+
{
|
|
113
|
+
content: astroConfigTemplate({
|
|
114
|
+
config,
|
|
115
|
+
contentRoutes: project.manifest.routes.map((route) => route.path),
|
|
116
|
+
context: relContext,
|
|
117
|
+
dataPath: "./src/generated/data.json",
|
|
118
|
+
examplesPath: "./src/generated/examples.ts",
|
|
119
|
+
needsReact,
|
|
120
|
+
needsSvelte,
|
|
121
|
+
needsVue,
|
|
122
|
+
pages: relPages,
|
|
123
|
+
searchClientPath: "./src/generated/search-client.ts",
|
|
124
|
+
themePath: "./src/generated/app.css",
|
|
125
|
+
}),
|
|
126
|
+
path: join(root, "astro.config.mjs"),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
content: runtimeTsconfigTemplate(),
|
|
130
|
+
path: join(root, "tsconfig.json"),
|
|
131
|
+
},
|
|
132
|
+
{ content: envTemplate(), path: join(srcDir, "env.d.ts") },
|
|
133
|
+
{
|
|
134
|
+
content: contentConfigTemplate({
|
|
135
|
+
config,
|
|
136
|
+
context: relContext,
|
|
137
|
+
staged: hasStaged,
|
|
138
|
+
stagedBase: stagedDir,
|
|
139
|
+
}),
|
|
140
|
+
path: join(srcDir, "content.config.ts"),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
content: catchAllPageTemplate({
|
|
144
|
+
askEnabled,
|
|
145
|
+
exportEpub,
|
|
146
|
+
exportPdf,
|
|
147
|
+
mathEnabled: config.markdown.math,
|
|
148
|
+
}),
|
|
149
|
+
path: join(srcDir, "pages", "[...slug].astro"),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
content: userComponentsTemplate(componentsImport),
|
|
153
|
+
path: join(genDir, "components.ts"),
|
|
154
|
+
},
|
|
155
|
+
// Island/example maps the catch-all imports; written even when empty so the
|
|
156
|
+
// relative import and the `blume:examples` alias always resolve.
|
|
157
|
+
{
|
|
158
|
+
content: islandMapTemplate(islands.islands),
|
|
159
|
+
path: join(genDir, "islands.ts"),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
content: exampleMapTemplate(examples.examples),
|
|
163
|
+
path: join(genDir, "examples.ts"),
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
content: tailwindEntryTemplate({
|
|
167
|
+
configTokens: buildThemeCss(config.theme),
|
|
168
|
+
// Relative paths from src/generated/app.css keep the ejected app portable.
|
|
169
|
+
sources: [
|
|
170
|
+
"../../node_modules/blume/src/**/*.{astro,ts,tsx}",
|
|
171
|
+
"../../**/*.{astro,mdx,ts,tsx}",
|
|
172
|
+
],
|
|
173
|
+
twoslashCss: twoslashCss(),
|
|
174
|
+
userTheme,
|
|
175
|
+
}),
|
|
176
|
+
path: join(genDir, "app.css"),
|
|
177
|
+
},
|
|
178
|
+
{ content: buildRuntimeData(project), path: join(genDir, "data.json") },
|
|
179
|
+
{
|
|
180
|
+
content: `${JSON.stringify(rawMarkdown)}\n`,
|
|
181
|
+
path: join(genDir, "raw-markdown.json"),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
content: rawMarkdownEndpointTemplate(),
|
|
185
|
+
path: join(srcDir, "pages", "[...slug].md.ts"),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
content: rawMarkdownEndpointTemplate(),
|
|
189
|
+
path: join(srcDir, "pages", "[...slug].mdx.ts"),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
if (askEnabled) {
|
|
194
|
+
files.push({
|
|
195
|
+
content: askEndpointTemplate(resolveAskBackend(config.ai.ask)),
|
|
196
|
+
path: join(srcDir, "pages", "api", "ask.ts"),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (config.seo.og.enabled) {
|
|
201
|
+
files.push({
|
|
202
|
+
content: ogEndpointTemplate(
|
|
203
|
+
customOgRoutes(pages, config.title, config.description)
|
|
204
|
+
),
|
|
205
|
+
path: join(srcDir, "pages", "og", "[...slug].png.ts"),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// The provider-specific client loader behind the `blume:search-client` alias.
|
|
210
|
+
files.push({
|
|
211
|
+
content: searchClientTemplate(config),
|
|
212
|
+
path: join(genDir, "search-client.ts"),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (servesStaticIndex(config.search.provider)) {
|
|
216
|
+
const documents = await buildSearchDocuments(project);
|
|
217
|
+
files.push(
|
|
218
|
+
{
|
|
219
|
+
content: `${JSON.stringify(documents)}\n`,
|
|
220
|
+
path: join(genDir, "search.json"),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
content: searchEndpointTemplate(),
|
|
224
|
+
path: join(srcDir, "pages", "blume-search.json.ts"),
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (config.search.provider === "mixedbread") {
|
|
230
|
+
files.push({
|
|
231
|
+
content: mixedbreadSearchEndpointTemplate(
|
|
232
|
+
config.search.mixedbread?.storeId ?? ""
|
|
233
|
+
),
|
|
234
|
+
path: join(srcDir, "pages", "api", "search.ts"),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const feeds = buildRssFeeds(project);
|
|
239
|
+
if (feeds.length > 0) {
|
|
240
|
+
const feedXml = Object.fromEntries(
|
|
241
|
+
feeds.map((feed) => [feed.type, renderRssFeed(feed)])
|
|
242
|
+
);
|
|
243
|
+
files.push(
|
|
244
|
+
{
|
|
245
|
+
content: `${JSON.stringify(feedXml)}\n`,
|
|
246
|
+
path: join(genDir, "rss.json"),
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
content: rssEndpointTemplate(),
|
|
250
|
+
path: join(srcDir, "pages", "[section]", "rss.xml.ts"),
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Scalar API/AsyncAPI reference pages, mirrored from the generated runtime so
|
|
256
|
+
// the ejected app keeps its reference routes.
|
|
257
|
+
if (hasReferences(config)) {
|
|
258
|
+
const references = await buildReferenceFiles({
|
|
259
|
+
config,
|
|
260
|
+
contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
|
|
261
|
+
root,
|
|
262
|
+
});
|
|
263
|
+
for (const file of references.files) {
|
|
264
|
+
files.push({
|
|
265
|
+
content: file.content,
|
|
266
|
+
path: join(srcDir, "pages", file.pagePath),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Per-island and per-example live wrappers referenced by the maps above.
|
|
272
|
+
files.push(
|
|
273
|
+
...islands.islands.map((island) => ({
|
|
274
|
+
content: islandWrapperTemplate(island),
|
|
275
|
+
path: join(genDir, "islands", `${island.name}.astro`),
|
|
276
|
+
})),
|
|
277
|
+
...examples.examples.map((example) => ({
|
|
278
|
+
content: exampleWrapperTemplate(example),
|
|
279
|
+
path: join(genDir, "examples", `${exampleSlug(example.path)}.astro`),
|
|
280
|
+
}))
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// Materialize staged source bodies under `<root>/blume-staged/<source>/<ref>`,
|
|
284
|
+
// matching the relative `staged` collection base in the ejected config.
|
|
285
|
+
for (const [entryId, content] of staged) {
|
|
286
|
+
files.push({ content, path: join(root, stagedDir, entryId) });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await Promise.all(
|
|
290
|
+
files.map(async (file) => {
|
|
291
|
+
await mkdir(join(file.path, ".."), { recursive: true });
|
|
292
|
+
await writeFile(file.path, file.content, "utf-8");
|
|
293
|
+
})
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Materialized source assets (e.g. downloaded Notion images) live under the
|
|
297
|
+
// hidden runtime's public dir; copy them into the owned project's `public/`
|
|
298
|
+
// so the staged content's `/blume-assets/…` references still resolve.
|
|
299
|
+
const assetsSrc = join(context.outDir, "public", "blume-assets");
|
|
300
|
+
if (existsSync(assetsSrc)) {
|
|
301
|
+
await cp(assetsSrc, join(root, "public", "blume-assets"), {
|
|
302
|
+
recursive: true,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The hidden runtime is no longer the source of truth.
|
|
307
|
+
await rm(context.outDir, { force: true, recursive: true });
|
|
308
|
+
|
|
309
|
+
return files.map((file) => file.path);
|
|
310
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { join } from "pathe";
|
|
2
|
+
|
|
3
|
+
import { packageRoot } from "../core/package-root.ts";
|
|
4
|
+
|
|
5
|
+
/** A file copied into the user's project by `blume add`. */
|
|
6
|
+
export interface RegistryFile {
|
|
7
|
+
/** Path to the source file, relative to the blume package `src` directory. */
|
|
8
|
+
source: string;
|
|
9
|
+
/** Destination path relative to the project root. */
|
|
10
|
+
target: string;
|
|
11
|
+
/** Rewrite the file's relative imports to `blume/*` specifiers on copy. */
|
|
12
|
+
rewrite?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RegistryItem {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
files: RegistryFile[];
|
|
19
|
+
/** Lines printed after install to guide the user. */
|
|
20
|
+
postInstall: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Absolute path to the blume package `src` directory (the copy source root). */
|
|
24
|
+
export const packageSrc = join(packageRoot(), "src");
|
|
25
|
+
|
|
26
|
+
/** Absolute path to the bundled registry item sources. */
|
|
27
|
+
export const itemsRoot = join(packageRoot(), "src", "registry", "items");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A built-in layout component offered as editable source. `blume add` rewrites
|
|
31
|
+
* its relative imports to `blume/*`, so it renders identically to the built-in
|
|
32
|
+
* until the user changes it, then registers under the matching `layout` slot.
|
|
33
|
+
*/
|
|
34
|
+
const layoutComponent = (config: {
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
/** Source basename under `src/components/layout`. */
|
|
38
|
+
file: string;
|
|
39
|
+
/** Layout slot key, also the import name in the post-install hint. */
|
|
40
|
+
slot: string;
|
|
41
|
+
}): RegistryItem => {
|
|
42
|
+
const target = `components/blume/${config.file}`;
|
|
43
|
+
return {
|
|
44
|
+
description: config.description,
|
|
45
|
+
files: [
|
|
46
|
+
{
|
|
47
|
+
rewrite: true,
|
|
48
|
+
source: `components/layout/${config.file}`,
|
|
49
|
+
target,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
name: config.name,
|
|
53
|
+
postInstall: [
|
|
54
|
+
"Register it in components.ts:",
|
|
55
|
+
' import { defineComponents } from "blume";',
|
|
56
|
+
` import ${config.slot} from "./${target}";`,
|
|
57
|
+
"",
|
|
58
|
+
` export default defineComponents({ layout: { ${config.slot} } });`,
|
|
59
|
+
"",
|
|
60
|
+
"It imports the rest from `blume/*`, so it matches the built-in until you edit it.",
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** The built-in, Blume-owned source registry. */
|
|
66
|
+
export const registry: RegistryItem[] = [
|
|
67
|
+
layoutComponent({
|
|
68
|
+
description: "The top navigation bar (logo, search, nav links).",
|
|
69
|
+
file: "Header.astro",
|
|
70
|
+
name: "header",
|
|
71
|
+
slot: "Header",
|
|
72
|
+
}),
|
|
73
|
+
layoutComponent({
|
|
74
|
+
description: "The sidebar navigation tree.",
|
|
75
|
+
file: "NavTree.astro",
|
|
76
|
+
name: "sidebar",
|
|
77
|
+
slot: "Sidebar",
|
|
78
|
+
}),
|
|
79
|
+
layoutComponent({
|
|
80
|
+
description: "The breadcrumb trail shown above page content.",
|
|
81
|
+
file: "Breadcrumbs.astro",
|
|
82
|
+
name: "breadcrumbs",
|
|
83
|
+
slot: "Breadcrumbs",
|
|
84
|
+
}),
|
|
85
|
+
layoutComponent({
|
|
86
|
+
description: "The on-this-page table of contents.",
|
|
87
|
+
file: "TableOfContents.astro",
|
|
88
|
+
name: "table-of-contents",
|
|
89
|
+
slot: "TableOfContents",
|
|
90
|
+
}),
|
|
91
|
+
layoutComponent({
|
|
92
|
+
description: "The previous/next pagination footer.",
|
|
93
|
+
file: "Pagination.astro",
|
|
94
|
+
name: "pagination",
|
|
95
|
+
slot: "Pagination",
|
|
96
|
+
}),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
export const findItem = (name: string): RegistryItem | undefined =>
|
|
100
|
+
registry.find((item) => item.name === name);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { dirname, relative, resolve } from "pathe";
|
|
2
|
+
|
|
3
|
+
// The module specifier in `... from "<spec>"` and side-effect `import "<spec>"`,
|
|
4
|
+
// limited to relative specifiers (those starting with `.`). Capturing the
|
|
5
|
+
// keyword, gap, and quote lets us rebuild the statement verbatim.
|
|
6
|
+
const RELATIVE_IMPORT =
|
|
7
|
+
/(?<kw>\bfrom|\bimport)(?<gap>\s+)(?<quote>["'])(?<spec>\.[^"']*)\k<quote>/gu;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Rewrite a built-in component's relative imports to `blume/*` package
|
|
11
|
+
* specifiers, so a copy installed by `blume add` resolves the rest of the
|
|
12
|
+
* framework from the package instead of broken relative paths.
|
|
13
|
+
*
|
|
14
|
+
* `sourceFile` is the original file's absolute path and `srcRoot` the package
|
|
15
|
+
* `src` directory; a relative import is resolved against the source, mapped to
|
|
16
|
+
* its path under `src`, and re-emitted as `blume/<that path>`. Two specifiers
|
|
17
|
+
* are left untouched: a self-reference (a component importing itself, e.g. the
|
|
18
|
+
* recursive nav tree) stays relative so the copy recurses into the copy, and
|
|
19
|
+
* anything resolving outside `src` is left as-is.
|
|
20
|
+
*/
|
|
21
|
+
export const rewriteImports = (
|
|
22
|
+
content: string,
|
|
23
|
+
sourceFile: string,
|
|
24
|
+
srcRoot: string
|
|
25
|
+
): string =>
|
|
26
|
+
content.replaceAll(
|
|
27
|
+
RELATIVE_IMPORT,
|
|
28
|
+
(match, kw: string, gap: string, quote: string, spec: string) => {
|
|
29
|
+
const resolved = resolve(dirname(sourceFile), spec);
|
|
30
|
+
if (resolved === sourceFile) {
|
|
31
|
+
return match;
|
|
32
|
+
}
|
|
33
|
+
const rel = relative(srcRoot, resolved);
|
|
34
|
+
if (rel.startsWith("..")) {
|
|
35
|
+
return match;
|
|
36
|
+
}
|
|
37
|
+
return `${kw}${gap}${quote}blume/${rel}${quote}`;
|
|
38
|
+
}
|
|
39
|
+
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime helpers usable inside `.astro` pages and islands.
|
|
3
|
+
*
|
|
4
|
+
* These give custom pages and components access to Blume project data
|
|
5
|
+
* (config, navigation, page collections) without reaching into generated
|
|
6
|
+
* runtime internals. The surface grows with the customization milestone.
|
|
7
|
+
*/
|
|
8
|
+
export type {
|
|
9
|
+
Heading,
|
|
10
|
+
NavNode,
|
|
11
|
+
Navigation,
|
|
12
|
+
NavTab,
|
|
13
|
+
PageRecord,
|
|
14
|
+
} from "../core/types.ts";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { join } from "pathe";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a local Pagefind search index over the built site. Pagefind only
|
|
5
|
+
* indexes elements marked with `data-pagefind-body`, which Blume adds to the
|
|
6
|
+
* content of indexable pages, so nav chrome and excluded pages are skipped.
|
|
7
|
+
*
|
|
8
|
+
* Returns the number of pages indexed.
|
|
9
|
+
*/
|
|
10
|
+
export const buildSearchIndex = async (outDir: string): Promise<number> => {
|
|
11
|
+
const pagefind = await import("pagefind");
|
|
12
|
+
|
|
13
|
+
const { index } = await pagefind.createIndex({});
|
|
14
|
+
if (!index) {
|
|
15
|
+
throw new Error("Failed to create Pagefind index.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = await index.addDirectory({ path: outDir });
|
|
19
|
+
await index.writeFiles({ outputPath: join(outDir, "pagefind") });
|
|
20
|
+
await pagefind.close();
|
|
21
|
+
|
|
22
|
+
return result.page_count;
|
|
23
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import matter from "../core/frontmatter.ts";
|
|
2
|
+
import { contentIndexable } from "../core/manifest.ts";
|
|
3
|
+
import type { BlumeProject } from "../core/project-graph.ts";
|
|
4
|
+
import { readEntryText } from "../core/sources/read.ts";
|
|
5
|
+
import type { NavNode } from "../core/types.ts";
|
|
6
|
+
|
|
7
|
+
/** A document indexed by the client-side search providers (Orama, FlexSearch). */
|
|
8
|
+
export interface SearchDocument {
|
|
9
|
+
route: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
content: string;
|
|
13
|
+
/** Ancestor section labels for the result breadcrumb, e.g. `["Guides"]`. */
|
|
14
|
+
breadcrumb: string[];
|
|
15
|
+
/** Top-level section label, used by the search filter pills. */
|
|
16
|
+
section: string;
|
|
17
|
+
/** Locale code, so the dialog can filter results to the active language. */
|
|
18
|
+
locale: string;
|
|
19
|
+
/** Frontmatter `search.tags`, surfaced for hosted-provider faceting. */
|
|
20
|
+
tags?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A record uploaded to a hosted search backend (Algolia, Orama Cloud,
|
|
25
|
+
* Typesense, Mixedbread). `_id` is the stable per-page key; each sync adapts it
|
|
26
|
+
* to the backend's own id field (`objectID`, `id`, …).
|
|
27
|
+
*/
|
|
28
|
+
export interface SearchRecord {
|
|
29
|
+
_id: string;
|
|
30
|
+
url: string;
|
|
31
|
+
title: string;
|
|
32
|
+
description: string;
|
|
33
|
+
content: string;
|
|
34
|
+
/** Locale code, carried as a facet for per-language filtering. */
|
|
35
|
+
locale: string;
|
|
36
|
+
/** Single faceting tag (the first frontmatter tag, when present). */
|
|
37
|
+
tag?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const CODE_FENCE = /```[\s\S]*?```/gu;
|
|
41
|
+
const INLINE_CODE = /`(?<code>[^`]+)`/gu;
|
|
42
|
+
const HTML_OR_JSX = /<[^>]+>/gu;
|
|
43
|
+
const IMAGE = /!\[[^\]]*\]\([^)]*\)/gu;
|
|
44
|
+
const LINK = /\[(?<text>[^\]]*)\]\([^)]*\)/gu;
|
|
45
|
+
const HEADING_MARK = /^#{1,6}\s+/gmu;
|
|
46
|
+
const MARKDOWN_PUNCT = /[*_~>]+/gu;
|
|
47
|
+
const WHITESPACE = /\s+/gu;
|
|
48
|
+
|
|
49
|
+
/** Reduce Markdown/MDX to plain, searchable text. */
|
|
50
|
+
const toPlainText = (markdown: string): string =>
|
|
51
|
+
markdown
|
|
52
|
+
.replaceAll(CODE_FENCE, " ")
|
|
53
|
+
.replaceAll(IMAGE, " ")
|
|
54
|
+
.replaceAll(LINK, "$<text>")
|
|
55
|
+
.replaceAll(HTML_OR_JSX, " ")
|
|
56
|
+
.replaceAll(INLINE_CODE, "$<code>")
|
|
57
|
+
.replaceAll(HEADING_MARK, "")
|
|
58
|
+
.replaceAll(MARKDOWN_PUNCT, " ")
|
|
59
|
+
.replaceAll(WHITESPACE, " ")
|
|
60
|
+
.trim();
|
|
61
|
+
|
|
62
|
+
interface Crumbs {
|
|
63
|
+
breadcrumb: string[];
|
|
64
|
+
section: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Map each page route to its ancestor section labels by walking the nav sidebar
|
|
69
|
+
* once. The nearest ancestor group — the sidebar section a page appears under —
|
|
70
|
+
* becomes its `section`, the dimension the search filter pills group by, so the
|
|
71
|
+
* pills mirror the visible sidebar (and honor folder-meta renames).
|
|
72
|
+
*/
|
|
73
|
+
const buildCrumbIndex = (sidebar: NavNode[]): Map<string, Crumbs> => {
|
|
74
|
+
const index = new Map<string, Crumbs>();
|
|
75
|
+
const walk = (nodes: NavNode[], trail: string[]): void => {
|
|
76
|
+
for (const node of nodes) {
|
|
77
|
+
if (node.kind === "group") {
|
|
78
|
+
walk(node.children, [...trail, node.label]);
|
|
79
|
+
} else if (node.route) {
|
|
80
|
+
index.set(node.route, {
|
|
81
|
+
breadcrumb: trail,
|
|
82
|
+
section: trail.at(-1) ?? "",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
walk(sidebar, []);
|
|
88
|
+
return index;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build search documents from the content graph. Only indexable pages are
|
|
93
|
+
* included (per the route manifest), and content comes from the source files,
|
|
94
|
+
* so the index is identical in dev and build.
|
|
95
|
+
*
|
|
96
|
+
* Pass `includeWhenDisabled` to index pages on their content merits even when
|
|
97
|
+
* the search provider is `none` — used by the MCP server, which is a separate
|
|
98
|
+
* feature from on-page search.
|
|
99
|
+
*/
|
|
100
|
+
export const buildSearchDocuments = async (
|
|
101
|
+
project: BlumeProject,
|
|
102
|
+
options?: { includeWhenDisabled?: boolean }
|
|
103
|
+
): Promise<SearchDocument[]> => {
|
|
104
|
+
const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));
|
|
105
|
+
|
|
106
|
+
// Build the crumb index from every locale's sidebar (their nodes carry
|
|
107
|
+
// locale-prefixed routes), so localized pages get the right section/breadcrumb.
|
|
108
|
+
// Falls back to the single default-locale nav when i18n is off.
|
|
109
|
+
const byLocale = Object.values(project.graph.navigationByLocale ?? {});
|
|
110
|
+
const sidebars =
|
|
111
|
+
byLocale.length > 0
|
|
112
|
+
? byLocale.map((nav) => nav.sidebar)
|
|
113
|
+
: [project.graph.navigation?.sidebar ?? []];
|
|
114
|
+
const crumbs = new Map<string, Crumbs>();
|
|
115
|
+
for (const sidebar of sidebars) {
|
|
116
|
+
for (const [route, crumb] of buildCrumbIndex(sidebar)) {
|
|
117
|
+
crumbs.set(route, crumb);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const indexable = project.manifest.routes.filter((route) => {
|
|
122
|
+
if (!options?.includeWhenDisabled) {
|
|
123
|
+
return route.indexable;
|
|
124
|
+
}
|
|
125
|
+
const page = pageById.get(route.id);
|
|
126
|
+
return page ? contentIndexable(page, project.config) : false;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return await Promise.all(
|
|
130
|
+
indexable.map(async (route) => {
|
|
131
|
+
const page = pageById.get(route.id);
|
|
132
|
+
const raw = page ? await readEntryText(project, page) : "";
|
|
133
|
+
const body = raw ? toPlainText(matter(raw).content) : "";
|
|
134
|
+
const tags = page?.meta?.search?.tags;
|
|
135
|
+
const crumb = crumbs.get(route.path);
|
|
136
|
+
return {
|
|
137
|
+
breadcrumb: crumb?.breadcrumb ?? [],
|
|
138
|
+
content: body,
|
|
139
|
+
description: page?.description ?? "",
|
|
140
|
+
locale: route.locale,
|
|
141
|
+
route: route.path,
|
|
142
|
+
section: crumb?.section || "Docs",
|
|
143
|
+
tags: tags && tags.length > 0 ? tags : undefined,
|
|
144
|
+
title: route.title,
|
|
145
|
+
};
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Map per-page search documents to the flat record shape hosted backends
|
|
152
|
+
* ingest. One record per page, keyed by route; the first tag becomes the
|
|
153
|
+
* faceting `tag`.
|
|
154
|
+
*/
|
|
155
|
+
export const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>
|
|
156
|
+
documents.map((doc) => ({
|
|
157
|
+
_id: doc.route,
|
|
158
|
+
content: doc.content,
|
|
159
|
+
description: doc.description,
|
|
160
|
+
locale: doc.locale,
|
|
161
|
+
tag: doc.tags?.[0],
|
|
162
|
+
title: doc.title,
|
|
163
|
+
url: doc.route,
|
|
164
|
+
}));
|