blume 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/blume.mjs +18 -0
- package/dist/cli/index.js +11989 -0
- package/dist/cli/index.js.map +141 -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 +187 -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 +271 -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 +565 -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 +140 -6
- package/src/ai/ask.ts +93 -0
- package/src/ai/llms.ts +65 -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/generate.ts +878 -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 +28 -0
- package/src/astro/templates.ts +1199 -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 +60 -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/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/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 +40 -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/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 +15 -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/Pagination.astro +56 -0
- package/src/components/layout/ReferenceLayout.astro +102 -0
- package/src/components/layout/RootLayout.astro +533 -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/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/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/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 +336 -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 +216 -0
- package/src/core/sources/mintlify.ts +161 -0
- package/src/core/sources/normalize.ts +227 -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/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 +27 -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 +159 -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 +106 -0
- package/src/migrate/fumadocs/content.ts +365 -0
- package/src/migrate/fumadocs/frontmatter.ts +18 -0
- package/src/migrate/fumadocs/index.ts +252 -0
- package/src/migrate/fumadocs/meta.ts +114 -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 +305 -0
- package/src/migrate/mintlify/transform.ts +81 -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 +623 -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 +263 -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 +165 -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 +608 -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,98 @@
|
|
|
1
|
+
import { dirname, join, relative } from "pathe";
|
|
2
|
+
|
|
3
|
+
import { rewriteCallouts } from "../shared.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Source-to-source rewrites that turn Mintlify-only MDX component syntax into
|
|
7
|
+
* idiomatic Blume markup. Runs once at migration time — no Mintlify-aware
|
|
8
|
+
* plugins remain in the Blume runtime.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Mintlify callout components mapped to Blume directive names. */
|
|
12
|
+
const CALLOUT_DIRECTIVES: Record<string, string> = {
|
|
13
|
+
Check: "success",
|
|
14
|
+
Danger: "danger",
|
|
15
|
+
Error: "danger",
|
|
16
|
+
Info: "info",
|
|
17
|
+
Note: "note",
|
|
18
|
+
Success: "success",
|
|
19
|
+
Tip: "tip",
|
|
20
|
+
Warning: "warning",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** `<Callout type="X">` values mapped to Blume directive names. */
|
|
24
|
+
const CALLOUT_TYPE_DIRECTIVES: Record<string, string> = {
|
|
25
|
+
caution: "warning",
|
|
26
|
+
check: "success",
|
|
27
|
+
danger: "danger",
|
|
28
|
+
error: "danger",
|
|
29
|
+
info: "info",
|
|
30
|
+
note: "note",
|
|
31
|
+
success: "success",
|
|
32
|
+
tip: "tip",
|
|
33
|
+
warning: "warning",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Convert Mintlify callout components (`<Note>`, `<Warning>`, `<Callout
|
|
38
|
+
* type="…">`, …) into Blume `:::` directives.
|
|
39
|
+
*/
|
|
40
|
+
export const rewriteMintlifyCallouts = (source: string): string =>
|
|
41
|
+
rewriteCallouts(source, {
|
|
42
|
+
defaultDirective: "note",
|
|
43
|
+
tagDirectives: CALLOUT_DIRECTIVES,
|
|
44
|
+
tags: ["Callout", ...Object.keys(CALLOUT_DIRECTIVES)],
|
|
45
|
+
typeDirectives: CALLOUT_TYPE_DIRECTIVES,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mintlify's `<RequestExample>`/`<ResponseExample>` are tab-style code wrappers
|
|
50
|
+
* with no Blume equivalent; rename them to `<CodeGroup>`, which renders the
|
|
51
|
+
* same titled-fence tabs.
|
|
52
|
+
*/
|
|
53
|
+
export const rewriteMintlifyExampleBlocks = (source: string): string =>
|
|
54
|
+
source.replaceAll(
|
|
55
|
+
/<(?<close>\/?)(?:Request|Response)Example\b/gu,
|
|
56
|
+
"<$<close>CodeGroup"
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const SNIPPET_IMPORT =
|
|
60
|
+
/^import\s+[\s\S]*?\s+from\s+["'](?<source>\/snippets\/[^"']+)["'];?[ \t]*\n?/gmu;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* After snippets are inlined, clean up leftover `/snippets/*` imports: drop the
|
|
64
|
+
* now-dead markdown imports, and rewrite component imports (`.jsx`/`.tsx`/…) to
|
|
65
|
+
* a path relative to the page so they still resolve once `/snippets` content is
|
|
66
|
+
* gone. Returns the rewritten source and the component files still referenced.
|
|
67
|
+
*/
|
|
68
|
+
export const rewriteSnippetImports = (
|
|
69
|
+
source: string,
|
|
70
|
+
options: { filePath: string; root: string }
|
|
71
|
+
): { components: string[]; source: string } => {
|
|
72
|
+
const components: string[] = [];
|
|
73
|
+
const next = source.replaceAll(
|
|
74
|
+
SNIPPET_IMPORT,
|
|
75
|
+
(match, importSource: string) => {
|
|
76
|
+
if (/\.mdx?$/u.test(importSource)) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
const target = join(options.root, importSource.replace(/^\/+/u, ""));
|
|
80
|
+
components.push(importSource.replace(/^\/+/u, ""));
|
|
81
|
+
let rel = relative(dirname(options.filePath), target);
|
|
82
|
+
if (!rel.startsWith(".")) {
|
|
83
|
+
rel = `./${rel}`;
|
|
84
|
+
}
|
|
85
|
+
return match.replace(importSource, rel);
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
return { components, source: next };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Component tags Blume has no equivalent for — reported for manual review. */
|
|
92
|
+
const UNSUPPORTED_COMPONENTS = ["ParamField", "ResponseField"];
|
|
93
|
+
|
|
94
|
+
/** Names of Mintlify components in `source` that need manual attention. */
|
|
95
|
+
export const unsupportedMintlifyComponents = (source: string): string[] =>
|
|
96
|
+
UNSUPPORTED_COMPONENTS.filter((name) =>
|
|
97
|
+
new RegExp(`<${name}\\b`, "u").test(source)
|
|
98
|
+
);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One-time translation of Mintlify page frontmatter into Blume's shape. This
|
|
5
|
+
* runs at migration time only — Blume's runtime page schema stays
|
|
6
|
+
* Mintlify-free and strict. `sidebarTitle`/`icon`/`tag`/`hidden` fold into
|
|
7
|
+
* `sidebar`, `hidden` implies `noindex`, `canonical`/`og:image` move under
|
|
8
|
+
* `seo`, and OpenAPI/AsyncAPI pages become `type: "api"`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const mintlifySidebarMetaSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
badge: z.string().optional(),
|
|
14
|
+
hidden: z.boolean().optional(),
|
|
15
|
+
icon: z.string().optional(),
|
|
16
|
+
label: z.string().optional(),
|
|
17
|
+
order: z.number().optional(),
|
|
18
|
+
})
|
|
19
|
+
.passthrough();
|
|
20
|
+
|
|
21
|
+
const mintlifyPageMetaInputSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
api: z.unknown().optional(),
|
|
24
|
+
asyncapi: z.string().optional(),
|
|
25
|
+
canonical: z.string().optional(),
|
|
26
|
+
hidden: z.boolean().optional(),
|
|
27
|
+
icon: z.string().optional(),
|
|
28
|
+
noindex: z.boolean().optional(),
|
|
29
|
+
"og:image": z.string().optional(),
|
|
30
|
+
openapi: z.string().optional(),
|
|
31
|
+
sidebar: mintlifySidebarMetaSchema.optional(),
|
|
32
|
+
sidebarTitle: z.string().optional(),
|
|
33
|
+
tag: z.string().optional(),
|
|
34
|
+
type: z.string().optional(),
|
|
35
|
+
})
|
|
36
|
+
.passthrough();
|
|
37
|
+
|
|
38
|
+
type MintlifyPageMetaInput = z.infer<typeof mintlifyPageMetaInputSchema>;
|
|
39
|
+
|
|
40
|
+
const normalizedMintlifySidebar = (meta: MintlifyPageMetaInput) => ({
|
|
41
|
+
...meta.sidebar,
|
|
42
|
+
...(meta.sidebarTitle !== undefined && meta.sidebar?.label === undefined
|
|
43
|
+
? { label: meta.sidebarTitle }
|
|
44
|
+
: {}),
|
|
45
|
+
...(meta.icon !== undefined && meta.sidebar?.icon === undefined
|
|
46
|
+
? { icon: meta.icon }
|
|
47
|
+
: {}),
|
|
48
|
+
...(meta.tag !== undefined && meta.sidebar?.badge === undefined
|
|
49
|
+
? { badge: meta.tag }
|
|
50
|
+
: {}),
|
|
51
|
+
...(meta.hidden === true && meta.sidebar?.hidden === undefined
|
|
52
|
+
? { hidden: true }
|
|
53
|
+
: {}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const mintlifyPageType = (meta: MintlifyPageMetaInput): string | undefined =>
|
|
57
|
+
meta.type ??
|
|
58
|
+
(meta.openapi !== undefined ||
|
|
59
|
+
meta.asyncapi !== undefined ||
|
|
60
|
+
meta.api !== undefined
|
|
61
|
+
? "api"
|
|
62
|
+
: undefined);
|
|
63
|
+
|
|
64
|
+
const mintlifyNoindex = (meta: MintlifyPageMetaInput): boolean | undefined =>
|
|
65
|
+
meta.hidden === true && meta.noindex === undefined ? true : meta.noindex;
|
|
66
|
+
|
|
67
|
+
/** Top-level Mintlify keys folded elsewhere — removed from the output. */
|
|
68
|
+
const CONSUMED_KEYS = new Set([
|
|
69
|
+
"canonical",
|
|
70
|
+
"hidden",
|
|
71
|
+
"icon",
|
|
72
|
+
"og:image",
|
|
73
|
+
"sidebarTitle",
|
|
74
|
+
"tag",
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
/** Map Mintlify page frontmatter onto Blume's frontmatter shape. */
|
|
78
|
+
export const normalizeMintlifyPageMeta = (
|
|
79
|
+
value: unknown
|
|
80
|
+
): Record<string, unknown> => {
|
|
81
|
+
const parsed = mintlifyPageMetaInputSchema.safeParse(value);
|
|
82
|
+
if (!parsed.success || typeof value !== "object" || value === null) {
|
|
83
|
+
return (value ?? {}) as Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const meta = parsed.data;
|
|
87
|
+
const source = value as Record<string, unknown>;
|
|
88
|
+
const data: Record<string, unknown> = { ...source };
|
|
89
|
+
|
|
90
|
+
const sidebar = normalizedMintlifySidebar(meta);
|
|
91
|
+
if (Object.keys(sidebar).length > 0) {
|
|
92
|
+
data.sidebar = sidebar;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const seo: Record<string, unknown> = {
|
|
96
|
+
...(typeof source.seo === "object" && source.seo !== null
|
|
97
|
+
? (source.seo as Record<string, unknown>)
|
|
98
|
+
: {}),
|
|
99
|
+
};
|
|
100
|
+
if (meta.canonical !== undefined && seo.canonical === undefined) {
|
|
101
|
+
seo.canonical = meta.canonical;
|
|
102
|
+
}
|
|
103
|
+
if (meta["og:image"] !== undefined && seo.image === undefined) {
|
|
104
|
+
seo.image = meta["og:image"];
|
|
105
|
+
}
|
|
106
|
+
if (Object.keys(seo).length > 0) {
|
|
107
|
+
data.seo = seo;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const noindex = mintlifyNoindex(meta);
|
|
111
|
+
if (noindex !== undefined) {
|
|
112
|
+
data.noindex = noindex;
|
|
113
|
+
}
|
|
114
|
+
const type = mintlifyPageType(meta);
|
|
115
|
+
if (type !== undefined) {
|
|
116
|
+
data.type = type;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const cleaned: Record<string, unknown> = {};
|
|
120
|
+
for (const [key, raw] of Object.entries(data)) {
|
|
121
|
+
if (!CONSUMED_KEYS.has(key)) {
|
|
122
|
+
cleaned[key] = raw;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return cleaned;
|
|
126
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BlumeConfig } from "../../core/schema.ts";
|
|
2
|
+
|
|
3
|
+
/** A Mintlify `navigation.languages[]` entry. */
|
|
4
|
+
interface MintlifyLanguage {
|
|
5
|
+
language: string;
|
|
6
|
+
default?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A display label for a locale code — the language's native name when the
|
|
11
|
+
* runtime knows it (`fr` -> `Français`), else the code itself.
|
|
12
|
+
*/
|
|
13
|
+
const localeLabel = (code: string): string => {
|
|
14
|
+
try {
|
|
15
|
+
const native = new Intl.DisplayNames([code], { type: "language" }).of(code);
|
|
16
|
+
if (native && native !== code) {
|
|
17
|
+
return native.charAt(0).toUpperCase() + native.slice(1);
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// Unknown code or no ICU data; fall through to the code.
|
|
21
|
+
}
|
|
22
|
+
return code;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Map a Mintlify `navigation.languages[]` array to a Blume `i18n` config. The
|
|
27
|
+
* entry marked `default: true` becomes `defaultLocale`; translated content
|
|
28
|
+
* already lives in ISO-code directories, which match Blume's `dir` parser.
|
|
29
|
+
*/
|
|
30
|
+
export const mintlifyI18n = (
|
|
31
|
+
spec: Record<string, unknown>
|
|
32
|
+
): BlumeConfig["i18n"] | null => {
|
|
33
|
+
const navigation = spec.navigation as
|
|
34
|
+
| { languages?: MintlifyLanguage[] }
|
|
35
|
+
| undefined;
|
|
36
|
+
const languages = navigation?.languages;
|
|
37
|
+
if (!Array.isArray(languages) || languages.length < 2) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const defaultLocale =
|
|
41
|
+
languages.find((entry) => entry.default)?.language ??
|
|
42
|
+
languages[0]?.language ??
|
|
43
|
+
"en";
|
|
44
|
+
return {
|
|
45
|
+
defaultLocale,
|
|
46
|
+
locales: languages.map((entry) => ({
|
|
47
|
+
code: entry.language,
|
|
48
|
+
label: localeLabel(entry.language),
|
|
49
|
+
})),
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const JSX_ATTRIBUTE_ALIASES: Record<string, string> = {
|
|
2
|
+
className: "class",
|
|
3
|
+
clipPath: "clip-path",
|
|
4
|
+
clipRule: "clip-rule",
|
|
5
|
+
fillOpacity: "fill-opacity",
|
|
6
|
+
fillRule: "fill-rule",
|
|
7
|
+
strokeDasharray: "stroke-dasharray",
|
|
8
|
+
strokeDashoffset: "stroke-dashoffset",
|
|
9
|
+
strokeLinecap: "stroke-linecap",
|
|
10
|
+
strokeLinejoin: "stroke-linejoin",
|
|
11
|
+
strokeMiterlimit: "stroke-miterlimit",
|
|
12
|
+
strokeOpacity: "stroke-opacity",
|
|
13
|
+
strokeWidth: "stroke-width",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const SVG_EXPRESSION = /^\s*\(?\s*<svg[\s\S]*<\/svg>\s*\)?\s*;?\s*$/u;
|
|
17
|
+
|
|
18
|
+
const escapeAttribute = (value: string): string =>
|
|
19
|
+
value
|
|
20
|
+
.replaceAll("&", "&")
|
|
21
|
+
.replaceAll('"', """)
|
|
22
|
+
.replaceAll("<", "<")
|
|
23
|
+
.replaceAll(">", ">");
|
|
24
|
+
|
|
25
|
+
const stripExpressionWrapper = (value: string): string => {
|
|
26
|
+
let next = value.trim().replaceAll(/;$/gu, "").trim();
|
|
27
|
+
if (next.startsWith("(") && next.endsWith(")")) {
|
|
28
|
+
next = next.slice(1, -1).trim();
|
|
29
|
+
}
|
|
30
|
+
return next;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const jsxExpressionAttribute = (
|
|
34
|
+
value: string,
|
|
35
|
+
quoted: string | undefined,
|
|
36
|
+
single: string | undefined,
|
|
37
|
+
template: string | undefined,
|
|
38
|
+
scalar: string | undefined
|
|
39
|
+
): string => {
|
|
40
|
+
const literal = quoted ?? single ?? template ?? scalar;
|
|
41
|
+
return literal === undefined ? value : `="${escapeAttribute(literal)}"`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const normalizeJsxSvg = (value: string): string | null => {
|
|
45
|
+
const stripped = stripExpressionWrapper(value);
|
|
46
|
+
if (!SVG_EXPRESSION.test(stripped)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let svg = stripped.replaceAll(
|
|
51
|
+
/[=]\{\s*(?:"(?<quoted>[^"]*)"|'(?<single>[^']*)'|`(?<template>[^`]*)`|(?<scalar>-?\d+(?:\.\d+)?|true|false))\s*\}/gu,
|
|
52
|
+
jsxExpressionAttribute
|
|
53
|
+
);
|
|
54
|
+
for (const [jsxName, htmlName] of Object.entries(JSX_ATTRIBUTE_ALIASES)) {
|
|
55
|
+
svg = svg.replaceAll(new RegExp(`\\b${jsxName}=`, "gu"), `${htmlName}=`);
|
|
56
|
+
}
|
|
57
|
+
return svg;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const findExpressionEnd = (source: string, start: number): number => {
|
|
61
|
+
let depth = 1;
|
|
62
|
+
let quote: '"' | "'" | "`" | null = null;
|
|
63
|
+
for (let index = start; index < source.length; index += 1) {
|
|
64
|
+
const char = source[index];
|
|
65
|
+
const previous = source[index - 1];
|
|
66
|
+
if (quote) {
|
|
67
|
+
if (char === quote && previous !== "\\") {
|
|
68
|
+
quote = null;
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
73
|
+
quote = char;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (char === "{") {
|
|
77
|
+
depth += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (char === "}") {
|
|
81
|
+
depth -= 1;
|
|
82
|
+
if (depth === 0) {
|
|
83
|
+
return index;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return -1;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const startsWithSvgExpression = (value: string): boolean => {
|
|
91
|
+
const trimmed = value.trimStart();
|
|
92
|
+
return trimmed.startsWith("<svg") || trimmed.startsWith("(<svg");
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Rewrite Mintlify inline-SVG icon JSX props (`icon={<svg .../>}`) to plain
|
|
97
|
+
* string props so the migrated MDX compiles under Astro. Runs once at
|
|
98
|
+
* migration time; non-SVG `icon={...}` expressions are left untouched.
|
|
99
|
+
*/
|
|
100
|
+
export const rewriteMintlifySvgIconProps = (source: string): string => {
|
|
101
|
+
let output = "";
|
|
102
|
+
let cursor = 0;
|
|
103
|
+
while (cursor < source.length) {
|
|
104
|
+
const start = source.indexOf("icon={", cursor);
|
|
105
|
+
if (start === -1) {
|
|
106
|
+
output += source.slice(cursor);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const expressionStart = start + "icon={".length;
|
|
111
|
+
const end = findExpressionEnd(source, expressionStart);
|
|
112
|
+
if (end === -1) {
|
|
113
|
+
output += source.slice(cursor);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const expression = source.slice(expressionStart, end);
|
|
118
|
+
const svg = startsWithSvgExpression(expression)
|
|
119
|
+
? normalizeJsxSvg(expression)
|
|
120
|
+
: null;
|
|
121
|
+
output += source.slice(cursor, start);
|
|
122
|
+
output += svg
|
|
123
|
+
? `icon={${JSON.stringify(svg)}}`
|
|
124
|
+
: source.slice(start, end + 1);
|
|
125
|
+
cursor = end + 1;
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import { dirname, join } from "pathe";
|
|
5
|
+
import { glob } from "tinyglobby";
|
|
6
|
+
|
|
7
|
+
import type { BlumeConfig } from "../../core/schema.ts";
|
|
8
|
+
import { loadMintlifyConfig } from "./config.ts";
|
|
9
|
+
import { mintlifyI18n } from "./i18n.ts";
|
|
10
|
+
import { transformMintlifyContent } from "./transform.ts";
|
|
11
|
+
|
|
12
|
+
export interface MintlifyMigrationResult {
|
|
13
|
+
moved: number;
|
|
14
|
+
warnings: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Recursively drop `undefined`, empty arrays, and empty objects. */
|
|
18
|
+
const prune = (value: unknown): unknown => {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return value.map(prune);
|
|
21
|
+
}
|
|
22
|
+
if (value && typeof value === "object") {
|
|
23
|
+
const out: Record<string, unknown> = {};
|
|
24
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
25
|
+
const pruned = prune(raw);
|
|
26
|
+
if (pruned === undefined) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(pruned) && pruned.length === 0) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
pruned &&
|
|
34
|
+
typeof pruned === "object" &&
|
|
35
|
+
!Array.isArray(pruned) &&
|
|
36
|
+
Object.keys(pruned).length === 0
|
|
37
|
+
) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
out[key] = pruned;
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const writeBlumeConfig = async (
|
|
48
|
+
root: string,
|
|
49
|
+
config: BlumeConfig
|
|
50
|
+
): Promise<void> => {
|
|
51
|
+
const body = `import { defineConfig } from "blume";\n\nexport default defineConfig(${JSON.stringify(prune(config), null, 2)});\n`;
|
|
52
|
+
await writeFile(join(root, "blume.config.ts"), body, "utf-8");
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Move a referenced top-level asset path (file or dir) under `public/`. */
|
|
56
|
+
const relocateAssets = async (
|
|
57
|
+
root: string,
|
|
58
|
+
refs: unknown[]
|
|
59
|
+
): Promise<string[]> => {
|
|
60
|
+
const segments = new Set<string>();
|
|
61
|
+
for (const ref of refs) {
|
|
62
|
+
if (typeof ref !== "string" || !ref.startsWith("/")) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const [segment] = ref.replace(/^\/+/u, "").split("/");
|
|
66
|
+
if (segment) {
|
|
67
|
+
segments.add(segment);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const moved: string[] = [];
|
|
72
|
+
for (const segment of segments) {
|
|
73
|
+
const source = join(root, segment);
|
|
74
|
+
if (!existsSync(source) || segment === "public") {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const dest = join(root, "public", segment);
|
|
78
|
+
if (existsSync(dest)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential fs moves
|
|
82
|
+
await mkdir(join(root, "public"), { recursive: true });
|
|
83
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential fs moves
|
|
84
|
+
await rename(source, dest);
|
|
85
|
+
moved.push(segment);
|
|
86
|
+
}
|
|
87
|
+
return moved;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Delete the inlined markdown snippets. Component files (e.g. `.jsx`) are kept
|
|
92
|
+
* because their imports were rewritten to resolve against `/snippets`.
|
|
93
|
+
*/
|
|
94
|
+
const cleanupSnippets = async (
|
|
95
|
+
root: string,
|
|
96
|
+
kept: Set<string>,
|
|
97
|
+
warnings: string[]
|
|
98
|
+
): Promise<void> => {
|
|
99
|
+
const dir = join(root, "snippets");
|
|
100
|
+
if (!existsSync(dir)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const markdown = await glob(["**/*.{md,mdx}"], { absolute: true, cwd: dir });
|
|
104
|
+
for (const file of markdown) {
|
|
105
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential fs removes
|
|
106
|
+
await rm(file, { force: true });
|
|
107
|
+
}
|
|
108
|
+
const remaining = await glob(["**/*"], { cwd: dir, dot: true });
|
|
109
|
+
if (remaining.length === 0) {
|
|
110
|
+
await rm(dir, { force: true, recursive: true });
|
|
111
|
+
warnings.push("Inlined and removed the /snippets directory.");
|
|
112
|
+
} else {
|
|
113
|
+
warnings.push(
|
|
114
|
+
`Inlined markdown snippets; kept ${remaining.length} component file(s) under /snippets.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (kept.size > 0) {
|
|
118
|
+
warnings.push(
|
|
119
|
+
`Rewrote ${kept.size} component snippet import(s) to relative paths; verify they resolve.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Asset paths referenced by the resolved config (logo, favicon, backgrounds). */
|
|
125
|
+
const assetRefs = (config: BlumeConfig): unknown[] => {
|
|
126
|
+
const refs: unknown[] = ["/images"];
|
|
127
|
+
const logo = config.logo as
|
|
128
|
+
| string
|
|
129
|
+
| { dark?: string; light?: string }
|
|
130
|
+
| undefined;
|
|
131
|
+
if (typeof logo === "string") {
|
|
132
|
+
refs.push(logo);
|
|
133
|
+
} else if (logo) {
|
|
134
|
+
refs.push(logo.light, logo.dark);
|
|
135
|
+
}
|
|
136
|
+
const favicon = config.favicon as
|
|
137
|
+
| string
|
|
138
|
+
| { dark?: string; light?: string }
|
|
139
|
+
| undefined;
|
|
140
|
+
if (typeof favicon === "string") {
|
|
141
|
+
refs.push(favicon);
|
|
142
|
+
} else if (favicon) {
|
|
143
|
+
refs.push(favicon.light, favicon.dark);
|
|
144
|
+
}
|
|
145
|
+
refs.push(config.theme?.backgroundImage, config.theme?.backgroundImageDark);
|
|
146
|
+
return refs;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Migrate a Mintlify project to Blume: translate `docs.json`/`mint.json` into
|
|
151
|
+
* `blume.config.ts`, rewrite every page to idiomatic Blume MDX in place, and
|
|
152
|
+
* relocate static assets. Content stays at the project root (`content.root`
|
|
153
|
+
* is `"."`).
|
|
154
|
+
*/
|
|
155
|
+
export const migrateMintlifyProject = async (
|
|
156
|
+
root: string
|
|
157
|
+
): Promise<MintlifyMigrationResult> => {
|
|
158
|
+
const warnings: string[] = [];
|
|
159
|
+
const configFile = existsSync(join(root, "docs.json"))
|
|
160
|
+
? join(root, "docs.json")
|
|
161
|
+
: join(root, "mint.json");
|
|
162
|
+
|
|
163
|
+
let config: BlumeConfig;
|
|
164
|
+
if (existsSync(configFile)) {
|
|
165
|
+
config = await loadMintlifyConfig(root, configFile);
|
|
166
|
+
const spec = JSON.parse(await readFile(configFile, "utf-8")) as Record<
|
|
167
|
+
string,
|
|
168
|
+
unknown
|
|
169
|
+
>;
|
|
170
|
+
const i18n = mintlifyI18n(spec);
|
|
171
|
+
if (i18n) {
|
|
172
|
+
config.i18n = i18n;
|
|
173
|
+
// Language switching is handled by Blume i18n, not a nav selector.
|
|
174
|
+
if (config.navigation?.selectors) {
|
|
175
|
+
config.navigation.selectors = config.navigation.selectors.filter(
|
|
176
|
+
(selector) => selector.kind !== "language"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
warnings.push(
|
|
180
|
+
`Mapped ${i18n.locales.length} languages to i18n.locales (default: ${i18n.defaultLocale}); review the locale labels.`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
warnings.push("No docs.json or mint.json found; writing a default config.");
|
|
185
|
+
config = { content: { root: "." }, title: "Documentation" };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const variables = (config.variables as Record<string, string>) ?? {};
|
|
189
|
+
// Globals are inlined into content below; Blume has no runtime substitution.
|
|
190
|
+
config.variables = undefined;
|
|
191
|
+
|
|
192
|
+
const files = await glob(["**/*.{md,mdx}"], {
|
|
193
|
+
absolute: true,
|
|
194
|
+
cwd: root,
|
|
195
|
+
ignore: [
|
|
196
|
+
"node_modules/**",
|
|
197
|
+
".blume/**",
|
|
198
|
+
"dist/**",
|
|
199
|
+
"public/**",
|
|
200
|
+
"snippets/**",
|
|
201
|
+
],
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
let moved = 0;
|
|
205
|
+
const removedKeys = new Set<string>();
|
|
206
|
+
const unsupported = new Set<string>();
|
|
207
|
+
const keptComponents = new Set<string>();
|
|
208
|
+
for (const file of files) {
|
|
209
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential fs writes
|
|
210
|
+
const raw = await readFile(file, "utf-8");
|
|
211
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential transforms
|
|
212
|
+
const result = await transformMintlifyContent(raw, {
|
|
213
|
+
filePath: file,
|
|
214
|
+
root,
|
|
215
|
+
variables,
|
|
216
|
+
});
|
|
217
|
+
if (result.content !== raw) {
|
|
218
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential fs writes
|
|
219
|
+
await mkdir(dirname(file), { recursive: true });
|
|
220
|
+
// oxlint-disable-next-line no-await-in-loop -- sequential fs writes
|
|
221
|
+
await writeFile(file, result.content, "utf-8");
|
|
222
|
+
}
|
|
223
|
+
for (const key of result.removed) {
|
|
224
|
+
removedKeys.add(key);
|
|
225
|
+
}
|
|
226
|
+
for (const name of result.unsupported) {
|
|
227
|
+
unsupported.add(name);
|
|
228
|
+
}
|
|
229
|
+
for (const name of result.components) {
|
|
230
|
+
keptComponents.add(name);
|
|
231
|
+
}
|
|
232
|
+
moved += 1;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const movedAssets = await relocateAssets(root, assetRefs(config));
|
|
236
|
+
await cleanupSnippets(root, keptComponents, warnings);
|
|
237
|
+
|
|
238
|
+
if (config.content?.exclude) {
|
|
239
|
+
config.content.exclude = [...new Set(config.content.exclude)];
|
|
240
|
+
}
|
|
241
|
+
await writeBlumeConfig(root, config);
|
|
242
|
+
|
|
243
|
+
if (Object.keys(variables).length > 0) {
|
|
244
|
+
warnings.push(
|
|
245
|
+
`Inlined ${Object.keys(variables).length} docs.json variable(s) into content; Blume has no runtime variable substitution.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (movedAssets.length > 0) {
|
|
249
|
+
warnings.push(`Moved assets into public/: ${movedAssets.join(", ")}.`);
|
|
250
|
+
}
|
|
251
|
+
if (removedKeys.size > 0) {
|
|
252
|
+
warnings.push(
|
|
253
|
+
`Dropped unsupported page frontmatter keys: ${[...removedKeys].join(", ")}.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (unsupported.size > 0) {
|
|
257
|
+
warnings.push(
|
|
258
|
+
`Components without a Blume equivalent need manual review (use the OpenAPI reference instead): ${[...unsupported].join(", ")}.`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
warnings.push(
|
|
262
|
+
"Review blume.config.ts; navigation, theme, and chrome were mapped from docs.json."
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
return { moved, warnings };
|
|
266
|
+
};
|