blume 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/blume.mjs +18 -0
- package/dist/cli/index.js +11989 -0
- package/dist/cli/index.js.map +141 -0
- package/docs/01-quickstart.mdx +99 -0
- package/docs/02-deployment.mdx +129 -0
- package/docs/advanced/api-reference.mdx +114 -0
- package/docs/advanced/blog.mdx +121 -0
- package/docs/advanced/changelog.mdx +113 -0
- package/docs/advanced/custom-pages.mdx +187 -0
- package/docs/advanced/meta.ts +7 -0
- package/docs/changelog/v0-1-0.mdx +12 -0
- package/docs/changelog/v0-2-0.mdx +16 -0
- package/docs/configuration/ai.mdx +228 -0
- package/docs/configuration/analytics.mdx +98 -0
- package/docs/configuration/customization.mdx +91 -0
- package/docs/configuration/export.mdx +70 -0
- package/docs/configuration/index.mdx +271 -0
- package/docs/configuration/meta.ts +15 -0
- package/docs/configuration/search.mdx +172 -0
- package/docs/configuration/seo.mdx +196 -0
- package/docs/configuration/theming.mdx +178 -0
- package/docs/content/components.mdx +565 -0
- package/docs/content/i18n.mdx +205 -0
- package/docs/content/index.mdx +161 -0
- package/docs/content/islands.mdx +94 -0
- package/docs/content/meta.mdx +119 -0
- package/docs/content/meta.ts +15 -0
- package/docs/content/navigation.mdx +168 -0
- package/docs/content/sources.mdx +216 -0
- package/docs/content/syntax.mdx +445 -0
- package/docs/index.mdx +112 -0
- package/docs/reference/cli.mdx +43 -0
- package/docs/reference/frontmatter.mdx +74 -0
- package/docs/reference/meta.ts +7 -0
- package/package.json +140 -6
- package/src/ai/ask.ts +93 -0
- package/src/ai/llms.ts +65 -0
- package/src/ai/markdown.ts +31 -0
- package/src/ai/mcp/data.ts +74 -0
- package/src/ai/mcp/discovery.ts +49 -0
- package/src/ai/mcp/server.ts +225 -0
- package/src/ai/mcp/tools.ts +47 -0
- package/src/assets/icon.png +0 -0
- package/src/astro/generate.ts +878 -0
- package/src/astro/index.ts +4 -0
- package/src/astro/integration.ts +74 -0
- package/src/astro/islands.ts +131 -0
- package/src/astro/markdown-negotiation.ts +68 -0
- package/src/astro/pages.ts +28 -0
- package/src/astro/templates.ts +1199 -0
- package/src/cli/commands/add.ts +81 -0
- package/src/cli/commands/build.ts +103 -0
- package/src/cli/commands/dev.ts +108 -0
- package/src/cli/commands/doctor.ts +74 -0
- package/src/cli/commands/eject.ts +57 -0
- package/src/cli/commands/init.ts +98 -0
- package/src/cli/commands/migrate.ts +39 -0
- package/src/cli/commands/preview.ts +39 -0
- package/src/cli/commands/sync.ts +52 -0
- package/src/cli/commands/validate.ts +60 -0
- package/src/cli/index.ts +35 -0
- package/src/cli/log.ts +37 -0
- package/src/cli/prepare.ts +80 -0
- package/src/components/Icon.astro +99 -0
- package/src/components/content/Accordion.astro +8 -0
- package/src/components/content/AccordionItem.astro +121 -0
- package/src/components/content/AutoTypeTable.astro +51 -0
- package/src/components/content/Badge.astro +124 -0
- package/src/components/content/Callout.astro +73 -0
- package/src/components/content/Card.astro +104 -0
- package/src/components/content/CardGroup.astro +14 -0
- package/src/components/content/CodeGroup.astro +13 -0
- package/src/components/content/Color.astro +15 -0
- package/src/components/content/ColorItem.astro +87 -0
- package/src/components/content/ColorRow.astro +10 -0
- package/src/components/content/Column.astro +6 -0
- package/src/components/content/Columns.astro +9 -0
- package/src/components/content/Expandable.astro +11 -0
- package/src/components/content/FileTree.astro +8 -0
- package/src/components/content/Frame.astro +70 -0
- package/src/components/content/GithubInfo.astro +110 -0
- package/src/components/content/Math.astro +24 -0
- package/src/components/content/Panel.astro +20 -0
- package/src/components/content/Prompt.astro +129 -0
- package/src/components/content/Step.astro +34 -0
- package/src/components/content/Steps.astro +20 -0
- package/src/components/content/Tab.astro +40 -0
- package/src/components/content/Tabs.astro +273 -0
- package/src/components/content/Tile.astro +42 -0
- package/src/components/content/Tooltip.astro +68 -0
- package/src/components/content/Tree.astro +300 -0
- package/src/components/content/TreeFile.astro +15 -0
- package/src/components/content/TreeFolder.astro +62 -0
- package/src/components/content/TypeTable.astro +106 -0
- package/src/components/content/Update.astro +66 -0
- package/src/components/content/Visibility.astro +12 -0
- package/src/components/content/Warning.astro +9 -0
- package/src/components/content/auto-type-table.ts +141 -0
- package/src/components/content/github-info.ts +79 -0
- package/src/components/content/mermaid-element.ts +68 -0
- package/src/components/github-mark.ts +9 -0
- package/src/components/index.ts +14 -0
- package/src/components/islands/AskAI.astro +12 -0
- package/src/components/islands/ask-ai.tsx +156 -0
- package/src/components/layout/Analytics.astro +63 -0
- package/src/components/layout/Banner.astro +50 -0
- package/src/components/layout/Breadcrumbs.astro +31 -0
- package/src/components/layout/Favicon.astro +15 -0
- package/src/components/layout/Fonts.astro +14 -0
- package/src/components/layout/Header.astro +188 -0
- package/src/components/layout/LanguageSwitcher.astro +56 -0
- package/src/components/layout/NavTree.astro +462 -0
- package/src/components/layout/PageActions.astro +438 -0
- package/src/components/layout/PageFeedback.astro +58 -0
- package/src/components/layout/Pagination.astro +56 -0
- package/src/components/layout/ReferenceLayout.astro +102 -0
- package/src/components/layout/RootLayout.astro +533 -0
- package/src/components/layout/Search.astro +608 -0
- package/src/components/layout/TableOfContents.astro +68 -0
- package/src/components/layout/analytics-client.ts +38 -0
- package/src/components/layout/nav-utils.ts +87 -0
- package/src/components/layout/overrides.ts +32 -0
- package/src/components/layout/search/algolia.ts +43 -0
- package/src/components/layout/search/endpoint.ts +22 -0
- package/src/components/layout/search/flexsearch.ts +52 -0
- package/src/components/layout/search/orama-cloud.ts +41 -0
- package/src/components/layout/search/orama.ts +26 -0
- package/src/components/layout/search/pagefind.ts +43 -0
- package/src/components/layout/search/types.ts +163 -0
- package/src/components/layout/search/typesense.ts +60 -0
- package/src/components/layout/toc-element.ts +108 -0
- package/src/core/bridge.ts +92 -0
- package/src/core/config.ts +112 -0
- package/src/core/content.ts +50 -0
- package/src/core/define-components.ts +34 -0
- package/src/core/define-meta.ts +20 -0
- package/src/core/deployment-env.ts +73 -0
- package/src/core/diagnostics.ts +104 -0
- package/src/core/graph.ts +128 -0
- package/src/core/i18n-ui.ts +171 -0
- package/src/core/i18n.ts +169 -0
- package/src/core/last-modified.ts +88 -0
- package/src/core/links.ts +336 -0
- package/src/core/load-module.ts +15 -0
- package/src/core/manifest.ts +126 -0
- package/src/core/meta.ts +97 -0
- package/src/core/navigation.ts +392 -0
- package/src/core/package-root.ts +37 -0
- package/src/core/project-graph.ts +153 -0
- package/src/core/project.ts +56 -0
- package/src/core/schema.ts +1057 -0
- package/src/core/server-features.ts +23 -0
- package/src/core/sources/assets.ts +77 -0
- package/src/core/sources/cache.ts +122 -0
- package/src/core/sources/filesystem.ts +99 -0
- package/src/core/sources/mdx-remote.ts +216 -0
- package/src/core/sources/mintlify.ts +161 -0
- package/src/core/sources/normalize.ts +227 -0
- package/src/core/sources/notion.ts +440 -0
- package/src/core/sources/portable-text.ts +143 -0
- package/src/core/sources/read.ts +36 -0
- package/src/core/sources/resolve.ts +158 -0
- package/src/core/sources/sanity.ts +218 -0
- package/src/core/sources/types.ts +105 -0
- package/src/core/types.ts +261 -0
- package/src/core/ui-packs/ar.ts +47 -0
- package/src/core/ui-packs/bg.ts +47 -0
- package/src/core/ui-packs/bn.ts +47 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +47 -0
- package/src/core/ui-packs/da.ts +47 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +47 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +47 -0
- package/src/core/ui-packs/fi.ts +47 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +47 -0
- package/src/core/ui-packs/hi.ts +47 -0
- package/src/core/ui-packs/hr.ts +47 -0
- package/src/core/ui-packs/hu.ts +47 -0
- package/src/core/ui-packs/id.ts +47 -0
- package/src/core/ui-packs/index.ts +87 -0
- package/src/core/ui-packs/it.ts +47 -0
- package/src/core/ui-packs/ja.ts +47 -0
- package/src/core/ui-packs/ko.ts +47 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +47 -0
- package/src/core/ui-packs/pl.ts +47 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +47 -0
- package/src/core/ui-packs/ro.ts +47 -0
- package/src/core/ui-packs/ru.ts +47 -0
- package/src/core/ui-packs/sk.ts +47 -0
- package/src/core/ui-packs/sr.ts +47 -0
- package/src/core/ui-packs/sv.ts +47 -0
- package/src/core/ui-packs/th.ts +47 -0
- package/src/core/ui-packs/tr.ts +47 -0
- package/src/core/ui-packs/uk.ts +47 -0
- package/src/core/ui-packs/vi.ts +47 -0
- package/src/core/ui-packs/zh-tw.ts +47 -0
- package/src/core/ui-packs/zh.ts +47 -0
- package/src/core/version.ts +23 -0
- package/src/deploy/robots.ts +20 -0
- package/src/deploy/rss.ts +128 -0
- package/src/deploy/sitemap.ts +28 -0
- package/src/index.ts +27 -0
- package/src/markdown/code-title.ts +71 -0
- package/src/markdown/directives.ts +83 -0
- package/src/markdown/heading-anchors.ts +137 -0
- package/src/markdown/index.ts +159 -0
- package/src/markdown/inline-code.ts +108 -0
- package/src/markdown/language-icon.ts +172 -0
- package/src/markdown/math.ts +32 -0
- package/src/markdown/mdast.ts +48 -0
- package/src/markdown/mermaid.ts +37 -0
- package/src/markdown/package-commands.ts +159 -0
- package/src/markdown/package-install.ts +40 -0
- package/src/migrate/fumadocs/config.ts +106 -0
- package/src/migrate/fumadocs/content.ts +365 -0
- package/src/migrate/fumadocs/frontmatter.ts +18 -0
- package/src/migrate/fumadocs/index.ts +252 -0
- package/src/migrate/fumadocs/meta.ts +114 -0
- package/src/migrate/migrate.ts +53 -0
- package/src/migrate/mintlify/config.ts +1040 -0
- package/src/migrate/mintlify/content.ts +98 -0
- package/src/migrate/mintlify/frontmatter.ts +126 -0
- package/src/migrate/mintlify/i18n.ts +51 -0
- package/src/migrate/mintlify/icons.ts +128 -0
- package/src/migrate/mintlify/index.ts +266 -0
- package/src/migrate/mintlify/snippets.ts +305 -0
- package/src/migrate/mintlify/transform.ts +81 -0
- package/src/migrate/nextra/content.ts +46 -0
- package/src/migrate/nextra/frontmatter.ts +40 -0
- package/src/migrate/nextra/index.ts +374 -0
- package/src/migrate/nextra/meta.ts +266 -0
- package/src/migrate/shared.ts +623 -0
- package/src/migrate/starlight/config.ts +459 -0
- package/src/migrate/starlight/content.ts +78 -0
- package/src/migrate/starlight/frontmatter.ts +111 -0
- package/src/migrate/starlight/i18n.ts +54 -0
- package/src/migrate/starlight/index.ts +131 -0
- package/src/og/card.ts +92 -0
- package/src/og/index.ts +2 -0
- package/src/openapi/scalar.ts +246 -0
- package/src/registry/eject.ts +263 -0
- package/src/registry/registry.ts +100 -0
- package/src/registry/rewrite-imports.ts +39 -0
- package/src/runtime/index.ts +14 -0
- package/src/search/build.ts +23 -0
- package/src/search/documents.ts +165 -0
- package/src/search/orama-index.ts +66 -0
- package/src/search/providers.ts +91 -0
- package/src/search/sync/algolia.ts +30 -0
- package/src/search/sync/index.ts +50 -0
- package/src/search/sync/orama-cloud.ts +40 -0
- package/src/search/sync/typesense.ts +65 -0
- package/src/seo/jsonld.ts +113 -0
- package/src/theme/entry.ts +608 -0
- package/src/theme/fonts.ts +198 -0
- package/src/theme/icons.ts +184 -0
- package/src/theme/palette.ts +143 -0
- package/src/theme/twoslash.ts +81 -0
|
@@ -0,0 +1,1040 @@
|
|
|
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
|
+
|
|
13
|
+
type JsonObject = Record<string, unknown>;
|
|
14
|
+
type NavigationSelectors = ResolvedConfig["navigation"]["selectors"];
|
|
15
|
+
type NavigationSelectorItem = NavigationSelectors[number]["items"][number];
|
|
16
|
+
type NavigationChromeVariants = NonNullable<
|
|
17
|
+
BlumeConfig["navigation"]
|
|
18
|
+
>["chromeVariants"];
|
|
19
|
+
type NavigationSidebarVariants =
|
|
20
|
+
ResolvedConfig["navigation"]["sidebarVariants"];
|
|
21
|
+
|
|
22
|
+
const MINTLIFY_DEFAULT_IGNORES = [
|
|
23
|
+
"**/_*",
|
|
24
|
+
"**/.*",
|
|
25
|
+
".git/**",
|
|
26
|
+
".github/**",
|
|
27
|
+
".claude/**",
|
|
28
|
+
".agents/**",
|
|
29
|
+
".mintlify/**",
|
|
30
|
+
".idea/**",
|
|
31
|
+
".vscode/**",
|
|
32
|
+
".blume/**",
|
|
33
|
+
"node_modules/**",
|
|
34
|
+
"build/**",
|
|
35
|
+
"dist/**",
|
|
36
|
+
"coverage/**",
|
|
37
|
+
".cache/**",
|
|
38
|
+
"snippets/**",
|
|
39
|
+
"tmp/**",
|
|
40
|
+
"temp/**",
|
|
41
|
+
"README.md",
|
|
42
|
+
"README.mdx",
|
|
43
|
+
"skill.md",
|
|
44
|
+
"LICENSE.md",
|
|
45
|
+
"CHANGELOG.md",
|
|
46
|
+
"CONTRIBUTING.md",
|
|
47
|
+
];
|
|
48
|
+
const API_ENDPOINT_REF =
|
|
49
|
+
/^(?<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(?<path>\/.*)$/iu;
|
|
50
|
+
const VARIABLE_NAME = /^[A-Za-z0-9-]+$/u;
|
|
51
|
+
|
|
52
|
+
const asObject = (value: unknown): JsonObject | null =>
|
|
53
|
+
value && typeof value === "object" && !Array.isArray(value)
|
|
54
|
+
? (value as JsonObject)
|
|
55
|
+
: null;
|
|
56
|
+
|
|
57
|
+
const asArray = (value: unknown): unknown[] =>
|
|
58
|
+
Array.isArray(value) ? value : [];
|
|
59
|
+
|
|
60
|
+
const asString = (value: unknown): string | undefined =>
|
|
61
|
+
typeof value === "string" && value.length > 0 ? value : undefined;
|
|
62
|
+
|
|
63
|
+
const asDirectoryMode = (value: unknown): DirectoryMode | undefined =>
|
|
64
|
+
value === "accordion" || value === "card" || value === "none"
|
|
65
|
+
? value
|
|
66
|
+
: undefined;
|
|
67
|
+
|
|
68
|
+
const withoutUndefined = <T extends JsonObject>(value: T): T =>
|
|
69
|
+
Object.fromEntries(
|
|
70
|
+
Object.entries(value).filter(([, item]) => item !== undefined)
|
|
71
|
+
) as T;
|
|
72
|
+
|
|
73
|
+
const hasOwn = (object: JsonObject, key: string): boolean =>
|
|
74
|
+
Object.hasOwn(object, key);
|
|
75
|
+
|
|
76
|
+
const isInsideRoot = (root: string, candidate: string): boolean => {
|
|
77
|
+
const rel = relative(root, candidate);
|
|
78
|
+
return rel === "" || (!rel.startsWith("..") && !rel.startsWith("/"));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const readJsonFile = async (file: string): Promise<unknown> => {
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(await readFile(file, "utf-8"));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new BlumeError({
|
|
86
|
+
code: "BLUME_MINTLIFY_CONFIG_INVALID",
|
|
87
|
+
file,
|
|
88
|
+
message: `Could not parse Mintlify config: ${(error as Error).message}`,
|
|
89
|
+
severity: "error",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const resolveRefs = async (
|
|
95
|
+
value: unknown,
|
|
96
|
+
options: { file: string; root: string; seen: Set<string> }
|
|
97
|
+
): Promise<unknown> => {
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
return Promise.all(
|
|
100
|
+
value.map((item) => resolveRefs(item, options))
|
|
101
|
+
) as Promise<unknown[]>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const object = asObject(value);
|
|
105
|
+
if (!object) {
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const ref = asString(object.$ref);
|
|
110
|
+
if (ref) {
|
|
111
|
+
const refFile = resolve(dirname(options.file), ref);
|
|
112
|
+
if (!isInsideRoot(options.root, refFile)) {
|
|
113
|
+
throw new BlumeError({
|
|
114
|
+
code: "BLUME_MINTLIFY_REF_OUTSIDE_ROOT",
|
|
115
|
+
file: options.file,
|
|
116
|
+
message: `Mintlify $ref points outside the project root: ${ref}`,
|
|
117
|
+
severity: "error",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (options.seen.has(refFile)) {
|
|
121
|
+
throw new BlumeError({
|
|
122
|
+
code: "BLUME_MINTLIFY_REF_CYCLE",
|
|
123
|
+
file: options.file,
|
|
124
|
+
message: `Mintlify $ref cycle detected at ${ref}`,
|
|
125
|
+
severity: "error",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
options.seen.add(refFile);
|
|
130
|
+
const resolved = await resolveRefs(await readJsonFile(refFile), {
|
|
131
|
+
file: refFile,
|
|
132
|
+
root: options.root,
|
|
133
|
+
seen: options.seen,
|
|
134
|
+
});
|
|
135
|
+
options.seen.delete(refFile);
|
|
136
|
+
|
|
137
|
+
const siblings = Object.fromEntries(
|
|
138
|
+
Object.entries(object).filter(([key]) => key !== "$ref")
|
|
139
|
+
);
|
|
140
|
+
if (asObject(resolved)) {
|
|
141
|
+
return resolveRefs({ ...(resolved as JsonObject), ...siblings }, options);
|
|
142
|
+
}
|
|
143
|
+
return resolved;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Object.fromEntries(
|
|
147
|
+
await Promise.all(
|
|
148
|
+
Object.entries(object).map(async ([key, item]) => [
|
|
149
|
+
key,
|
|
150
|
+
await resolveRefs(item, options),
|
|
151
|
+
])
|
|
152
|
+
)
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const normalizePageRef = (ref: string): string =>
|
|
157
|
+
ref.replace(/\.(?<ext>mdx?|mdoc)$/u, "");
|
|
158
|
+
|
|
159
|
+
const labelForNavItem = (item: JsonObject): string | undefined =>
|
|
160
|
+
asString(item.group) ??
|
|
161
|
+
asString(item.label) ??
|
|
162
|
+
asString(item.anchor) ??
|
|
163
|
+
asString(item.tab) ??
|
|
164
|
+
asString(item.dropdown) ??
|
|
165
|
+
asString(item.product) ??
|
|
166
|
+
asString(item.version) ??
|
|
167
|
+
asString(item.language) ??
|
|
168
|
+
asString(item.item) ??
|
|
169
|
+
asString(item.name) ??
|
|
170
|
+
asString(item.title);
|
|
171
|
+
|
|
172
|
+
const childNavigationArrays = (item: JsonObject): unknown[][] => [
|
|
173
|
+
asArray(item.pages),
|
|
174
|
+
asArray(item.groups),
|
|
175
|
+
asArray(item.menu),
|
|
176
|
+
asArray(item.tabs),
|
|
177
|
+
asArray(item.anchors),
|
|
178
|
+
asArray(item.dropdowns),
|
|
179
|
+
asArray(item.products),
|
|
180
|
+
asArray(item.versions),
|
|
181
|
+
asArray(item.languages),
|
|
182
|
+
asArray(item.items),
|
|
183
|
+
asArray(item.children),
|
|
184
|
+
];
|
|
185
|
+
|
|
186
|
+
const childItemsFor = (item: JsonObject): unknown[] => {
|
|
187
|
+
for (const items of childNavigationArrays(item)) {
|
|
188
|
+
if (items.length > 0) {
|
|
189
|
+
return items;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return [];
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const mintlifyNavigationItems = (navigation: JsonObject): unknown[] => {
|
|
196
|
+
const pages = asArray(navigation.pages);
|
|
197
|
+
if (pages.length > 0) {
|
|
198
|
+
return pages;
|
|
199
|
+
}
|
|
200
|
+
const groups = asArray(navigation.groups);
|
|
201
|
+
if (groups.length > 0) {
|
|
202
|
+
return groups;
|
|
203
|
+
}
|
|
204
|
+
return [
|
|
205
|
+
...asArray(navigation.tabs),
|
|
206
|
+
...asArray(navigation.anchors),
|
|
207
|
+
...asArray(navigation.dropdowns),
|
|
208
|
+
...asArray(navigation.products),
|
|
209
|
+
...asArray(navigation.versions),
|
|
210
|
+
...asArray(navigation.languages),
|
|
211
|
+
];
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const normalizeDirectory = (value: string): string =>
|
|
215
|
+
normalizePageRef(value).replaceAll(/^\/+|\/+$/gu, "");
|
|
216
|
+
|
|
217
|
+
const tabPathFromRef = (ref: string): string => {
|
|
218
|
+
const normalized = normalizeDirectory(ref).replaceAll(/\/index$/gu, "");
|
|
219
|
+
return normalized.length === 0 || normalized === "index"
|
|
220
|
+
? "/"
|
|
221
|
+
: `/${normalized}`;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const navItemPath = (item: unknown): string | undefined => {
|
|
225
|
+
if (typeof item === "string") {
|
|
226
|
+
return API_ENDPOINT_REF.test(item) ? undefined : tabPathFromRef(item);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const object = asObject(item);
|
|
230
|
+
if (!object) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const href = asString(object.href);
|
|
235
|
+
if (href) {
|
|
236
|
+
return href;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const root = asString(object.root);
|
|
240
|
+
if (root) {
|
|
241
|
+
return tabPathFromRef(root);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const child of childItemsFor(object)) {
|
|
245
|
+
const path = navItemPath(child);
|
|
246
|
+
if (path) {
|
|
247
|
+
return path;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return undefined;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const optionalBoolean = (value: unknown): boolean | undefined => {
|
|
254
|
+
if (value === true) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (value === false) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const mintlifyBackgroundColor = (
|
|
264
|
+
value: unknown
|
|
265
|
+
): { dark?: string; light?: string } => {
|
|
266
|
+
const object = asObject(value);
|
|
267
|
+
if (!object) {
|
|
268
|
+
return {};
|
|
269
|
+
}
|
|
270
|
+
const color = asObject(object.color);
|
|
271
|
+
return {
|
|
272
|
+
dark: asString(color?.dark),
|
|
273
|
+
light: asString(color?.light),
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const mintlifyBackgroundImage = (
|
|
278
|
+
value: unknown
|
|
279
|
+
): { dark?: string; light?: string } => {
|
|
280
|
+
const object = asObject(value);
|
|
281
|
+
if (!object) {
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
if (typeof object.image === "string") {
|
|
285
|
+
return { light: object.image };
|
|
286
|
+
}
|
|
287
|
+
const image = asObject(object.image);
|
|
288
|
+
return {
|
|
289
|
+
dark: asString(image?.dark),
|
|
290
|
+
light: asString(image?.light),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const mintlifyBackgroundDecoration = (
|
|
295
|
+
value: unknown
|
|
296
|
+
): "gradient" | "grid" | "windows" | undefined => {
|
|
297
|
+
const object = asObject(value);
|
|
298
|
+
const decoration = asString(object?.decoration);
|
|
299
|
+
if (
|
|
300
|
+
decoration === "gradient" ||
|
|
301
|
+
decoration === "grid" ||
|
|
302
|
+
decoration === "windows"
|
|
303
|
+
) {
|
|
304
|
+
return decoration;
|
|
305
|
+
}
|
|
306
|
+
return undefined;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const sidebarItemPaths = (items: SidebarItemConfig[]): string[] =>
|
|
310
|
+
items.flatMap((item) => {
|
|
311
|
+
if (typeof item === "string") {
|
|
312
|
+
return [tabPathFromRef(item)];
|
|
313
|
+
}
|
|
314
|
+
if (item.href) {
|
|
315
|
+
return [item.href];
|
|
316
|
+
}
|
|
317
|
+
const paths = item.root ? [tabPathFromRef(item.root)] : [];
|
|
318
|
+
return item.items ? [...paths, ...sidebarItemPaths(item.items)] : paths;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const collapsedFromExpanded = (value: unknown): boolean | undefined => {
|
|
322
|
+
if (value === false) {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
if (value === true) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return undefined;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
interface SidebarOptions {
|
|
332
|
+
directory?: DirectoryMode;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
type SidebarItemConverter = (
|
|
336
|
+
items: unknown[],
|
|
337
|
+
options: SidebarOptions
|
|
338
|
+
) => Promise<SidebarItemConfig[]>;
|
|
339
|
+
|
|
340
|
+
const toSidebarObjectItems = async (
|
|
341
|
+
object: JsonObject,
|
|
342
|
+
options: SidebarOptions,
|
|
343
|
+
convertItems: SidebarItemConverter
|
|
344
|
+
): Promise<SidebarItemConfig[]> => {
|
|
345
|
+
const label = labelForNavItem(object);
|
|
346
|
+
const href = asString(object.href);
|
|
347
|
+
const icon = asString(object.icon);
|
|
348
|
+
const groupLabel = asString(object.group);
|
|
349
|
+
const tag = groupLabel ? asString(object.tag) : undefined;
|
|
350
|
+
const root = asString(object.root);
|
|
351
|
+
const rootRef = root && groupLabel ? normalizePageRef(root) : undefined;
|
|
352
|
+
const directory = asDirectoryMode(object.directory) ?? options.directory;
|
|
353
|
+
const rootDirectory = rootRef && directory !== "none" ? directory : undefined;
|
|
354
|
+
const children = childItemsFor(object);
|
|
355
|
+
const nested = await convertItems(children, { ...options, directory });
|
|
356
|
+
|
|
357
|
+
if (nested.length > 0) {
|
|
358
|
+
return label
|
|
359
|
+
? [
|
|
360
|
+
withoutUndefined({
|
|
361
|
+
badge: tag,
|
|
362
|
+
collapsed: collapsedFromExpanded(object.expanded),
|
|
363
|
+
directory: rootDirectory,
|
|
364
|
+
icon,
|
|
365
|
+
items: nested,
|
|
366
|
+
label,
|
|
367
|
+
root: rootRef,
|
|
368
|
+
}),
|
|
369
|
+
]
|
|
370
|
+
: nested;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (label && rootRef) {
|
|
374
|
+
return [
|
|
375
|
+
withoutUndefined({
|
|
376
|
+
badge: tag,
|
|
377
|
+
directory: rootDirectory,
|
|
378
|
+
icon,
|
|
379
|
+
label,
|
|
380
|
+
root: rootRef,
|
|
381
|
+
}),
|
|
382
|
+
];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (label && href) {
|
|
386
|
+
return [withoutUndefined({ badge: tag, href, icon, label })];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return [];
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const toSidebarItems = async (
|
|
393
|
+
items: unknown[],
|
|
394
|
+
options: SidebarOptions
|
|
395
|
+
): Promise<SidebarItemConfig[]> => {
|
|
396
|
+
const itemLists = await Promise.all(
|
|
397
|
+
items.map((item): SidebarItemConfig[] | Promise<SidebarItemConfig[]> => {
|
|
398
|
+
if (typeof item === "string") {
|
|
399
|
+
return [normalizePageRef(item)];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const object = asObject(item);
|
|
403
|
+
if (!object) {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return toSidebarObjectItems(object, options, toSidebarItems);
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
return itemLists.flat();
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const selectorItemsFor = (items: unknown[]): NavigationSelectorItem[] =>
|
|
414
|
+
items.flatMap((item) => {
|
|
415
|
+
const object = asObject(item);
|
|
416
|
+
if (!object) {
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
const label = labelForNavItem(object);
|
|
420
|
+
const path = navItemPath(object);
|
|
421
|
+
if (!label || !path) {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
return [
|
|
425
|
+
withoutUndefined({
|
|
426
|
+
description: asString(object.description),
|
|
427
|
+
icon: asString(object.icon),
|
|
428
|
+
label,
|
|
429
|
+
path,
|
|
430
|
+
tag: asString(object.tag),
|
|
431
|
+
}),
|
|
432
|
+
];
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const isExternalRoute = (path: string): boolean =>
|
|
436
|
+
path.startsWith("http://") ||
|
|
437
|
+
path.startsWith("https://") ||
|
|
438
|
+
path.startsWith("mailto:") ||
|
|
439
|
+
path.startsWith("tel:");
|
|
440
|
+
|
|
441
|
+
const hasOwnNavigationContent = (item: JsonObject): boolean =>
|
|
442
|
+
Boolean(asString(item.root)) || childItemsFor(item).length > 0;
|
|
443
|
+
|
|
444
|
+
const mintlifySidebarVariants = async (
|
|
445
|
+
spec: JsonObject
|
|
446
|
+
): Promise<NavigationSidebarVariants> => {
|
|
447
|
+
const navigation = asObject(spec.navigation) ?? {};
|
|
448
|
+
const global = asObject(navigation.global) ?? {};
|
|
449
|
+
interface SidebarVariantCandidate {
|
|
450
|
+
item: unknown;
|
|
451
|
+
path: string;
|
|
452
|
+
}
|
|
453
|
+
const candidates: SidebarVariantCandidate[] = [];
|
|
454
|
+
|
|
455
|
+
const addCandidate = (item: unknown): void => {
|
|
456
|
+
const path = navItemPath(item);
|
|
457
|
+
if (!path || isExternalRoute(path)) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
candidates.push({ item, path });
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
for (const item of [
|
|
465
|
+
...asArray(navigation.tabs),
|
|
466
|
+
...asArray(navigation.anchors),
|
|
467
|
+
...asArray(global.anchors),
|
|
468
|
+
]) {
|
|
469
|
+
const object = asObject(item);
|
|
470
|
+
if (!object) {
|
|
471
|
+
addCandidate(item);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
for (const menuItem of asArray(object.menu)) {
|
|
476
|
+
addCandidate(menuItem);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (hasOwnNavigationContent(object)) {
|
|
480
|
+
addCandidate(item);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
for (const item of [
|
|
485
|
+
...asArray(navigation.dropdowns),
|
|
486
|
+
...asArray(navigation.products),
|
|
487
|
+
...asArray(navigation.versions),
|
|
488
|
+
...asArray(navigation.languages),
|
|
489
|
+
]) {
|
|
490
|
+
addCandidate(item);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const resolved = await Promise.all(
|
|
494
|
+
candidates.map(async (candidate) => {
|
|
495
|
+
const items = await toSidebarItems([candidate.item], {});
|
|
496
|
+
if (items.length === 0) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
return [candidate.path, ...sidebarItemPaths(items)]
|
|
500
|
+
.filter((path) => !isExternalRoute(path))
|
|
501
|
+
.map((path) => ({ items, path }));
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
const seen = new Set<string>();
|
|
505
|
+
return resolved.flat().flatMap((variant) => {
|
|
506
|
+
if (!variant || seen.has(variant.path)) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
seen.add(variant.path);
|
|
510
|
+
return [variant];
|
|
511
|
+
});
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const mintlifyTabs = (
|
|
515
|
+
spec: JsonObject
|
|
516
|
+
): NonNullable<BlumeConfig["navigation"]>["tabs"] => {
|
|
517
|
+
const navigation = asObject(spec.navigation) ?? {};
|
|
518
|
+
const global = asObject(navigation.global) ?? {};
|
|
519
|
+
|
|
520
|
+
const items = [
|
|
521
|
+
...asArray(navigation.tabs),
|
|
522
|
+
...asArray(global.anchors),
|
|
523
|
+
].filter(Boolean);
|
|
524
|
+
const seen = new Set<string>();
|
|
525
|
+
|
|
526
|
+
return items.flatMap((item) => {
|
|
527
|
+
const object = asObject(item);
|
|
528
|
+
if (!object) {
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
const label = labelForNavItem(object);
|
|
532
|
+
const path = navItemPath(object);
|
|
533
|
+
if (!label || !path) {
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
const key = `${label}\u0000${path}`;
|
|
537
|
+
if (seen.has(key)) {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
seen.add(key);
|
|
541
|
+
const menu = asArray(object.menu);
|
|
542
|
+
const menuItems = selectorItemsFor(
|
|
543
|
+
menu.length > 0 ? menu : childItemsFor(object)
|
|
544
|
+
);
|
|
545
|
+
return [
|
|
546
|
+
withoutUndefined({
|
|
547
|
+
icon: asString(object.icon),
|
|
548
|
+
items: menuItems.length > 0 ? menuItems : undefined,
|
|
549
|
+
label,
|
|
550
|
+
path,
|
|
551
|
+
}),
|
|
552
|
+
];
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const mintlifySelectors = (spec: JsonObject): NavigationSelectors => {
|
|
557
|
+
const navigation = asObject(spec.navigation) ?? {};
|
|
558
|
+
return [
|
|
559
|
+
{
|
|
560
|
+
items: selectorItemsFor(asArray(navigation.dropdowns)),
|
|
561
|
+
kind: "dropdown" as const,
|
|
562
|
+
label: "Dropdowns",
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
items: selectorItemsFor(asArray(navigation.products)),
|
|
566
|
+
kind: "product" as const,
|
|
567
|
+
label: "Products",
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
items: selectorItemsFor(asArray(navigation.versions)),
|
|
571
|
+
kind: "version" as const,
|
|
572
|
+
label: "Versions",
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
items: selectorItemsFor(asArray(navigation.languages)),
|
|
576
|
+
kind: "language" as const,
|
|
577
|
+
label: "Languages",
|
|
578
|
+
},
|
|
579
|
+
].filter((selector) => selector.items.length > 0);
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const navbarTypeLabel = (type: string | undefined): string | undefined => {
|
|
583
|
+
if (type === "github") {
|
|
584
|
+
return "GitHub";
|
|
585
|
+
}
|
|
586
|
+
if (type === "discord") {
|
|
587
|
+
return "Discord";
|
|
588
|
+
}
|
|
589
|
+
return undefined;
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const navbarLinkType = (
|
|
593
|
+
type: string | undefined
|
|
594
|
+
): "github" | "discord" | undefined =>
|
|
595
|
+
type === "github" || type === "discord" ? type : undefined;
|
|
596
|
+
|
|
597
|
+
const navbarPrimaryType = (
|
|
598
|
+
type: string | undefined
|
|
599
|
+
): "button" | "github" | "discord" =>
|
|
600
|
+
type === "github" || type === "discord" ? type : "button";
|
|
601
|
+
|
|
602
|
+
const mintlifyNavbar = (value: unknown): NonNullable<BlumeConfig["navbar"]> => {
|
|
603
|
+
const object = asObject(value);
|
|
604
|
+
if (!object) {
|
|
605
|
+
return { links: [] };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const links = asArray(object.links).flatMap((item) => {
|
|
609
|
+
const itemObject = asObject(item);
|
|
610
|
+
const href = itemObject ? asString(itemObject.href) : undefined;
|
|
611
|
+
const type = itemObject ? asString(itemObject.type) : undefined;
|
|
612
|
+
const label = itemObject
|
|
613
|
+
? (asString(itemObject.label) ?? navbarTypeLabel(type))
|
|
614
|
+
: undefined;
|
|
615
|
+
if (!itemObject || !href || !label) {
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
618
|
+
return [
|
|
619
|
+
withoutUndefined({
|
|
620
|
+
href,
|
|
621
|
+
icon: asString(itemObject.icon),
|
|
622
|
+
label,
|
|
623
|
+
type: navbarLinkType(type),
|
|
624
|
+
}),
|
|
625
|
+
];
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const primaryObject = asObject(object.primary);
|
|
629
|
+
const primaryHref = primaryObject ? asString(primaryObject.href) : undefined;
|
|
630
|
+
const primaryType = primaryObject
|
|
631
|
+
? (asString(primaryObject.type) ?? "button")
|
|
632
|
+
: undefined;
|
|
633
|
+
const primaryLabel = primaryObject
|
|
634
|
+
? (asString(primaryObject.label) ?? navbarTypeLabel(primaryType))
|
|
635
|
+
: undefined;
|
|
636
|
+
const primary =
|
|
637
|
+
primaryObject && primaryHref && primaryLabel
|
|
638
|
+
? withoutUndefined({
|
|
639
|
+
href: primaryHref,
|
|
640
|
+
label: primaryLabel,
|
|
641
|
+
type: navbarPrimaryType(primaryType),
|
|
642
|
+
})
|
|
643
|
+
: undefined;
|
|
644
|
+
|
|
645
|
+
return withoutUndefined({ links, primary });
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
const mintignorePatterns = async (root: string): Promise<string[]> => {
|
|
649
|
+
try {
|
|
650
|
+
const raw = await readFile(resolve(root, ".mintignore"), "utf-8");
|
|
651
|
+
return raw
|
|
652
|
+
.split("\n")
|
|
653
|
+
.map((line) => line.trim())
|
|
654
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
655
|
+
.filter((line) => !line.startsWith("!"))
|
|
656
|
+
.map((line) => (line.endsWith("/") ? `${line}**` : line));
|
|
657
|
+
} catch {
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const mintlifyRedirects = (
|
|
663
|
+
spec: JsonObject
|
|
664
|
+
): NonNullable<BlumeConfig["redirects"]> =>
|
|
665
|
+
asArray(spec.redirects).flatMap((redirect) => {
|
|
666
|
+
const object = asObject(redirect);
|
|
667
|
+
if (!object) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
const from = asString(object.source) ?? asString(object.from);
|
|
671
|
+
const to =
|
|
672
|
+
asString(object.destination) ??
|
|
673
|
+
asString(object.to) ??
|
|
674
|
+
asString(object.redirect);
|
|
675
|
+
if (!from || !to) {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
return [{ from, to }];
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const mintlifyContextual = (
|
|
682
|
+
value: unknown
|
|
683
|
+
): NonNullable<BlumeConfig["contextual"]> => {
|
|
684
|
+
const object = asObject(value);
|
|
685
|
+
if (!object) {
|
|
686
|
+
return { options: [] };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const display = object.display === "toc" ? "toc" : "header";
|
|
690
|
+
const options: NonNullable<BlumeConfig["contextual"]>["options"] = [];
|
|
691
|
+
for (const option of asArray(object.options)) {
|
|
692
|
+
if (typeof option === "string") {
|
|
693
|
+
options.push(option);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const optionObject = asObject(option);
|
|
698
|
+
const title = optionObject ? asString(optionObject.title) : undefined;
|
|
699
|
+
if (!optionObject || !title) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
options.push(
|
|
704
|
+
withoutUndefined({
|
|
705
|
+
description: asString(optionObject.description),
|
|
706
|
+
href: asString(optionObject.href),
|
|
707
|
+
icon: asString(optionObject.icon),
|
|
708
|
+
title,
|
|
709
|
+
})
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return { display, options };
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const mintlifyFooter = (value: unknown): NonNullable<BlumeConfig["footer"]> => {
|
|
717
|
+
const object = asObject(value);
|
|
718
|
+
const socials = asObject(object?.socials);
|
|
719
|
+
const links = asArray(object?.links)
|
|
720
|
+
.flatMap((group) => {
|
|
721
|
+
const groupObject = asObject(group);
|
|
722
|
+
const items = asArray(groupObject?.items).flatMap((item) => {
|
|
723
|
+
const itemObject = asObject(item);
|
|
724
|
+
const label = itemObject ? asString(itemObject.label) : undefined;
|
|
725
|
+
const href = itemObject ? asString(itemObject.href) : undefined;
|
|
726
|
+
return label && href ? [{ href, label }] : [];
|
|
727
|
+
});
|
|
728
|
+
if (!groupObject || items.length === 0) {
|
|
729
|
+
return [];
|
|
730
|
+
}
|
|
731
|
+
return [
|
|
732
|
+
withoutUndefined({
|
|
733
|
+
header: asString(groupObject.header),
|
|
734
|
+
items,
|
|
735
|
+
}),
|
|
736
|
+
];
|
|
737
|
+
})
|
|
738
|
+
.slice(0, 4);
|
|
739
|
+
|
|
740
|
+
return {
|
|
741
|
+
links,
|
|
742
|
+
socials: socials
|
|
743
|
+
? Object.fromEntries(
|
|
744
|
+
Object.entries(socials).flatMap(([label, href]) => {
|
|
745
|
+
const hrefValue = asString(href);
|
|
746
|
+
return hrefValue ? [[label, hrefValue]] : [];
|
|
747
|
+
})
|
|
748
|
+
)
|
|
749
|
+
: {},
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const mintlifyLogo = (value: unknown): BlumeConfig["logo"] => {
|
|
754
|
+
if (typeof value === "string") {
|
|
755
|
+
return value;
|
|
756
|
+
}
|
|
757
|
+
const object = asObject(value);
|
|
758
|
+
if (!object) {
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
const dark = asString(object.dark);
|
|
762
|
+
const light = asString(object.light);
|
|
763
|
+
const href = asString(object.href);
|
|
764
|
+
if (!dark && !light && !href) {
|
|
765
|
+
return undefined;
|
|
766
|
+
}
|
|
767
|
+
return withoutUndefined({ dark, href, light });
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const mintlifyFavicon = (value: unknown): BlumeConfig["favicon"] => {
|
|
771
|
+
if (typeof value === "string") {
|
|
772
|
+
return value;
|
|
773
|
+
}
|
|
774
|
+
const object = asObject(value);
|
|
775
|
+
if (!object) {
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
const dark = asString(object.dark);
|
|
779
|
+
const light = asString(object.light);
|
|
780
|
+
if (!dark && !light) {
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
return withoutUndefined({ dark, light });
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
const mintlifyBanner = (value: unknown): BlumeConfig["banner"] => {
|
|
787
|
+
const object = asObject(value);
|
|
788
|
+
const content = object ? asString(object.content) : undefined;
|
|
789
|
+
if (!object || !content) {
|
|
790
|
+
return undefined;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const color = asObject(object.color);
|
|
794
|
+
const dark = color ? asString(color.dark) : undefined;
|
|
795
|
+
const light = color ? asString(color.light) : undefined;
|
|
796
|
+
const rawType = asString(object.type);
|
|
797
|
+
let type: "info" | "warning" | "critical" | undefined;
|
|
798
|
+
if (rawType === "warning" || rawType === "critical" || rawType === "info") {
|
|
799
|
+
type = rawType;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return withoutUndefined({
|
|
803
|
+
color:
|
|
804
|
+
dark || light
|
|
805
|
+
? withoutUndefined({
|
|
806
|
+
dark,
|
|
807
|
+
light,
|
|
808
|
+
})
|
|
809
|
+
: undefined,
|
|
810
|
+
content,
|
|
811
|
+
dismissible: object.dismissible === true ? true : undefined,
|
|
812
|
+
type,
|
|
813
|
+
});
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const mintlifyChromeVariants = (spec: JsonObject): NavigationChromeVariants => {
|
|
817
|
+
const navigation = asObject(spec.navigation) ?? {};
|
|
818
|
+
|
|
819
|
+
return asArray(navigation.languages).flatMap((item) => {
|
|
820
|
+
const object = asObject(item);
|
|
821
|
+
const path = navItemPath(item);
|
|
822
|
+
if (!object || !path || isExternalRoute(path)) {
|
|
823
|
+
return [];
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const banner = hasOwn(object, "banner")
|
|
827
|
+
? mintlifyBanner(object.banner)
|
|
828
|
+
: undefined;
|
|
829
|
+
const footer = hasOwn(object, "footer")
|
|
830
|
+
? mintlifyFooter(object.footer)
|
|
831
|
+
: undefined;
|
|
832
|
+
const navbar = hasOwn(object, "navbar")
|
|
833
|
+
? mintlifyNavbar(object.navbar)
|
|
834
|
+
: undefined;
|
|
835
|
+
if (!banner && !footer && !navbar) {
|
|
836
|
+
return [];
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return [
|
|
840
|
+
withoutUndefined({
|
|
841
|
+
banner,
|
|
842
|
+
footer,
|
|
843
|
+
navbar,
|
|
844
|
+
path,
|
|
845
|
+
}),
|
|
846
|
+
];
|
|
847
|
+
});
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
const mintlifyVariables = (
|
|
851
|
+
value: unknown
|
|
852
|
+
): NonNullable<BlumeConfig["variables"]> => {
|
|
853
|
+
const object = asObject(value);
|
|
854
|
+
if (!object) {
|
|
855
|
+
return {};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return Object.fromEntries(
|
|
859
|
+
Object.entries(object).flatMap(([key, item]) =>
|
|
860
|
+
VARIABLE_NAME.test(key) && typeof item === "string" ? [[key, item]] : []
|
|
861
|
+
)
|
|
862
|
+
);
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
const mintlifyCodeTheme = (
|
|
866
|
+
value: unknown
|
|
867
|
+
):
|
|
868
|
+
| NonNullable<NonNullable<BlumeConfig["markdown"]>["codeBlocks"]>["theme"]
|
|
869
|
+
| undefined => {
|
|
870
|
+
if (value === "system" || value === undefined) {
|
|
871
|
+
return undefined;
|
|
872
|
+
}
|
|
873
|
+
if (value === "dark") {
|
|
874
|
+
return { dark: "github-dark", light: "github-dark" };
|
|
875
|
+
}
|
|
876
|
+
const theme = asString(value);
|
|
877
|
+
if (theme) {
|
|
878
|
+
return { dark: theme, light: theme };
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const object = asObject(value);
|
|
882
|
+
if (!object) {
|
|
883
|
+
return undefined;
|
|
884
|
+
}
|
|
885
|
+
const light = asString(object.light);
|
|
886
|
+
const dark = asString(object.dark);
|
|
887
|
+
if (!light && !dark) {
|
|
888
|
+
return undefined;
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
dark: dark ?? light ?? "github-dark",
|
|
892
|
+
light: light ?? dark ?? "github-light",
|
|
893
|
+
};
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
const mintlifyCodeBlocks = (
|
|
897
|
+
value: unknown
|
|
898
|
+
): NonNullable<BlumeConfig["markdown"]>["codeBlocks"] | undefined => {
|
|
899
|
+
const object = asObject(value);
|
|
900
|
+
const theme = mintlifyCodeTheme(object ? object.theme : value);
|
|
901
|
+
return theme ? { theme } : undefined;
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
const mintlifyMarkdown = (
|
|
905
|
+
value: unknown,
|
|
906
|
+
styling: unknown
|
|
907
|
+
): NonNullable<BlumeConfig["markdown"]> => {
|
|
908
|
+
const object = asObject(value);
|
|
909
|
+
const stylingObject = asObject(styling);
|
|
910
|
+
return withoutUndefined({
|
|
911
|
+
codeBlocks: mintlifyCodeBlocks(stylingObject?.codeblocks),
|
|
912
|
+
math: optionalBoolean(stylingObject?.latex),
|
|
913
|
+
schema: object?.schema === false ? false : undefined,
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
const mintlifyStyling = (
|
|
918
|
+
value: unknown
|
|
919
|
+
): NonNullable<BlumeConfig["styling"]> => {
|
|
920
|
+
const object = asObject(value);
|
|
921
|
+
const eyebrows = asString(object?.eyebrows);
|
|
922
|
+
return withoutUndefined({
|
|
923
|
+
eyebrows:
|
|
924
|
+
eyebrows === "breadcrumbs" || eyebrows === "section"
|
|
925
|
+
? eyebrows
|
|
926
|
+
: undefined,
|
|
927
|
+
});
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const mintlifySeo = (value: unknown): NonNullable<BlumeConfig["seo"]> => {
|
|
931
|
+
const object = asObject(value);
|
|
932
|
+
const metatags = asObject(object?.metatags);
|
|
933
|
+
return {
|
|
934
|
+
metatags: metatags
|
|
935
|
+
? Object.fromEntries(
|
|
936
|
+
Object.entries(metatags).flatMap(([key, item]) => {
|
|
937
|
+
const content = asString(item);
|
|
938
|
+
return content ? [[key, content]] : [];
|
|
939
|
+
})
|
|
940
|
+
)
|
|
941
|
+
: {},
|
|
942
|
+
};
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
const mintlifyIcons = (value: unknown): NonNullable<BlumeConfig["icons"]> => {
|
|
946
|
+
const object = asObject(value);
|
|
947
|
+
const library = object?.library;
|
|
948
|
+
return withoutUndefined({
|
|
949
|
+
library:
|
|
950
|
+
library === "fontawesome" || library === "lucide" || library === "tabler"
|
|
951
|
+
? library
|
|
952
|
+
: undefined,
|
|
953
|
+
});
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
export const loadMintlifyConfig = async (
|
|
957
|
+
root: string,
|
|
958
|
+
file: string
|
|
959
|
+
): Promise<BlumeConfig> => {
|
|
960
|
+
const projectRoot = resolve(root);
|
|
961
|
+
const configFile = resolve(file);
|
|
962
|
+
const spec = asObject(
|
|
963
|
+
await resolveRefs(await readJsonFile(configFile), {
|
|
964
|
+
file: configFile,
|
|
965
|
+
root: projectRoot,
|
|
966
|
+
seen: new Set([configFile]),
|
|
967
|
+
})
|
|
968
|
+
);
|
|
969
|
+
if (!spec) {
|
|
970
|
+
throw new BlumeError({
|
|
971
|
+
code: "BLUME_MINTLIFY_CONFIG_INVALID",
|
|
972
|
+
file: configFile,
|
|
973
|
+
message: "Mintlify config must be a JSON object.",
|
|
974
|
+
severity: "error",
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const navigation = asObject(spec.navigation) ?? {};
|
|
979
|
+
const colors = asObject(spec.colors) ?? {};
|
|
980
|
+
const appearance = asObject(spec.appearance) ?? {};
|
|
981
|
+
const backgroundColor = mintlifyBackgroundColor(spec.background);
|
|
982
|
+
const backgroundImage = mintlifyBackgroundImage(spec.background);
|
|
983
|
+
const seo = asObject(spec.seo) ?? {};
|
|
984
|
+
const search = asObject(spec.search) ?? {};
|
|
985
|
+
const styling = asObject(spec.styling) ?? {};
|
|
986
|
+
|
|
987
|
+
return {
|
|
988
|
+
banner: mintlifyBanner(spec.banner),
|
|
989
|
+
content: {
|
|
990
|
+
exclude: [
|
|
991
|
+
...MINTLIFY_DEFAULT_IGNORES,
|
|
992
|
+
...(await mintignorePatterns(projectRoot)),
|
|
993
|
+
],
|
|
994
|
+
root: ".",
|
|
995
|
+
},
|
|
996
|
+
contextual: mintlifyContextual(spec.contextual),
|
|
997
|
+
description: asString(spec.description),
|
|
998
|
+
favicon: mintlifyFavicon(spec.favicon),
|
|
999
|
+
footer: mintlifyFooter(spec.footer),
|
|
1000
|
+
icons: mintlifyIcons(spec.icons),
|
|
1001
|
+
logo: mintlifyLogo(spec.logo),
|
|
1002
|
+
markdown: mintlifyMarkdown(spec.markdown, styling),
|
|
1003
|
+
navbar: mintlifyNavbar(spec.navbar),
|
|
1004
|
+
navigation: {
|
|
1005
|
+
chromeVariants: mintlifyChromeVariants(spec),
|
|
1006
|
+
selectors: mintlifySelectors(spec),
|
|
1007
|
+
sidebar: await toSidebarItems(mintlifyNavigationItems(navigation), {}),
|
|
1008
|
+
sidebarVariants: await mintlifySidebarVariants(spec),
|
|
1009
|
+
tabs: mintlifyTabs(spec),
|
|
1010
|
+
},
|
|
1011
|
+
redirects: mintlifyRedirects(spec),
|
|
1012
|
+
search: {
|
|
1013
|
+
indexing: {
|
|
1014
|
+
includeHiddenPages: seo.indexing === "all",
|
|
1015
|
+
},
|
|
1016
|
+
prompt: asString(search.prompt),
|
|
1017
|
+
},
|
|
1018
|
+
seo: mintlifySeo(seo),
|
|
1019
|
+
styling: mintlifyStyling(styling),
|
|
1020
|
+
theme: {
|
|
1021
|
+
accent: asString(colors.primary) ?? "blue",
|
|
1022
|
+
accentDark: asString(colors.light),
|
|
1023
|
+
action: asString(colors.dark),
|
|
1024
|
+
background: backgroundColor.light,
|
|
1025
|
+
backgroundDark: backgroundColor.dark,
|
|
1026
|
+
backgroundDecoration: mintlifyBackgroundDecoration(spec.background),
|
|
1027
|
+
backgroundImage: backgroundImage.light,
|
|
1028
|
+
backgroundImageDark: backgroundImage.dark,
|
|
1029
|
+
mode:
|
|
1030
|
+
appearance.default === "light" ||
|
|
1031
|
+
appearance.default === "dark" ||
|
|
1032
|
+
appearance.default === "system"
|
|
1033
|
+
? appearance.default
|
|
1034
|
+
: "system",
|
|
1035
|
+
strict: appearance.strict === true,
|
|
1036
|
+
},
|
|
1037
|
+
title: asString(spec.name) ?? asString(spec.title) ?? "Documentation",
|
|
1038
|
+
variables: mintlifyVariables(spec.variables),
|
|
1039
|
+
};
|
|
1040
|
+
};
|