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,305 @@
|
|
|
1
|
+
import { readFile as readFileFromDisk } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import { dirname, relative, resolve } from "pathe";
|
|
5
|
+
|
|
6
|
+
const MARKDOWN_SNIPPET_IMPORT =
|
|
7
|
+
/^import\s+(?<name>[$A-Z_a-z][$\w]*)\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
|
|
8
|
+
const NAMED_SNIPPET_IMPORT =
|
|
9
|
+
/^import\s+\{(?<names>[^}]+)\}\s+from\s+["'](?<source>[^"']+\.mdx?)["'];?\s*$/gmu;
|
|
10
|
+
const EXPORTED_STRING_CONST =
|
|
11
|
+
/^export\s+const\s+(?<name>[$A-Z_a-z][$\w]*)\s*=\s*(?:"(?<double>(?:\\.|[^"\\])*)"|'(?<single>(?:\\.|[^'\\])*)'|`(?<template>(?:\\.|[^`\\])*)`)\s*;?\s*$/gmu;
|
|
12
|
+
const ATTRIBUTE =
|
|
13
|
+
/\s+(?<name>[$A-Z_a-z][$\w:-]*)(?:=(?:"(?<quoted>[^"]*)"|'(?<single>[^']*)'|\{(?<expression>[^}]*)\}))?/gu;
|
|
14
|
+
const PLACEHOLDER = /\{(?<name>[$A-Z_a-z][$\w]*)\}/gu;
|
|
15
|
+
const GLOBAL_VARIABLE = /\{\{\s*(?<name>[A-Za-z0-9-]+)\s*\}\}/gu;
|
|
16
|
+
const FRONTMATTER_BLOCK = /^---\r?\n[\s\S]*?\r?\n---\r?\n?/u;
|
|
17
|
+
const USER_EXPORT =
|
|
18
|
+
/^(?:export\s+)?(?:const|let|var)\s+user\s*=|^import\s+\{\s*user\s*\}/mu;
|
|
19
|
+
|
|
20
|
+
interface SnippetImport {
|
|
21
|
+
importText: string;
|
|
22
|
+
name: string;
|
|
23
|
+
source: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SnippetVariableImport {
|
|
27
|
+
names: { imported: string; local: string }[];
|
|
28
|
+
source: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface SnippetTransformOptions {
|
|
32
|
+
filePath: string;
|
|
33
|
+
root: string;
|
|
34
|
+
readFile?: (file: string) => Promise<string>;
|
|
35
|
+
seen?: Set<string>;
|
|
36
|
+
trail?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const isInsideRoot = (root: string, candidate: string): boolean => {
|
|
40
|
+
const rel = relative(root, candidate);
|
|
41
|
+
return rel === "" || (!rel.startsWith("..") && !rel.startsWith("/"));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const escapeRegExp = (value: string): string =>
|
|
45
|
+
value.replaceAll(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
46
|
+
|
|
47
|
+
const snippetSelfClosingTagPattern = (name: string): RegExp =>
|
|
48
|
+
new RegExp(`<${escapeRegExp(name)}(?<attrs>[^>]*?)\\s*/>`, "gu");
|
|
49
|
+
|
|
50
|
+
const snippetPairedTagPattern = (name: string): RegExp =>
|
|
51
|
+
new RegExp(
|
|
52
|
+
`<${escapeRegExp(name)}(?<attrs>[^>]*?)>[\\s\\S]*?</${escapeRegExp(name)}>`,
|
|
53
|
+
"gu"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const rootRelativePath = (root: string, file: string): string => {
|
|
57
|
+
const rel = relative(root, file);
|
|
58
|
+
return rel ? `/${rel}` : "/";
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const snippetCycleMessage = (
|
|
62
|
+
root: string,
|
|
63
|
+
file: string,
|
|
64
|
+
trail: string[]
|
|
65
|
+
): string => {
|
|
66
|
+
const cycleStart = trail.indexOf(file);
|
|
67
|
+
const cycle = [...(cycleStart === -1 ? trail : trail.slice(cycleStart)), file]
|
|
68
|
+
.map((entry) => rootRelativePath(root, entry))
|
|
69
|
+
.join(" -> ");
|
|
70
|
+
return `Circular Mintlify snippet import detected: ${cycle}`;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const resolveSnippetPath = (options: {
|
|
74
|
+
filePath: string;
|
|
75
|
+
root: string;
|
|
76
|
+
source: string;
|
|
77
|
+
}): string | null => {
|
|
78
|
+
const target = options.source.startsWith("/")
|
|
79
|
+
? resolve(options.root, options.source.slice(1))
|
|
80
|
+
: resolve(dirname(options.filePath), options.source);
|
|
81
|
+
return isInsideRoot(options.root, target) ? target : null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const collectImports = (source: string): SnippetImport[] =>
|
|
85
|
+
[...source.matchAll(MARKDOWN_SNIPPET_IMPORT)].flatMap((match) => {
|
|
86
|
+
const name = match.groups?.name;
|
|
87
|
+
const importSource = match.groups?.source;
|
|
88
|
+
if (!(name && importSource)) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return [{ importText: match[0], name, source: importSource }];
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const collectVariableImports = (source: string): SnippetVariableImport[] =>
|
|
95
|
+
[...source.matchAll(NAMED_SNIPPET_IMPORT)].flatMap((match) => {
|
|
96
|
+
const names = match.groups?.names;
|
|
97
|
+
const importSource = match.groups?.source;
|
|
98
|
+
if (!(names && importSource)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const parsedNames = names.split(",").flatMap((entry) => {
|
|
102
|
+
const [imported, local] = entry.trim().split(/\s+as\s+/u);
|
|
103
|
+
return imported ? [{ imported, local: local ?? imported }] : [];
|
|
104
|
+
});
|
|
105
|
+
return parsedNames.length
|
|
106
|
+
? [{ names: parsedNames, source: importSource }]
|
|
107
|
+
: [];
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const decodeStringLiteral = (value: string): string =>
|
|
111
|
+
value
|
|
112
|
+
.replaceAll("\\n", "\n")
|
|
113
|
+
.replaceAll("\\r", "\r")
|
|
114
|
+
.replaceAll("\\t", "\t")
|
|
115
|
+
.replaceAll(/\\(?<escaped>[\\'"`])/gu, "$<escaped>");
|
|
116
|
+
|
|
117
|
+
const collectStringExports = (source: string): Map<string, string> => {
|
|
118
|
+
const exports = new Map<string, string>();
|
|
119
|
+
for (const match of source.matchAll(EXPORTED_STRING_CONST)) {
|
|
120
|
+
const name = match.groups?.name;
|
|
121
|
+
const value =
|
|
122
|
+
match.groups?.double ?? match.groups?.single ?? match.groups?.template;
|
|
123
|
+
if (name && value !== undefined) {
|
|
124
|
+
exports.set(name, decodeStringLiteral(value));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return exports;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const parseAttributes = (source: string): Record<string, string> => {
|
|
131
|
+
const props: Record<string, string> = {};
|
|
132
|
+
for (const match of source.matchAll(ATTRIBUTE)) {
|
|
133
|
+
const name = match.groups?.name;
|
|
134
|
+
if (!name) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
props[name] =
|
|
138
|
+
match.groups?.quoted ??
|
|
139
|
+
match.groups?.single ??
|
|
140
|
+
match.groups?.expression?.trim() ??
|
|
141
|
+
"true";
|
|
142
|
+
}
|
|
143
|
+
return props;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const interpolateProps = (
|
|
147
|
+
source: string,
|
|
148
|
+
props: Record<string, string>
|
|
149
|
+
): string =>
|
|
150
|
+
source.replaceAll(PLACEHOLDER, (value, name: string) => props[name] ?? value);
|
|
151
|
+
|
|
152
|
+
const stripImport = (source: string, importText: string): string =>
|
|
153
|
+
source.replace(importText, "").replaceAll(/\n{3,}/gu, "\n\n");
|
|
154
|
+
|
|
155
|
+
const replacePlaceholder = (
|
|
156
|
+
source: string,
|
|
157
|
+
name: string,
|
|
158
|
+
value: string
|
|
159
|
+
): string =>
|
|
160
|
+
source.replaceAll(new RegExp(`\\{${escapeRegExp(name)}\\}`, "gu"), value);
|
|
161
|
+
|
|
162
|
+
const inlineSnippetTags = (options: {
|
|
163
|
+
name: string;
|
|
164
|
+
snippet: string;
|
|
165
|
+
source: string;
|
|
166
|
+
}): string => {
|
|
167
|
+
const inline = (_value: string, attrs: string): string =>
|
|
168
|
+
interpolateProps(options.snippet, parseAttributes(attrs));
|
|
169
|
+
return options.source
|
|
170
|
+
.replaceAll(snippetSelfClosingTagPattern(options.name), inline)
|
|
171
|
+
.replaceAll(snippetPairedTagPattern(options.name), inline);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Mintlify markdown snippets are source-level includes, not standalone pages.
|
|
176
|
+
* Inline markdown snippets before Astro MDX compiles so `.md` imports, nested
|
|
177
|
+
* markdown snippets, and `{prop}` interpolation inside prose or code fences work.
|
|
178
|
+
*/
|
|
179
|
+
export const rewriteMintlifyMarkdownSnippets = async (
|
|
180
|
+
source: string,
|
|
181
|
+
options: SnippetTransformOptions
|
|
182
|
+
): Promise<string> => {
|
|
183
|
+
const loadSnippet = async (file: string): Promise<string> => {
|
|
184
|
+
const seen = options.seen ?? new Set<string>();
|
|
185
|
+
if (seen.has(file)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
snippetCycleMessage(options.root, file, options.trail ?? [])
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
seen.add(file);
|
|
191
|
+
const readFile = options.readFile ?? readFileFromDisk;
|
|
192
|
+
try {
|
|
193
|
+
const raw = await readFile(file);
|
|
194
|
+
const content = matter(raw).content.trim();
|
|
195
|
+
const transformed = await rewriteMintlifyMarkdownSnippets(content, {
|
|
196
|
+
...options,
|
|
197
|
+
filePath: file,
|
|
198
|
+
seen,
|
|
199
|
+
trail: [...(options.trail ?? []), file],
|
|
200
|
+
});
|
|
201
|
+
return transformed.trim();
|
|
202
|
+
} finally {
|
|
203
|
+
seen.delete(file);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const inlineImport = async (
|
|
208
|
+
current: string,
|
|
209
|
+
snippetImport: SnippetImport
|
|
210
|
+
): Promise<string> => {
|
|
211
|
+
const file = resolveSnippetPath({
|
|
212
|
+
filePath: options.filePath,
|
|
213
|
+
root: options.root,
|
|
214
|
+
source: snippetImport.source,
|
|
215
|
+
});
|
|
216
|
+
if (!file) {
|
|
217
|
+
return current;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const snippet = await loadSnippet(file);
|
|
221
|
+
const next = inlineSnippetTags({
|
|
222
|
+
name: snippetImport.name,
|
|
223
|
+
snippet,
|
|
224
|
+
source: current,
|
|
225
|
+
});
|
|
226
|
+
return next === current
|
|
227
|
+
? current
|
|
228
|
+
: stripImport(next, snippetImport.importText);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const imports = collectImports(source);
|
|
232
|
+
const inlineAt = async (index: number, current: string): Promise<string> => {
|
|
233
|
+
const snippetImport = imports[index];
|
|
234
|
+
if (!snippetImport) {
|
|
235
|
+
return current;
|
|
236
|
+
}
|
|
237
|
+
return inlineAt(index + 1, await inlineImport(current, snippetImport));
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
return await inlineAt(0, source);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/** Resolve imported string constants from Mintlify snippets for generated text. */
|
|
244
|
+
export const rewriteMintlifySnippetVariables = async (
|
|
245
|
+
source: string,
|
|
246
|
+
options: SnippetTransformOptions
|
|
247
|
+
): Promise<string> => {
|
|
248
|
+
const readFile = options.readFile ?? readFileFromDisk;
|
|
249
|
+
const inlineImport = async (
|
|
250
|
+
current: string,
|
|
251
|
+
variableImport: SnippetVariableImport
|
|
252
|
+
): Promise<string> => {
|
|
253
|
+
const file = resolveSnippetPath({
|
|
254
|
+
filePath: options.filePath,
|
|
255
|
+
root: options.root,
|
|
256
|
+
source: variableImport.source,
|
|
257
|
+
});
|
|
258
|
+
if (!file) {
|
|
259
|
+
return current;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const exports = collectStringExports(matter(await readFile(file)).content);
|
|
263
|
+
let next = current;
|
|
264
|
+
for (const name of variableImport.names) {
|
|
265
|
+
const value = exports.get(name.imported);
|
|
266
|
+
if (value !== undefined) {
|
|
267
|
+
next = replacePlaceholder(next, name.local, value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return next;
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const imports = collectVariableImports(source);
|
|
274
|
+
const inlineAt = async (index: number, current: string): Promise<string> => {
|
|
275
|
+
const variableImport = imports[index];
|
|
276
|
+
if (!variableImport) {
|
|
277
|
+
return current;
|
|
278
|
+
}
|
|
279
|
+
return inlineAt(index + 1, await inlineImport(current, variableImport));
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return await inlineAt(0, source);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/** Replace docs.json Mintlify globals such as `{{product-name}}`. */
|
|
286
|
+
export const rewriteMintlifyGlobalVariables = (
|
|
287
|
+
source: string,
|
|
288
|
+
variables: Record<string, string>
|
|
289
|
+
): string =>
|
|
290
|
+
source.replaceAll(GLOBAL_VARIABLE, (value, name: string) =>
|
|
291
|
+
Object.hasOwn(variables, name) ? (variables[name] ?? value) : value
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
/** Mintlify exposes a logged-out `user` object to MDX personalization code. */
|
|
295
|
+
export const rewriteMintlifyUserVariable = (source: string): string => {
|
|
296
|
+
if (USER_EXPORT.test(source)) {
|
|
297
|
+
return source;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const frontmatter = source.match(FRONTMATTER_BLOCK)?.[0] ?? "";
|
|
301
|
+
const body = (
|
|
302
|
+
frontmatter ? source.slice(frontmatter.length) : source
|
|
303
|
+
).replace(/^\r?\n/u, "");
|
|
304
|
+
return `${frontmatter}export const user = {};\n\n${body}`;
|
|
305
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
|
|
3
|
+
import { stripUnknownPageMeta } from "../shared.ts";
|
|
4
|
+
import {
|
|
5
|
+
rewriteMintlifyCallouts,
|
|
6
|
+
rewriteMintlifyExampleBlocks,
|
|
7
|
+
rewriteSnippetImports,
|
|
8
|
+
unsupportedMintlifyComponents,
|
|
9
|
+
} from "./content.ts";
|
|
10
|
+
import { normalizeMintlifyPageMeta } from "./frontmatter.ts";
|
|
11
|
+
import { rewriteMintlifySvgIconProps } from "./icons.ts";
|
|
12
|
+
import {
|
|
13
|
+
rewriteMintlifyGlobalVariables,
|
|
14
|
+
rewriteMintlifyMarkdownSnippets,
|
|
15
|
+
rewriteMintlifySnippetVariables,
|
|
16
|
+
rewriteMintlifyUserVariable,
|
|
17
|
+
} from "./snippets.ts";
|
|
18
|
+
|
|
19
|
+
const USER_REFERENCE = /\{[^{}]*\buser\b/u;
|
|
20
|
+
|
|
21
|
+
/** The outcome of transforming a single Mintlify page to Blume MDX. */
|
|
22
|
+
export interface MintlifyTransformResult {
|
|
23
|
+
/** Component snippet imports kept (rewritten to relative paths). */
|
|
24
|
+
components: string[];
|
|
25
|
+
/** The rewritten file text, including normalized frontmatter. */
|
|
26
|
+
content: string;
|
|
27
|
+
/** Frontmatter keys dropped because Blume has no equivalent. */
|
|
28
|
+
removed: string[];
|
|
29
|
+
/** Components encountered that have no Blume equivalent. */
|
|
30
|
+
unsupported: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Apply the per-file source transforms that turn Mintlify MDX into Blume MDX:
|
|
35
|
+
* inline snippets and variables, rewrite callouts (`<Note>` → `:::note`),
|
|
36
|
+
* example blocks, and inline-SVG icon props, then normalize frontmatter. Pure
|
|
37
|
+
* text-in/text-out (snippets read from disk relative to `root`), so both the
|
|
38
|
+
* one-shot migrator and the live bridge source share identical behavior.
|
|
39
|
+
*/
|
|
40
|
+
export const transformMintlifyContent = async (
|
|
41
|
+
raw: string,
|
|
42
|
+
options: { filePath: string; root: string; variables: Record<string, string> }
|
|
43
|
+
): Promise<MintlifyTransformResult> => {
|
|
44
|
+
let text = await rewriteMintlifyMarkdownSnippets(raw, {
|
|
45
|
+
filePath: options.filePath,
|
|
46
|
+
root: options.root,
|
|
47
|
+
});
|
|
48
|
+
text = await rewriteMintlifySnippetVariables(text, {
|
|
49
|
+
filePath: options.filePath,
|
|
50
|
+
root: options.root,
|
|
51
|
+
});
|
|
52
|
+
text = rewriteMintlifyGlobalVariables(text, options.variables);
|
|
53
|
+
if (USER_REFERENCE.test(text)) {
|
|
54
|
+
text = rewriteMintlifyUserVariable(text);
|
|
55
|
+
}
|
|
56
|
+
const snippetImports = rewriteSnippetImports(text, {
|
|
57
|
+
filePath: options.filePath,
|
|
58
|
+
root: options.root,
|
|
59
|
+
});
|
|
60
|
+
text = snippetImports.source;
|
|
61
|
+
text = rewriteMintlifySvgIconProps(text);
|
|
62
|
+
text = rewriteMintlifyExampleBlocks(text);
|
|
63
|
+
text = rewriteMintlifyCallouts(text);
|
|
64
|
+
|
|
65
|
+
const unsupported = unsupportedMintlifyComponents(text);
|
|
66
|
+
|
|
67
|
+
const parsed = matter(text);
|
|
68
|
+
const mapped = normalizeMintlifyPageMeta(parsed.data);
|
|
69
|
+
const { data, removed } = stripUnknownPageMeta(mapped);
|
|
70
|
+
const content =
|
|
71
|
+
Object.keys(data).length > 0
|
|
72
|
+
? matter.stringify(parsed.content, data)
|
|
73
|
+
: parsed.content;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
components: snippetImports.components,
|
|
77
|
+
content,
|
|
78
|
+
removed,
|
|
79
|
+
unsupported,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { rewriteCallouts } from "../shared.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Source-to-source rewrites that turn Nextra-only MDX into idiomatic Blume
|
|
5
|
+
* markup. Runs once at migration time.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Nextra `<Callout type="X">` values mapped to Blume directive names. */
|
|
9
|
+
const CALLOUT_TYPE_DIRECTIVES: Record<string, string> = {
|
|
10
|
+
default: "note",
|
|
11
|
+
error: "danger",
|
|
12
|
+
info: "info",
|
|
13
|
+
warning: "warning",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Convert Nextra `<Callout>` components (with an optional `type`) into Blume
|
|
18
|
+
* `:::` directives. A bare `<Callout>` becomes `:::note`; the `emoji` prop is
|
|
19
|
+
* dropped.
|
|
20
|
+
*/
|
|
21
|
+
export const rewriteNextraCallouts = (source: string): string =>
|
|
22
|
+
rewriteCallouts(source, {
|
|
23
|
+
defaultDirective: "note",
|
|
24
|
+
tagDirectives: {},
|
|
25
|
+
tags: ["Callout"],
|
|
26
|
+
typeDirectives: CALLOUT_TYPE_DIRECTIVES,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const NEXTRA_IMPORT =
|
|
30
|
+
/^import\s+[\s\S]*?\s+from\s+["']nextra(?:\/[^"']*)?["'];?[ \t]*\n?/gmu;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Drop `import … from "nextra/…"` statements. Blume injects its components
|
|
34
|
+
* globally, so these imports would fail to resolve once `nextra` is gone.
|
|
35
|
+
*/
|
|
36
|
+
export const stripNextraImports = (source: string): string =>
|
|
37
|
+
source.replace(NEXTRA_IMPORT, "");
|
|
38
|
+
|
|
39
|
+
/** Nextra components with no drop-in Blume equivalent — flagged for review. */
|
|
40
|
+
const UNSUPPORTED_COMPONENTS = ["Bleed", "Cards", "FileTree", "Steps", "Tabs"];
|
|
41
|
+
|
|
42
|
+
/** Names of Nextra components in `source` that need manual attention. */
|
|
43
|
+
export const unsupportedNextraComponents = (source: string): string[] =>
|
|
44
|
+
UNSUPPORTED_COMPONENTS.filter((name) =>
|
|
45
|
+
new RegExp(`<${name}\\b`, "u").test(source)
|
|
46
|
+
);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { stripUnknownPageMeta } from "../shared.ts";
|
|
2
|
+
|
|
3
|
+
export interface NextraPageOverride {
|
|
4
|
+
hidden?: boolean;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fold Nextra `_meta` overrides (a sidebar label, `display: "hidden"`) into a
|
|
10
|
+
* page's frontmatter, then drop any keys Blume's strict page schema rejects.
|
|
11
|
+
* Nextra's own `title`/`description`/`sidebarTitle` already validate and pass
|
|
12
|
+
* through untouched. Explicit page frontmatter wins over `_meta` overrides.
|
|
13
|
+
*/
|
|
14
|
+
export const normalizeNextraPageMeta = (
|
|
15
|
+
value: unknown,
|
|
16
|
+
override: NextraPageOverride = {}
|
|
17
|
+
): { data: Record<string, unknown>; removed: string[] } => {
|
|
18
|
+
const source =
|
|
19
|
+
value && typeof value === "object"
|
|
20
|
+
? (value as Record<string, unknown>)
|
|
21
|
+
: {};
|
|
22
|
+
const data: Record<string, unknown> = { ...source };
|
|
23
|
+
|
|
24
|
+
const sidebar: Record<string, unknown> = {
|
|
25
|
+
...(typeof source.sidebar === "object" && source.sidebar !== null
|
|
26
|
+
? (source.sidebar as Record<string, unknown>)
|
|
27
|
+
: {}),
|
|
28
|
+
};
|
|
29
|
+
if (override.label !== undefined && sidebar.label === undefined) {
|
|
30
|
+
sidebar.label = override.label;
|
|
31
|
+
}
|
|
32
|
+
if (override.hidden && sidebar.hidden === undefined) {
|
|
33
|
+
sidebar.hidden = true;
|
|
34
|
+
}
|
|
35
|
+
if (Object.keys(sidebar).length > 0) {
|
|
36
|
+
data.sidebar = sidebar;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return stripUnknownPageMeta(data);
|
|
40
|
+
};
|