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.
Files changed (263) hide show
  1. package/bin/blume.mjs +18 -0
  2. package/dist/cli/index.js +11989 -0
  3. package/dist/cli/index.js.map +141 -0
  4. package/docs/01-quickstart.mdx +99 -0
  5. package/docs/02-deployment.mdx +129 -0
  6. package/docs/advanced/api-reference.mdx +114 -0
  7. package/docs/advanced/blog.mdx +121 -0
  8. package/docs/advanced/changelog.mdx +113 -0
  9. package/docs/advanced/custom-pages.mdx +187 -0
  10. package/docs/advanced/meta.ts +7 -0
  11. package/docs/changelog/v0-1-0.mdx +12 -0
  12. package/docs/changelog/v0-2-0.mdx +16 -0
  13. package/docs/configuration/ai.mdx +228 -0
  14. package/docs/configuration/analytics.mdx +98 -0
  15. package/docs/configuration/customization.mdx +91 -0
  16. package/docs/configuration/export.mdx +70 -0
  17. package/docs/configuration/index.mdx +271 -0
  18. package/docs/configuration/meta.ts +15 -0
  19. package/docs/configuration/search.mdx +172 -0
  20. package/docs/configuration/seo.mdx +196 -0
  21. package/docs/configuration/theming.mdx +178 -0
  22. package/docs/content/components.mdx +565 -0
  23. package/docs/content/i18n.mdx +205 -0
  24. package/docs/content/index.mdx +161 -0
  25. package/docs/content/islands.mdx +94 -0
  26. package/docs/content/meta.mdx +119 -0
  27. package/docs/content/meta.ts +15 -0
  28. package/docs/content/navigation.mdx +168 -0
  29. package/docs/content/sources.mdx +216 -0
  30. package/docs/content/syntax.mdx +445 -0
  31. package/docs/index.mdx +112 -0
  32. package/docs/reference/cli.mdx +43 -0
  33. package/docs/reference/frontmatter.mdx +74 -0
  34. package/docs/reference/meta.ts +7 -0
  35. package/package.json +140 -6
  36. package/src/ai/ask.ts +93 -0
  37. package/src/ai/llms.ts +65 -0
  38. package/src/ai/markdown.ts +31 -0
  39. package/src/ai/mcp/data.ts +74 -0
  40. package/src/ai/mcp/discovery.ts +49 -0
  41. package/src/ai/mcp/server.ts +225 -0
  42. package/src/ai/mcp/tools.ts +47 -0
  43. package/src/assets/icon.png +0 -0
  44. package/src/astro/generate.ts +878 -0
  45. package/src/astro/index.ts +4 -0
  46. package/src/astro/integration.ts +74 -0
  47. package/src/astro/islands.ts +131 -0
  48. package/src/astro/markdown-negotiation.ts +68 -0
  49. package/src/astro/pages.ts +28 -0
  50. package/src/astro/templates.ts +1199 -0
  51. package/src/cli/commands/add.ts +81 -0
  52. package/src/cli/commands/build.ts +103 -0
  53. package/src/cli/commands/dev.ts +108 -0
  54. package/src/cli/commands/doctor.ts +74 -0
  55. package/src/cli/commands/eject.ts +57 -0
  56. package/src/cli/commands/init.ts +98 -0
  57. package/src/cli/commands/migrate.ts +39 -0
  58. package/src/cli/commands/preview.ts +39 -0
  59. package/src/cli/commands/sync.ts +52 -0
  60. package/src/cli/commands/validate.ts +60 -0
  61. package/src/cli/index.ts +35 -0
  62. package/src/cli/log.ts +37 -0
  63. package/src/cli/prepare.ts +80 -0
  64. package/src/components/Icon.astro +99 -0
  65. package/src/components/content/Accordion.astro +8 -0
  66. package/src/components/content/AccordionItem.astro +121 -0
  67. package/src/components/content/AutoTypeTable.astro +51 -0
  68. package/src/components/content/Badge.astro +124 -0
  69. package/src/components/content/Callout.astro +73 -0
  70. package/src/components/content/Card.astro +104 -0
  71. package/src/components/content/CardGroup.astro +14 -0
  72. package/src/components/content/CodeGroup.astro +13 -0
  73. package/src/components/content/Color.astro +15 -0
  74. package/src/components/content/ColorItem.astro +87 -0
  75. package/src/components/content/ColorRow.astro +10 -0
  76. package/src/components/content/Column.astro +6 -0
  77. package/src/components/content/Columns.astro +9 -0
  78. package/src/components/content/Expandable.astro +11 -0
  79. package/src/components/content/FileTree.astro +8 -0
  80. package/src/components/content/Frame.astro +70 -0
  81. package/src/components/content/GithubInfo.astro +110 -0
  82. package/src/components/content/Math.astro +24 -0
  83. package/src/components/content/Panel.astro +20 -0
  84. package/src/components/content/Prompt.astro +129 -0
  85. package/src/components/content/Step.astro +34 -0
  86. package/src/components/content/Steps.astro +20 -0
  87. package/src/components/content/Tab.astro +40 -0
  88. package/src/components/content/Tabs.astro +273 -0
  89. package/src/components/content/Tile.astro +42 -0
  90. package/src/components/content/Tooltip.astro +68 -0
  91. package/src/components/content/Tree.astro +300 -0
  92. package/src/components/content/TreeFile.astro +15 -0
  93. package/src/components/content/TreeFolder.astro +62 -0
  94. package/src/components/content/TypeTable.astro +106 -0
  95. package/src/components/content/Update.astro +66 -0
  96. package/src/components/content/Visibility.astro +12 -0
  97. package/src/components/content/Warning.astro +9 -0
  98. package/src/components/content/auto-type-table.ts +141 -0
  99. package/src/components/content/github-info.ts +79 -0
  100. package/src/components/content/mermaid-element.ts +68 -0
  101. package/src/components/github-mark.ts +9 -0
  102. package/src/components/index.ts +14 -0
  103. package/src/components/islands/AskAI.astro +12 -0
  104. package/src/components/islands/ask-ai.tsx +156 -0
  105. package/src/components/layout/Analytics.astro +63 -0
  106. package/src/components/layout/Banner.astro +50 -0
  107. package/src/components/layout/Breadcrumbs.astro +31 -0
  108. package/src/components/layout/Favicon.astro +15 -0
  109. package/src/components/layout/Fonts.astro +14 -0
  110. package/src/components/layout/Header.astro +188 -0
  111. package/src/components/layout/LanguageSwitcher.astro +56 -0
  112. package/src/components/layout/NavTree.astro +462 -0
  113. package/src/components/layout/PageActions.astro +438 -0
  114. package/src/components/layout/PageFeedback.astro +58 -0
  115. package/src/components/layout/Pagination.astro +56 -0
  116. package/src/components/layout/ReferenceLayout.astro +102 -0
  117. package/src/components/layout/RootLayout.astro +533 -0
  118. package/src/components/layout/Search.astro +608 -0
  119. package/src/components/layout/TableOfContents.astro +68 -0
  120. package/src/components/layout/analytics-client.ts +38 -0
  121. package/src/components/layout/nav-utils.ts +87 -0
  122. package/src/components/layout/overrides.ts +32 -0
  123. package/src/components/layout/search/algolia.ts +43 -0
  124. package/src/components/layout/search/endpoint.ts +22 -0
  125. package/src/components/layout/search/flexsearch.ts +52 -0
  126. package/src/components/layout/search/orama-cloud.ts +41 -0
  127. package/src/components/layout/search/orama.ts +26 -0
  128. package/src/components/layout/search/pagefind.ts +43 -0
  129. package/src/components/layout/search/types.ts +163 -0
  130. package/src/components/layout/search/typesense.ts +60 -0
  131. package/src/components/layout/toc-element.ts +108 -0
  132. package/src/core/bridge.ts +92 -0
  133. package/src/core/config.ts +112 -0
  134. package/src/core/content.ts +50 -0
  135. package/src/core/define-components.ts +34 -0
  136. package/src/core/define-meta.ts +20 -0
  137. package/src/core/deployment-env.ts +73 -0
  138. package/src/core/diagnostics.ts +104 -0
  139. package/src/core/graph.ts +128 -0
  140. package/src/core/i18n-ui.ts +171 -0
  141. package/src/core/i18n.ts +169 -0
  142. package/src/core/last-modified.ts +88 -0
  143. package/src/core/links.ts +336 -0
  144. package/src/core/load-module.ts +15 -0
  145. package/src/core/manifest.ts +126 -0
  146. package/src/core/meta.ts +97 -0
  147. package/src/core/navigation.ts +392 -0
  148. package/src/core/package-root.ts +37 -0
  149. package/src/core/project-graph.ts +153 -0
  150. package/src/core/project.ts +56 -0
  151. package/src/core/schema.ts +1057 -0
  152. package/src/core/server-features.ts +23 -0
  153. package/src/core/sources/assets.ts +77 -0
  154. package/src/core/sources/cache.ts +122 -0
  155. package/src/core/sources/filesystem.ts +99 -0
  156. package/src/core/sources/mdx-remote.ts +216 -0
  157. package/src/core/sources/mintlify.ts +161 -0
  158. package/src/core/sources/normalize.ts +227 -0
  159. package/src/core/sources/notion.ts +440 -0
  160. package/src/core/sources/portable-text.ts +143 -0
  161. package/src/core/sources/read.ts +36 -0
  162. package/src/core/sources/resolve.ts +158 -0
  163. package/src/core/sources/sanity.ts +218 -0
  164. package/src/core/sources/types.ts +105 -0
  165. package/src/core/types.ts +261 -0
  166. package/src/core/ui-packs/ar.ts +47 -0
  167. package/src/core/ui-packs/bg.ts +47 -0
  168. package/src/core/ui-packs/bn.ts +47 -0
  169. package/src/core/ui-packs/ca.ts +47 -0
  170. package/src/core/ui-packs/cs.ts +47 -0
  171. package/src/core/ui-packs/da.ts +47 -0
  172. package/src/core/ui-packs/de.ts +47 -0
  173. package/src/core/ui-packs/el.ts +47 -0
  174. package/src/core/ui-packs/es.ts +47 -0
  175. package/src/core/ui-packs/fa.ts +47 -0
  176. package/src/core/ui-packs/fi.ts +47 -0
  177. package/src/core/ui-packs/fr.ts +47 -0
  178. package/src/core/ui-packs/he.ts +47 -0
  179. package/src/core/ui-packs/hi.ts +47 -0
  180. package/src/core/ui-packs/hr.ts +47 -0
  181. package/src/core/ui-packs/hu.ts +47 -0
  182. package/src/core/ui-packs/id.ts +47 -0
  183. package/src/core/ui-packs/index.ts +87 -0
  184. package/src/core/ui-packs/it.ts +47 -0
  185. package/src/core/ui-packs/ja.ts +47 -0
  186. package/src/core/ui-packs/ko.ts +47 -0
  187. package/src/core/ui-packs/nl.ts +47 -0
  188. package/src/core/ui-packs/no.ts +47 -0
  189. package/src/core/ui-packs/pl.ts +47 -0
  190. package/src/core/ui-packs/pt-br.ts +47 -0
  191. package/src/core/ui-packs/pt.ts +47 -0
  192. package/src/core/ui-packs/ro.ts +47 -0
  193. package/src/core/ui-packs/ru.ts +47 -0
  194. package/src/core/ui-packs/sk.ts +47 -0
  195. package/src/core/ui-packs/sr.ts +47 -0
  196. package/src/core/ui-packs/sv.ts +47 -0
  197. package/src/core/ui-packs/th.ts +47 -0
  198. package/src/core/ui-packs/tr.ts +47 -0
  199. package/src/core/ui-packs/uk.ts +47 -0
  200. package/src/core/ui-packs/vi.ts +47 -0
  201. package/src/core/ui-packs/zh-tw.ts +47 -0
  202. package/src/core/ui-packs/zh.ts +47 -0
  203. package/src/core/version.ts +23 -0
  204. package/src/deploy/robots.ts +20 -0
  205. package/src/deploy/rss.ts +128 -0
  206. package/src/deploy/sitemap.ts +28 -0
  207. package/src/index.ts +27 -0
  208. package/src/markdown/code-title.ts +71 -0
  209. package/src/markdown/directives.ts +83 -0
  210. package/src/markdown/heading-anchors.ts +137 -0
  211. package/src/markdown/index.ts +159 -0
  212. package/src/markdown/inline-code.ts +108 -0
  213. package/src/markdown/language-icon.ts +172 -0
  214. package/src/markdown/math.ts +32 -0
  215. package/src/markdown/mdast.ts +48 -0
  216. package/src/markdown/mermaid.ts +37 -0
  217. package/src/markdown/package-commands.ts +159 -0
  218. package/src/markdown/package-install.ts +40 -0
  219. package/src/migrate/fumadocs/config.ts +106 -0
  220. package/src/migrate/fumadocs/content.ts +365 -0
  221. package/src/migrate/fumadocs/frontmatter.ts +18 -0
  222. package/src/migrate/fumadocs/index.ts +252 -0
  223. package/src/migrate/fumadocs/meta.ts +114 -0
  224. package/src/migrate/migrate.ts +53 -0
  225. package/src/migrate/mintlify/config.ts +1040 -0
  226. package/src/migrate/mintlify/content.ts +98 -0
  227. package/src/migrate/mintlify/frontmatter.ts +126 -0
  228. package/src/migrate/mintlify/i18n.ts +51 -0
  229. package/src/migrate/mintlify/icons.ts +128 -0
  230. package/src/migrate/mintlify/index.ts +266 -0
  231. package/src/migrate/mintlify/snippets.ts +305 -0
  232. package/src/migrate/mintlify/transform.ts +81 -0
  233. package/src/migrate/nextra/content.ts +46 -0
  234. package/src/migrate/nextra/frontmatter.ts +40 -0
  235. package/src/migrate/nextra/index.ts +374 -0
  236. package/src/migrate/nextra/meta.ts +266 -0
  237. package/src/migrate/shared.ts +623 -0
  238. package/src/migrate/starlight/config.ts +459 -0
  239. package/src/migrate/starlight/content.ts +78 -0
  240. package/src/migrate/starlight/frontmatter.ts +111 -0
  241. package/src/migrate/starlight/i18n.ts +54 -0
  242. package/src/migrate/starlight/index.ts +131 -0
  243. package/src/og/card.ts +92 -0
  244. package/src/og/index.ts +2 -0
  245. package/src/openapi/scalar.ts +246 -0
  246. package/src/registry/eject.ts +263 -0
  247. package/src/registry/registry.ts +100 -0
  248. package/src/registry/rewrite-imports.ts +39 -0
  249. package/src/runtime/index.ts +14 -0
  250. package/src/search/build.ts +23 -0
  251. package/src/search/documents.ts +165 -0
  252. package/src/search/orama-index.ts +66 -0
  253. package/src/search/providers.ts +91 -0
  254. package/src/search/sync/algolia.ts +30 -0
  255. package/src/search/sync/index.ts +50 -0
  256. package/src/search/sync/orama-cloud.ts +40 -0
  257. package/src/search/sync/typesense.ts +65 -0
  258. package/src/seo/jsonld.ts +113 -0
  259. package/src/theme/entry.ts +608 -0
  260. package/src/theme/fonts.ts +198 -0
  261. package/src/theme/icons.ts +184 -0
  262. package/src/theme/palette.ts +143 -0
  263. 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
+ };