blume 0.5.3 → 0.6.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/dist/cli/index.js +3349 -7024
- package/dist/cli/index.js.map +39 -69
- package/dist/types/core/config.d.ts +0 -8
- package/dist/types/core/data.d.ts +6 -2
- package/dist/types/core/i18n-ui.d.ts +50 -0
- package/dist/types/core/schema.d.ts +379 -485
- package/dist/types/core/types.d.ts +8 -6
- package/docs/advanced/meta.ts +1 -8
- package/docs/advanced/skills.mdx +28 -0
- package/docs/configuration/ai.mdx +58 -0
- package/docs/configuration/index.mdx +13 -17
- package/docs/configuration/seo.mdx +59 -1
- package/docs/configuration/theming.mdx +15 -18
- package/docs/content/components.mdx +2 -53
- package/docs/content/i18n.mdx +0 -4
- package/docs/content/meta.mdx +3 -17
- package/docs/content/navigation.mdx +41 -4
- package/docs/content/syntax.mdx +1 -1
- package/docs/index.mdx +0 -1
- package/docs/reference/cli.mdx +12 -13
- package/package.json +6 -6
- package/skills/blume/SKILL.md +71 -0
- package/skills/blume-update-docs/SKILL.md +52 -0
- package/skills/blume-update-docs/references/audit-checklist.md +46 -0
- package/src/ai/agent-readability.ts +97 -0
- package/src/ai/ask-context.ts +131 -8
- package/src/ai/ask-data.ts +4 -1
- package/src/astro/generate.ts +19 -12
- package/src/astro/integration.ts +0 -21
- package/src/astro/templates.ts +33 -21
- package/src/cli/commands/build.ts +15 -0
- package/src/cli/commands/dev.ts +31 -20
- package/src/cli/commands/validate.ts +0 -2
- package/src/cli/dev-lock.ts +94 -21
- package/src/cli/index.ts +0 -2
- package/src/components/BlumePage.astro +0 -6
- package/src/components/Icon.astro +1 -12
- package/src/components/content/AccordionItem.astro +3 -6
- package/src/components/content/Badge.astro +1 -3
- package/src/components/content/Callout.astro +3 -9
- package/src/components/content/Card.astro +2 -3
- package/src/components/content/ColorItem.astro +2 -2
- package/src/components/content/Column.astro +1 -1
- package/src/components/content/GithubInfo.astro +11 -10
- package/src/components/content/Prompt.astro +1 -1
- package/src/components/content/Step.astro +3 -4
- package/src/components/content/Tab.astro +2 -3
- package/src/components/content/TypeTable.astro +13 -8
- package/src/components/content/Update.astro +1 -1
- package/src/components/islands/AskAI.astro +66 -2
- package/src/components/islands/ask-ai.tsx +289 -53
- package/src/components/layout/Header.astro +27 -4
- package/src/components/layout/Logo.astro +5 -1
- package/src/components/layout/NavSelector.astro +1 -1
- package/src/components/layout/NavTree.astro +15 -15
- package/src/components/layout/PageActions.astro +73 -30
- package/src/components/layout/PageLayout.astro +42 -0
- package/src/components/layout/ReferenceLayout.astro +1 -0
- package/src/components/layout/RootLayout.astro +79 -4
- package/src/components/layout/Search.astro +5 -5
- package/src/components/layout/nav-utils.ts +9 -4
- package/src/components/openapi/ApiOverview.astro +4 -50
- package/src/components/openapi/ApiTagOperations.astro +42 -0
- package/src/core/builtin-tags.ts +1 -3
- package/src/core/config.ts +5 -28
- package/src/core/data.ts +6 -2
- package/src/core/graph.ts +8 -6
- package/src/core/i18n-ui.ts +5 -0
- package/src/core/links.ts +5 -19
- package/src/core/meta.ts +1 -1
- package/src/core/nav-diagnostics.ts +7 -0
- package/src/core/navigation.ts +38 -17
- package/src/core/project-graph.ts +0 -5
- package/src/core/schema.ts +133 -95
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/resolve.ts +0 -13
- package/src/core/sources/watch.ts +43 -11
- package/src/core/types.ts +8 -6
- package/src/deploy/robots.ts +37 -4
- package/src/openapi/parse.ts +197 -14
- package/src/openapi/render-mdx.ts +44 -10
- package/src/openapi/scalar.ts +1 -1
- package/src/openapi/source.ts +19 -2
- package/src/search/documents.ts +9 -2
- package/src/theme/entry.ts +45 -17
- package/src/theme/icons.ts +18 -109
- package/src/theme/palette.ts +25 -51
- package/src/theme/twoslash.ts +6 -1
- package/dist/types/core/bridge.d.ts +0 -24
- package/dist/types/core/package-json.d.ts +0 -12
- package/dist/types/migrate/mintlify/assets.d.ts +0 -8
- package/dist/types/migrate/mintlify/config.d.ts +0 -16
- package/dist/types/migrate/mintlify/i18n.d.ts +0 -7
- package/dist/types/migrate/shared.d.ts +0 -153
- package/docs/advanced/bridge.mdx +0 -76
- package/docs/advanced/migrate.mdx +0 -124
- package/src/astro/static-assets.ts +0 -124
- package/src/cli/commands/migrate.ts +0 -39
- package/src/components/content/ApiField.astro +0 -75
- package/src/components/content/ParamField.astro +0 -39
- package/src/components/content/RequestField.astro +0 -23
- package/src/components/content/ResponseField.astro +0 -23
- package/src/components/content/Warning.astro +0 -9
- package/src/core/assets.ts +0 -31
- package/src/core/bridge.ts +0 -102
- package/src/core/sources/mintlify.ts +0 -190
- package/src/migrate/fumadocs/config.ts +0 -155
- package/src/migrate/fumadocs/content.ts +0 -376
- package/src/migrate/fumadocs/frontmatter.ts +0 -18
- package/src/migrate/fumadocs/groups.ts +0 -237
- package/src/migrate/fumadocs/index.ts +0 -355
- package/src/migrate/fumadocs/meta.ts +0 -244
- package/src/migrate/migrate.ts +0 -53
- package/src/migrate/mintlify/assets.ts +0 -46
- package/src/migrate/mintlify/config.ts +0 -954
- package/src/migrate/mintlify/content.ts +0 -120
- package/src/migrate/mintlify/frontmatter.ts +0 -126
- package/src/migrate/mintlify/i18n.ts +0 -51
- package/src/migrate/mintlify/icons.ts +0 -128
- package/src/migrate/mintlify/index.ts +0 -459
- package/src/migrate/mintlify/snippets.ts +0 -315
- package/src/migrate/mintlify/transform.ts +0 -82
- package/src/migrate/nextra/content.ts +0 -46
- package/src/migrate/nextra/frontmatter.ts +0 -40
- package/src/migrate/nextra/index.ts +0 -389
- package/src/migrate/nextra/meta.ts +0 -266
- package/src/migrate/shared.ts +0 -801
- package/src/migrate/starlight/config.ts +0 -455
- package/src/migrate/starlight/content.ts +0 -75
- package/src/migrate/starlight/frontmatter.ts +0 -111
- package/src/migrate/starlight/i18n.ts +0 -54
- package/src/migrate/starlight/index.ts +0 -131
package/src/theme/icons.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Icon resolution backed by the open Iconify
|
|
3
|
-
* Lucide, and Tabler — the three libraries Mintlify exposes. Resolution runs at
|
|
2
|
+
* Icon resolution backed by the open Iconify Lucide set. Resolution runs at
|
|
4
3
|
* **build time, server-side**, and returns ready-to-inline SVG, so icons stay
|
|
5
|
-
* zero-JS and fully self-contained (no runtime CDN fetch
|
|
4
|
+
* zero-JS and fully self-contained (no runtime CDN fetch).
|
|
6
5
|
*
|
|
7
6
|
* Because the Iconify set data is large, this module must only be imported from
|
|
8
7
|
* server contexts (`.astro` frontmatter, the CLI). Client scripts use the tiny
|
|
9
8
|
* hand-inlined set in `./chrome-icons.ts` instead.
|
|
10
|
-
*
|
|
11
|
-
* Coverage vs Mintlify: full parity for every Font Awesome *free* name, Lucide,
|
|
12
|
-
* and Tabler. Font Awesome Pro styles (`light`/`thin`/`duotone`/`sharp-solid`)
|
|
13
|
-
* aren't in the open data, so they fall back to `solid`.
|
|
14
9
|
*/
|
|
15
10
|
import { createRequire } from "node:module";
|
|
16
11
|
|
|
@@ -25,61 +20,22 @@ const requireJson = createRequire(import.meta.url);
|
|
|
25
20
|
const loadSet = (pkg: string): IconifyJSON => requireJson(pkg) as IconifyJSON;
|
|
26
21
|
|
|
27
22
|
const SETS: Record<string, IconifyJSON> = {
|
|
28
|
-
"fa6-brands": loadSet("@iconify-json/fa6-brands/icons.json"),
|
|
29
|
-
"fa6-regular": loadSet("@iconify-json/fa6-regular/icons.json"),
|
|
30
|
-
"fa6-solid": loadSet("@iconify-json/fa6-solid/icons.json"),
|
|
31
23
|
lucide: loadSet("@iconify-json/lucide/icons.json"),
|
|
32
|
-
tabler: loadSet("@iconify-json/tabler/icons.json"),
|
|
33
24
|
};
|
|
34
25
|
|
|
35
|
-
/** Blume's
|
|
26
|
+
/** Blume's only icon library. */
|
|
36
27
|
const DEFAULT_SET = "lucide";
|
|
37
28
|
|
|
38
|
-
/** `
|
|
39
|
-
const LIBRARY_SETS: Record<string, string> = {
|
|
40
|
-
fa: "fa6-solid",
|
|
41
|
-
"font-awesome": "fa6-solid",
|
|
42
|
-
fontawesome: "fa6-solid",
|
|
43
|
-
lucide: "lucide",
|
|
44
|
-
tabler: "tabler",
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Mintlify Font Awesome `iconType` → Iconify set. The Pro-only styles
|
|
49
|
-
* (`light`/`thin`/`duotone`/`sharp-solid`) aren't in the free data, so they map
|
|
50
|
-
* to `solid` rather than render nothing.
|
|
51
|
-
*/
|
|
52
|
-
const ICON_TYPE_SETS: Record<string, string> = {
|
|
53
|
-
brands: "fa6-brands",
|
|
54
|
-
duotone: "fa6-solid",
|
|
55
|
-
light: "fa6-solid",
|
|
56
|
-
regular: "fa6-regular",
|
|
57
|
-
"sharp-solid": "fa6-solid",
|
|
58
|
-
solid: "fa6-solid",
|
|
59
|
-
thin: "fa6-solid",
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/** Explicit `prefix:name` prefixes (Iconify prefixes + common FA aliases). */
|
|
29
|
+
/** Explicit `prefix:name` prefixes. Lucide is the only bundled set. */
|
|
63
30
|
const PREFIX_SETS: Record<string, string> = {
|
|
64
|
-
|
|
65
|
-
fa: "fa6-solid",
|
|
66
|
-
"fa-brands": "fa6-brands",
|
|
67
|
-
"fa-regular": "fa6-regular",
|
|
68
|
-
"fa-solid": "fa6-solid",
|
|
69
|
-
"fa6-brands": "fa6-brands",
|
|
70
|
-
"fa6-regular": "fa6-regular",
|
|
71
|
-
"fa6-solid": "fa6-solid",
|
|
72
|
-
fab: "fa6-brands",
|
|
73
|
-
far: "fa6-regular",
|
|
74
|
-
fas: "fa6-solid",
|
|
75
|
-
ti: "tabler",
|
|
31
|
+
lucide: "lucide",
|
|
76
32
|
};
|
|
77
33
|
|
|
78
34
|
/**
|
|
79
|
-
* Own-property map lookup. Icon names and
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
35
|
+
* Own-property map lookup. Icon names come from content and config, so a value
|
|
36
|
+
* like `constructor:x` would otherwise resolve an Object.prototype member (a
|
|
37
|
+
* function) and crash resolution deep in the build with no pointer to the
|
|
38
|
+
* offending page.
|
|
83
39
|
*/
|
|
84
40
|
const ownEntry = <T>(map: Record<string, T>, key: string): T | undefined =>
|
|
85
41
|
Object.hasOwn(map, key) ? map[key] : undefined;
|
|
@@ -89,40 +45,16 @@ export interface ResolvedIcon {
|
|
|
89
45
|
body: string;
|
|
90
46
|
/** The resolved icon name. */
|
|
91
47
|
name: string;
|
|
92
|
-
/** The icon's viewBox
|
|
48
|
+
/** The icon's viewBox (Lucide is 24×24). */
|
|
93
49
|
viewBox: string;
|
|
94
50
|
}
|
|
95
51
|
|
|
96
|
-
export interface ResolveIconOptions {
|
|
97
|
-
/** Font Awesome style selector (`solid`, `regular`, `brands`, …). */
|
|
98
|
-
iconType?: string;
|
|
99
|
-
/** Default library for a bare name (`fontawesome` | `lucide` | `tabler`). */
|
|
100
|
-
library?: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
52
|
const normalize = (name: string): string =>
|
|
104
53
|
name
|
|
105
54
|
.trim()
|
|
106
55
|
.toLowerCase()
|
|
107
56
|
.replaceAll(/[\s_]+/gu, "-");
|
|
108
57
|
|
|
109
|
-
/** Which set a bare name resolves against, given library/iconType hints. */
|
|
110
|
-
const setFor = (options: ResolveIconOptions): string => {
|
|
111
|
-
if (options.iconType) {
|
|
112
|
-
const set = ownEntry(ICON_TYPE_SETS, normalize(options.iconType));
|
|
113
|
-
if (set) {
|
|
114
|
-
return set;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (options.library) {
|
|
118
|
-
const set = ownEntry(LIBRARY_SETS, normalize(options.library));
|
|
119
|
-
if (set) {
|
|
120
|
-
return set;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return DEFAULT_SET;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
58
|
const fromSet = (setName: string, iconName: string): ResolvedIcon | null => {
|
|
127
59
|
const set = ownEntry(SETS, setName);
|
|
128
60
|
const data = set && getIconData(set, iconName);
|
|
@@ -133,46 +65,23 @@ const fromSet = (setName: string, iconName: string): ResolvedIcon | null => {
|
|
|
133
65
|
return { body, name: iconName, viewBox: attributes.viewBox };
|
|
134
66
|
};
|
|
135
67
|
|
|
136
|
-
// Font Awesome splits brands into their own set, so a bare `github` under a
|
|
137
|
-
// solid/regular default still resolves.
|
|
138
|
-
const fromFaSet = (setName: string, name: string): ResolvedIcon | null =>
|
|
139
|
-
fromSet(setName, name) ?? fromSet("fa6-brands", name);
|
|
140
|
-
|
|
141
|
-
const resolveInSet = (setName: string, name: string): ResolvedIcon | null =>
|
|
142
|
-
setName.startsWith("fa6-")
|
|
143
|
-
? fromFaSet(setName, name)
|
|
144
|
-
: fromSet(setName, name);
|
|
145
|
-
|
|
146
68
|
/**
|
|
147
|
-
* Resolve an icon name to inline SVG. Honors an explicit `
|
|
148
|
-
*
|
|
149
|
-
* `library`, falling back to Lucide.
|
|
69
|
+
* Resolve an icon name to inline SVG. Honors an explicit `lucide:name` prefix;
|
|
70
|
+
* a bare name resolves against Lucide.
|
|
150
71
|
*/
|
|
151
|
-
export const resolveIcon = (
|
|
152
|
-
name: string,
|
|
153
|
-
options: ResolveIconOptions = {}
|
|
154
|
-
): ResolvedIcon | null => {
|
|
72
|
+
export const resolveIcon = (name: string): ResolvedIcon | null => {
|
|
155
73
|
const normalized = normalize(name);
|
|
156
74
|
const colon = normalized.indexOf(":");
|
|
157
75
|
if (colon > 0) {
|
|
158
76
|
const setName = ownEntry(PREFIX_SETS, normalized.slice(0, colon));
|
|
159
|
-
|
|
160
|
-
return resolveInSet(setName, normalized.slice(colon + 1));
|
|
161
|
-
}
|
|
77
|
+
return setName ? fromSet(setName, normalized.slice(colon + 1)) : null;
|
|
162
78
|
}
|
|
163
|
-
return
|
|
79
|
+
return fromSet(DEFAULT_SET, normalized);
|
|
164
80
|
};
|
|
165
81
|
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* just because the project's default library is Lucide.
|
|
170
|
-
*/
|
|
171
|
-
export const hasIcon = (
|
|
172
|
-
name: string,
|
|
173
|
-
options: ResolveIconOptions = {}
|
|
174
|
-
): boolean => {
|
|
175
|
-
if (resolveIcon(name, options)) {
|
|
82
|
+
/** Whether a name resolves to a known Lucide icon. */
|
|
83
|
+
export const hasIcon = (name: string): boolean => {
|
|
84
|
+
if (resolveIcon(name)) {
|
|
176
85
|
return true;
|
|
177
86
|
}
|
|
178
87
|
const normalized = normalize(name);
|
package/src/theme/palette.ts
CHANGED
|
@@ -52,36 +52,11 @@ const backgroundImageCss = (image: string): string =>
|
|
|
52
52
|
const cssToken = (name: string, value?: string | null): string[] =>
|
|
53
53
|
value ? [` ${name}: ${value};`] : [];
|
|
54
54
|
|
|
55
|
-
const backgroundDecorationCss = (
|
|
56
|
-
decoration: ResolvedConfig["theme"]["backgroundDecoration"]
|
|
57
|
-
): string => {
|
|
58
|
-
if (decoration === "gradient") {
|
|
59
|
-
return ` --blume-background-decoration: radial-gradient(circle at top left, color-mix(in oklab, var(--blume-accent) 18%, transparent), transparent 28rem), radial-gradient(circle at top right, color-mix(in oklab, var(--blume-action) 12%, transparent), transparent 24rem);
|
|
60
|
-
--blume-background-decoration-repeat: no-repeat, no-repeat;
|
|
61
|
-
--blume-background-decoration-size: auto, auto;
|
|
62
|
-
`;
|
|
63
|
-
}
|
|
64
|
-
if (decoration === "grid") {
|
|
65
|
-
return ` --blume-background-decoration: linear-gradient(var(--blume-border) 1px, transparent 1px), linear-gradient(90deg, var(--blume-border) 1px, transparent 1px);
|
|
66
|
-
--blume-background-decoration-repeat: repeat, repeat;
|
|
67
|
-
--blume-background-decoration-size: 2rem 2rem, 2rem 2rem;
|
|
68
|
-
`;
|
|
69
|
-
}
|
|
70
|
-
if (decoration === "windows") {
|
|
71
|
-
return ` --blume-background-decoration: linear-gradient(90deg, color-mix(in oklab, var(--blume-border) 70%, transparent) 1px, transparent 1px), linear-gradient(var(--blume-border) 1px, transparent 1px);
|
|
72
|
-
--blume-background-decoration-repeat: repeat, repeat;
|
|
73
|
-
--blume-background-decoration-size: 7rem 4.5rem, 7rem 4.5rem;
|
|
74
|
-
`;
|
|
75
|
-
}
|
|
76
|
-
return "";
|
|
77
|
-
};
|
|
78
|
-
|
|
79
55
|
const themeRootCss = (
|
|
80
56
|
theme: ResolvedConfig["theme"],
|
|
81
57
|
options: {
|
|
82
58
|
accent: string;
|
|
83
59
|
action: string | null;
|
|
84
|
-
backgroundDecoration: string;
|
|
85
60
|
radius: string;
|
|
86
61
|
}
|
|
87
62
|
): string =>
|
|
@@ -92,12 +67,13 @@ const themeRootCss = (
|
|
|
92
67
|
"--blume-action-foreground",
|
|
93
68
|
options.action ? "oklch(1 0 0)" : null
|
|
94
69
|
),
|
|
95
|
-
...cssToken("--blume-background", safeColorOrNull(theme.background)),
|
|
70
|
+
...cssToken("--blume-background", safeColorOrNull(theme.background?.light)),
|
|
96
71
|
...cssToken(
|
|
97
72
|
"--blume-background-image",
|
|
98
|
-
theme.backgroundImage
|
|
73
|
+
theme.backgroundImage?.light
|
|
74
|
+
? backgroundImageCss(theme.backgroundImage.light)
|
|
75
|
+
: null
|
|
99
76
|
),
|
|
100
|
-
options.backgroundDecoration.trimEnd(),
|
|
101
77
|
` --blume-radius: ${options.radius};`,
|
|
102
78
|
]
|
|
103
79
|
.filter(Boolean)
|
|
@@ -108,13 +84,12 @@ const themeDarkCss = (
|
|
|
108
84
|
options: {
|
|
109
85
|
accent: string;
|
|
110
86
|
action: string | null;
|
|
111
|
-
backgroundDecoration: string;
|
|
112
87
|
}
|
|
113
88
|
): string => {
|
|
114
|
-
// Mode-shared tokens (accent, action
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
89
|
+
// Mode-shared tokens (accent, action) must be re-declared here: the base
|
|
90
|
+
// stylesheet's own `:root[data-theme="dark"]` block outranks the `:root`
|
|
91
|
+
// config tokens on specificity, so without this block dark mode would
|
|
92
|
+
// silently keep its neutral defaults and ignore the config.
|
|
118
93
|
const tokens = [
|
|
119
94
|
` --blume-accent: ${options.accent};`,
|
|
120
95
|
" --blume-accent-foreground: oklch(1 0 0);",
|
|
@@ -123,14 +98,13 @@ const themeDarkCss = (
|
|
|
123
98
|
"--blume-action-foreground",
|
|
124
99
|
options.action ? "oklch(1 0 0)" : null
|
|
125
100
|
),
|
|
126
|
-
...cssToken("--blume-background", safeColorOrNull(theme.
|
|
101
|
+
...cssToken("--blume-background", safeColorOrNull(theme.background?.dark)),
|
|
127
102
|
...cssToken(
|
|
128
103
|
"--blume-background-image",
|
|
129
|
-
theme.
|
|
130
|
-
? backgroundImageCss(theme.
|
|
104
|
+
theme.backgroundImage?.dark
|
|
105
|
+
? backgroundImageCss(theme.backgroundImage.dark)
|
|
131
106
|
: null
|
|
132
107
|
),
|
|
133
|
-
options.backgroundDecoration.trimEnd(),
|
|
134
108
|
].filter(Boolean);
|
|
135
109
|
return `:root[data-theme="dark"] {
|
|
136
110
|
${tokens.join("\n")}
|
|
@@ -139,12 +113,18 @@ ${tokens.join("\n")}
|
|
|
139
113
|
};
|
|
140
114
|
|
|
141
115
|
/**
|
|
142
|
-
* Resolve the configured accent to
|
|
143
|
-
* preset; any other value is treated as a raw CSS color so
|
|
144
|
-
* arbitrary colors without a config change.
|
|
116
|
+
* Resolve the configured accent to per-mode CSS colors. A named accent
|
|
117
|
+
* resolves to its preset; any other value is treated as a raw CSS color so
|
|
118
|
+
* users can pass arbitrary colors without a config change. A string accent
|
|
119
|
+
* has already been normalized by the config schema to the same color for
|
|
120
|
+
* both modes.
|
|
145
121
|
*/
|
|
146
|
-
export const resolveAccent = (
|
|
147
|
-
|
|
122
|
+
export const resolveAccent = (
|
|
123
|
+
theme: ResolvedConfig["theme"]
|
|
124
|
+
): { dark: string; light: string } => ({
|
|
125
|
+
dark: presetOrColor(theme.accent.dark),
|
|
126
|
+
light: presetOrColor(theme.accent.light),
|
|
127
|
+
});
|
|
148
128
|
|
|
149
129
|
/** Resolve the configured radius preset to a CSS length. */
|
|
150
130
|
export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
@@ -156,23 +136,17 @@ export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
|
156
136
|
* arbitrary colors without a config change.
|
|
157
137
|
*/
|
|
158
138
|
export const buildThemeCss = (theme: ResolvedConfig["theme"]): string => {
|
|
159
|
-
const accent =
|
|
160
|
-
const accentDark = theme.accentDark ? presetOrColor(theme.accentDark) : null;
|
|
139
|
+
const accent = resolveAccent(theme);
|
|
161
140
|
const action = theme.action ? presetOrColor(theme.action) : null;
|
|
162
|
-
const backgroundDecoration = backgroundDecorationCss(
|
|
163
|
-
theme.backgroundDecoration
|
|
164
|
-
);
|
|
165
141
|
const radius = RADII[theme.radius];
|
|
166
142
|
const root = themeRootCss(theme, {
|
|
167
|
-
accent,
|
|
143
|
+
accent: accent.light,
|
|
168
144
|
action,
|
|
169
|
-
backgroundDecoration,
|
|
170
145
|
radius,
|
|
171
146
|
});
|
|
172
147
|
const dark = themeDarkCss(theme, {
|
|
173
|
-
accent:
|
|
148
|
+
accent: accent.dark,
|
|
174
149
|
action,
|
|
175
|
-
backgroundDecoration,
|
|
176
150
|
});
|
|
177
151
|
|
|
178
152
|
return `/* Generated by Blume from theme config. */
|
package/src/theme/twoslash.ts
CHANGED
|
@@ -33,9 +33,14 @@ const OVERRIDES = `
|
|
|
33
33
|
|
|
34
34
|
/* Popups are absolutely positioned and must escape the pre's scroll container.
|
|
35
35
|
The base prose pre rule (from the typography layer) wins the cascade here
|
|
36
|
-
despite lower specificity, so !important is needed to force visibility.
|
|
36
|
+
despite lower specificity, so !important is needed to force visibility.
|
|
37
|
+
Regular code blocks scroll their inner code element and carry the horizontal
|
|
38
|
+
padding there; twoslash code opts out of that scroller (popups again), so the
|
|
39
|
+
padding is restored on the pre. */
|
|
37
40
|
.prose pre.twoslash {
|
|
38
41
|
overflow: visible !important;
|
|
42
|
+
padding-left: 1.25rem;
|
|
43
|
+
padding-right: 1.25rem;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/* The rich renderer renders each popup's type signature as a nested Shiki
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { BlumeConfig } from "./schema.ts";
|
|
2
|
-
/** A detected docs-tool config that Blume can serve without a migration. */
|
|
3
|
-
export interface BridgeDetection {
|
|
4
|
-
/** Which foreign docs tool was detected. */
|
|
5
|
-
tool: "mintlify";
|
|
6
|
-
/** Absolute path of the detected config file (`docs.json`/`mint.json`). */
|
|
7
|
-
configFile: string;
|
|
8
|
-
/** A Blume config synthesized from the foreign config, ready to validate. */
|
|
9
|
-
raw: BlumeConfig;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Detect a Mintlify project at `root` and synthesize an equivalent Blume config.
|
|
13
|
-
*
|
|
14
|
-
* "Bridge mode" lets a team swap `mintlify dev` for `blume dev` with no file
|
|
15
|
-
* changes: `docs.json` is translated to Blume config (`loadMintlifyConfig`) and
|
|
16
|
-
* its content block is rewired to a single `mintlify` content source, which
|
|
17
|
-
* transforms each MDX page to Blume idiom at scan time. The original
|
|
18
|
-
* `content.root`/`exclude` and `variables` move onto the source (Blume has no
|
|
19
|
-
* runtime variable substitution, so globals are inlined into content there).
|
|
20
|
-
*
|
|
21
|
-
* Returns `null` when no Mintlify config is present. Only called when no
|
|
22
|
-
* `blume.config.*` exists, so an explicit Blume config always takes precedence.
|
|
23
|
-
*/
|
|
24
|
-
export declare const detectMintlifyBridge: (root: string) => Promise<BridgeDetection | null>;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Derive a valid npm package name from a directory name, falling back to
|
|
3
|
-
* `docs` when nothing usable remains.
|
|
4
|
-
*/
|
|
5
|
-
export declare const toPackageName: (raw: string) => string;
|
|
6
|
-
/**
|
|
7
|
-
* A minimal, runnable `package.json` body for a Blume project: the `blume`
|
|
8
|
-
* dependency pinned to the installed version plus `dev`/`build`/`doctor`
|
|
9
|
-
* scripts, so `npm install && npm run dev` works immediately. Shared by
|
|
10
|
-
* `blume init` and the migrators, which scaffold one when a project has none.
|
|
11
|
-
*/
|
|
12
|
-
export declare const blumePackageJson: (name: string) => string;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { BlumeConfig } from "../../core/schema.ts";
|
|
2
|
-
/**
|
|
3
|
-
* Top-level path segments referenced as static assets by a Mintlify config
|
|
4
|
-
* (the conventional `/images`, plus logo/favicon/background paths). These are
|
|
5
|
-
* the root-served folders Mintlify exposes at the site root; Blume serves them
|
|
6
|
-
* via `content.assets` (bridge) or relocates them under `public/` (migrator).
|
|
7
|
-
*/
|
|
8
|
-
export declare const assetSegments: (config: BlumeConfig) => string[];
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { BlumeConfig } from "../../core/schema.ts";
|
|
2
|
-
type JsonObject = Record<string, unknown>;
|
|
3
|
-
export interface MintlifyRedirectPartition {
|
|
4
|
-
/** Static redirects Blume can honor, translated to Blume's `from`/`to` shape. */
|
|
5
|
-
kept: NonNullable<BlumeConfig["redirects"]>;
|
|
6
|
-
/** Source paths of dynamic redirects dropped because Blume can't model them. */
|
|
7
|
-
dropped: string[];
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Split a spec's redirects into the static ones Blume emits and the dynamic
|
|
11
|
-
* (wildcard/param) ones it drops. Keeping a dynamic redirect crashes the Astro
|
|
12
|
-
* build, so the migrator surfaces the dropped sources as a warning instead.
|
|
13
|
-
*/
|
|
14
|
-
export declare const partitionMintlifyRedirects: (spec: JsonObject) => MintlifyRedirectPartition;
|
|
15
|
-
export declare const loadMintlifyConfig: (root: string, file: string) => Promise<BlumeConfig>;
|
|
16
|
-
export {};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { BlumeConfig } from "../../core/schema.ts";
|
|
2
|
-
/**
|
|
3
|
-
* Map a Mintlify `navigation.languages[]` array to a Blume `i18n` config. The
|
|
4
|
-
* entry marked `default: true` becomes `defaultLocale`; translated content
|
|
5
|
-
* already lives in ISO-code directories, which match Blume's `dir` parser.
|
|
6
|
-
*/
|
|
7
|
-
export declare const mintlifyI18n: (spec: Record<string, unknown>) => BlumeConfig["i18n"] | null;
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import type { BlumeConfig } from "../core/schema.ts";
|
|
2
|
-
/**
|
|
3
|
-
* Whether `candidate` resolves to a path inside `root` (or is `root` itself).
|
|
4
|
-
* Guards migrators against `../` traversal in author-controlled source paths
|
|
5
|
-
* (`pages` entries, `<include>` targets) that would otherwise read or move
|
|
6
|
-
* files outside the docs tree.
|
|
7
|
-
*/
|
|
8
|
-
export declare const isInsideRoot: (root: string, candidate: string) => boolean;
|
|
9
|
-
/**
|
|
10
|
-
* Framework-agnostic helpers shared by more than one migrator. Each piece here
|
|
11
|
-
* was generalized from a migrator-specific implementation so Mintlify, Nextra,
|
|
12
|
-
* and future migrators converge on a single copy.
|
|
13
|
-
*/
|
|
14
|
-
/** Serialize a `BlumeConfig` to a `blume.config.ts` at the project root. */
|
|
15
|
-
export declare const writeBlumeConfig: (root: string, config: BlumeConfig) => Promise<void>;
|
|
16
|
-
/**
|
|
17
|
-
* Rewrite a migrated project's npm scripts off the old framework's CLI. A
|
|
18
|
-
* `dev`/`build`/`start` script whose command invokes `cli` (e.g. `/\bnext\b/`)
|
|
19
|
-
* is repointed at the matching Blume command (`start` -> `blume preview`); a
|
|
20
|
-
* script whose command matches `remove` (e.g. a `fumadocs-mdx` postinstall) is
|
|
21
|
-
* dropped. Scripts that don't match either are left untouched, so custom tasks
|
|
22
|
-
* survive. Returns true when `package.json` changed.
|
|
23
|
-
*/
|
|
24
|
-
export declare const rewriteFrameworkScripts: (root: string, cli: RegExp, remove?: RegExp) => Promise<boolean>;
|
|
25
|
-
/** Of the candidate project-relative paths, the ones that still exist — the old
|
|
26
|
-
* framework files a migration leaves behind for the user to remove by hand. */
|
|
27
|
-
export declare const leftoverFiles: (root: string, candidates: string[]) => string[];
|
|
28
|
-
/**
|
|
29
|
-
* Scaffold a minimal, runnable `package.json` when the migrated project has
|
|
30
|
-
* none. Config-only sources (e.g. a Mintlify `docs.json`) ship no npm manifest,
|
|
31
|
-
* so a fresh migration has nothing to run `blume dev` with; this writes a stub
|
|
32
|
-
* with `blume` as a dependency and `dev`/`build`/`doctor` scripts, making
|
|
33
|
-
* `npm install && npm run dev` work immediately. A pre-existing `package.json`
|
|
34
|
-
* is left untouched — {@link rewriteFrameworkScripts} repoints those instead.
|
|
35
|
-
* Returns true when a file was created.
|
|
36
|
-
*/
|
|
37
|
-
export declare const ensurePackageJson: (root: string) => Promise<boolean>;
|
|
38
|
-
/**
|
|
39
|
-
* Remove every match of an import pattern, collapsing the blank gap each
|
|
40
|
-
* removal leaves behind — without touching blank runs elsewhere in the
|
|
41
|
-
* document (double blank lines inside code fences are real content that a
|
|
42
|
-
* whole-document `\n{3,}` collapse used to corrupt).
|
|
43
|
-
*/
|
|
44
|
-
export declare const stripImports: (source: string, pattern: RegExp) => string;
|
|
45
|
-
export interface CalloutRewriteOptions {
|
|
46
|
-
/** Directive for a type-bearing tag with no `type` attribute (e.g. bare `<Callout>`). */
|
|
47
|
-
defaultDirective: string;
|
|
48
|
-
/** Tag names whose directive is fixed by the tag itself (e.g. `<Warning>`). */
|
|
49
|
-
tagDirectives: Record<string, string>;
|
|
50
|
-
/** Component tag names to convert. */
|
|
51
|
-
tags: string[];
|
|
52
|
-
/** `type="…"` values mapped to Blume directive names. */
|
|
53
|
-
typeDirectives: Record<string, string>;
|
|
54
|
-
}
|
|
55
|
-
/** Read a quoted string attribute (`name="…"` or `name='…'`) from a tag. */
|
|
56
|
-
export declare const attribute: (attrs: string, name: string) => string | undefined;
|
|
57
|
-
/**
|
|
58
|
-
* Find the `>` that closes an opening JSX tag, honoring quotes and `{…}`
|
|
59
|
-
* expression attributes (so a `>` inside `icon={"<svg…>"}` is not mistaken for
|
|
60
|
-
* the tag end). Returns -1 if unterminated.
|
|
61
|
-
*/
|
|
62
|
-
export declare const findOpenTagEnd: (source: string, from: number) => number;
|
|
63
|
-
/**
|
|
64
|
-
* Convert callout-style JSX components into Blume `:::` directives. Uses a
|
|
65
|
-
* quote/brace-aware tag scanner so callouts carrying JSX-expression attributes
|
|
66
|
-
* (e.g. inline-SVG icons) convert cleanly; non-convertible attributes (icons,
|
|
67
|
-
* colors, emoji) are dropped. A tag whose resolved directive is unknown is left
|
|
68
|
-
* untouched.
|
|
69
|
-
*/
|
|
70
|
-
export declare const rewriteCallouts: (source: string, options: CalloutRewriteOptions) => string;
|
|
71
|
-
/**
|
|
72
|
-
* Remove frontmatter keys Blume's strict page schema would reject (e.g. stray
|
|
73
|
-
* `og:*`/`twitter:*` metatags) so the migrated page validates, reporting what
|
|
74
|
-
* was dropped. Validation errors other than stray keys are left for `blume dev`
|
|
75
|
-
* to surface.
|
|
76
|
-
*/
|
|
77
|
-
export declare const stripUnknownPageMeta: (data: Record<string, unknown>) => {
|
|
78
|
-
data: Record<string, unknown>;
|
|
79
|
-
removed: string[];
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* Rename a JSX tag (open and close) while preserving its attributes. The
|
|
83
|
-
* trailing lookahead means a longer tag (e.g. `CardGrid`) is never matched by a
|
|
84
|
-
* rule for its shorter prefix (`Card`), so prefix-sharing renames can be chained
|
|
85
|
-
* — run the item-level rename before the container rename.
|
|
86
|
-
*/
|
|
87
|
-
export declare const renameTag: (source: string, from: string, to: string) => string;
|
|
88
|
-
/**
|
|
89
|
-
* Static readers for JS/TS config files (Nextra `_meta`, Starlight
|
|
90
|
-
* `astro.config`). Config is parsed by walking the source as text — quote-,
|
|
91
|
-
* comment-, and bracket-aware — rather than executing user code, matching the
|
|
92
|
-
* other migrators (which never eval). Values that aren't pure literals (an
|
|
93
|
-
* identifier, call, JSX, or interpolated template) are reported as `UNPARSEABLE`
|
|
94
|
-
* so the caller can drop the field and warn.
|
|
95
|
-
*/
|
|
96
|
-
/** Index of a string within a JS source: the close quote matching `s[open]`. */
|
|
97
|
-
export declare const findStringEnd: (s: string, open: number) => number;
|
|
98
|
-
export declare const unescapeString: (inner: string) => string;
|
|
99
|
-
export interface ObjectScanResult {
|
|
100
|
-
end: number;
|
|
101
|
-
entries: string[];
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Walk a `{…}` object literal starting at `openIndex`, returning the matching
|
|
105
|
-
* close-brace index and the raw `key: value` text of each top-level entry.
|
|
106
|
-
* Quote-, comment-, and bracket-aware so commas/braces nested in strings,
|
|
107
|
-
* arrays, or child objects don't split entries. Returns null if unterminated.
|
|
108
|
-
*/
|
|
109
|
-
export declare const scanObject: (source: string, openIndex: number) => ObjectScanResult | null;
|
|
110
|
-
export interface ArrayScanResult {
|
|
111
|
-
elements: string[];
|
|
112
|
-
end: number;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Walk a `[…]` array literal starting at `openIndex`, returning the matching
|
|
116
|
-
* close-bracket index and the raw text of each top-level element. The sibling of
|
|
117
|
-
* {@link scanObject}; a trailing comma yields no empty element.
|
|
118
|
-
*/
|
|
119
|
-
export declare const scanArray: (source: string, openIndex: number) => ArrayScanResult | null;
|
|
120
|
-
/**
|
|
121
|
-
* Strip `//` and block comments so they don't leak into entry text (the scanner
|
|
122
|
-
* splits on slices, so an inter-entry comment would otherwise attach to the next
|
|
123
|
-
* entry). String literals are preserved verbatim.
|
|
124
|
-
*/
|
|
125
|
-
export declare const stripJsComments: (source: string) => string;
|
|
126
|
-
export interface KeyValue {
|
|
127
|
-
key: string;
|
|
128
|
-
value: string;
|
|
129
|
-
}
|
|
130
|
-
/** Split a raw `key: value` entry at its top-level colon. */
|
|
131
|
-
export declare const splitKeyValue: (entry: string) => KeyValue | null;
|
|
132
|
-
/** Read an object key, unquoting it when it is a string literal. */
|
|
133
|
-
export declare const parseKey: (key: string) => string;
|
|
134
|
-
/** Read a clean string literal value, or null if it's an expression. */
|
|
135
|
-
export declare const readString: (value: string) => string | null;
|
|
136
|
-
/** A value that isn't a pure literal (identifier, call, JSX, computed, …). */
|
|
137
|
-
export declare const UNPARSEABLE: unique symbol;
|
|
138
|
-
export type LiteralValue = LiteralValue[] | boolean | null | number | string | typeof UNPARSEABLE | {
|
|
139
|
-
[key: string]: LiteralValue;
|
|
140
|
-
};
|
|
141
|
-
/**
|
|
142
|
-
* Evaluate a JS literal expression (string / number / boolean / null / array /
|
|
143
|
-
* object) into its value without executing it. Anything else resolves to
|
|
144
|
-
* {@link UNPARSEABLE}; inside arrays the sentinel keeps the element's position,
|
|
145
|
-
* inside objects it stays as the field's value so the caller can warn and drop.
|
|
146
|
-
*/
|
|
147
|
-
export declare const parseLiteral: (source: string) => LiteralValue;
|
|
148
|
-
/** Narrow a parsed literal to a string. */
|
|
149
|
-
export declare const asLiteralString: (value: LiteralValue | undefined) => string | undefined;
|
|
150
|
-
/** Narrow a parsed literal to a plain object (not an array or `UNPARSEABLE`). */
|
|
151
|
-
export declare const isLiteralObject: (value: LiteralValue | undefined) => value is Record<string, LiteralValue>;
|
|
152
|
-
/** Narrow a parsed literal to an array. */
|
|
153
|
-
export declare const asLiteralArray: (value: LiteralValue | undefined) => LiteralValue[] | undefined;
|
package/docs/advanced/bridge.mdx
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Bridge
|
|
3
|
-
description: Run blume dev directly on a Mintlify codebase — no config, no migration, no file changes. Blume detects docs.json and serves it in place.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Bridge mode lets you point Blume at a **Mintlify** project and run it as-is. There's no config to write and no migration to commit: drop `blume dev` into a directory that has a `docs.json` (or legacy `mint.json`) and Blume detects it, synthesizes an equivalent config in memory, and serves your existing MDX — transformed to Blume idiom on the fly, on disk untouched.
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
cd my-mintlify-docs
|
|
10
|
-
npx blume dev
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
```txt
|
|
14
|
-
ℹ Detected docs.json — running in Mintlify bridge mode (no migration).
|
|
15
|
-
Run "blume migrate mintlify" to convert permanently.
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
It's the zero-commitment way to see what your docs look like on Blume — you can keep running `mintlify dev` in one terminal and `blume dev` in another, side by side, and decide later.
|
|
19
|
-
|
|
20
|
-
## When it activates
|
|
21
|
-
|
|
22
|
-
Bridge mode is entirely automatic, gated on two conditions:
|
|
23
|
-
|
|
24
|
-
- **No Blume config.** There's no `blume.config.{ts,js,mjs}` at the project root. An explicit Blume config always wins — the moment one exists, Blume runs a normal project and bridge mode never triggers.
|
|
25
|
-
- **A Mintlify config is present.** A `docs.json` or `mint.json` sits at the root.
|
|
26
|
-
|
|
27
|
-
With both true, Blume treats the Mintlify config as its source of truth for that run.
|
|
28
|
-
|
|
29
|
-
:::note
|
|
30
|
-
Bridge mode applies to `blume build` too, not just `blume dev` — you can produce a static site from an unconverted Mintlify project. Only `blume dev` prints the detection notice.
|
|
31
|
-
:::
|
|
32
|
-
|
|
33
|
-
## What happens under the hood
|
|
34
|
-
|
|
35
|
-
Bridge mode is the **read-only twin** of [`blume migrate mintlify`](/docs/advanced/migrate#mintlify): the same translation, without writing anything back.
|
|
36
|
-
|
|
37
|
-
- **Config is synthesized in memory.** `docs.json` is translated to a Blume config — navigation, theme, and chrome mapped across — exactly as the migrator would, but nothing is written to disk.
|
|
38
|
-
- **Content is served through a Mintlify source.** The config's content block is rewired to a single [`mintlify` content source](/docs/content/sources) rooted at the project. Each MDX page is transformed to Blume markup **at scan time** as it's read, so your files never change.
|
|
39
|
-
- **Variables are inlined at scan time.** `docs.json` `variables` (`{{name}}`) are substituted into content as it's scanned — Blume has no runtime substitution.
|
|
40
|
-
- **Assets are served in place.** Referenced folders like `images/` are served through [`content.assets`](/docs/content/sources) rather than moved to `public/`, so nothing is relocated.
|
|
41
|
-
- **Languages map to i18n.** A multi-language `docs.json` maps to Blume's [`i18n`](/docs/content/i18n), with the language switch handled by Blume's locale routing instead of a nav selector.
|
|
42
|
-
|
|
43
|
-
Because it reads the same `docs.json` and runs the same transforms, what you see in bridge mode is what you'd get from a full migration.
|
|
44
|
-
|
|
45
|
-
## Bridge vs. migrate
|
|
46
|
-
|
|
47
|
-
<CardGroup cols={2}>
|
|
48
|
-
<Card title="Bridge mode" icon="cable">
|
|
49
|
-
**Try Blume with zero changes.** Nothing is written; your Mintlify project
|
|
50
|
-
stays exactly as it is. Ideal for evaluating Blume, running both dev servers
|
|
51
|
-
side by side, or a reversible spike.
|
|
52
|
-
</Card>
|
|
53
|
-
<Card title="Migrate" href="/docs/advanced/migrate" icon="arrow-right">
|
|
54
|
-
**Convert for good.** [`blume migrate mintlify`](/docs/advanced/migrate)
|
|
55
|
-
rewrites your pages, config, and assets in place so Blume becomes the source
|
|
56
|
-
of truth. Do this once you've decided to switch.
|
|
57
|
-
</Card>
|
|
58
|
-
</CardGroup>
|
|
59
|
-
|
|
60
|
-
Everything you can do in bridge mode, you can do permanently by migrating — bridge is the preview, migrate is the commit.
|
|
61
|
-
|
|
62
|
-
## Limitations
|
|
63
|
-
|
|
64
|
-
- **Mintlify only.** Bridge detection is Mintlify-specific. The other frameworks — Fumadocs, Nextra, Starlight — need a [one-shot migration](/docs/advanced/migrate).
|
|
65
|
-
- **Same idiom gaps as the migrator.** Components without a Blume equivalent are transformed on a best-effort basis; when you hit one, [migrate](/docs/advanced/migrate) and address the warnings, or switch that page to a Blume-native equivalent such as the [OpenAPI reference](/docs/advanced/api-reference).
|
|
66
|
-
- **No runtime variables.** As with a migration, `{{variable}}` values are inlined at scan time rather than substituted at runtime.
|
|
67
|
-
|
|
68
|
-
<CardGroup cols={2}>
|
|
69
|
-
<Card title="Migrate" href="/docs/advanced/migrate" icon="arrow-right">
|
|
70
|
-
Convert a Mintlify, Fumadocs, Nextra, or Starlight project permanently.
|
|
71
|
-
</Card>
|
|
72
|
-
<Card title="Content sources" href="/docs/content/sources" icon="folder-tree">
|
|
73
|
-
How the `mintlify` source and `content.assets` fit into Blume's content
|
|
74
|
-
graph.
|
|
75
|
-
</Card>
|
|
76
|
-
</CardGroup>
|