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,120 +0,0 @@
|
|
|
1
|
-
import { dirname, join, relative } from "pathe";
|
|
2
|
-
|
|
3
|
-
import { rewriteCallouts } from "../shared.ts";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Source-to-source rewrites that turn Mintlify-only MDX component syntax into
|
|
7
|
-
* idiomatic Blume markup. Runs once at migration time — no Mintlify-aware
|
|
8
|
-
* plugins remain in the Blume runtime.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/** Mintlify callout components mapped to Blume directive names. */
|
|
12
|
-
const CALLOUT_DIRECTIVES: Record<string, string> = {
|
|
13
|
-
Check: "success",
|
|
14
|
-
Danger: "danger",
|
|
15
|
-
Error: "danger",
|
|
16
|
-
Info: "info",
|
|
17
|
-
Note: "note",
|
|
18
|
-
Success: "success",
|
|
19
|
-
Tip: "tip",
|
|
20
|
-
Warning: "warning",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** `<Callout type="X">` values mapped to Blume directive names. */
|
|
24
|
-
const CALLOUT_TYPE_DIRECTIVES: Record<string, string> = {
|
|
25
|
-
caution: "warning",
|
|
26
|
-
check: "success",
|
|
27
|
-
danger: "danger",
|
|
28
|
-
error: "danger",
|
|
29
|
-
info: "info",
|
|
30
|
-
note: "note",
|
|
31
|
-
success: "success",
|
|
32
|
-
tip: "tip",
|
|
33
|
-
warning: "warning",
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Convert Mintlify callout components (`<Note>`, `<Warning>`, `<Callout
|
|
38
|
-
* type="…">`, …) into Blume `:::` directives.
|
|
39
|
-
*/
|
|
40
|
-
export const rewriteMintlifyCallouts = (source: string): string =>
|
|
41
|
-
rewriteCallouts(source, {
|
|
42
|
-
defaultDirective: "note",
|
|
43
|
-
tagDirectives: CALLOUT_DIRECTIVES,
|
|
44
|
-
tags: ["Callout", ...Object.keys(CALLOUT_DIRECTIVES)],
|
|
45
|
-
typeDirectives: CALLOUT_TYPE_DIRECTIVES,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Mintlify's `<RequestExample>`/`<ResponseExample>` are tab-style code wrappers
|
|
50
|
-
* with no Blume equivalent; rename them to `<CodeGroup>`, which renders the
|
|
51
|
-
* same titled-fence tabs.
|
|
52
|
-
*/
|
|
53
|
-
export const rewriteMintlifyExampleBlocks = (source: string): string =>
|
|
54
|
-
source.replaceAll(
|
|
55
|
-
/<(?<close>\/?)(?:Request|Response)Example\b/gu,
|
|
56
|
-
"<$<close>CodeGroup"
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Mintlify nests `<Accordion title="…">` items inside an `<AccordionGroup>`.
|
|
61
|
-
* Blume inverts that: `<Accordion>` is the container and each item is an
|
|
62
|
-
* `<AccordionItem title="…">`. Rewrite both in a single pass — the `\b` after
|
|
63
|
-
* `Accordion` keeps `<AccordionGroup>` (the `Group` branch) distinct from an
|
|
64
|
-
* item, so open/close tags of either map correctly regardless of order. Without
|
|
65
|
-
* this, migrated pages keep `<AccordionGroup>`, which Blume doesn't ship and the
|
|
66
|
-
* MDX build rejects with "Expected component AccordionGroup to be defined".
|
|
67
|
-
*/
|
|
68
|
-
export const rewriteMintlifyAccordions = (source: string): string =>
|
|
69
|
-
source.replaceAll(
|
|
70
|
-
/<(?<close>\/?)Accordion(?<group>Group)?\b/gu,
|
|
71
|
-
(_match, close: string, group: string | undefined) =>
|
|
72
|
-
group ? `<${close}Accordion` : `<${close}AccordionItem`
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const SNIPPET_IMPORT =
|
|
76
|
-
/^import\s+[\s\S]*?\s+from\s+["'](?<source>\/snippets\/[^"']+)["'];?[ \t]*\n?/gmu;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* After snippets are inlined, clean up leftover `/snippets/*` imports: drop the
|
|
80
|
-
* now-dead markdown imports, and rewrite component imports (`.jsx`/`.tsx`/…) to
|
|
81
|
-
* a path relative to the page so they still resolve once `/snippets` content is
|
|
82
|
-
* gone. Returns the rewritten source and the component files still referenced.
|
|
83
|
-
*/
|
|
84
|
-
export const rewriteSnippetImports = (
|
|
85
|
-
source: string,
|
|
86
|
-
options: { filePath: string; root: string }
|
|
87
|
-
): { components: string[]; source: string } => {
|
|
88
|
-
const components: string[] = [];
|
|
89
|
-
const next = source.replaceAll(
|
|
90
|
-
SNIPPET_IMPORT,
|
|
91
|
-
(match, importSource: string) => {
|
|
92
|
-
if (/\.mdx?$/u.test(importSource)) {
|
|
93
|
-
return "";
|
|
94
|
-
}
|
|
95
|
-
const target = join(options.root, importSource.replace(/^\/+/u, ""));
|
|
96
|
-
components.push(importSource.replace(/^\/+/u, ""));
|
|
97
|
-
let rel = relative(dirname(options.filePath), target);
|
|
98
|
-
if (!rel.startsWith(".")) {
|
|
99
|
-
rel = `./${rel}`;
|
|
100
|
-
}
|
|
101
|
-
return match.replace(importSource, rel);
|
|
102
|
-
}
|
|
103
|
-
);
|
|
104
|
-
return { components, source: next };
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Component tags Blume has no equivalent for — reported for manual review.
|
|
109
|
-
* `<ParamField>`/`<ResponseField>`/`<RequestField>` are no longer here: Blume
|
|
110
|
-
* ships compat components for them, so migrated docs render as-is. Mintlify's
|
|
111
|
-
* `<Update>` changelog entry has no component form in Blume (changelog is
|
|
112
|
-
* frontmatter-driven via `type: changelog`), so it stays flagged.
|
|
113
|
-
*/
|
|
114
|
-
const UNSUPPORTED_COMPONENTS = ["Update"];
|
|
115
|
-
|
|
116
|
-
/** Names of Mintlify components in `source` that need manual attention. */
|
|
117
|
-
export const unsupportedMintlifyComponents = (source: string): string[] =>
|
|
118
|
-
UNSUPPORTED_COMPONENTS.filter((name) =>
|
|
119
|
-
new RegExp(`<${name}\\b`, "u").test(source)
|
|
120
|
-
);
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* One-time translation of Mintlify page frontmatter into Blume's shape. This
|
|
5
|
-
* runs at migration time only — Blume's runtime page schema stays
|
|
6
|
-
* Mintlify-free and strict. `sidebarTitle`/`icon`/`tag`/`hidden` fold into
|
|
7
|
-
* `sidebar`, `hidden` implies `noindex`, `canonical`/`og:image` move under
|
|
8
|
-
* `seo`, and OpenAPI/AsyncAPI pages become `type: "api"`.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const mintlifySidebarMetaSchema = z
|
|
12
|
-
.object({
|
|
13
|
-
badge: z.string().optional(),
|
|
14
|
-
hidden: z.boolean().optional(),
|
|
15
|
-
icon: z.string().optional(),
|
|
16
|
-
label: z.string().optional(),
|
|
17
|
-
order: z.number().optional(),
|
|
18
|
-
})
|
|
19
|
-
.passthrough();
|
|
20
|
-
|
|
21
|
-
const mintlifyPageMetaInputSchema = z
|
|
22
|
-
.object({
|
|
23
|
-
api: z.unknown().optional(),
|
|
24
|
-
asyncapi: z.string().optional(),
|
|
25
|
-
canonical: z.string().optional(),
|
|
26
|
-
hidden: z.boolean().optional(),
|
|
27
|
-
icon: z.string().optional(),
|
|
28
|
-
noindex: z.boolean().optional(),
|
|
29
|
-
"og:image": z.string().optional(),
|
|
30
|
-
openapi: z.string().optional(),
|
|
31
|
-
sidebar: mintlifySidebarMetaSchema.optional(),
|
|
32
|
-
sidebarTitle: z.string().optional(),
|
|
33
|
-
tag: z.string().optional(),
|
|
34
|
-
type: z.string().optional(),
|
|
35
|
-
})
|
|
36
|
-
.passthrough();
|
|
37
|
-
|
|
38
|
-
type MintlifyPageMetaInput = z.infer<typeof mintlifyPageMetaInputSchema>;
|
|
39
|
-
|
|
40
|
-
const normalizedMintlifySidebar = (meta: MintlifyPageMetaInput) => ({
|
|
41
|
-
...meta.sidebar,
|
|
42
|
-
...(meta.sidebarTitle !== undefined && meta.sidebar?.label === undefined
|
|
43
|
-
? { label: meta.sidebarTitle }
|
|
44
|
-
: {}),
|
|
45
|
-
...(meta.icon !== undefined && meta.sidebar?.icon === undefined
|
|
46
|
-
? { icon: meta.icon }
|
|
47
|
-
: {}),
|
|
48
|
-
...(meta.tag !== undefined && meta.sidebar?.badge === undefined
|
|
49
|
-
? { badge: meta.tag }
|
|
50
|
-
: {}),
|
|
51
|
-
...(meta.hidden === true && meta.sidebar?.hidden === undefined
|
|
52
|
-
? { hidden: true }
|
|
53
|
-
: {}),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const mintlifyPageType = (meta: MintlifyPageMetaInput): string | undefined =>
|
|
57
|
-
meta.type ??
|
|
58
|
-
(meta.openapi !== undefined ||
|
|
59
|
-
meta.asyncapi !== undefined ||
|
|
60
|
-
meta.api !== undefined
|
|
61
|
-
? "api"
|
|
62
|
-
: undefined);
|
|
63
|
-
|
|
64
|
-
const mintlifyNoindex = (meta: MintlifyPageMetaInput): boolean | undefined =>
|
|
65
|
-
meta.hidden === true && meta.noindex === undefined ? true : meta.noindex;
|
|
66
|
-
|
|
67
|
-
/** Top-level Mintlify keys folded elsewhere — removed from the output. */
|
|
68
|
-
const CONSUMED_KEYS = new Set([
|
|
69
|
-
"canonical",
|
|
70
|
-
"hidden",
|
|
71
|
-
"icon",
|
|
72
|
-
"og:image",
|
|
73
|
-
"sidebarTitle",
|
|
74
|
-
"tag",
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
/** Map Mintlify page frontmatter onto Blume's frontmatter shape. */
|
|
78
|
-
export const normalizeMintlifyPageMeta = (
|
|
79
|
-
value: unknown
|
|
80
|
-
): Record<string, unknown> => {
|
|
81
|
-
const parsed = mintlifyPageMetaInputSchema.safeParse(value);
|
|
82
|
-
if (!parsed.success || typeof value !== "object" || value === null) {
|
|
83
|
-
return (value ?? {}) as Record<string, unknown>;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const meta = parsed.data;
|
|
87
|
-
const source = value as Record<string, unknown>;
|
|
88
|
-
const data: Record<string, unknown> = { ...source };
|
|
89
|
-
|
|
90
|
-
const sidebar = normalizedMintlifySidebar(meta);
|
|
91
|
-
if (Object.keys(sidebar).length > 0) {
|
|
92
|
-
data.sidebar = sidebar;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const seo: Record<string, unknown> = {
|
|
96
|
-
...(typeof source.seo === "object" && source.seo !== null
|
|
97
|
-
? (source.seo as Record<string, unknown>)
|
|
98
|
-
: {}),
|
|
99
|
-
};
|
|
100
|
-
if (meta.canonical !== undefined && seo.canonical === undefined) {
|
|
101
|
-
seo.canonical = meta.canonical;
|
|
102
|
-
}
|
|
103
|
-
if (meta["og:image"] !== undefined && seo.image === undefined) {
|
|
104
|
-
seo.image = meta["og:image"];
|
|
105
|
-
}
|
|
106
|
-
if (Object.keys(seo).length > 0) {
|
|
107
|
-
data.seo = seo;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const noindex = mintlifyNoindex(meta);
|
|
111
|
-
if (noindex !== undefined) {
|
|
112
|
-
data.noindex = noindex;
|
|
113
|
-
}
|
|
114
|
-
const type = mintlifyPageType(meta);
|
|
115
|
-
if (type !== undefined) {
|
|
116
|
-
data.type = type;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const cleaned: Record<string, unknown> = {};
|
|
120
|
-
for (const [key, raw] of Object.entries(data)) {
|
|
121
|
-
if (!CONSUMED_KEYS.has(key)) {
|
|
122
|
-
cleaned[key] = raw;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return cleaned;
|
|
126
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { BlumeConfig } from "../../core/schema.ts";
|
|
2
|
-
|
|
3
|
-
/** A Mintlify `navigation.languages[]` entry. */
|
|
4
|
-
interface MintlifyLanguage {
|
|
5
|
-
language: string;
|
|
6
|
-
default?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* A display label for a locale code — the language's native name when the
|
|
11
|
-
* runtime knows it (`fr` -> `Français`), else the code itself.
|
|
12
|
-
*/
|
|
13
|
-
const localeLabel = (code: string): string => {
|
|
14
|
-
try {
|
|
15
|
-
const native = new Intl.DisplayNames([code], { type: "language" }).of(code);
|
|
16
|
-
if (native && native !== code) {
|
|
17
|
-
return native.charAt(0).toUpperCase() + native.slice(1);
|
|
18
|
-
}
|
|
19
|
-
} catch {
|
|
20
|
-
// Unknown code or no ICU data; fall through to the code.
|
|
21
|
-
}
|
|
22
|
-
return code;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Map a Mintlify `navigation.languages[]` array to a Blume `i18n` config. The
|
|
27
|
-
* entry marked `default: true` becomes `defaultLocale`; translated content
|
|
28
|
-
* already lives in ISO-code directories, which match Blume's `dir` parser.
|
|
29
|
-
*/
|
|
30
|
-
export const mintlifyI18n = (
|
|
31
|
-
spec: Record<string, unknown>
|
|
32
|
-
): BlumeConfig["i18n"] | null => {
|
|
33
|
-
const navigation = spec.navigation as
|
|
34
|
-
| { languages?: MintlifyLanguage[] }
|
|
35
|
-
| undefined;
|
|
36
|
-
const languages = navigation?.languages;
|
|
37
|
-
if (!Array.isArray(languages) || languages.length < 2) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
const defaultLocale =
|
|
41
|
-
languages.find((entry) => entry.default)?.language ??
|
|
42
|
-
languages[0]?.language ??
|
|
43
|
-
"en";
|
|
44
|
-
return {
|
|
45
|
-
defaultLocale,
|
|
46
|
-
locales: languages.map((entry) => ({
|
|
47
|
-
code: entry.language,
|
|
48
|
-
label: localeLabel(entry.language),
|
|
49
|
-
})),
|
|
50
|
-
};
|
|
51
|
-
};
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
const JSX_ATTRIBUTE_ALIASES: Record<string, string> = {
|
|
2
|
-
className: "class",
|
|
3
|
-
clipPath: "clip-path",
|
|
4
|
-
clipRule: "clip-rule",
|
|
5
|
-
fillOpacity: "fill-opacity",
|
|
6
|
-
fillRule: "fill-rule",
|
|
7
|
-
strokeDasharray: "stroke-dasharray",
|
|
8
|
-
strokeDashoffset: "stroke-dashoffset",
|
|
9
|
-
strokeLinecap: "stroke-linecap",
|
|
10
|
-
strokeLinejoin: "stroke-linejoin",
|
|
11
|
-
strokeMiterlimit: "stroke-miterlimit",
|
|
12
|
-
strokeOpacity: "stroke-opacity",
|
|
13
|
-
strokeWidth: "stroke-width",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const SVG_EXPRESSION = /^\s*\(?\s*<svg[\s\S]*<\/svg>\s*\)?\s*;?\s*$/u;
|
|
17
|
-
|
|
18
|
-
const escapeAttribute = (value: string): string =>
|
|
19
|
-
value
|
|
20
|
-
.replaceAll("&", "&")
|
|
21
|
-
.replaceAll('"', """)
|
|
22
|
-
.replaceAll("<", "<")
|
|
23
|
-
.replaceAll(">", ">");
|
|
24
|
-
|
|
25
|
-
const stripExpressionWrapper = (value: string): string => {
|
|
26
|
-
let next = value.trim().replaceAll(/;$/gu, "").trim();
|
|
27
|
-
if (next.startsWith("(") && next.endsWith(")")) {
|
|
28
|
-
next = next.slice(1, -1).trim();
|
|
29
|
-
}
|
|
30
|
-
return next;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const jsxExpressionAttribute = (
|
|
34
|
-
value: string,
|
|
35
|
-
quoted: string | undefined,
|
|
36
|
-
single: string | undefined,
|
|
37
|
-
template: string | undefined,
|
|
38
|
-
scalar: string | undefined
|
|
39
|
-
): string => {
|
|
40
|
-
const literal = quoted ?? single ?? template ?? scalar;
|
|
41
|
-
return literal === undefined ? value : `="${escapeAttribute(literal)}"`;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const normalizeJsxSvg = (value: string): string | null => {
|
|
45
|
-
const stripped = stripExpressionWrapper(value);
|
|
46
|
-
if (!SVG_EXPRESSION.test(stripped)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let svg = stripped.replaceAll(
|
|
51
|
-
/[=]\{\s*(?:"(?<quoted>[^"]*)"|'(?<single>[^']*)'|`(?<template>[^`]*)`|(?<scalar>-?\d+(?:\.\d+)?|true|false))\s*\}/gu,
|
|
52
|
-
jsxExpressionAttribute
|
|
53
|
-
);
|
|
54
|
-
for (const [jsxName, htmlName] of Object.entries(JSX_ATTRIBUTE_ALIASES)) {
|
|
55
|
-
svg = svg.replaceAll(new RegExp(`\\b${jsxName}=`, "gu"), `${htmlName}=`);
|
|
56
|
-
}
|
|
57
|
-
return svg;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const findExpressionEnd = (source: string, start: number): number => {
|
|
61
|
-
let depth = 1;
|
|
62
|
-
let quote: '"' | "'" | "`" | null = null;
|
|
63
|
-
for (let index = start; index < source.length; index += 1) {
|
|
64
|
-
const char = source[index];
|
|
65
|
-
const previous = source[index - 1];
|
|
66
|
-
if (quote) {
|
|
67
|
-
if (char === quote && previous !== "\\") {
|
|
68
|
-
quote = null;
|
|
69
|
-
}
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
73
|
-
quote = char;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (char === "{") {
|
|
77
|
-
depth += 1;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (char === "}") {
|
|
81
|
-
depth -= 1;
|
|
82
|
-
if (depth === 0) {
|
|
83
|
-
return index;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return -1;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const startsWithSvgExpression = (value: string): boolean => {
|
|
91
|
-
const trimmed = value.trimStart();
|
|
92
|
-
return trimmed.startsWith("<svg") || trimmed.startsWith("(<svg");
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Rewrite Mintlify inline-SVG icon JSX props (`icon={<svg .../>}`) to plain
|
|
97
|
-
* string props so the migrated MDX compiles under Astro. Runs once at
|
|
98
|
-
* migration time; non-SVG `icon={...}` expressions are left untouched.
|
|
99
|
-
*/
|
|
100
|
-
export const rewriteMintlifySvgIconProps = (source: string): string => {
|
|
101
|
-
let output = "";
|
|
102
|
-
let cursor = 0;
|
|
103
|
-
while (cursor < source.length) {
|
|
104
|
-
const start = source.indexOf("icon={", cursor);
|
|
105
|
-
if (start === -1) {
|
|
106
|
-
output += source.slice(cursor);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const expressionStart = start + "icon={".length;
|
|
111
|
-
const end = findExpressionEnd(source, expressionStart);
|
|
112
|
-
if (end === -1) {
|
|
113
|
-
output += source.slice(cursor);
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const expression = source.slice(expressionStart, end);
|
|
118
|
-
const svg = startsWithSvgExpression(expression)
|
|
119
|
-
? normalizeJsxSvg(expression)
|
|
120
|
-
: null;
|
|
121
|
-
output += source.slice(cursor, start);
|
|
122
|
-
output += svg
|
|
123
|
-
? `icon={${JSON.stringify(svg)}}`
|
|
124
|
-
: source.slice(start, end + 1);
|
|
125
|
-
cursor = end + 1;
|
|
126
|
-
}
|
|
127
|
-
return output;
|
|
128
|
-
};
|