blume 0.5.2 → 0.5.4
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 +2235 -5942
- package/dist/cli/index.js.map +36 -65
- package/dist/types/core/config.d.ts +0 -8
- package/dist/types/core/data.d.ts +2 -2
- package/dist/types/core/schema.d.ts +58 -453
- package/dist/types/core/types.d.ts +1 -7
- package/docs/advanced/meta.ts +1 -8
- package/docs/advanced/skills.mdx +28 -0
- package/docs/configuration/ai.mdx +3 -1
- package/docs/configuration/index.mdx +13 -17
- package/docs/configuration/theming.mdx +3 -11
- package/docs/content/components.mdx +2 -53
- package/docs/content/i18n.mdx +1 -5
- package/docs/content/sources.mdx +1 -1
- package/docs/content/syntax.mdx +1 -1
- package/docs/index.mdx +0 -1
- package/docs/reference/cli.mdx +12 -13
- package/package.json +4 -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/mcp/discovery.ts +3 -1
- package/src/ai/mcp/server.ts +3 -1
- package/src/astro/component-slots.ts +10 -2
- package/src/astro/generate.ts +15 -12
- package/src/astro/integration.ts +0 -21
- package/src/astro/templates.ts +62 -37
- package/src/cli/coalesce.ts +43 -0
- package/src/cli/commands/dev.ts +30 -22
- package/src/cli/commands/validate.ts +0 -2
- package/src/cli/dev-lock.ts +4 -2
- 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 +8 -5
- package/src/components/content/Column.astro +1 -1
- package/src/components/content/Prompt.astro +8 -4
- package/src/components/content/Step.astro +3 -4
- package/src/components/content/Tab.astro +2 -3
- package/src/components/content/Tabs.astro +13 -2
- package/src/components/content/TypeTable.astro +5 -5
- package/src/components/content/Update.astro +1 -1
- package/src/components/content/mermaid-element.ts +20 -2
- package/src/components/islands/ask-ai.tsx +4 -8
- package/src/components/islands/base-path.ts +30 -0
- package/src/components/islands/hooks.ts +12 -8
- package/src/components/layout/Header.astro +26 -3
- package/src/components/layout/Logo.astro +5 -1
- package/src/components/layout/NavSelector.astro +1 -1
- package/src/components/layout/NavTree.astro +14 -14
- package/src/components/layout/PageActions.astro +17 -11
- package/src/components/layout/PageLayout.astro +42 -0
- package/src/components/layout/ReferenceLayout.astro +1 -0
- package/src/components/layout/RootLayout.astro +31 -2
- package/src/components/layout/Search.astro +9 -6
- package/src/components/layout/nav-utils.ts +9 -4
- package/src/components/layout/search/types.ts +16 -5
- package/src/components/openapi/ApiOverview.astro +4 -50
- package/src/components/openapi/ApiTagOperations.astro +42 -0
- package/src/components/openapi/ParametersTable.astro +1 -1
- package/src/components/openapi/SchemaProperty.astro +1 -1
- package/src/components/openapi/SchemaTable.astro +3 -3
- package/src/components/openapi/helpers.ts +17 -8
- package/src/components/openapi/snippets.ts +17 -4
- package/src/core/builtin-tags.ts +1 -3
- package/src/core/config.ts +19 -33
- package/src/core/data.ts +2 -2
- package/src/core/graph.ts +7 -5
- package/src/core/links.ts +5 -19
- package/src/core/meta.ts +1 -1
- package/src/core/navigation.ts +5 -6
- package/src/core/project-graph.ts +0 -5
- package/src/core/schema.ts +13 -90
- package/src/core/sources/filesystem.ts +19 -1
- package/src/core/sources/mdx-remote.ts +20 -4
- package/src/core/sources/normalize.ts +28 -6
- package/src/core/sources/resolve.ts +0 -13
- package/src/core/sources/watch.ts +45 -0
- package/src/core/types.ts +1 -8
- package/src/markdown/code-title.ts +6 -3
- package/src/markdown/package-install.ts +3 -1
- package/src/og/card.ts +16 -4
- package/src/openapi/parse.ts +197 -14
- package/src/openapi/render-mdx.ts +54 -11
- package/src/openapi/source.ts +19 -2
- package/src/search/sync/orama-cloud.ts +2 -0
- package/src/search/sync/typesense.ts +4 -0
- package/src/theme/entry.ts +45 -17
- package/src/theme/icons.ts +22 -104
- package/src/theme/palette.ts +35 -47
- package/src/theme/twoslash.ts +6 -1
- package/dist/types/core/bridge.d.ts +0 -24
- 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/docs/advanced/bridge.mdx +0 -76
- package/docs/advanced/migrate.mdx +0 -123
- package/src/astro/static-assets.ts +0 -117
- 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 -204
- package/src/migrate/fumadocs/config.ts +0 -155
- package/src/migrate/fumadocs/content.ts +0 -378
- package/src/migrate/fumadocs/frontmatter.ts +0 -18
- package/src/migrate/fumadocs/groups.ts +0 -237
- package/src/migrate/fumadocs/index.ts +0 -340
- 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 -958
- 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 -372
- package/src/migrate/mintlify/snippets.ts +0 -306
- 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 -374
- package/src/migrate/nextra/meta.ts +0 -266
- package/src/migrate/shared.ts +0 -728
- package/src/migrate/starlight/config.ts +0 -455
- package/src/migrate/starlight/content.ts +0 -78
- package/src/migrate/starlight/frontmatter.ts +0 -111
- package/src/migrate/starlight/i18n.ts +0 -54
- package/src/migrate/starlight/index.ts +0 -131
|
@@ -1,958 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
import { dirname, relative, resolve } from "pathe";
|
|
4
|
-
|
|
5
|
-
import { BlumeError } from "../../core/diagnostics.ts";
|
|
6
|
-
import type {
|
|
7
|
-
BlumeConfig,
|
|
8
|
-
DirectoryMode,
|
|
9
|
-
ResolvedConfig,
|
|
10
|
-
SidebarItemConfig,
|
|
11
|
-
} from "../../core/schema.ts";
|
|
12
|
-
import { GOOGLE_FONTS } from "../../theme/fonts.ts";
|
|
13
|
-
|
|
14
|
-
type JsonObject = Record<string, unknown>;
|
|
15
|
-
type NavigationSelectors = ResolvedConfig["navigation"]["selectors"];
|
|
16
|
-
type NavigationSelectorItem = NavigationSelectors[number]["items"][number];
|
|
17
|
-
type NavigationChromeVariants = NonNullable<
|
|
18
|
-
BlumeConfig["navigation"]
|
|
19
|
-
>["chromeVariants"];
|
|
20
|
-
|
|
21
|
-
const MINTLIFY_DEFAULT_IGNORES = [
|
|
22
|
-
"**/_*",
|
|
23
|
-
"**/.*",
|
|
24
|
-
".git/**",
|
|
25
|
-
".github/**",
|
|
26
|
-
".claude/**",
|
|
27
|
-
".agents/**",
|
|
28
|
-
".mintlify/**",
|
|
29
|
-
".idea/**",
|
|
30
|
-
".vscode/**",
|
|
31
|
-
".blume/**",
|
|
32
|
-
"node_modules/**",
|
|
33
|
-
"build/**",
|
|
34
|
-
"dist/**",
|
|
35
|
-
"coverage/**",
|
|
36
|
-
".cache/**",
|
|
37
|
-
"snippets/**",
|
|
38
|
-
"tmp/**",
|
|
39
|
-
"temp/**",
|
|
40
|
-
"README.md",
|
|
41
|
-
"README.mdx",
|
|
42
|
-
"skill.md",
|
|
43
|
-
"LICENSE.md",
|
|
44
|
-
"CHANGELOG.md",
|
|
45
|
-
"CONTRIBUTING.md",
|
|
46
|
-
];
|
|
47
|
-
const API_ENDPOINT_REF =
|
|
48
|
-
/^(?<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(?<path>\/.*)$/iu;
|
|
49
|
-
const VARIABLE_NAME = /^[A-Za-z0-9-]+$/u;
|
|
50
|
-
|
|
51
|
-
const asObject = (value: unknown): JsonObject | null =>
|
|
52
|
-
value && typeof value === "object" && !Array.isArray(value)
|
|
53
|
-
? (value as JsonObject)
|
|
54
|
-
: null;
|
|
55
|
-
|
|
56
|
-
const asArray = (value: unknown): unknown[] =>
|
|
57
|
-
Array.isArray(value) ? value : [];
|
|
58
|
-
|
|
59
|
-
const asString = (value: unknown): string | undefined =>
|
|
60
|
-
typeof value === "string" && value.length > 0 ? value : undefined;
|
|
61
|
-
|
|
62
|
-
const asDirectoryMode = (value: unknown): DirectoryMode | undefined =>
|
|
63
|
-
value === "accordion" || value === "card" || value === "none"
|
|
64
|
-
? value
|
|
65
|
-
: undefined;
|
|
66
|
-
|
|
67
|
-
const withoutUndefined = <T extends JsonObject>(value: T): T =>
|
|
68
|
-
Object.fromEntries(
|
|
69
|
-
Object.entries(value).filter(([, item]) => item !== undefined)
|
|
70
|
-
) as T;
|
|
71
|
-
|
|
72
|
-
const hasOwn = (object: JsonObject, key: string): boolean =>
|
|
73
|
-
Object.hasOwn(object, key);
|
|
74
|
-
|
|
75
|
-
const isInsideRoot = (root: string, candidate: string): boolean => {
|
|
76
|
-
const rel = relative(root, candidate);
|
|
77
|
-
return rel === "" || (!rel.startsWith("..") && !rel.startsWith("/"));
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const readJsonFile = async (file: string): Promise<unknown> => {
|
|
81
|
-
try {
|
|
82
|
-
return JSON.parse(await readFile(file, "utf-8"));
|
|
83
|
-
} catch (error) {
|
|
84
|
-
throw new BlumeError({
|
|
85
|
-
code: "BLUME_MINTLIFY_CONFIG_INVALID",
|
|
86
|
-
file,
|
|
87
|
-
message: `Could not parse Mintlify config: ${(error as Error).message}`,
|
|
88
|
-
severity: "error",
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const resolveRefs = async (
|
|
94
|
-
value: unknown,
|
|
95
|
-
options: { file: string; root: string; seen: Set<string> }
|
|
96
|
-
): Promise<unknown> => {
|
|
97
|
-
if (Array.isArray(value)) {
|
|
98
|
-
return Promise.all(
|
|
99
|
-
value.map((item) => resolveRefs(item, options))
|
|
100
|
-
) as Promise<unknown[]>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const object = asObject(value);
|
|
104
|
-
if (!object) {
|
|
105
|
-
return value;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const ref = asString(object.$ref);
|
|
109
|
-
if (ref) {
|
|
110
|
-
const refFile = resolve(dirname(options.file), ref);
|
|
111
|
-
if (!isInsideRoot(options.root, refFile)) {
|
|
112
|
-
throw new BlumeError({
|
|
113
|
-
code: "BLUME_MINTLIFY_REF_OUTSIDE_ROOT",
|
|
114
|
-
file: options.file,
|
|
115
|
-
message: `Mintlify $ref points outside the project root: ${ref}`,
|
|
116
|
-
severity: "error",
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
if (options.seen.has(refFile)) {
|
|
120
|
-
throw new BlumeError({
|
|
121
|
-
code: "BLUME_MINTLIFY_REF_CYCLE",
|
|
122
|
-
file: options.file,
|
|
123
|
-
message: `Mintlify $ref cycle detected at ${ref}`,
|
|
124
|
-
severity: "error",
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
options.seen.add(refFile);
|
|
129
|
-
const resolved = await resolveRefs(await readJsonFile(refFile), {
|
|
130
|
-
file: refFile,
|
|
131
|
-
root: options.root,
|
|
132
|
-
seen: options.seen,
|
|
133
|
-
});
|
|
134
|
-
options.seen.delete(refFile);
|
|
135
|
-
|
|
136
|
-
const siblings = Object.fromEntries(
|
|
137
|
-
Object.entries(object).filter(([key]) => key !== "$ref")
|
|
138
|
-
);
|
|
139
|
-
if (asObject(resolved)) {
|
|
140
|
-
return resolveRefs({ ...(resolved as JsonObject), ...siblings }, options);
|
|
141
|
-
}
|
|
142
|
-
return resolved;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return Object.fromEntries(
|
|
146
|
-
await Promise.all(
|
|
147
|
-
Object.entries(object).map(async ([key, item]) => [
|
|
148
|
-
key,
|
|
149
|
-
await resolveRefs(item, options),
|
|
150
|
-
])
|
|
151
|
-
)
|
|
152
|
-
);
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const normalizePageRef = (ref: string): string =>
|
|
156
|
-
ref.replace(/\.(?<ext>mdx?|mdoc)$/u, "");
|
|
157
|
-
|
|
158
|
-
const labelForNavItem = (item: JsonObject): string | undefined =>
|
|
159
|
-
asString(item.group) ??
|
|
160
|
-
asString(item.label) ??
|
|
161
|
-
asString(item.anchor) ??
|
|
162
|
-
asString(item.tab) ??
|
|
163
|
-
asString(item.dropdown) ??
|
|
164
|
-
asString(item.product) ??
|
|
165
|
-
asString(item.version) ??
|
|
166
|
-
asString(item.language) ??
|
|
167
|
-
asString(item.item) ??
|
|
168
|
-
asString(item.name) ??
|
|
169
|
-
asString(item.title);
|
|
170
|
-
|
|
171
|
-
const childNavigationArrays = (item: JsonObject): unknown[][] => [
|
|
172
|
-
asArray(item.pages),
|
|
173
|
-
asArray(item.groups),
|
|
174
|
-
asArray(item.menu),
|
|
175
|
-
asArray(item.tabs),
|
|
176
|
-
asArray(item.anchors),
|
|
177
|
-
asArray(item.dropdowns),
|
|
178
|
-
asArray(item.products),
|
|
179
|
-
asArray(item.versions),
|
|
180
|
-
asArray(item.languages),
|
|
181
|
-
asArray(item.items),
|
|
182
|
-
asArray(item.children),
|
|
183
|
-
];
|
|
184
|
-
|
|
185
|
-
const childItemsFor = (item: JsonObject): unknown[] => {
|
|
186
|
-
for (const items of childNavigationArrays(item)) {
|
|
187
|
-
if (items.length > 0) {
|
|
188
|
-
return items;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return [];
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const mintlifyNavigationItems = (navigation: JsonObject): unknown[] => {
|
|
195
|
-
const pages = asArray(navigation.pages);
|
|
196
|
-
if (pages.length > 0) {
|
|
197
|
-
return pages;
|
|
198
|
-
}
|
|
199
|
-
const groups = asArray(navigation.groups);
|
|
200
|
-
if (groups.length > 0) {
|
|
201
|
-
return groups;
|
|
202
|
-
}
|
|
203
|
-
return [
|
|
204
|
-
...asArray(navigation.tabs),
|
|
205
|
-
...asArray(navigation.anchors),
|
|
206
|
-
...asArray(navigation.dropdowns),
|
|
207
|
-
...asArray(navigation.products),
|
|
208
|
-
...asArray(navigation.versions),
|
|
209
|
-
...asArray(navigation.languages),
|
|
210
|
-
];
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const normalizeDirectory = (value: string): string =>
|
|
214
|
-
normalizePageRef(value).replaceAll(/^\/+|\/+$/gu, "");
|
|
215
|
-
|
|
216
|
-
const tabPathFromRef = (ref: string): string => {
|
|
217
|
-
const normalized = normalizeDirectory(ref).replaceAll(/\/index$/gu, "");
|
|
218
|
-
return normalized.length === 0 || normalized === "index"
|
|
219
|
-
? "/"
|
|
220
|
-
: `/${normalized}`;
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const navItemPath = (item: unknown): string | undefined => {
|
|
224
|
-
if (typeof item === "string") {
|
|
225
|
-
return API_ENDPOINT_REF.test(item) ? undefined : tabPathFromRef(item);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const object = asObject(item);
|
|
229
|
-
if (!object) {
|
|
230
|
-
return undefined;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const href = asString(object.href);
|
|
234
|
-
if (href) {
|
|
235
|
-
return href;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const root = asString(object.root);
|
|
239
|
-
if (root) {
|
|
240
|
-
return tabPathFromRef(root);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
for (const child of childItemsFor(object)) {
|
|
244
|
-
const path = navItemPath(child);
|
|
245
|
-
if (path) {
|
|
246
|
-
return path;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return undefined;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
const optionalBoolean = (value: unknown): boolean | undefined => {
|
|
253
|
-
if (value === true) {
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
if (value === false) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
return undefined;
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
const mintlifyBackgroundColor = (
|
|
263
|
-
value: unknown
|
|
264
|
-
): { dark?: string; light?: string } => {
|
|
265
|
-
const object = asObject(value);
|
|
266
|
-
if (!object) {
|
|
267
|
-
return {};
|
|
268
|
-
}
|
|
269
|
-
const color = asObject(object.color);
|
|
270
|
-
return {
|
|
271
|
-
dark: asString(color?.dark),
|
|
272
|
-
light: asString(color?.light),
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const mintlifyBackgroundImage = (
|
|
277
|
-
value: unknown
|
|
278
|
-
): { dark?: string; light?: string } => {
|
|
279
|
-
const object = asObject(value);
|
|
280
|
-
if (!object) {
|
|
281
|
-
return {};
|
|
282
|
-
}
|
|
283
|
-
if (typeof object.image === "string") {
|
|
284
|
-
return { light: object.image };
|
|
285
|
-
}
|
|
286
|
-
const image = asObject(object.image);
|
|
287
|
-
return {
|
|
288
|
-
dark: asString(image?.dark),
|
|
289
|
-
light: asString(image?.light),
|
|
290
|
-
};
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
const mintlifyBackgroundDecoration = (
|
|
294
|
-
value: unknown
|
|
295
|
-
): "gradient" | "grid" | "windows" | undefined => {
|
|
296
|
-
const object = asObject(value);
|
|
297
|
-
const decoration = asString(object?.decoration);
|
|
298
|
-
if (
|
|
299
|
-
decoration === "gradient" ||
|
|
300
|
-
decoration === "grid" ||
|
|
301
|
-
decoration === "windows"
|
|
302
|
-
) {
|
|
303
|
-
return decoration;
|
|
304
|
-
}
|
|
305
|
-
return undefined;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const collapsedFromExpanded = (value: unknown): boolean | undefined => {
|
|
309
|
-
if (value === false) {
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
if (value === true) {
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
return undefined;
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
interface SidebarOptions {
|
|
319
|
-
directory?: DirectoryMode;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
type SidebarItemConverter = (
|
|
323
|
-
items: unknown[],
|
|
324
|
-
options: SidebarOptions
|
|
325
|
-
) => Promise<SidebarItemConfig[]>;
|
|
326
|
-
|
|
327
|
-
const toSidebarObjectItems = async (
|
|
328
|
-
object: JsonObject,
|
|
329
|
-
options: SidebarOptions,
|
|
330
|
-
convertItems: SidebarItemConverter
|
|
331
|
-
): Promise<SidebarItemConfig[]> => {
|
|
332
|
-
const label = labelForNavItem(object);
|
|
333
|
-
const href = asString(object.href);
|
|
334
|
-
const icon = asString(object.icon);
|
|
335
|
-
const groupLabel = asString(object.group);
|
|
336
|
-
const tag = groupLabel ? asString(object.tag) : undefined;
|
|
337
|
-
const root = asString(object.root);
|
|
338
|
-
const rootRef = root && groupLabel ? normalizePageRef(root) : undefined;
|
|
339
|
-
const directory = asDirectoryMode(object.directory) ?? options.directory;
|
|
340
|
-
const rootDirectory = rootRef && directory !== "none" ? directory : undefined;
|
|
341
|
-
const children = childItemsFor(object);
|
|
342
|
-
const nested = await convertItems(children, { ...options, directory });
|
|
343
|
-
|
|
344
|
-
if (nested.length > 0) {
|
|
345
|
-
return label
|
|
346
|
-
? [
|
|
347
|
-
withoutUndefined({
|
|
348
|
-
badge: tag,
|
|
349
|
-
collapsed: collapsedFromExpanded(object.expanded),
|
|
350
|
-
directory: rootDirectory,
|
|
351
|
-
icon,
|
|
352
|
-
items: nested,
|
|
353
|
-
label,
|
|
354
|
-
root: rootRef,
|
|
355
|
-
}),
|
|
356
|
-
]
|
|
357
|
-
: nested;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (label && rootRef) {
|
|
361
|
-
return [
|
|
362
|
-
withoutUndefined({
|
|
363
|
-
badge: tag,
|
|
364
|
-
directory: rootDirectory,
|
|
365
|
-
icon,
|
|
366
|
-
label,
|
|
367
|
-
root: rootRef,
|
|
368
|
-
}),
|
|
369
|
-
];
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (label && href) {
|
|
373
|
-
return [withoutUndefined({ badge: tag, href, icon, label })];
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return [];
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
const toSidebarItems = async (
|
|
380
|
-
items: unknown[],
|
|
381
|
-
options: SidebarOptions
|
|
382
|
-
): Promise<SidebarItemConfig[]> => {
|
|
383
|
-
const itemLists = await Promise.all(
|
|
384
|
-
items.map((item): SidebarItemConfig[] | Promise<SidebarItemConfig[]> => {
|
|
385
|
-
if (typeof item === "string") {
|
|
386
|
-
return [normalizePageRef(item)];
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const object = asObject(item);
|
|
390
|
-
if (!object) {
|
|
391
|
-
return [];
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return toSidebarObjectItems(object, options, toSidebarItems);
|
|
395
|
-
})
|
|
396
|
-
);
|
|
397
|
-
return itemLists.flat();
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
const selectorItemsFor = (items: unknown[]): NavigationSelectorItem[] =>
|
|
401
|
-
items.flatMap((item) => {
|
|
402
|
-
const object = asObject(item);
|
|
403
|
-
if (!object) {
|
|
404
|
-
return [];
|
|
405
|
-
}
|
|
406
|
-
const label = labelForNavItem(object);
|
|
407
|
-
const path = navItemPath(object);
|
|
408
|
-
if (!label || !path) {
|
|
409
|
-
return [];
|
|
410
|
-
}
|
|
411
|
-
return [
|
|
412
|
-
withoutUndefined({
|
|
413
|
-
description: asString(object.description),
|
|
414
|
-
icon: asString(object.icon),
|
|
415
|
-
label,
|
|
416
|
-
path,
|
|
417
|
-
tag: asString(object.tag),
|
|
418
|
-
}),
|
|
419
|
-
];
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
const isExternalRoute = (path: string): boolean =>
|
|
423
|
-
path.startsWith("http://") ||
|
|
424
|
-
path.startsWith("https://") ||
|
|
425
|
-
path.startsWith("mailto:") ||
|
|
426
|
-
path.startsWith("tel:");
|
|
427
|
-
|
|
428
|
-
const mintlifyTabs = (
|
|
429
|
-
spec: JsonObject
|
|
430
|
-
): NonNullable<BlumeConfig["navigation"]>["tabs"] => {
|
|
431
|
-
const navigation = asObject(spec.navigation) ?? {};
|
|
432
|
-
const global = asObject(navigation.global) ?? {};
|
|
433
|
-
|
|
434
|
-
const items = [
|
|
435
|
-
...asArray(navigation.tabs),
|
|
436
|
-
...asArray(global.anchors),
|
|
437
|
-
].filter(Boolean);
|
|
438
|
-
const seen = new Set<string>();
|
|
439
|
-
|
|
440
|
-
return items.flatMap((item) => {
|
|
441
|
-
const object = asObject(item);
|
|
442
|
-
if (!object) {
|
|
443
|
-
return [];
|
|
444
|
-
}
|
|
445
|
-
const label = labelForNavItem(object);
|
|
446
|
-
const path = navItemPath(object);
|
|
447
|
-
if (!label || !path) {
|
|
448
|
-
return [];
|
|
449
|
-
}
|
|
450
|
-
const key = `${label}\u0000${path}`;
|
|
451
|
-
if (seen.has(key)) {
|
|
452
|
-
return [];
|
|
453
|
-
}
|
|
454
|
-
seen.add(key);
|
|
455
|
-
const menu = asArray(object.menu);
|
|
456
|
-
const menuItems = selectorItemsFor(
|
|
457
|
-
menu.length > 0 ? menu : childItemsFor(object)
|
|
458
|
-
);
|
|
459
|
-
return [
|
|
460
|
-
withoutUndefined({
|
|
461
|
-
icon: asString(object.icon),
|
|
462
|
-
items: menuItems.length > 0 ? menuItems : undefined,
|
|
463
|
-
label,
|
|
464
|
-
path,
|
|
465
|
-
}),
|
|
466
|
-
];
|
|
467
|
-
});
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const mintlifySelectors = (spec: JsonObject): NavigationSelectors => {
|
|
471
|
-
const navigation = asObject(spec.navigation) ?? {};
|
|
472
|
-
return [
|
|
473
|
-
{
|
|
474
|
-
items: selectorItemsFor(asArray(navigation.dropdowns)),
|
|
475
|
-
kind: "dropdown" as const,
|
|
476
|
-
label: "Dropdowns",
|
|
477
|
-
},
|
|
478
|
-
{
|
|
479
|
-
items: selectorItemsFor(asArray(navigation.products)),
|
|
480
|
-
kind: "product" as const,
|
|
481
|
-
label: "Products",
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
items: selectorItemsFor(asArray(navigation.versions)),
|
|
485
|
-
kind: "version" as const,
|
|
486
|
-
label: "Versions",
|
|
487
|
-
},
|
|
488
|
-
{
|
|
489
|
-
items: selectorItemsFor(asArray(navigation.languages)),
|
|
490
|
-
kind: "language" as const,
|
|
491
|
-
label: "Languages",
|
|
492
|
-
},
|
|
493
|
-
].filter((selector) => selector.items.length > 0);
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
const mintignorePatterns = async (root: string): Promise<string[]> => {
|
|
497
|
-
try {
|
|
498
|
-
const raw = await readFile(resolve(root, ".mintignore"), "utf-8");
|
|
499
|
-
return raw
|
|
500
|
-
.split("\n")
|
|
501
|
-
.map((line) => line.trim())
|
|
502
|
-
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
503
|
-
.filter((line) => !line.startsWith("!"))
|
|
504
|
-
.map((line) => (line.endsWith("/") ? `${line}**` : line));
|
|
505
|
-
} catch {
|
|
506
|
-
return [];
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
// path-to-regexp param (:slug, :slug*, :id?) → Astro dynamic segment.
|
|
511
|
-
// *,+ (repeatable) → spread [...name]; bare/? → single [name]. Name must start
|
|
512
|
-
// with a letter/underscore so URL ports (:8080) and protocols (https:) are left alone.
|
|
513
|
-
const REDIRECT_PARAM = /:(?<name>[A-Za-z_]\w*)(?<modifier>[*+?])?/gu;
|
|
514
|
-
|
|
515
|
-
const toAstroRedirectPath = (path: string): string =>
|
|
516
|
-
path.replaceAll(REDIRECT_PARAM, (_match, name: string, modifier?: string) =>
|
|
517
|
-
modifier === "*" || modifier === "+" ? `[...${name}]` : `[${name}]`
|
|
518
|
-
);
|
|
519
|
-
|
|
520
|
-
// A converted path is dynamic once it holds an Astro segment (`[id]`/`[...slug]`
|
|
521
|
-
// from a `:param`). Blume redirects are static path-to-path: a dynamic segment
|
|
522
|
-
// has no matching route, so Astro aborts the build ("destination does not match
|
|
523
|
-
// any existing route"), and the platform redirect files emit the segment
|
|
524
|
-
// verbatim, which no host understands. Such redirects are dropped, not emitted.
|
|
525
|
-
const isDynamicRedirectPath = (path: string): boolean => path.includes("[");
|
|
526
|
-
|
|
527
|
-
export interface MintlifyRedirectPartition {
|
|
528
|
-
/** Static redirects Blume can honor, translated to Blume's `from`/`to` shape. */
|
|
529
|
-
kept: NonNullable<BlumeConfig["redirects"]>;
|
|
530
|
-
/** Source paths of dynamic redirects dropped because Blume can't model them. */
|
|
531
|
-
dropped: string[];
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Split a spec's redirects into the static ones Blume emits and the dynamic
|
|
536
|
-
* (wildcard/param) ones it drops. Keeping a dynamic redirect crashes the Astro
|
|
537
|
-
* build, so the migrator surfaces the dropped sources as a warning instead.
|
|
538
|
-
*/
|
|
539
|
-
export const partitionMintlifyRedirects = (
|
|
540
|
-
spec: JsonObject
|
|
541
|
-
): MintlifyRedirectPartition => {
|
|
542
|
-
const kept: NonNullable<BlumeConfig["redirects"]> = [];
|
|
543
|
-
const dropped: string[] = [];
|
|
544
|
-
for (const redirect of asArray(spec.redirects)) {
|
|
545
|
-
const object = asObject(redirect);
|
|
546
|
-
if (!object) {
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
const source = asString(object.source) ?? asString(object.from);
|
|
550
|
-
const destination =
|
|
551
|
-
asString(object.destination) ??
|
|
552
|
-
asString(object.to) ??
|
|
553
|
-
asString(object.redirect);
|
|
554
|
-
if (!source || !destination) {
|
|
555
|
-
continue;
|
|
556
|
-
}
|
|
557
|
-
const from = toAstroRedirectPath(source);
|
|
558
|
-
const to = toAstroRedirectPath(destination);
|
|
559
|
-
if (isDynamicRedirectPath(from) || isDynamicRedirectPath(to)) {
|
|
560
|
-
dropped.push(source);
|
|
561
|
-
} else {
|
|
562
|
-
kept.push({ from, to });
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
return { dropped, kept };
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
interface OpenApiSourceDraft {
|
|
569
|
-
label?: string;
|
|
570
|
-
route?: string;
|
|
571
|
-
spec: string;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// A Mintlify `{ source, directory }` object's `directory` is the URL path its
|
|
575
|
-
// generated pages mount under; map it to a Blume per-source `route`.
|
|
576
|
-
const openapiRouteFromDirectory = (value: unknown): string | undefined => {
|
|
577
|
-
const directory = normalizeDirectory(asString(value) ?? "");
|
|
578
|
-
return directory.length > 0 ? `/${directory}` : undefined;
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
// Resolve a Mintlify `openapi` value (a spec string, an array, or a
|
|
582
|
-
// `{ source, directory }` object) into spec sources. Endpoint refs (`GET /path`)
|
|
583
|
-
// are skipped — Blume's native renderer generates those pages from the spec.
|
|
584
|
-
const openapiSourcesFromValue = (
|
|
585
|
-
value: unknown,
|
|
586
|
-
context: { label?: string; route?: string }
|
|
587
|
-
): OpenApiSourceDraft[] => {
|
|
588
|
-
if (typeof value === "string") {
|
|
589
|
-
if (value.length === 0 || API_ENDPOINT_REF.test(value)) {
|
|
590
|
-
return [];
|
|
591
|
-
}
|
|
592
|
-
return [
|
|
593
|
-
withoutUndefined({
|
|
594
|
-
label: context.label,
|
|
595
|
-
route: context.route,
|
|
596
|
-
spec: value,
|
|
597
|
-
}),
|
|
598
|
-
];
|
|
599
|
-
}
|
|
600
|
-
if (Array.isArray(value)) {
|
|
601
|
-
return value.flatMap((item) => openapiSourcesFromValue(item, context));
|
|
602
|
-
}
|
|
603
|
-
const object = asObject(value);
|
|
604
|
-
if (!object) {
|
|
605
|
-
return [];
|
|
606
|
-
}
|
|
607
|
-
return openapiSourcesFromValue(object.source ?? object.openapi, {
|
|
608
|
-
label: context.label,
|
|
609
|
-
route: openapiRouteFromDirectory(object.directory) ?? context.route,
|
|
610
|
-
});
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
// Walk the navigation tree collecting every `openapi` source — a group or tab
|
|
614
|
-
// can declare one alongside its pages — then fold in the top-level specs
|
|
615
|
-
// (legacy `mint.json` `openapi`, newer `api.openapi`) and dedupe by spec so a
|
|
616
|
-
// Mintlify API reference maps to Blume's native renderer instead of dropping.
|
|
617
|
-
const mintlifyOpenapi = (spec: JsonObject): BlumeConfig["openapi"] => {
|
|
618
|
-
const drafts: OpenApiSourceDraft[] = [];
|
|
619
|
-
|
|
620
|
-
const visit = (node: unknown): void => {
|
|
621
|
-
if (Array.isArray(node)) {
|
|
622
|
-
for (const item of node) {
|
|
623
|
-
visit(item);
|
|
624
|
-
}
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
const object = asObject(node);
|
|
628
|
-
if (!object) {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (hasOwn(object, "openapi")) {
|
|
632
|
-
drafts.push(
|
|
633
|
-
...openapiSourcesFromValue(object.openapi, {
|
|
634
|
-
label: labelForNavItem(object),
|
|
635
|
-
})
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
for (const children of childNavigationArrays(object)) {
|
|
639
|
-
visit(children);
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
visit(spec.navigation);
|
|
644
|
-
drafts.push(...openapiSourcesFromValue(spec.openapi, {}));
|
|
645
|
-
drafts.push(...openapiSourcesFromValue(asObject(spec.api)?.openapi, {}));
|
|
646
|
-
|
|
647
|
-
const seen = new Set<string>();
|
|
648
|
-
const sources = drafts.flatMap((draft) => {
|
|
649
|
-
if (seen.has(draft.spec)) {
|
|
650
|
-
return [];
|
|
651
|
-
}
|
|
652
|
-
seen.add(draft.spec);
|
|
653
|
-
return [draft];
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
return sources.length > 0 ? { enabled: true, sources } : undefined;
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
const mintlifyLogo = (value: unknown): BlumeConfig["logo"] => {
|
|
660
|
-
if (typeof value === "string") {
|
|
661
|
-
return value;
|
|
662
|
-
}
|
|
663
|
-
const object = asObject(value);
|
|
664
|
-
if (!object) {
|
|
665
|
-
return undefined;
|
|
666
|
-
}
|
|
667
|
-
const dark = asString(object.dark);
|
|
668
|
-
const light = asString(object.light);
|
|
669
|
-
const href = asString(object.href);
|
|
670
|
-
if (!dark && !light && !href) {
|
|
671
|
-
return undefined;
|
|
672
|
-
}
|
|
673
|
-
return withoutUndefined({ dark, href, light });
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
const mintlifyFavicon = (value: unknown): BlumeConfig["favicon"] => {
|
|
677
|
-
if (typeof value === "string") {
|
|
678
|
-
return value;
|
|
679
|
-
}
|
|
680
|
-
const object = asObject(value);
|
|
681
|
-
if (!object) {
|
|
682
|
-
return undefined;
|
|
683
|
-
}
|
|
684
|
-
const dark = asString(object.dark);
|
|
685
|
-
const light = asString(object.light);
|
|
686
|
-
if (!dark && !light) {
|
|
687
|
-
return undefined;
|
|
688
|
-
}
|
|
689
|
-
return withoutUndefined({ dark, light });
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
// Mintlify defaults to Font Awesome, so a migrated site's bare `icon` names are
|
|
693
|
-
// FA names unless it opted into Lucide/Tabler. Set the default library to match.
|
|
694
|
-
const mintlifyIcons = (value: unknown): BlumeConfig["icons"] => {
|
|
695
|
-
const library = asString(asObject(value)?.library);
|
|
696
|
-
return {
|
|
697
|
-
library:
|
|
698
|
-
library === "lucide" || library === "tabler" ? library : "fontawesome",
|
|
699
|
-
};
|
|
700
|
-
};
|
|
701
|
-
|
|
702
|
-
const mintlifyBanner = (value: unknown): BlumeConfig["banner"] => {
|
|
703
|
-
const object = asObject(value);
|
|
704
|
-
const content = object ? asString(object.content) : undefined;
|
|
705
|
-
if (!object || !content) {
|
|
706
|
-
return undefined;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const color = asObject(object.color);
|
|
710
|
-
const dark = color ? asString(color.dark) : undefined;
|
|
711
|
-
const light = color ? asString(color.light) : undefined;
|
|
712
|
-
const rawType = asString(object.type);
|
|
713
|
-
let type: "info" | "warning" | "critical" | undefined;
|
|
714
|
-
if (rawType === "warning" || rawType === "critical" || rawType === "info") {
|
|
715
|
-
type = rawType;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return withoutUndefined({
|
|
719
|
-
color:
|
|
720
|
-
dark || light
|
|
721
|
-
? withoutUndefined({
|
|
722
|
-
dark,
|
|
723
|
-
light,
|
|
724
|
-
})
|
|
725
|
-
: undefined,
|
|
726
|
-
content,
|
|
727
|
-
dismissible: object.dismissible === true ? true : undefined,
|
|
728
|
-
type,
|
|
729
|
-
});
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
const mintlifyChromeVariants = (spec: JsonObject): NavigationChromeVariants => {
|
|
733
|
-
const navigation = asObject(spec.navigation) ?? {};
|
|
734
|
-
|
|
735
|
-
return asArray(navigation.languages).flatMap((item) => {
|
|
736
|
-
const object = asObject(item);
|
|
737
|
-
const path = navItemPath(item);
|
|
738
|
-
if (!object || !path || isExternalRoute(path)) {
|
|
739
|
-
return [];
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
const banner = hasOwn(object, "banner")
|
|
743
|
-
? mintlifyBanner(object.banner)
|
|
744
|
-
: undefined;
|
|
745
|
-
if (!banner) {
|
|
746
|
-
return [];
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
return [
|
|
750
|
-
withoutUndefined({
|
|
751
|
-
banner,
|
|
752
|
-
path,
|
|
753
|
-
}),
|
|
754
|
-
];
|
|
755
|
-
});
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
const mintlifyVariables = (
|
|
759
|
-
value: unknown
|
|
760
|
-
): NonNullable<BlumeConfig["variables"]> => {
|
|
761
|
-
const object = asObject(value);
|
|
762
|
-
if (!object) {
|
|
763
|
-
return {};
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
return Object.fromEntries(
|
|
767
|
-
Object.entries(object).flatMap(([key, item]) =>
|
|
768
|
-
VARIABLE_NAME.test(key) && typeof item === "string" ? [[key, item]] : []
|
|
769
|
-
)
|
|
770
|
-
);
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
const mintlifyCodeTheme = (
|
|
774
|
-
value: unknown
|
|
775
|
-
):
|
|
776
|
-
| NonNullable<NonNullable<BlumeConfig["markdown"]>["codeBlocks"]>["theme"]
|
|
777
|
-
| undefined => {
|
|
778
|
-
if (value === "system" || value === undefined) {
|
|
779
|
-
return undefined;
|
|
780
|
-
}
|
|
781
|
-
if (value === "dark") {
|
|
782
|
-
return { dark: "github-dark", light: "github-dark" };
|
|
783
|
-
}
|
|
784
|
-
const theme = asString(value);
|
|
785
|
-
if (theme) {
|
|
786
|
-
return { dark: theme, light: theme };
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const object = asObject(value);
|
|
790
|
-
if (!object) {
|
|
791
|
-
return undefined;
|
|
792
|
-
}
|
|
793
|
-
const light = asString(object.light);
|
|
794
|
-
const dark = asString(object.dark);
|
|
795
|
-
if (!light && !dark) {
|
|
796
|
-
return undefined;
|
|
797
|
-
}
|
|
798
|
-
return {
|
|
799
|
-
dark: dark ?? light ?? "github-dark",
|
|
800
|
-
light: light ?? dark ?? "github-light",
|
|
801
|
-
};
|
|
802
|
-
};
|
|
803
|
-
|
|
804
|
-
const mintlifyCodeBlocks = (
|
|
805
|
-
value: unknown
|
|
806
|
-
): NonNullable<BlumeConfig["markdown"]>["codeBlocks"] | undefined => {
|
|
807
|
-
const object = asObject(value);
|
|
808
|
-
const theme = mintlifyCodeTheme(object ? object.theme : value);
|
|
809
|
-
return theme ? { theme } : undefined;
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
const mintlifyMarkdown = (
|
|
813
|
-
value: unknown,
|
|
814
|
-
styling: unknown
|
|
815
|
-
): NonNullable<BlumeConfig["markdown"]> => {
|
|
816
|
-
const object = asObject(value);
|
|
817
|
-
const stylingObject = asObject(styling);
|
|
818
|
-
return withoutUndefined({
|
|
819
|
-
codeBlocks: mintlifyCodeBlocks(stylingObject?.codeblocks),
|
|
820
|
-
math: optionalBoolean(stylingObject?.latex),
|
|
821
|
-
schema: object?.schema === false ? false : undefined,
|
|
822
|
-
});
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
type ThemeFonts = NonNullable<NonNullable<BlumeConfig["theme"]>["fonts"]>;
|
|
826
|
-
|
|
827
|
-
// Blume's curated Google-font family names, indexed by lowercased family so a
|
|
828
|
-
// Mintlify `fonts.family: "Space Grotesk"` resolves to the `space-grotesk` slug.
|
|
829
|
-
const FAMILY_TO_SLUG: Record<string, string> = Object.fromEntries(
|
|
830
|
-
Object.entries(GOOGLE_FONTS).map(([slug, def]) => [
|
|
831
|
-
def.family.toLowerCase(),
|
|
832
|
-
slug,
|
|
833
|
-
])
|
|
834
|
-
);
|
|
835
|
-
|
|
836
|
-
const fontSlugForFamily = (value: unknown): string | undefined => {
|
|
837
|
-
const family = asString(value);
|
|
838
|
-
return family ? FAMILY_TO_SLUG[family.toLowerCase()] : undefined;
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
/**
|
|
842
|
-
* Map Mintlify `fonts` onto Blume `theme.fonts`. Mintlify sets one family for
|
|
843
|
-
* everything (`fonts.family`) or splits heading/body (`fonts.heading.family`,
|
|
844
|
-
* `fonts.body.family`); each maps to a curated Blume slug when one matches.
|
|
845
|
-
* Families outside Blume's set are left unset (defaults) and warned about.
|
|
846
|
-
*/
|
|
847
|
-
const mintlifyFonts = (value: unknown): ThemeFonts | undefined => {
|
|
848
|
-
const object = asObject(value);
|
|
849
|
-
if (!object) {
|
|
850
|
-
return undefined;
|
|
851
|
-
}
|
|
852
|
-
const heading = asObject(object.heading);
|
|
853
|
-
const body = asObject(object.body);
|
|
854
|
-
const fonts = withoutUndefined({
|
|
855
|
-
body: fontSlugForFamily(body?.family) ?? fontSlugForFamily(object.family),
|
|
856
|
-
display:
|
|
857
|
-
fontSlugForFamily(heading?.family) ?? fontSlugForFamily(object.family),
|
|
858
|
-
});
|
|
859
|
-
return Object.keys(fonts).length > 0 ? (fonts as ThemeFonts) : undefined;
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
const mintlifySeo = (value: unknown): NonNullable<BlumeConfig["seo"]> => {
|
|
863
|
-
const object = asObject(value);
|
|
864
|
-
const metatags = asObject(object?.metatags);
|
|
865
|
-
return {
|
|
866
|
-
metatags: metatags
|
|
867
|
-
? Object.fromEntries(
|
|
868
|
-
Object.entries(metatags).flatMap(([key, item]) => {
|
|
869
|
-
const content = asString(item);
|
|
870
|
-
return content ? [[key, content]] : [];
|
|
871
|
-
})
|
|
872
|
-
)
|
|
873
|
-
: {},
|
|
874
|
-
};
|
|
875
|
-
};
|
|
876
|
-
|
|
877
|
-
export const loadMintlifyConfig = async (
|
|
878
|
-
root: string,
|
|
879
|
-
file: string
|
|
880
|
-
): Promise<BlumeConfig> => {
|
|
881
|
-
const projectRoot = resolve(root);
|
|
882
|
-
const configFile = resolve(file);
|
|
883
|
-
const spec = asObject(
|
|
884
|
-
await resolveRefs(await readJsonFile(configFile), {
|
|
885
|
-
file: configFile,
|
|
886
|
-
root: projectRoot,
|
|
887
|
-
seen: new Set([configFile]),
|
|
888
|
-
})
|
|
889
|
-
);
|
|
890
|
-
if (!spec) {
|
|
891
|
-
throw new BlumeError({
|
|
892
|
-
code: "BLUME_MINTLIFY_CONFIG_INVALID",
|
|
893
|
-
file: configFile,
|
|
894
|
-
message: "Mintlify config must be a JSON object.",
|
|
895
|
-
severity: "error",
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
const navigation = asObject(spec.navigation) ?? {};
|
|
900
|
-
const colors = asObject(spec.colors) ?? {};
|
|
901
|
-
const appearance = asObject(spec.appearance) ?? {};
|
|
902
|
-
const backgroundColor = mintlifyBackgroundColor(spec.background);
|
|
903
|
-
const backgroundImage = mintlifyBackgroundImage(spec.background);
|
|
904
|
-
const seo = asObject(spec.seo) ?? {};
|
|
905
|
-
const search = asObject(spec.search) ?? {};
|
|
906
|
-
const styling = asObject(spec.styling) ?? {};
|
|
907
|
-
|
|
908
|
-
return {
|
|
909
|
-
banner: mintlifyBanner(spec.banner),
|
|
910
|
-
content: {
|
|
911
|
-
exclude: [
|
|
912
|
-
...MINTLIFY_DEFAULT_IGNORES,
|
|
913
|
-
...(await mintignorePatterns(projectRoot)),
|
|
914
|
-
],
|
|
915
|
-
root: ".",
|
|
916
|
-
},
|
|
917
|
-
description: asString(spec.description),
|
|
918
|
-
favicon: mintlifyFavicon(spec.favicon),
|
|
919
|
-
icons: mintlifyIcons(spec.icons),
|
|
920
|
-
logo: mintlifyLogo(spec.logo),
|
|
921
|
-
markdown: mintlifyMarkdown(spec.markdown, styling),
|
|
922
|
-
navigation: {
|
|
923
|
-
chromeVariants: mintlifyChromeVariants(spec),
|
|
924
|
-
selectors: mintlifySelectors(spec),
|
|
925
|
-
sidebar: await toSidebarItems(mintlifyNavigationItems(navigation), {}),
|
|
926
|
-
tabs: mintlifyTabs(spec),
|
|
927
|
-
},
|
|
928
|
-
openapi: mintlifyOpenapi(spec),
|
|
929
|
-
redirects: partitionMintlifyRedirects(spec).kept,
|
|
930
|
-
search: {
|
|
931
|
-
indexing: {
|
|
932
|
-
includeHiddenPages: seo.indexing === "all",
|
|
933
|
-
},
|
|
934
|
-
prompt: asString(search.prompt),
|
|
935
|
-
},
|
|
936
|
-
seo: mintlifySeo(seo),
|
|
937
|
-
theme: {
|
|
938
|
-
accent: asString(colors.primary) ?? "blue",
|
|
939
|
-
accentDark: asString(colors.light),
|
|
940
|
-
action: asString(colors.dark),
|
|
941
|
-
background: backgroundColor.light,
|
|
942
|
-
backgroundDark: backgroundColor.dark,
|
|
943
|
-
backgroundDecoration: mintlifyBackgroundDecoration(spec.background),
|
|
944
|
-
backgroundImage: backgroundImage.light,
|
|
945
|
-
backgroundImageDark: backgroundImage.dark,
|
|
946
|
-
fonts: mintlifyFonts(spec.fonts ?? spec.font),
|
|
947
|
-
mode:
|
|
948
|
-
appearance.default === "light" ||
|
|
949
|
-
appearance.default === "dark" ||
|
|
950
|
-
appearance.default === "system"
|
|
951
|
-
? appearance.default
|
|
952
|
-
: "system",
|
|
953
|
-
strict: appearance.strict === true,
|
|
954
|
-
},
|
|
955
|
-
title: asString(spec.name) ?? asString(spec.title) ?? "Documentation",
|
|
956
|
-
variables: mintlifyVariables(spec.variables),
|
|
957
|
-
};
|
|
958
|
-
};
|