blume 0.5.0 → 0.5.1
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 +40 -105
- package/dist/cli/index.js.map +11 -11
- package/dist/types/core/schema.d.ts +0 -26
- package/dist/types/core/types.d.ts +0 -6
- package/dist/types/migrate/mintlify/config.d.ts +14 -0
- package/docs/01-quickstart.mdx +6 -2
- package/docs/02-deployment.mdx +3 -1
- package/docs/advanced/api-reference.mdx +6 -2
- package/docs/advanced/bridge.mdx +3 -1
- package/docs/advanced/custom-pages.mdx +3 -1
- package/docs/advanced/migrate.mdx +6 -2
- package/docs/configuration/ai.mdx +3 -1
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/export.mdx +6 -2
- package/docs/configuration/seo.mdx +3 -1
- package/docs/content/i18n.mdx +6 -2
- package/docs/content/islands.mdx +6 -2
- package/docs/content/meta.mdx +3 -1
- package/docs/content/syntax.mdx +40 -14
- package/package.json +1 -1
- package/src/astro/generate.ts +0 -1
- package/src/core/graph.ts +0 -3
- package/src/core/nav-diagnostics.ts +2 -12
- package/src/core/navigation.ts +0 -10
- package/src/core/schema.ts +0 -8
- package/src/core/types.ts +0 -7
- package/src/migrate/mintlify/config.ts +40 -99
- package/src/migrate/mintlify/content.ts +16 -0
- package/src/migrate/mintlify/index.ts +18 -1
- package/src/migrate/mintlify/transform.ts +2 -0
|
@@ -17,8 +17,6 @@ type NavigationSelectorItem = NavigationSelectors[number]["items"][number];
|
|
|
17
17
|
type NavigationChromeVariants = NonNullable<
|
|
18
18
|
BlumeConfig["navigation"]
|
|
19
19
|
>["chromeVariants"];
|
|
20
|
-
type NavigationSidebarVariants =
|
|
21
|
-
ResolvedConfig["navigation"]["sidebarVariants"];
|
|
22
20
|
|
|
23
21
|
const MINTLIFY_DEFAULT_IGNORES = [
|
|
24
22
|
"**/_*",
|
|
@@ -307,18 +305,6 @@ const mintlifyBackgroundDecoration = (
|
|
|
307
305
|
return undefined;
|
|
308
306
|
};
|
|
309
307
|
|
|
310
|
-
const sidebarItemPaths = (items: SidebarItemConfig[]): string[] =>
|
|
311
|
-
items.flatMap((item) => {
|
|
312
|
-
if (typeof item === "string") {
|
|
313
|
-
return [tabPathFromRef(item)];
|
|
314
|
-
}
|
|
315
|
-
if (item.href) {
|
|
316
|
-
return [item.href];
|
|
317
|
-
}
|
|
318
|
-
const paths = item.root ? [tabPathFromRef(item.root)] : [];
|
|
319
|
-
return item.items ? [...paths, ...sidebarItemPaths(item.items)] : paths;
|
|
320
|
-
});
|
|
321
|
-
|
|
322
308
|
const collapsedFromExpanded = (value: unknown): boolean | undefined => {
|
|
323
309
|
if (value === false) {
|
|
324
310
|
return true;
|
|
@@ -439,79 +425,6 @@ const isExternalRoute = (path: string): boolean =>
|
|
|
439
425
|
path.startsWith("mailto:") ||
|
|
440
426
|
path.startsWith("tel:");
|
|
441
427
|
|
|
442
|
-
const hasOwnNavigationContent = (item: JsonObject): boolean =>
|
|
443
|
-
Boolean(asString(item.root)) || childItemsFor(item).length > 0;
|
|
444
|
-
|
|
445
|
-
const mintlifySidebarVariants = async (
|
|
446
|
-
spec: JsonObject
|
|
447
|
-
): Promise<NavigationSidebarVariants> => {
|
|
448
|
-
const navigation = asObject(spec.navigation) ?? {};
|
|
449
|
-
const global = asObject(navigation.global) ?? {};
|
|
450
|
-
interface SidebarVariantCandidate {
|
|
451
|
-
item: unknown;
|
|
452
|
-
path: string;
|
|
453
|
-
}
|
|
454
|
-
const candidates: SidebarVariantCandidate[] = [];
|
|
455
|
-
|
|
456
|
-
const addCandidate = (item: unknown): void => {
|
|
457
|
-
const path = navItemPath(item);
|
|
458
|
-
if (!path || isExternalRoute(path)) {
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
candidates.push({ item, path });
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
for (const item of [
|
|
466
|
-
...asArray(navigation.tabs),
|
|
467
|
-
...asArray(navigation.anchors),
|
|
468
|
-
...asArray(global.anchors),
|
|
469
|
-
]) {
|
|
470
|
-
const object = asObject(item);
|
|
471
|
-
if (!object) {
|
|
472
|
-
addCandidate(item);
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
for (const menuItem of asArray(object.menu)) {
|
|
477
|
-
addCandidate(menuItem);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (hasOwnNavigationContent(object)) {
|
|
481
|
-
addCandidate(item);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
for (const item of [
|
|
486
|
-
...asArray(navigation.dropdowns),
|
|
487
|
-
...asArray(navigation.products),
|
|
488
|
-
...asArray(navigation.versions),
|
|
489
|
-
...asArray(navigation.languages),
|
|
490
|
-
]) {
|
|
491
|
-
addCandidate(item);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
const resolved = await Promise.all(
|
|
495
|
-
candidates.map(async (candidate) => {
|
|
496
|
-
const items = await toSidebarItems([candidate.item], {});
|
|
497
|
-
if (items.length === 0) {
|
|
498
|
-
return [];
|
|
499
|
-
}
|
|
500
|
-
return [candidate.path, ...sidebarItemPaths(items)]
|
|
501
|
-
.filter((path) => !isExternalRoute(path))
|
|
502
|
-
.map((path) => ({ items, path }));
|
|
503
|
-
})
|
|
504
|
-
);
|
|
505
|
-
const seen = new Set<string>();
|
|
506
|
-
return resolved.flat().flatMap((variant) => {
|
|
507
|
-
if (!variant || seen.has(variant.path)) {
|
|
508
|
-
return [];
|
|
509
|
-
}
|
|
510
|
-
seen.add(variant.path);
|
|
511
|
-
return [variant];
|
|
512
|
-
});
|
|
513
|
-
};
|
|
514
|
-
|
|
515
428
|
const mintlifyTabs = (
|
|
516
429
|
spec: JsonObject
|
|
517
430
|
): NonNullable<BlumeConfig["navigation"]>["tabs"] => {
|
|
@@ -604,24 +517,53 @@ const toAstroRedirectPath = (path: string): string =>
|
|
|
604
517
|
modifier === "*" || modifier === "+" ? `[...${name}]` : `[${name}]`
|
|
605
518
|
);
|
|
606
519
|
|
|
607
|
-
|
|
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 = (
|
|
608
540
|
spec: JsonObject
|
|
609
|
-
):
|
|
610
|
-
|
|
541
|
+
): MintlifyRedirectPartition => {
|
|
542
|
+
const kept: NonNullable<BlumeConfig["redirects"]> = [];
|
|
543
|
+
const dropped: string[] = [];
|
|
544
|
+
for (const redirect of asArray(spec.redirects)) {
|
|
611
545
|
const object = asObject(redirect);
|
|
612
546
|
if (!object) {
|
|
613
|
-
|
|
547
|
+
continue;
|
|
614
548
|
}
|
|
615
|
-
const
|
|
616
|
-
const
|
|
549
|
+
const source = asString(object.source) ?? asString(object.from);
|
|
550
|
+
const destination =
|
|
617
551
|
asString(object.destination) ??
|
|
618
552
|
asString(object.to) ??
|
|
619
553
|
asString(object.redirect);
|
|
620
|
-
if (!
|
|
621
|
-
|
|
554
|
+
if (!source || !destination) {
|
|
555
|
+
continue;
|
|
622
556
|
}
|
|
623
|
-
|
|
624
|
-
|
|
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
|
+
};
|
|
625
567
|
|
|
626
568
|
interface OpenApiSourceDraft {
|
|
627
569
|
label?: string;
|
|
@@ -981,11 +923,10 @@ export const loadMintlifyConfig = async (
|
|
|
981
923
|
chromeVariants: mintlifyChromeVariants(spec),
|
|
982
924
|
selectors: mintlifySelectors(spec),
|
|
983
925
|
sidebar: await toSidebarItems(mintlifyNavigationItems(navigation), {}),
|
|
984
|
-
sidebarVariants: await mintlifySidebarVariants(spec),
|
|
985
926
|
tabs: mintlifyTabs(spec),
|
|
986
927
|
},
|
|
987
928
|
openapi: mintlifyOpenapi(spec),
|
|
988
|
-
redirects:
|
|
929
|
+
redirects: partitionMintlifyRedirects(spec).kept,
|
|
989
930
|
search: {
|
|
990
931
|
indexing: {
|
|
991
932
|
includeHiddenPages: seo.indexing === "all",
|
|
@@ -56,6 +56,22 @@ export const rewriteMintlifyExampleBlocks = (source: string): string =>
|
|
|
56
56
|
"<$<close>CodeGroup"
|
|
57
57
|
);
|
|
58
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
|
+
|
|
59
75
|
const SNIPPET_IMPORT =
|
|
60
76
|
/^import\s+[\s\S]*?\s+from\s+["'](?<source>\/snippets\/[^"']+)["'];?[ \t]*\n?/gmu;
|
|
61
77
|
|
|
@@ -6,7 +6,7 @@ import { glob } from "tinyglobby";
|
|
|
6
6
|
|
|
7
7
|
import type { BlumeConfig } from "../../core/schema.ts";
|
|
8
8
|
import { assetSegments } from "./assets.ts";
|
|
9
|
-
import { loadMintlifyConfig } from "./config.ts";
|
|
9
|
+
import { loadMintlifyConfig, partitionMintlifyRedirects } from "./config.ts";
|
|
10
10
|
import { mintlifyI18n } from "./i18n.ts";
|
|
11
11
|
import { transformMintlifyContent } from "./transform.ts";
|
|
12
12
|
|
|
@@ -65,6 +65,22 @@ const droppedChromeWarnings = (
|
|
|
65
65
|
return warnings;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Warn about dynamic (wildcard/param) redirects the migrator dropped. Blume
|
|
70
|
+
* redirects are static path-to-path, so a `:slug*`/`:id` source becomes an
|
|
71
|
+
* unroutable Astro destination — kept ones crash the build. Point the user at
|
|
72
|
+
* host-level rules that do support wildcards.
|
|
73
|
+
*/
|
|
74
|
+
const droppedRedirectWarnings = (spec: Record<string, unknown>): string[] => {
|
|
75
|
+
const { dropped } = partitionMintlifyRedirects(spec);
|
|
76
|
+
if (dropped.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
`Dropped ${dropped.length} dynamic redirect(s) Blume can't model as static path-to-path (${dropped.join(", ")}); re-add them as host-level rules (e.g. _redirects or vercel.json).`,
|
|
81
|
+
];
|
|
82
|
+
};
|
|
83
|
+
|
|
68
84
|
/** Recursively drop `undefined`, empty arrays, and empty objects. */
|
|
69
85
|
const prune = (value: unknown): unknown => {
|
|
70
86
|
if (Array.isArray(value)) {
|
|
@@ -247,6 +263,7 @@ export const migrateMintlifyProject = async (
|
|
|
247
263
|
);
|
|
248
264
|
}
|
|
249
265
|
warnings.push(...droppedChromeWarnings(spec, config));
|
|
266
|
+
warnings.push(...droppedRedirectWarnings(spec));
|
|
250
267
|
} else {
|
|
251
268
|
warnings.push("No docs.json or mint.json found; writing a default config.");
|
|
252
269
|
config = { content: { root: "." }, title: "Documentation" };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import matter from "../../core/frontmatter.ts";
|
|
2
2
|
import { stripUnknownPageMeta } from "../shared.ts";
|
|
3
3
|
import {
|
|
4
|
+
rewriteMintlifyAccordions,
|
|
4
5
|
rewriteMintlifyCallouts,
|
|
5
6
|
rewriteMintlifyExampleBlocks,
|
|
6
7
|
rewriteSnippetImports,
|
|
@@ -59,6 +60,7 @@ export const transformMintlifyContent = async (
|
|
|
59
60
|
text = snippetImports.source;
|
|
60
61
|
text = rewriteMintlifySvgIconProps(text);
|
|
61
62
|
text = rewriteMintlifyExampleBlocks(text);
|
|
63
|
+
text = rewriteMintlifyAccordions(text);
|
|
62
64
|
text = rewriteMintlifyCallouts(text);
|
|
63
65
|
|
|
64
66
|
const unsupported = unsupportedMintlifyComponents(text);
|