blume 0.5.2 → 0.5.3
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 +2755 -2564
- package/dist/cli/index.js.map +33 -31
- package/dist/types/core/package-json.d.ts +12 -0
- package/dist/types/migrate/shared.d.ts +153 -0
- package/docs/advanced/migrate.mdx +1 -0
- package/docs/configuration/ai.mdx +1 -1
- package/docs/configuration/theming.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/sources.mdx +1 -1
- package/package.json +1 -1
- 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/static-assets.ts +10 -3
- package/src/astro/templates.ts +53 -21
- package/src/cli/coalesce.ts +43 -0
- package/src/cli/commands/dev.ts +31 -17
- package/src/cli/dev-lock.ts +4 -2
- package/src/components/content/ColorItem.astro +6 -3
- package/src/components/content/Prompt.astro +7 -3
- package/src/components/content/Tabs.astro +13 -2
- 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/PageActions.astro +17 -11
- package/src/components/layout/Search.astro +4 -1
- package/src/components/layout/search/types.ts +16 -5
- 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/config.ts +15 -6
- package/src/core/graph.ts +6 -1
- package/src/core/navigation.ts +5 -1
- package/src/core/sources/filesystem.ts +19 -1
- package/src/core/sources/mdx-remote.ts +20 -4
- package/src/core/sources/mintlify.ts +14 -28
- package/src/core/sources/normalize.ts +28 -6
- package/src/core/sources/watch.ts +44 -0
- package/src/markdown/code-title.ts +6 -3
- package/src/markdown/package-install.ts +3 -1
- package/src/migrate/fumadocs/content.ts +3 -5
- package/src/migrate/fumadocs/index.ts +24 -9
- package/src/migrate/mintlify/config.ts +2 -6
- package/src/migrate/mintlify/index.ts +119 -32
- package/src/migrate/mintlify/snippets.ts +17 -8
- package/src/migrate/nextra/index.ts +16 -1
- package/src/migrate/shared.ts +77 -4
- package/src/migrate/starlight/content.ts +3 -6
- package/src/og/card.ts +16 -4
- package/src/openapi/render-mdx.ts +10 -1
- package/src/search/sync/orama-cloud.ts +2 -0
- package/src/search/sync/typesense.ts +4 -0
- package/src/theme/icons.ts +13 -4
- package/src/theme/palette.ts +38 -17
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renameTag, rewriteCallouts } from "../shared.ts";
|
|
1
|
+
import { renameTag, rewriteCallouts, stripImports } from "../shared.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Source-to-source rewrites that turn Starlight-only MDX into idiomatic Blume
|
|
@@ -14,11 +14,8 @@ const STARLIGHT_IMPORT =
|
|
|
14
14
|
* Blume injects its components globally, so these imports would fail to resolve
|
|
15
15
|
* once the Starlight packages are gone.
|
|
16
16
|
*/
|
|
17
|
-
export const stripStarlightImports = (source: string): string =>
|
|
18
|
-
|
|
19
|
-
// Collapse the blank gap a removed import block leaves behind.
|
|
20
|
-
return stripped === source ? source : stripped.replaceAll(/\n{3,}/gu, "\n\n");
|
|
21
|
-
};
|
|
17
|
+
export const stripStarlightImports = (source: string): string =>
|
|
18
|
+
stripImports(source, STARLIGHT_IMPORT);
|
|
22
19
|
|
|
23
20
|
/** Starlight `<Aside type="X">` values mapped to Blume directive names. */
|
|
24
21
|
const ASIDE_TYPE_DIRECTIVES: Record<string, string> = {
|
package/src/og/card.ts
CHANGED
|
@@ -19,10 +19,19 @@ const ACCENT_HEX: Record<string, string> = {
|
|
|
19
19
|
teal: "#14b8a6",
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const HEX_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/iu;
|
|
23
|
+
|
|
22
24
|
// OG rendering uses hex (Takumi's color parser does not accept oklch); named
|
|
23
|
-
// presets map to hex,
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
// presets map to hex, well-formed hex passes through, anything else falls
|
|
26
|
+
// back — a malformed hex (`#12345` typo) would throw inside Takumi and fail
|
|
27
|
+
// the build at OG prerender with an opaque native error. `hasOwn` keeps a
|
|
28
|
+
// preset name like "constructor" from resolving up the prototype chain.
|
|
29
|
+
const resolveAccent = (accent: string): string => {
|
|
30
|
+
if (Object.hasOwn(ACCENT_HEX, accent)) {
|
|
31
|
+
return ACCENT_HEX[accent] as string;
|
|
32
|
+
}
|
|
33
|
+
return HEX_COLOR.test(accent) ? accent : "#3b82f6";
|
|
34
|
+
};
|
|
26
35
|
|
|
27
36
|
export interface OgCardOptions {
|
|
28
37
|
/** Large headline — the page title. */
|
|
@@ -75,7 +84,10 @@ export const truncate = (value: string, max: number): string => {
|
|
|
75
84
|
// stays within the lockup.
|
|
76
85
|
const MARK_HEIGHT = 32;
|
|
77
86
|
const MARK_MAX_WIDTH = 100;
|
|
78
|
-
|
|
87
|
+
// Accept either quote style and a non-zero min-x/min-y; only width/height
|
|
88
|
+
// matter for the aspect ratio. A miss falls back to a square mark.
|
|
89
|
+
const VIEW_BOX =
|
|
90
|
+
/viewBox=(?<q>["'])[\d.-]+[\s,]+[\d.-]+[\s,]+(?<w>[\d.]+)[\s,]+(?<h>[\d.]+)\k<q>/u;
|
|
79
91
|
|
|
80
92
|
// Render the configured logo as the brand mark. A `currentColor` logo carries
|
|
81
93
|
// no intrinsic color, so it is painted in the foreground to read on the light
|
|
@@ -21,8 +21,17 @@ const ENTITIES: Record<string, string> = {
|
|
|
21
21
|
"{": "{",
|
|
22
22
|
"}": "}",
|
|
23
23
|
};
|
|
24
|
+
// MDX also parses lines starting with `import`/`export` as ESM ("import the
|
|
25
|
+
// SDK…" is common spec prose). Entity-escape the keyword's first letter so the
|
|
26
|
+
// construct can't match; it still renders as the literal word.
|
|
27
|
+
const MDX_ESM_KEYWORD = /^(?<keyword>import|export)\b/gmu;
|
|
24
28
|
const mdxSafe = (text: string): string =>
|
|
25
|
-
text
|
|
29
|
+
text
|
|
30
|
+
.replace(MDX_UNSAFE, (char) => ENTITIES[char] ?? char)
|
|
31
|
+
.replace(
|
|
32
|
+
MDX_ESM_KEYWORD,
|
|
33
|
+
(keyword) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`
|
|
34
|
+
);
|
|
26
35
|
|
|
27
36
|
/** Frontmatter + body for one operation or overview page. */
|
|
28
37
|
export interface RenderedPage {
|
|
@@ -31,6 +31,8 @@ export const syncOramaCloud = async (
|
|
|
31
31
|
content: record.content,
|
|
32
32
|
description: record.description,
|
|
33
33
|
id: record._id,
|
|
34
|
+
// Carried so an i18n site can filter hosted results per language.
|
|
35
|
+
locale: record.locale,
|
|
34
36
|
tag: record.tag,
|
|
35
37
|
title: record.title,
|
|
36
38
|
url: record.url,
|
|
@@ -54,6 +54,9 @@ export const syncTypesense = async (
|
|
|
54
54
|
{ name: "content", type: "string" },
|
|
55
55
|
{ name: "url", type: "string" },
|
|
56
56
|
{ facet: true, name: "tag", optional: true, type: "string" },
|
|
57
|
+
// Carried as a facet so an i18n site can filter hosted results per
|
|
58
|
+
// language (the SearchRecord contract).
|
|
59
|
+
{ facet: true, name: "locale", optional: true, type: "string" },
|
|
57
60
|
],
|
|
58
61
|
name: config.collection,
|
|
59
62
|
});
|
|
@@ -62,6 +65,7 @@ export const syncTypesense = async (
|
|
|
62
65
|
content: record.content,
|
|
63
66
|
description: record.description,
|
|
64
67
|
id: record._id,
|
|
68
|
+
locale: record.locale,
|
|
65
69
|
tag: record.tag,
|
|
66
70
|
title: record.title,
|
|
67
71
|
url: record.url,
|
package/src/theme/icons.ts
CHANGED
|
@@ -75,6 +75,15 @@ const PREFIX_SETS: Record<string, string> = {
|
|
|
75
75
|
ti: "tabler",
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Own-property map lookup. Icon names and library/iconType hints come from
|
|
80
|
+
* content and config, so a value like `constructor:x` would otherwise resolve
|
|
81
|
+
* an Object.prototype member (a function) and crash resolution deep in the
|
|
82
|
+
* build with no pointer to the offending page.
|
|
83
|
+
*/
|
|
84
|
+
const ownEntry = <T>(map: Record<string, T>, key: string): T | undefined =>
|
|
85
|
+
Object.hasOwn(map, key) ? map[key] : undefined;
|
|
86
|
+
|
|
78
87
|
export interface ResolvedIcon {
|
|
79
88
|
/** Inner SVG markup (self-styled: carries its own fill/stroke). */
|
|
80
89
|
body: string;
|
|
@@ -100,13 +109,13 @@ const normalize = (name: string): string =>
|
|
|
100
109
|
/** Which set a bare name resolves against, given library/iconType hints. */
|
|
101
110
|
const setFor = (options: ResolveIconOptions): string => {
|
|
102
111
|
if (options.iconType) {
|
|
103
|
-
const set = ICON_TYPE_SETS
|
|
112
|
+
const set = ownEntry(ICON_TYPE_SETS, normalize(options.iconType));
|
|
104
113
|
if (set) {
|
|
105
114
|
return set;
|
|
106
115
|
}
|
|
107
116
|
}
|
|
108
117
|
if (options.library) {
|
|
109
|
-
const set = LIBRARY_SETS
|
|
118
|
+
const set = ownEntry(LIBRARY_SETS, normalize(options.library));
|
|
110
119
|
if (set) {
|
|
111
120
|
return set;
|
|
112
121
|
}
|
|
@@ -115,7 +124,7 @@ const setFor = (options: ResolveIconOptions): string => {
|
|
|
115
124
|
};
|
|
116
125
|
|
|
117
126
|
const fromSet = (setName: string, iconName: string): ResolvedIcon | null => {
|
|
118
|
-
const set = SETS
|
|
127
|
+
const set = ownEntry(SETS, setName);
|
|
119
128
|
const data = set && getIconData(set, iconName);
|
|
120
129
|
if (!data) {
|
|
121
130
|
return null;
|
|
@@ -146,7 +155,7 @@ export const resolveIcon = (
|
|
|
146
155
|
const normalized = normalize(name);
|
|
147
156
|
const colon = normalized.indexOf(":");
|
|
148
157
|
if (colon > 0) {
|
|
149
|
-
const setName = PREFIX_SETS
|
|
158
|
+
const setName = ownEntry(PREFIX_SETS, normalized.slice(0, colon));
|
|
150
159
|
if (setName) {
|
|
151
160
|
return resolveInSet(setName, normalized.slice(colon + 1));
|
|
152
161
|
}
|
package/src/theme/palette.ts
CHANGED
|
@@ -22,6 +22,17 @@ const CSS_COLOR = /^[\w\s#%.,()/+-]+$/u;
|
|
|
22
22
|
const safeColor = (value: string, fallback: string): string =>
|
|
23
23
|
CSS_COLOR.test(value.trim()) ? value.trim() : fallback;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a named preset or fall back to {@link safeColor}. `hasOwn` keeps a
|
|
27
|
+
* value like "constructor" from resolving an Object.prototype member — which
|
|
28
|
+
* would stringify a function into the generated CSS, breaking the rule (the
|
|
29
|
+
* exact breakout safeColor exists to prevent).
|
|
30
|
+
*/
|
|
31
|
+
const presetOrColor = (value: string): string =>
|
|
32
|
+
Object.hasOwn(ACCENTS, value)
|
|
33
|
+
? (ACCENTS[value] as string)
|
|
34
|
+
: safeColor(value, FALLBACK_ACCENT);
|
|
35
|
+
|
|
25
36
|
/** Like {@link safeColor} but drops an unsafe/absent value to `null`. */
|
|
26
37
|
const safeColorOrNull = (value: string | undefined): string | null =>
|
|
27
38
|
value && CSS_COLOR.test(value.trim()) ? value.trim() : null;
|
|
@@ -94,10 +105,24 @@ const themeRootCss = (
|
|
|
94
105
|
|
|
95
106
|
const themeDarkCss = (
|
|
96
107
|
theme: ResolvedConfig["theme"],
|
|
97
|
-
|
|
108
|
+
options: {
|
|
109
|
+
accent: string;
|
|
110
|
+
action: string | null;
|
|
111
|
+
backgroundDecoration: string;
|
|
112
|
+
}
|
|
98
113
|
): string => {
|
|
114
|
+
// Mode-shared tokens (accent, action, decoration) must be re-declared here:
|
|
115
|
+
// the base stylesheet's own `:root[data-theme="dark"]` block outranks the
|
|
116
|
+
// `:root` config tokens on specificity, so without this block dark mode
|
|
117
|
+
// would silently keep its neutral defaults and ignore the config.
|
|
99
118
|
const tokens = [
|
|
100
|
-
|
|
119
|
+
` --blume-accent: ${options.accent};`,
|
|
120
|
+
" --blume-accent-foreground: oklch(1 0 0);",
|
|
121
|
+
...cssToken("--blume-action", options.action),
|
|
122
|
+
...cssToken(
|
|
123
|
+
"--blume-action-foreground",
|
|
124
|
+
options.action ? "oklch(1 0 0)" : null
|
|
125
|
+
),
|
|
101
126
|
...cssToken("--blume-background", safeColorOrNull(theme.backgroundDark)),
|
|
102
127
|
...cssToken(
|
|
103
128
|
"--blume-background-image",
|
|
@@ -105,10 +130,8 @@ const themeDarkCss = (
|
|
|
105
130
|
? backgroundImageCss(theme.backgroundImageDark)
|
|
106
131
|
: null
|
|
107
132
|
),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return "";
|
|
111
|
-
}
|
|
133
|
+
options.backgroundDecoration.trimEnd(),
|
|
134
|
+
].filter(Boolean);
|
|
112
135
|
return `:root[data-theme="dark"] {
|
|
113
136
|
${tokens.join("\n")}
|
|
114
137
|
}
|
|
@@ -121,7 +144,7 @@ ${tokens.join("\n")}
|
|
|
121
144
|
* arbitrary colors without a config change.
|
|
122
145
|
*/
|
|
123
146
|
export const resolveAccent = (theme: ResolvedConfig["theme"]): string =>
|
|
124
|
-
|
|
147
|
+
presetOrColor(theme.accent);
|
|
125
148
|
|
|
126
149
|
/** Resolve the configured radius preset to a CSS length. */
|
|
127
150
|
export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
@@ -133,15 +156,9 @@ export const resolveRadius = (theme: ResolvedConfig["theme"]): string =>
|
|
|
133
156
|
* arbitrary colors without a config change.
|
|
134
157
|
*/
|
|
135
158
|
export const buildThemeCss = (theme: ResolvedConfig["theme"]): string => {
|
|
136
|
-
const accent =
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
? (ACCENTS[theme.accentDark] ??
|
|
140
|
-
safeColor(theme.accentDark, FALLBACK_ACCENT))
|
|
141
|
-
: null;
|
|
142
|
-
const action = theme.action
|
|
143
|
-
? (ACCENTS[theme.action] ?? safeColor(theme.action, FALLBACK_ACCENT))
|
|
144
|
-
: null;
|
|
159
|
+
const accent = presetOrColor(theme.accent);
|
|
160
|
+
const accentDark = theme.accentDark ? presetOrColor(theme.accentDark) : null;
|
|
161
|
+
const action = theme.action ? presetOrColor(theme.action) : null;
|
|
145
162
|
const backgroundDecoration = backgroundDecorationCss(
|
|
146
163
|
theme.backgroundDecoration
|
|
147
164
|
);
|
|
@@ -152,7 +169,11 @@ export const buildThemeCss = (theme: ResolvedConfig["theme"]): string => {
|
|
|
152
169
|
backgroundDecoration,
|
|
153
170
|
radius,
|
|
154
171
|
});
|
|
155
|
-
const dark = themeDarkCss(theme,
|
|
172
|
+
const dark = themeDarkCss(theme, {
|
|
173
|
+
accent: accentDark ?? accent,
|
|
174
|
+
action,
|
|
175
|
+
backgroundDecoration,
|
|
176
|
+
});
|
|
156
177
|
|
|
157
178
|
return `/* Generated by Blume from theme config. */
|
|
158
179
|
:root {
|