blume 0.6.7 → 0.7.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/dist/cli/index.js +1179 -738
- package/dist/cli/index.js.map +52 -51
- package/dist/types/core/base-path.d.ts +38 -0
- package/dist/types/core/config-input.d.ts +74 -10
- package/dist/types/core/config.d.ts +3 -2
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +1 -3
- package/dist/types/core/schema.d.ts +95 -52
- package/dist/types/core/sources/types.d.ts +2 -0
- package/dist/types/core/types.d.ts +6 -1
- package/docs/02-deployment.mdx +16 -1
- package/docs/03-faq.mdx +8 -8
- package/docs/configuration/index.mdx +6 -0
- package/docs/content/components.mdx +29 -2
- package/docs/content/islands.mdx +8 -0
- package/docs/content/syntax.mdx +13 -0
- package/package.json +2 -1
- package/src/ai/agent-readability.ts +7 -2
- package/src/ai/ask.ts +12 -7
- package/src/ai/llms.ts +15 -4
- package/src/ai/mcp/data.ts +8 -4
- package/src/ai/mcp/server.ts +3 -0
- package/src/astro/component-slots.ts +5 -3
- package/src/astro/examples.ts +12 -7
- package/src/astro/generate.ts +317 -144
- package/src/astro/index.ts +5 -1
- package/src/astro/integration.ts +8 -4
- package/src/astro/islands.ts +11 -5
- package/src/astro/markdown-negotiation.ts +1 -1
- package/src/astro/pages.ts +8 -3
- package/src/astro/templates.ts +166 -19
- package/src/cli/commands/build.ts +32 -19
- package/src/cli/commands/dev.ts +48 -15
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/validate.ts +1 -0
- package/src/cli/dev-lock.ts +26 -15
- package/src/cli/required-secrets.ts +2 -1
- package/src/components/content/CodeBlock.astro +3 -0
- package/src/components/content/Component.astro +30 -16
- package/src/components/content/Diff.astro +3 -1
- package/src/components/content/auto-type-table.ts +18 -8
- package/src/components/content/diff.ts +12 -6
- package/src/components/content/mermaid-element.ts +3 -0
- package/src/components/index.ts +23 -1
- package/src/components/islands/ask-ai.tsx +12 -6
- package/src/components/islands/base-path.ts +28 -0
- package/src/components/islands/hooks.ts +16 -1
- package/src/components/layout/Banner.astro +2 -1
- package/src/components/layout/Breadcrumbs.astro +2 -1
- package/src/components/layout/Favicon.astro +3 -2
- package/src/components/layout/Header.astro +2 -1
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +2 -1
- package/src/components/layout/NavSelector.astro +2 -1
- package/src/components/layout/NavTree.astro +5 -4
- package/src/components/layout/PageFeedback.astro +4 -1
- package/src/components/layout/PageLayout.astro +9 -4
- package/src/components/layout/Pagination.astro +3 -2
- package/src/components/layout/RootLayout.astro +7 -4
- package/src/components/layout/Search.astro +13 -5
- package/src/components/layout/nav-utils.ts +18 -10
- package/src/components/layout/search/pagefind.ts +3 -0
- package/src/components/layout/toc-element.ts +7 -1
- package/src/components/openapi/RequestPanel.astro +7 -1
- package/src/components/openapi/snippets.ts +25 -11
- package/src/core/base-path.ts +70 -0
- package/src/core/component-overrides.ts +103 -74
- package/src/core/config-input.ts +81 -15
- package/src/core/config.ts +5 -3
- package/src/core/content.ts +2 -0
- package/src/core/data.ts +2 -0
- package/src/core/diagnostics.ts +54 -34
- package/src/core/gitignore.ts +4 -1
- package/src/core/graph.ts +156 -88
- package/src/core/i18n-ui.ts +18 -3
- package/src/core/last-modified.ts +2 -0
- package/src/core/links.ts +38 -18
- package/src/core/manifest.ts +62 -45
- package/src/core/nav-diagnostics.ts +1 -1
- package/src/core/navigation.ts +116 -55
- package/src/core/project-graph.ts +10 -9
- package/src/core/schema.ts +572 -621
- package/src/core/sources/github-releases.ts +2 -1
- package/src/core/sources/mdx-remote.ts +58 -54
- package/src/core/sources/normalize.ts +116 -73
- package/src/core/sources/notion.ts +19 -10
- package/src/core/sources/types.ts +2 -0
- package/src/core/tsconfig-aliases.ts +59 -30
- package/src/core/types.ts +6 -1
- package/src/deploy/redirects.ts +18 -0
- package/src/deploy/robots.ts +6 -1
- package/src/deploy/rss.ts +10 -3
- package/src/deploy/sitemap.ts +14 -10
- package/src/markdown/base-links.ts +58 -0
- package/src/markdown/code-title.ts +11 -14
- package/src/markdown/index.ts +34 -9
- package/src/markdown/inline-code.ts +7 -2
- package/src/markdown/themes.ts +24 -0
- package/src/openapi/model.ts +3 -1
- package/src/openapi/references.ts +41 -17
- package/src/openapi/render-mdx.ts +11 -6
- package/src/openapi/scalar.ts +32 -16
- package/src/registry/eject.ts +64 -8
- package/src/search/build.ts +3 -0
- package/src/search/documents.ts +2 -2
- package/src/search/sync/typesense.ts +6 -4
- package/src/seo/jsonld.ts +16 -6
- package/src/theme/entry.ts +85 -20
package/src/core/schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import { FONT_SLUGS, isFontSlug } from "../theme/fonts.ts";
|
|
4
|
+
import { normalizeBasePath } from "./base-path.ts";
|
|
4
5
|
import { uiLocaleOverridesSchema } from "./i18n-ui.ts";
|
|
5
6
|
import type { ContentSource } from "./sources/types.ts";
|
|
6
7
|
|
|
@@ -18,6 +19,9 @@ import type { ContentSource } from "./sources/types.ts";
|
|
|
18
19
|
/** Icon inputs in serializable contexts (frontmatter, meta files). */
|
|
19
20
|
const iconName = z.string().min(1);
|
|
20
21
|
|
|
22
|
+
/** Default include glob for filesystem-backed content sources. */
|
|
23
|
+
const DEFAULT_CONTENT_GLOB = "**/*.{md,mdx}";
|
|
24
|
+
|
|
21
25
|
const hydrationMode = z.enum(["load", "idle", "visible", "media", "only"]);
|
|
22
26
|
export type HydrationMode = z.infer<typeof hydrationMode>;
|
|
23
27
|
|
|
@@ -33,41 +37,36 @@ const dateSchema = z
|
|
|
33
37
|
// Page frontmatter
|
|
34
38
|
// ---------------------------------------------------------------------------
|
|
35
39
|
|
|
36
|
-
const sidebarMetaSchema = z
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
})
|
|
44
|
-
.strict();
|
|
45
|
-
|
|
46
|
-
const seoMetaSchema = z
|
|
47
|
-
.object({
|
|
48
|
-
canonical: z.string().url().optional(),
|
|
49
|
-
description: z.string().optional(),
|
|
50
|
-
image: z.string().optional(),
|
|
51
|
-
noindex: z.boolean().default(false),
|
|
52
|
-
title: z.string().optional(),
|
|
53
|
-
})
|
|
54
|
-
.strict();
|
|
40
|
+
const sidebarMetaSchema = z.strictObject({
|
|
41
|
+
badge: z.string().optional(),
|
|
42
|
+
hidden: z.boolean().default(false),
|
|
43
|
+
icon: iconName.optional(),
|
|
44
|
+
label: z.string().optional(),
|
|
45
|
+
order: z.number().optional(),
|
|
46
|
+
});
|
|
55
47
|
|
|
56
|
-
const
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
48
|
+
const seoMetaSchema = z.strictObject({
|
|
49
|
+
// blume bundles Zod 3; top-level `z.url()` is undefined at runtime and
|
|
50
|
+
// schemas must stay dual-compatible with consumer projects on Zod 4.
|
|
51
|
+
// oxlint-disable-next-line react-doctor/zod-v4-prefer-top-level-string-formats
|
|
52
|
+
canonical: z.string().url().optional(),
|
|
53
|
+
description: z.string().optional(),
|
|
54
|
+
image: z.string().optional(),
|
|
55
|
+
noindex: z.boolean().default(false),
|
|
56
|
+
title: z.string().optional(),
|
|
57
|
+
});
|
|
63
58
|
|
|
64
|
-
const
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
const searchMetaSchema = z.strictObject({
|
|
60
|
+
boost: z.number().optional(),
|
|
61
|
+
exclude: z.boolean().default(false),
|
|
62
|
+
tags: z.array(z.string()).optional(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const changelogMetaSchema = z.strictObject({
|
|
66
|
+
category: z.string().optional(),
|
|
67
|
+
date: dateSchema.optional(),
|
|
68
|
+
version: z.string().optional(),
|
|
69
|
+
});
|
|
71
70
|
|
|
72
71
|
/**
|
|
73
72
|
* A post author: a bare name/handle, or an object with a name plus optional
|
|
@@ -85,34 +84,32 @@ const authorSchema = z.union([
|
|
|
85
84
|
name: z.string(),
|
|
86
85
|
url: z.string().optional(),
|
|
87
86
|
})
|
|
88
|
-
.
|
|
87
|
+
.catchall(z.unknown()),
|
|
89
88
|
]);
|
|
90
89
|
|
|
91
90
|
/** Frontmatter accepted on any content page. */
|
|
92
|
-
const pageMetaBaseSchema = z
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
.strict();
|
|
91
|
+
const pageMetaBaseSchema = z.strictObject({
|
|
92
|
+
/** Post author(s) for blog/changelog content; preserved, not yet rendered. */
|
|
93
|
+
authors: z.union([authorSchema, z.array(authorSchema)]).optional(),
|
|
94
|
+
changelog: changelogMetaSchema.optional(),
|
|
95
|
+
/** Publish date for feed-backed content like blog/changelog. */
|
|
96
|
+
date: dateSchema.optional(),
|
|
97
|
+
deprecated: z.boolean().default(false),
|
|
98
|
+
description: z.string().optional(),
|
|
99
|
+
draft: z.boolean().default(false),
|
|
100
|
+
hidden: z.boolean().default(false),
|
|
101
|
+
icon: iconName.optional(),
|
|
102
|
+
/** Overrides the git-derived last-modified date when `lastModified` is on. */
|
|
103
|
+
lastModified: dateSchema.optional(),
|
|
104
|
+
noindex: z.boolean().default(false),
|
|
105
|
+
search: searchMetaSchema.default({}),
|
|
106
|
+
seo: seoMetaSchema.default({}),
|
|
107
|
+
sidebar: sidebarMetaSchema.default({}),
|
|
108
|
+
slug: z.string().optional(),
|
|
109
|
+
title: z.string().optional(),
|
|
110
|
+
// No default: an absent `type` must fall through to `content.defaultType`.
|
|
111
|
+
type: z.string().optional(),
|
|
112
|
+
});
|
|
116
113
|
|
|
117
114
|
export const pageMetaSchema = pageMetaBaseSchema;
|
|
118
115
|
|
|
@@ -133,16 +130,14 @@ export type PageMetaInput = z.input<typeof pageMetaBaseSchema>;
|
|
|
133
130
|
const sidebarDisplaySchema = z.enum(["flat", "group", "page"]);
|
|
134
131
|
export type SidebarDisplay = z.infer<typeof sidebarDisplaySchema>;
|
|
135
132
|
|
|
136
|
-
export const folderMetaSchema = z
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
})
|
|
145
|
-
.strict();
|
|
133
|
+
export const folderMetaSchema = z.strictObject({
|
|
134
|
+
collapsed: z.boolean().optional(),
|
|
135
|
+
icon: iconName.optional(),
|
|
136
|
+
order: z.number().optional(),
|
|
137
|
+
/** Explicit child ordering by slug segment (without numeric prefix). */
|
|
138
|
+
pages: z.array(z.string()).optional(),
|
|
139
|
+
title: z.string().optional(),
|
|
140
|
+
});
|
|
146
141
|
|
|
147
142
|
export type FolderMeta = z.infer<typeof folderMetaSchema>;
|
|
148
143
|
|
|
@@ -153,13 +148,11 @@ export type FolderMeta = z.infer<typeof folderMetaSchema>;
|
|
|
153
148
|
/** The logo mark: a single image path/URL, or light/dark variants with alt text. */
|
|
154
149
|
const logoImageSchema = z.union([
|
|
155
150
|
z.string(),
|
|
156
|
-
z
|
|
157
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
})
|
|
162
|
-
.strict(),
|
|
151
|
+
z.strictObject({
|
|
152
|
+
alt: z.string().optional(),
|
|
153
|
+
dark: z.string().optional(),
|
|
154
|
+
light: z.string().optional(),
|
|
155
|
+
}),
|
|
163
156
|
]);
|
|
164
157
|
|
|
165
158
|
/**
|
|
@@ -171,75 +164,63 @@ const logoImageSchema = z.union([
|
|
|
171
164
|
*/
|
|
172
165
|
const logoConfigSchema = z.union([
|
|
173
166
|
z.string(),
|
|
174
|
-
z
|
|
175
|
-
.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})
|
|
180
|
-
.strict(),
|
|
167
|
+
z.strictObject({
|
|
168
|
+
href: z.string().optional(),
|
|
169
|
+
image: logoImageSchema.optional(),
|
|
170
|
+
text: z.string().optional(),
|
|
171
|
+
}),
|
|
181
172
|
]);
|
|
182
173
|
|
|
183
174
|
/** Site-wide announcement banner: a string, or text with an optional link. */
|
|
184
175
|
const bannerConfigSchema = z.union([
|
|
185
176
|
z.string(),
|
|
186
|
-
z
|
|
187
|
-
.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
.object({ href: z.string(), text: z.string() })
|
|
195
|
-
.strict()
|
|
196
|
-
.optional(),
|
|
197
|
-
})
|
|
198
|
-
.strict(),
|
|
177
|
+
z.strictObject({
|
|
178
|
+
content: z.string(),
|
|
179
|
+
/** Show a dismiss button; the choice is remembered per visitor. */
|
|
180
|
+
dismissible: z.boolean().default(false),
|
|
181
|
+
/** Stable key for remembering dismissal; defaults to the content. */
|
|
182
|
+
id: z.string().optional(),
|
|
183
|
+
link: z.strictObject({ href: z.string(), text: z.string() }).optional(),
|
|
184
|
+
}),
|
|
199
185
|
]);
|
|
200
186
|
|
|
201
187
|
/** A local filesystem content source. */
|
|
202
|
-
const filesystemSourceSchema = z
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
})
|
|
211
|
-
.strict();
|
|
188
|
+
const filesystemSourceSchema = z.strictObject({
|
|
189
|
+
exclude: z.array(z.string()).default(["**/_*", "**/.*"]),
|
|
190
|
+
include: z.array(z.string()).default([DEFAULT_CONTENT_GLOB]),
|
|
191
|
+
/** Namespaces the source's routes under `/<prefix>/`. */
|
|
192
|
+
prefix: z.string().optional(),
|
|
193
|
+
root: z.string().default("docs"),
|
|
194
|
+
type: z.literal("filesystem"),
|
|
195
|
+
});
|
|
212
196
|
|
|
213
197
|
/**
|
|
214
198
|
* Remote Markdown/MDX fetched over HTTP. Enumerate files either explicitly
|
|
215
199
|
* (`files` against a raw `url` base) or from a GitHub repo subtree (`github`).
|
|
216
200
|
* The token, when needed, comes from `GITHUB_TOKEN` — never inlined here.
|
|
217
201
|
*/
|
|
218
|
-
const mdxRemoteSourceSchema = z
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
url: z.string().optional(),
|
|
241
|
-
})
|
|
242
|
-
.strict();
|
|
202
|
+
const mdxRemoteSourceSchema = z.strictObject({
|
|
203
|
+
/** Explicit list of source-relative file paths to fetch from `url`. */
|
|
204
|
+
files: z.array(z.string()).optional(),
|
|
205
|
+
/** Enumerate a GitHub repo subtree via the git-trees API. */
|
|
206
|
+
github: z
|
|
207
|
+
.strictObject({
|
|
208
|
+
owner: z.string(),
|
|
209
|
+
path: z.string().default(""),
|
|
210
|
+
ref: z.string().default("main"),
|
|
211
|
+
repo: z.string(),
|
|
212
|
+
})
|
|
213
|
+
.optional(),
|
|
214
|
+
/** Glob patterns applied to enumerated refs. */
|
|
215
|
+
include: z.array(z.string()).default([DEFAULT_CONTENT_GLOB]),
|
|
216
|
+
/** Opt-in dev polling interval (seconds); omit to freeze for the session. */
|
|
217
|
+
pollInterval: z.number().positive().optional(),
|
|
218
|
+
/** Namespaces the source's routes under `/<prefix>/`. */
|
|
219
|
+
prefix: z.string().optional(),
|
|
220
|
+
type: z.literal("mdx-remote"),
|
|
221
|
+
/** Raw base URL, e.g. `https://raw.githubusercontent.com/acme/sdk/main/docs`. */
|
|
222
|
+
url: z.string().optional(),
|
|
223
|
+
});
|
|
243
224
|
|
|
244
225
|
/** A Sanity dataset queried with GROQ; Portable Text bodies become Markdown. */
|
|
245
226
|
const sanitySourceSchema = z.object({
|
|
@@ -248,14 +229,13 @@ const sanitySourceSchema = z.object({
|
|
|
248
229
|
dataset: z.string(),
|
|
249
230
|
/** Field paths mapping a document onto Blume meta + body. */
|
|
250
231
|
fields: z
|
|
251
|
-
.
|
|
232
|
+
.strictObject({
|
|
252
233
|
body: z.string().optional(),
|
|
253
234
|
description: z.string().optional(),
|
|
254
235
|
lastModified: z.string().optional(),
|
|
255
236
|
slug: z.string().optional(),
|
|
256
237
|
title: z.string().optional(),
|
|
257
238
|
})
|
|
258
|
-
.strict()
|
|
259
239
|
.optional(),
|
|
260
240
|
/** Opt-in dev polling interval (seconds); omit to freeze for the session. */
|
|
261
241
|
pollInterval: z.number().positive().optional(),
|
|
@@ -274,14 +254,13 @@ const notionSourceSchema = z.object({
|
|
|
274
254
|
prefix: z.string().optional(),
|
|
275
255
|
/** Notion property names mapped onto Blume meta. */
|
|
276
256
|
properties: z
|
|
277
|
-
.
|
|
257
|
+
.strictObject({
|
|
278
258
|
description: z.string().optional(),
|
|
279
259
|
order: z.string().optional(),
|
|
280
260
|
slug: z.string().optional(),
|
|
281
261
|
status: z.string().optional(),
|
|
282
262
|
title: z.string().optional(),
|
|
283
263
|
})
|
|
284
|
-
.strict()
|
|
285
264
|
.optional(),
|
|
286
265
|
/** Status value treated as published; others map to `draft`. Default `Published`. */
|
|
287
266
|
publishedValue: z.string().optional(),
|
|
@@ -293,25 +272,23 @@ const notionSourceSchema = z.object({
|
|
|
293
272
|
* notes become the changelog with no files to maintain. A private repo reads a
|
|
294
273
|
* token from `GITHUB_TOKEN`; it is never inlined here.
|
|
295
274
|
*/
|
|
296
|
-
const githubReleasesSourceSchema = z
|
|
297
|
-
.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
})
|
|
314
|
-
.strict();
|
|
275
|
+
const githubReleasesSourceSchema = z.strictObject({
|
|
276
|
+
/** Include draft releases (needs a token with repo write access). */
|
|
277
|
+
drafts: z.boolean().optional(),
|
|
278
|
+
/** Cap the number of releases materialized, newest-first. Default 100. */
|
|
279
|
+
limit: z.number().positive().optional(),
|
|
280
|
+
/** Repository owner (user or org). */
|
|
281
|
+
owner: z.string(),
|
|
282
|
+
/** Opt-in dev polling interval (seconds); omit to freeze for the session. */
|
|
283
|
+
pollInterval: z.number().positive().optional(),
|
|
284
|
+
/** Namespaces the source's routes under `/<prefix>/`; e.g. `changelog`. */
|
|
285
|
+
prefix: z.string().optional(),
|
|
286
|
+
/** Include prereleases. */
|
|
287
|
+
prereleases: z.boolean().optional(),
|
|
288
|
+
/** Repository name. */
|
|
289
|
+
repo: z.string(),
|
|
290
|
+
type: z.literal("github-releases"),
|
|
291
|
+
});
|
|
315
292
|
|
|
316
293
|
/**
|
|
317
294
|
* A user-provided `ContentSource` instance, passed straight through from
|
|
@@ -343,60 +320,50 @@ const contentSourceSchema = z.discriminatedUnion("type", [
|
|
|
343
320
|
/** A resolved content-source config entry (post-defaults). */
|
|
344
321
|
export type ContentSourceConfig = z.infer<typeof contentSourceSchema>;
|
|
345
322
|
|
|
346
|
-
const contentConfigSchema = z
|
|
347
|
-
.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
})
|
|
360
|
-
.strict();
|
|
361
|
-
|
|
362
|
-
const navTabSchema = z
|
|
363
|
-
.object({
|
|
364
|
-
icon: iconName.optional(),
|
|
365
|
-
items: z
|
|
366
|
-
.array(
|
|
367
|
-
z
|
|
368
|
-
.object({
|
|
369
|
-
description: z.string().optional(),
|
|
370
|
-
icon: iconName.optional(),
|
|
371
|
-
label: z.string(),
|
|
372
|
-
path: z.string(),
|
|
373
|
-
tag: z.string().optional(),
|
|
374
|
-
})
|
|
375
|
-
.strict()
|
|
376
|
-
)
|
|
377
|
-
.optional(),
|
|
378
|
-
label: z.string(),
|
|
379
|
-
path: z.string(),
|
|
380
|
-
})
|
|
381
|
-
.strict();
|
|
382
|
-
|
|
383
|
-
const navSelectorItemSchema = z
|
|
384
|
-
.object({
|
|
385
|
-
description: z.string().optional(),
|
|
386
|
-
icon: iconName.optional(),
|
|
387
|
-
label: z.string(),
|
|
388
|
-
path: z.string(),
|
|
389
|
-
tag: z.string().optional(),
|
|
390
|
-
})
|
|
391
|
-
.strict();
|
|
323
|
+
const contentConfigSchema = z.strictObject({
|
|
324
|
+
defaultType: z.string().default("doc"),
|
|
325
|
+
exclude: z.array(z.string()).default(["**/_*", "**/.*"]),
|
|
326
|
+
include: z.array(z.string()).default([DEFAULT_CONTENT_GLOB]),
|
|
327
|
+
pages: z.string().default("pages"),
|
|
328
|
+
root: z.string().default("docs"),
|
|
329
|
+
/**
|
|
330
|
+
* Pluggable content sources. When omitted, the top-level
|
|
331
|
+
* `root`/`include`/`exclude` desugar to one implicit filesystem source, so
|
|
332
|
+
* existing projects are unchanged.
|
|
333
|
+
*/
|
|
334
|
+
sources: z.array(contentSourceSchema).optional(),
|
|
335
|
+
});
|
|
392
336
|
|
|
393
|
-
const
|
|
394
|
-
.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
337
|
+
const navTabSchema = z.strictObject({
|
|
338
|
+
icon: iconName.optional(),
|
|
339
|
+
items: z
|
|
340
|
+
.array(
|
|
341
|
+
z.strictObject({
|
|
342
|
+
description: z.string().optional(),
|
|
343
|
+
icon: iconName.optional(),
|
|
344
|
+
label: z.string(),
|
|
345
|
+
path: z.string(),
|
|
346
|
+
tag: z.string().optional(),
|
|
347
|
+
})
|
|
348
|
+
)
|
|
349
|
+
.optional(),
|
|
350
|
+
label: z.string(),
|
|
351
|
+
path: z.string(),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const navSelectorItemSchema = z.strictObject({
|
|
355
|
+
description: z.string().optional(),
|
|
356
|
+
icon: iconName.optional(),
|
|
357
|
+
label: z.string(),
|
|
358
|
+
path: z.string(),
|
|
359
|
+
tag: z.string().optional(),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const navSelectorSchema = z.strictObject({
|
|
363
|
+
items: z.array(navSelectorItemSchema).default([]),
|
|
364
|
+
kind: z.enum(["dropdown", "language", "product", "version"]),
|
|
365
|
+
label: z.string(),
|
|
366
|
+
});
|
|
400
367
|
|
|
401
368
|
const directoryModeSchema = z.enum(["accordion", "card", "none"]);
|
|
402
369
|
export type DirectoryMode = z.infer<typeof directoryModeSchema>;
|
|
@@ -419,26 +386,29 @@ export type SidebarItemConfig =
|
|
|
419
386
|
const sidebarItemSchema: z.ZodType<SidebarItemConfig> = z.lazy(() =>
|
|
420
387
|
z.union([
|
|
421
388
|
z.string(),
|
|
422
|
-
z
|
|
423
|
-
.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
})
|
|
434
|
-
.strict(),
|
|
389
|
+
z.strictObject({
|
|
390
|
+
badge: z.string().optional(),
|
|
391
|
+
collapsed: z.boolean().optional(),
|
|
392
|
+
directory: directoryModeSchema.optional(),
|
|
393
|
+
display: sidebarDisplaySchema.optional(),
|
|
394
|
+
href: z.string().optional(),
|
|
395
|
+
icon: iconName.optional(),
|
|
396
|
+
items: z.array(sidebarItemSchema).optional(),
|
|
397
|
+
label: z.string(),
|
|
398
|
+
root: z.string().optional(),
|
|
399
|
+
}),
|
|
435
400
|
])
|
|
436
401
|
);
|
|
437
402
|
|
|
438
403
|
/** A curated Google Font slug (see `theme/fonts.ts`). */
|
|
439
|
-
const fontSlug = z.string().
|
|
440
|
-
|
|
441
|
-
|
|
404
|
+
const fontSlug = z.string().superRefine((value, ctx) => {
|
|
405
|
+
if (!isFontSlug(value)) {
|
|
406
|
+
ctx.addIssue({
|
|
407
|
+
code: z.ZodIssueCode.custom,
|
|
408
|
+
message: `Unknown font "${value}". Supported fonts: ${FONT_SLUGS.join(", ")}.`,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
});
|
|
442
412
|
|
|
443
413
|
/**
|
|
444
414
|
* An optional per-mode theme value: a string applies to both color modes; a
|
|
@@ -448,79 +418,69 @@ const fontSlug = z.string().refine(isFontSlug, (value) => ({
|
|
|
448
418
|
const perModeValueSchema = z
|
|
449
419
|
.union([
|
|
450
420
|
z.string(),
|
|
451
|
-
z
|
|
452
|
-
|
|
453
|
-
.
|
|
421
|
+
z.strictObject({
|
|
422
|
+
dark: z.string().optional(),
|
|
423
|
+
light: z.string().optional(),
|
|
424
|
+
}),
|
|
454
425
|
])
|
|
455
426
|
.optional()
|
|
456
427
|
.transform((value) =>
|
|
457
428
|
typeof value === "string" ? { dark: value, light: value } : value
|
|
458
429
|
);
|
|
459
430
|
|
|
460
|
-
const themeConfigSchema = z
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
radius: z.enum(["none", "sm", "md", "lg"]).default("md"),
|
|
485
|
-
})
|
|
486
|
-
.strict();
|
|
431
|
+
const themeConfigSchema = z.strictObject({
|
|
432
|
+
accent: z
|
|
433
|
+
.union([
|
|
434
|
+
z.string(),
|
|
435
|
+
z.strictObject({ dark: z.string(), light: z.string() }),
|
|
436
|
+
])
|
|
437
|
+
.default("blue")
|
|
438
|
+
.transform((value) =>
|
|
439
|
+
typeof value === "string" ? { dark: value, light: value } : value
|
|
440
|
+
),
|
|
441
|
+
action: z.string().optional(),
|
|
442
|
+
background: perModeValueSchema,
|
|
443
|
+
backgroundImage: perModeValueSchema,
|
|
444
|
+
fonts: z
|
|
445
|
+
.strictObject({
|
|
446
|
+
body: fontSlug.default("inter"),
|
|
447
|
+
display: fontSlug.default("inter-tight"),
|
|
448
|
+
mono: fontSlug.default("ibm-plex-mono"),
|
|
449
|
+
})
|
|
450
|
+
.default({}),
|
|
451
|
+
layout: z.enum(["sidebar"]).default("sidebar"),
|
|
452
|
+
mode: z.enum(["system", "light", "dark"]).default("system"),
|
|
453
|
+
radius: z.enum(["none", "sm", "md", "lg"]).default("md"),
|
|
454
|
+
});
|
|
487
455
|
|
|
488
456
|
/** Public credentials for the Algolia search backend (sync key is an env var). */
|
|
489
|
-
const algoliaSearchSchema = z
|
|
490
|
-
.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
})
|
|
495
|
-
.strict();
|
|
457
|
+
const algoliaSearchSchema = z.strictObject({
|
|
458
|
+
appId: z.string(),
|
|
459
|
+
indexName: z.string(),
|
|
460
|
+
searchApiKey: z.string(),
|
|
461
|
+
});
|
|
496
462
|
|
|
497
463
|
/** Public credentials for the Orama Cloud search backend. */
|
|
498
|
-
const oramaCloudSearchSchema = z
|
|
499
|
-
.
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
})
|
|
505
|
-
.strict();
|
|
464
|
+
const oramaCloudSearchSchema = z.strictObject({
|
|
465
|
+
apiKey: z.string(),
|
|
466
|
+
endpoint: z.string(),
|
|
467
|
+
/** Index id used by the build-time sync (with `ORAMA_PRIVATE_API_KEY`). */
|
|
468
|
+
indexId: z.string().optional(),
|
|
469
|
+
});
|
|
506
470
|
|
|
507
471
|
/** Public credentials for a (self-hosted or cloud) Typesense backend. */
|
|
508
|
-
const typesenseSearchSchema = z
|
|
509
|
-
.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
})
|
|
516
|
-
.strict();
|
|
472
|
+
const typesenseSearchSchema = z.strictObject({
|
|
473
|
+
collection: z.string(),
|
|
474
|
+
host: z.string(),
|
|
475
|
+
port: z.number().int().positive().optional(),
|
|
476
|
+
protocol: z.enum(["http", "https"]).optional(),
|
|
477
|
+
searchApiKey: z.string(),
|
|
478
|
+
});
|
|
517
479
|
|
|
518
480
|
/** Mixedbread semantic search: the store the server endpoint queries. */
|
|
519
|
-
const mixedbreadSearchSchema = z
|
|
520
|
-
.
|
|
521
|
-
|
|
522
|
-
})
|
|
523
|
-
.strict();
|
|
481
|
+
const mixedbreadSearchSchema = z.strictObject({
|
|
482
|
+
storeId: z.string(),
|
|
483
|
+
});
|
|
524
484
|
|
|
525
485
|
export const searchProviders = [
|
|
526
486
|
"orama",
|
|
@@ -542,20 +502,18 @@ const PROVIDER_CONFIG_KEY = {
|
|
|
542
502
|
} as const;
|
|
543
503
|
|
|
544
504
|
const searchConfigSchema = z
|
|
545
|
-
.
|
|
505
|
+
.strictObject({
|
|
546
506
|
algolia: algoliaSearchSchema.optional(),
|
|
547
507
|
indexing: z
|
|
548
|
-
.
|
|
508
|
+
.strictObject({
|
|
549
509
|
includeHiddenPages: z.boolean().default(false),
|
|
550
510
|
})
|
|
551
|
-
.strict()
|
|
552
511
|
.default({}),
|
|
553
512
|
mixedbread: mixedbreadSearchSchema.optional(),
|
|
554
513
|
oramaCloud: oramaCloudSearchSchema.optional(),
|
|
555
514
|
provider: z.enum(searchProviders).default("orama"),
|
|
556
515
|
typesense: typesenseSearchSchema.optional(),
|
|
557
516
|
})
|
|
558
|
-
.strict()
|
|
559
517
|
.superRefine((value, ctx) => {
|
|
560
518
|
// Hosted providers can't work without their credentials; flag a missing
|
|
561
519
|
// block with a path so the diagnostic points at `search.<provider>`.
|
|
@@ -579,95 +537,84 @@ export const askAiProviders = [
|
|
|
579
537
|
"openai-compatible",
|
|
580
538
|
] as const;
|
|
581
539
|
|
|
582
|
-
const aiConfigSchema = z
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
llmsTxt: z.boolean().default(true),
|
|
623
|
-
})
|
|
624
|
-
.strict();
|
|
540
|
+
const aiConfigSchema = z.strictObject({
|
|
541
|
+
ask: z
|
|
542
|
+
.strictObject({
|
|
543
|
+
// Name of the env var holding the provider's API key; each provider has
|
|
544
|
+
// a sensible default, so this only needs setting to override it.
|
|
545
|
+
apiKeyEnv: z.string().optional(),
|
|
546
|
+
// Base URL of the backend. Required for `openai-compatible`; for the
|
|
547
|
+
// named providers it overrides the built-in preset.
|
|
548
|
+
// blume bundles Zod 3; top-level `z.url()` is undefined at runtime.
|
|
549
|
+
// oxlint-disable-next-line react-doctor/zod-v4-prefer-top-level-string-formats
|
|
550
|
+
baseUrl: z.string().url().optional(),
|
|
551
|
+
enabled: z.boolean().default(false),
|
|
552
|
+
model: z.string().default("openai/gpt-5.5"),
|
|
553
|
+
provider: z.enum(askAiProviders).default("gateway"),
|
|
554
|
+
// Empty-state prompts shown before the first question. Each renders as a
|
|
555
|
+
// clickable suggestion; `icon` is an optional Lucide name beside it.
|
|
556
|
+
suggestions: z
|
|
557
|
+
.array(
|
|
558
|
+
z.strictObject({
|
|
559
|
+
icon: iconName.optional(),
|
|
560
|
+
label: z.string().min(1),
|
|
561
|
+
})
|
|
562
|
+
)
|
|
563
|
+
.default([]),
|
|
564
|
+
})
|
|
565
|
+
.superRefine((value, ctx) => {
|
|
566
|
+
// A generic OpenAI-compatible backend has no preset URL, so the user
|
|
567
|
+
// must supply one; the named providers fall back to their preset.
|
|
568
|
+
if (value.provider === "openai-compatible" && !value.baseUrl) {
|
|
569
|
+
ctx.addIssue({
|
|
570
|
+
code: z.ZodIssueCode.custom,
|
|
571
|
+
message:
|
|
572
|
+
'ai.ask.baseUrl is required when provider is "openai-compatible".',
|
|
573
|
+
path: ["baseUrl"],
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
})
|
|
577
|
+
.optional(),
|
|
578
|
+
llmsTxt: z.boolean().default(true),
|
|
579
|
+
});
|
|
625
580
|
|
|
626
581
|
/**
|
|
627
582
|
* A pinned link rendered above the sidebar sections — a blog, changelog, or
|
|
628
583
|
* contact page that should always be reachable, regardless of the active tab.
|
|
629
584
|
* `href` may be an external URL or an internal route.
|
|
630
585
|
*/
|
|
631
|
-
const featuredLinkSchema = z
|
|
632
|
-
.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
.
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
z.array(sidebarItemSchema),
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
.transform((value) =>
|
|
664
|
-
Array.isArray(value)
|
|
665
|
-
? { display: "flat" as const, items: value }
|
|
666
|
-
: value
|
|
667
|
-
),
|
|
668
|
-
tabs: z.array(navTabSchema).optional(),
|
|
669
|
-
})
|
|
670
|
-
.strict();
|
|
586
|
+
const featuredLinkSchema = z.strictObject({
|
|
587
|
+
href: z.string(),
|
|
588
|
+
icon: iconName.optional(),
|
|
589
|
+
label: z.string(),
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const navigationConfigSchema = z.strictObject({
|
|
593
|
+
/** Pinned links shown above the generated sidebar sections. */
|
|
594
|
+
featured: z.array(featuredLinkSchema).default([]),
|
|
595
|
+
/** Show a GitHub repo link in the header (requires `github` configured). */
|
|
596
|
+
repo: z.boolean().default(true),
|
|
597
|
+
selectors: z.array(navSelectorSchema).default([]),
|
|
598
|
+
/**
|
|
599
|
+
* Sidebar behavior. `display` sets how every group renders (a group in an
|
|
600
|
+
* explicit `items` config may still override it); `items` is an explicit
|
|
601
|
+
* sidebar — when omitted the sidebar is generated from the content tree.
|
|
602
|
+
* A bare array is shorthand for `{ items }`.
|
|
603
|
+
*/
|
|
604
|
+
sidebar: z
|
|
605
|
+
.union([
|
|
606
|
+
z.array(sidebarItemSchema),
|
|
607
|
+
z.strictObject({
|
|
608
|
+
display: sidebarDisplaySchema.default("flat"),
|
|
609
|
+
items: z.array(sidebarItemSchema).optional(),
|
|
610
|
+
}),
|
|
611
|
+
])
|
|
612
|
+
.default({})
|
|
613
|
+
.transform((value) =>
|
|
614
|
+
Array.isArray(value) ? { display: "flat" as const, items: value } : value
|
|
615
|
+
),
|
|
616
|
+
tabs: z.array(navTabSchema).optional(),
|
|
617
|
+
});
|
|
671
618
|
|
|
672
619
|
export type AskAiProvider = (typeof askAiProviders)[number];
|
|
673
620
|
export type AskAiConfig = NonNullable<z.infer<typeof aiConfigSchema>["ask"]>;
|
|
@@ -679,37 +626,31 @@ export type AskAiConfig = NonNullable<z.infer<typeof aiConfigSchema>["ask"]>;
|
|
|
679
626
|
const exportConfigSchema = z
|
|
680
627
|
.union([
|
|
681
628
|
z.boolean(),
|
|
682
|
-
z
|
|
683
|
-
.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
})
|
|
687
|
-
.strict(),
|
|
629
|
+
z.strictObject({
|
|
630
|
+
epub: z.boolean().default(false),
|
|
631
|
+
pdf: z.boolean().default(false),
|
|
632
|
+
}),
|
|
688
633
|
])
|
|
689
634
|
.transform((value) =>
|
|
690
635
|
typeof value === "boolean" ? { epub: value, pdf: value } : value
|
|
691
636
|
);
|
|
692
637
|
|
|
693
|
-
const mcpConfigSchema = z
|
|
694
|
-
.
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
})
|
|
702
|
-
.strict();
|
|
638
|
+
const mcpConfigSchema = z.strictObject({
|
|
639
|
+
enabled: z.boolean().default(false),
|
|
640
|
+
/** Optional system hint passed to connecting agents. */
|
|
641
|
+
instructions: z.string().optional(),
|
|
642
|
+
/** Server name shown to clients; defaults to the site title. */
|
|
643
|
+
name: z.string().optional(),
|
|
644
|
+
route: z.string().default("/mcp"),
|
|
645
|
+
});
|
|
703
646
|
|
|
704
647
|
/** A configured locale: ISO-ish code plus display metadata for the switcher. */
|
|
705
|
-
const localeSchema = z
|
|
706
|
-
.
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
})
|
|
712
|
-
.strict();
|
|
648
|
+
const localeSchema = z.strictObject({
|
|
649
|
+
code: z.string().min(1),
|
|
650
|
+
/** Text direction; drives `<html dir>` and a future RTL pass. */
|
|
651
|
+
dir: z.enum(["ltr", "rtl"]).default("ltr"),
|
|
652
|
+
label: z.string(),
|
|
653
|
+
});
|
|
713
654
|
|
|
714
655
|
/**
|
|
715
656
|
* Internationalization. Opt-in: when absent, Blume is single-locale and behaves
|
|
@@ -717,7 +658,7 @@ const localeSchema = z
|
|
|
717
658
|
* are top-level directories named by `code` (the `dir` parser).
|
|
718
659
|
*/
|
|
719
660
|
const i18nConfigSchema = z
|
|
720
|
-
.
|
|
661
|
+
.strictObject({
|
|
721
662
|
defaultLocale: z.string().default("en"),
|
|
722
663
|
/** Locale rendered for a missing translation; `null` disables fallback. */
|
|
723
664
|
fallbackLocale: z.string().nullable().optional(),
|
|
@@ -729,7 +670,6 @@ const i18nConfigSchema = z
|
|
|
729
670
|
/** Per-locale UI string overrides: `{ fr: { search: { button: "…" } } }`. */
|
|
730
671
|
ui: uiLocaleOverridesSchema.optional(),
|
|
731
672
|
})
|
|
732
|
-
.strict()
|
|
733
673
|
.superRefine((value, ctx) => {
|
|
734
674
|
const codes = new Set(value.locales.map((locale) => locale.code));
|
|
735
675
|
if (!codes.has(value.defaultLocale)) {
|
|
@@ -753,7 +693,7 @@ const i18nConfigSchema = z
|
|
|
753
693
|
});
|
|
754
694
|
|
|
755
695
|
const analyticsScriptSchema = z
|
|
756
|
-
.
|
|
696
|
+
.strictObject({
|
|
757
697
|
// Extra attributes (e.g. `data-domain`, `id`) spread onto the <script>.
|
|
758
698
|
attributes: z.record(z.string(), z.string()).optional(),
|
|
759
699
|
// Inline script body, mutually exclusive with `src`.
|
|
@@ -763,69 +703,59 @@ const analyticsScriptSchema = z
|
|
|
763
703
|
// Load strategy for an external script.
|
|
764
704
|
strategy: z.enum(["async", "defer"]).optional(),
|
|
765
705
|
})
|
|
766
|
-
.strict()
|
|
767
706
|
.refine((value) => Boolean(value.src) !== Boolean(value.content), {
|
|
768
707
|
message: "An analytics script must set exactly one of `src` or `content`.",
|
|
769
708
|
});
|
|
770
709
|
|
|
771
|
-
const analyticsConfigSchema = z
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
.
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
.
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
.
|
|
819
|
-
|
|
820
|
-
const rssConfigSchema = z
|
|
821
|
-
.object({
|
|
822
|
-
enabled: z.boolean().default(true),
|
|
823
|
-
/** Max items per feed, newest first. */
|
|
824
|
-
limit: z.number().int().positive().default(50),
|
|
825
|
-
/** Content types that each get a feed at `/<type>/rss.xml`. */
|
|
826
|
-
types: z.array(z.string()).default(["blog", "changelog"]),
|
|
827
|
-
})
|
|
828
|
-
.strict();
|
|
710
|
+
const analyticsConfigSchema = z.strictObject({
|
|
711
|
+
posthog: z
|
|
712
|
+
.strictObject({
|
|
713
|
+
host: z.string().optional(),
|
|
714
|
+
key: z.string(),
|
|
715
|
+
})
|
|
716
|
+
.optional(),
|
|
717
|
+
// Escape hatch for any other provider (Plausible, Fathom, GA, Umami, …).
|
|
718
|
+
scripts: z.array(analyticsScriptSchema).optional(),
|
|
719
|
+
vercel: z.boolean().optional(),
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const deploymentConfigSchema = z.strictObject({
|
|
723
|
+
adapter: z
|
|
724
|
+
.enum(["vercel", "node", "netlify", "cloudflare"])
|
|
725
|
+
.nullable()
|
|
726
|
+
.default(null),
|
|
727
|
+
base: z.string().optional(),
|
|
728
|
+
output: z.enum(["static", "server"]).default("static"),
|
|
729
|
+
// blume bundles Zod 3; top-level `z.url()` is undefined at runtime.
|
|
730
|
+
// oxlint-disable-next-line react-doctor/zod-v4-prefer-top-level-string-formats
|
|
731
|
+
site: z.string().url().optional(),
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const redirectSchema = z.strictObject({
|
|
735
|
+
from: z.string(),
|
|
736
|
+
status: z
|
|
737
|
+
.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)])
|
|
738
|
+
.default(301),
|
|
739
|
+
to: z.string(),
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const ogConfigSchema = z.strictObject({
|
|
743
|
+
/**
|
|
744
|
+
* Generate a per-page Open Graph image. Defaults to on once a deployment
|
|
745
|
+
* site URL is known (set or auto-detected) and off otherwise, since
|
|
746
|
+
* `og:image` must be absolute to be useful to crawlers — resolved in
|
|
747
|
+
* `loadConfig`. An explicit value here always wins.
|
|
748
|
+
*/
|
|
749
|
+
enabled: z.boolean().optional(),
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
const rssConfigSchema = z.strictObject({
|
|
753
|
+
enabled: z.boolean().default(true),
|
|
754
|
+
/** Max items per feed, newest first. */
|
|
755
|
+
limit: z.number().int().positive().default(50),
|
|
756
|
+
/** Content types that each get a feed at `/<type>/rss.xml`. */
|
|
757
|
+
types: z.array(z.string()).default(["blog", "changelog"]),
|
|
758
|
+
});
|
|
829
759
|
|
|
830
760
|
/**
|
|
831
761
|
* robots.txt `Content-Signal` preferences — the emerging content-usage
|
|
@@ -835,13 +765,11 @@ const rssConfigSchema = z
|
|
|
835
765
|
* - `aiInput` → `ai-input` (grounding / RAG use at answer time)
|
|
836
766
|
* - `aiTrain` → `ai-train` (model training)
|
|
837
767
|
*/
|
|
838
|
-
const contentSignalsObjectSchema = z
|
|
839
|
-
.
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
})
|
|
844
|
-
.strict();
|
|
768
|
+
const contentSignalsObjectSchema = z.strictObject({
|
|
769
|
+
aiInput: z.boolean().default(true),
|
|
770
|
+
aiTrain: z.boolean().default(true),
|
|
771
|
+
search: z.boolean().default(true),
|
|
772
|
+
});
|
|
845
773
|
|
|
846
774
|
/**
|
|
847
775
|
* Content signals accept a boolean shorthand or a per-signal object, and
|
|
@@ -863,49 +791,70 @@ const contentSignalsSchema = z
|
|
|
863
791
|
});
|
|
864
792
|
|
|
865
793
|
/** Discoverability features: OG images, feeds, sitemap, structured data. */
|
|
866
|
-
const seoConfigSchema = z
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
})
|
|
885
|
-
.strict();
|
|
886
|
-
|
|
887
|
-
const githubConfigSchema = z
|
|
888
|
-
.object({
|
|
889
|
-
branch: z.string().default("main"),
|
|
890
|
-
/** Path from the repo root to the project root (for monorepos). */
|
|
891
|
-
dir: z.string().optional(),
|
|
892
|
-
owner: z.string(),
|
|
893
|
-
repo: z.string(),
|
|
894
|
-
})
|
|
895
|
-
.strict();
|
|
794
|
+
const seoConfigSchema = z.strictObject({
|
|
795
|
+
/**
|
|
796
|
+
* Emit `agent-readability.json` at the site root: a manifest that indexes
|
|
797
|
+
* the agent-facing surface (llms.txt, Markdown mirrors, MCP server, feeds)
|
|
798
|
+
* so agents can discover it without scraping HTML.
|
|
799
|
+
*/
|
|
800
|
+
agentReadability: z.boolean().default(true),
|
|
801
|
+
/** robots.txt `Content-Signal` usage declaration (on by default). */
|
|
802
|
+
contentSignals: contentSignalsSchema.default(true),
|
|
803
|
+
og: ogConfigSchema.default({}),
|
|
804
|
+
/** Generate robots.txt (with a Sitemap reference when available). */
|
|
805
|
+
robots: z.boolean().default(true),
|
|
806
|
+
rss: rssConfigSchema.default({}),
|
|
807
|
+
/** Generate sitemap.xml (requires deployment.site). */
|
|
808
|
+
sitemap: z.boolean().default(true),
|
|
809
|
+
/** Emit schema.org JSON-LD in each page's <head>. */
|
|
810
|
+
structuredData: z.boolean().default(true),
|
|
811
|
+
});
|
|
896
812
|
|
|
897
|
-
const
|
|
898
|
-
.
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
.
|
|
813
|
+
const githubConfigSchema = z.strictObject({
|
|
814
|
+
branch: z.string().default("main"),
|
|
815
|
+
/** Path from the repo root to the project root (for monorepos). */
|
|
816
|
+
dir: z.string().optional(),
|
|
817
|
+
owner: z.string(),
|
|
818
|
+
repo: z.string(),
|
|
819
|
+
});
|
|
903
820
|
|
|
904
|
-
const
|
|
905
|
-
.
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
821
|
+
const codeBlockThemeSchema = z.strictObject({
|
|
822
|
+
dark: z.string().default("github-dark"),
|
|
823
|
+
light: z.string().default("github-light"),
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
const codeBlocksConfigSchema = z.strictObject({
|
|
827
|
+
theme: codeBlockThemeSchema.default({}),
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* `<Component />` example previews. A string is shorthand for `{ source }`:
|
|
832
|
+
* where examples live, relative to the project root (default `examples`).
|
|
833
|
+
* `source` may be a glob (anything with `*`/`?`/`[]`/`{}`/`!`), in which case
|
|
834
|
+
* only matching files are discovered and each `<Component path>` key is
|
|
835
|
+
* relative to the glob's static prefix — use this for a registry layout that
|
|
836
|
+
* colocates component sources with their examples
|
|
837
|
+
* (`registry/<pkg>/**\/examples/*`), leaving the sources (which have no
|
|
838
|
+
* default export to wrap) out.
|
|
839
|
+
*
|
|
840
|
+
* `css` names a stylesheet (relative to the project root) injected into every
|
|
841
|
+
* preview frame after Blume's default tokens. Previews render in an isolated
|
|
842
|
+
* iframe that the site's docs styles never reach, so this is where design
|
|
843
|
+
* tokens for the previewed components live — e.g. shadcn variables and
|
|
844
|
+
* `@theme` mappings. Tailwind itself is already provided; the file should
|
|
845
|
+
* hold tokens and custom styles, not another `@import "tailwindcss"`.
|
|
846
|
+
*/
|
|
847
|
+
const examplesConfigSchema = z
|
|
848
|
+
.union([
|
|
849
|
+
z.string(),
|
|
850
|
+
z.strictObject({
|
|
851
|
+
css: z.string().optional(),
|
|
852
|
+
source: z.string().default("examples"),
|
|
853
|
+
}),
|
|
854
|
+
])
|
|
855
|
+
.transform((value): { css?: string; source: string } =>
|
|
856
|
+
typeof value === "string" ? { source: value } : value
|
|
857
|
+
);
|
|
909
858
|
|
|
910
859
|
/**
|
|
911
860
|
* "Last updated" timestamps for content pages. `false` (default) disables the
|
|
@@ -914,58 +863,63 @@ const codeBlocksConfigSchema = z
|
|
|
914
863
|
*/
|
|
915
864
|
const lastModifiedConfigSchema = z.union([
|
|
916
865
|
z.boolean(),
|
|
917
|
-
z.
|
|
866
|
+
z.strictObject({ type: z.enum(["git", "frontmatter"]).default("git") }),
|
|
918
867
|
]);
|
|
919
868
|
|
|
920
869
|
/** Code-block rendering options (`markdown.code`). */
|
|
921
|
-
const codeConfigSchema = z
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
.
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
870
|
+
const codeConfigSchema = z.strictObject({
|
|
871
|
+
/**
|
|
872
|
+
* Show a brand language icon in the code-block header (TypeScript, Python,
|
|
873
|
+
* …). On by default; recognized languages only.
|
|
874
|
+
*/
|
|
875
|
+
icons: z.boolean().default(true),
|
|
876
|
+
/**
|
|
877
|
+
* Wrap long lines instead of scrolling horizontally. Off by default, so
|
|
878
|
+
* code keeps its original line breaks and overflows into a scroll area.
|
|
879
|
+
*/
|
|
880
|
+
wrap: z.boolean().default(false),
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
const markdownConfigSchema = z.strictObject({
|
|
884
|
+
/** Code-block rendering: language icons and line wrapping. */
|
|
885
|
+
code: codeConfigSchema.default({}),
|
|
886
|
+
codeBlocks: codeBlocksConfigSchema.default({}),
|
|
887
|
+
/**
|
|
888
|
+
* Wrap each `##`–`######` heading in a link to its own anchor so readers can
|
|
889
|
+
* click to copy, bookmark, or share a permalink to that section. On by
|
|
890
|
+
* default; set to `false` to render plain headings.
|
|
891
|
+
*/
|
|
892
|
+
headingAnchors: z.boolean().default(true),
|
|
893
|
+
/**
|
|
894
|
+
* Make content images click-to-zoom (open in a lightbox). On by default;
|
|
895
|
+
* opt a single image out with `data-no-zoom`.
|
|
896
|
+
*/
|
|
897
|
+
imageZoom: z.boolean().default(true),
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
/** React island behavior (`react`). */
|
|
901
|
+
const reactConfigSchema = z.strictObject({
|
|
902
|
+
/**
|
|
903
|
+
* Auto-memoize React components/hooks with the React Compiler
|
|
904
|
+
* (`babel-plugin-react-compiler`). On by default whenever React is enabled
|
|
905
|
+
* (a project `.tsx`/`.jsx`, a React island/example/override, or Ask AI); set
|
|
906
|
+
* to `false` to skip the compiler's babel pass.
|
|
907
|
+
*/
|
|
908
|
+
compiler: z.boolean().default(true),
|
|
909
|
+
});
|
|
954
910
|
|
|
955
911
|
/**
|
|
956
912
|
* A single spec rendered by the API reference. `spec` is a local path or an
|
|
957
913
|
* `http(s)` URL (OpenAPI for the Blume renderer; OpenAPI or AsyncAPI for Scalar).
|
|
958
914
|
*/
|
|
959
|
-
const openapiSourceSchema = z
|
|
960
|
-
.
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
})
|
|
968
|
-
.strict();
|
|
915
|
+
const openapiSourceSchema = z.strictObject({
|
|
916
|
+
/** Nav/section label for this source. */
|
|
917
|
+
label: z.string().optional(),
|
|
918
|
+
/** Per-source route; defaults to the block's `route` (or a derived path). */
|
|
919
|
+
route: z.string().optional(),
|
|
920
|
+
/** Local path or `http(s)` URL to the spec. */
|
|
921
|
+
spec: z.string(),
|
|
922
|
+
});
|
|
969
923
|
|
|
970
924
|
export type OpenApiSource = z.infer<typeof openapiSourceSchema>;
|
|
971
925
|
|
|
@@ -976,39 +930,35 @@ export type OpenApiSource = z.infer<typeof openapiSourceSchema>;
|
|
|
976
930
|
* `renderer: "scalar"` to fall back to the embedded Scalar SPA (a single
|
|
977
931
|
* self-contained route that doesn't weave into the sidebar or search).
|
|
978
932
|
*/
|
|
979
|
-
const openapiConfigSchema = z
|
|
980
|
-
.
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
})
|
|
997
|
-
.strict();
|
|
933
|
+
const openapiConfigSchema = z.strictObject({
|
|
934
|
+
/** Code-sample languages shown per operation (Blume renderer). */
|
|
935
|
+
codeSamples: z.array(z.string()).default(["curl", "js", "python"]),
|
|
936
|
+
enabled: z.boolean().default(false),
|
|
937
|
+
/** Start nested schema rows expanded rather than collapsed (Blume renderer). */
|
|
938
|
+
expandSchemas: z.boolean().default(false),
|
|
939
|
+
/** Who renders the reference: Blume's own UI, or the embedded Scalar SPA. */
|
|
940
|
+
renderer: z.enum(["blume", "scalar"]).default("blume"),
|
|
941
|
+
/** Where the reference mounts. */
|
|
942
|
+
route: z.string().default("/reference"),
|
|
943
|
+
/** One or more specs; each renders on its own route by default. */
|
|
944
|
+
sources: z.array(openapiSourceSchema).default([]),
|
|
945
|
+
/** Shorthand for a single source: `sources: [{ spec }]`. */
|
|
946
|
+
spec: z.string().optional(),
|
|
947
|
+
/** Scalar theme name (Scalar renderer only). */
|
|
948
|
+
theme: z.string().optional(),
|
|
949
|
+
});
|
|
998
950
|
|
|
999
951
|
/**
|
|
1000
952
|
* AsyncAPI reference. Same shape and Scalar pipeline as {@link openapiConfigSchema}
|
|
1001
953
|
* (Scalar auto-detects the document type); only the default `route` differs.
|
|
1002
954
|
*/
|
|
1003
|
-
const asyncapiConfigSchema = z
|
|
1004
|
-
.
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
})
|
|
1011
|
-
.strict();
|
|
955
|
+
const asyncapiConfigSchema = z.strictObject({
|
|
956
|
+
enabled: z.boolean().default(false),
|
|
957
|
+
route: z.string().default("/events"),
|
|
958
|
+
sources: z.array(openapiSourceSchema).default([]),
|
|
959
|
+
spec: z.string().optional(),
|
|
960
|
+
theme: z.string().optional(),
|
|
961
|
+
});
|
|
1012
962
|
|
|
1013
963
|
/** Full user-facing config schema. All fields optional with defaults. */
|
|
1014
964
|
/**
|
|
@@ -1019,12 +969,10 @@ const asyncapiConfigSchema = z
|
|
|
1019
969
|
const tocConfigSchema = z
|
|
1020
970
|
.union([
|
|
1021
971
|
z.boolean(),
|
|
1022
|
-
z
|
|
1023
|
-
.
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
})
|
|
1027
|
-
.strict(),
|
|
972
|
+
z.strictObject({
|
|
973
|
+
maxHeadingLevel: z.number().int().min(1).max(6).optional(),
|
|
974
|
+
minHeadingLevel: z.number().int().min(1).max(6).optional(),
|
|
975
|
+
}),
|
|
1028
976
|
])
|
|
1029
977
|
.default(true)
|
|
1030
978
|
.transform((value) => {
|
|
@@ -1038,47 +986,50 @@ const tocConfigSchema = z
|
|
|
1038
986
|
};
|
|
1039
987
|
});
|
|
1040
988
|
|
|
1041
|
-
export const blumeConfigSchema = z
|
|
1042
|
-
.
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
})
|
|
1081
|
-
.
|
|
989
|
+
export const blumeConfigSchema = z.strictObject({
|
|
990
|
+
ai: aiConfigSchema.default({}),
|
|
991
|
+
analytics: analyticsConfigSchema.optional(),
|
|
992
|
+
asyncapi: asyncapiConfigSchema.default({}),
|
|
993
|
+
banner: bannerConfigSchema.optional(),
|
|
994
|
+
/**
|
|
995
|
+
* Site-wide mount point prepended to every generated route (e.g. `/docs`),
|
|
996
|
+
* while staying invisible to the sidebar/nav tree. Distinct from a per-source
|
|
997
|
+
* `prefix` (which creates a group) and from `deployment.base` (Astro's
|
|
998
|
+
* host-subdirectory base); the two compose. Normalized to `""` or `/seg`.
|
|
999
|
+
*/
|
|
1000
|
+
basePath: z
|
|
1001
|
+
.string()
|
|
1002
|
+
.optional()
|
|
1003
|
+
.transform((value) => normalizeBasePath(value)),
|
|
1004
|
+
content: contentConfigSchema.default({}),
|
|
1005
|
+
deployment: deploymentConfigSchema.default({}),
|
|
1006
|
+
description: z.string().optional(),
|
|
1007
|
+
/**
|
|
1008
|
+
* Where `<Component path>` resolves live previews and their source from.
|
|
1009
|
+
* A string is shorthand for `{ source }` — the directory (or glob, for
|
|
1010
|
+
* colocated registry layouts) under the project root that holds example
|
|
1011
|
+
* files. The object form adds `css`: a stylesheet injected into every
|
|
1012
|
+
* preview frame (design tokens, shadcn variables, `@theme` mappings).
|
|
1013
|
+
*/
|
|
1014
|
+
examples: examplesConfigSchema.default("examples"),
|
|
1015
|
+
export: exportConfigSchema.default(false),
|
|
1016
|
+
feedback: z.boolean().default(true),
|
|
1017
|
+
github: githubConfigSchema.optional(),
|
|
1018
|
+
i18n: i18nConfigSchema.optional(),
|
|
1019
|
+
lastModified: lastModifiedConfigSchema.default(false),
|
|
1020
|
+
logo: logoConfigSchema.optional(),
|
|
1021
|
+
markdown: markdownConfigSchema.default({}),
|
|
1022
|
+
mcp: mcpConfigSchema.default({}),
|
|
1023
|
+
navigation: navigationConfigSchema.default({}),
|
|
1024
|
+
openapi: openapiConfigSchema.default({}),
|
|
1025
|
+
react: reactConfigSchema.default({}),
|
|
1026
|
+
redirects: z.array(redirectSchema).default([]),
|
|
1027
|
+
search: searchConfigSchema.default({}),
|
|
1028
|
+
seo: seoConfigSchema.default({}),
|
|
1029
|
+
theme: themeConfigSchema.default({}),
|
|
1030
|
+
title: z.string().default("Documentation"),
|
|
1031
|
+
toc: tocConfigSchema,
|
|
1032
|
+
});
|
|
1082
1033
|
|
|
1083
1034
|
/** Resolved config: every field present after defaults are applied. */
|
|
1084
1035
|
export type ResolvedConfig = z.infer<typeof blumeConfigSchema>;
|