blume 1.4.2 → 1.5.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/CHANGELOG.md +71 -0
- package/dist/cli/index.js +2260 -1100
- package/dist/cli/index.js.map +123 -117
- package/dist/types/ai/component-markdown.d.ts +14 -4
- package/dist/types/core/base-path.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +87 -27
- package/dist/types/core/config.d.ts +2 -1
- package/dist/types/core/data.d.ts +16 -1
- package/dist/types/core/diagnostics.d.ts +5 -1
- package/dist/types/core/i18n-ui.d.ts +12 -0
- package/dist/types/core/schema.d.ts +116 -15
- package/dist/types/core/sources/types.d.ts +11 -1
- package/dist/types/core/standard-schema.d.ts +7 -3
- package/dist/types/core/types.d.ts +43 -2
- package/dist/types/core/ui-packs/index.d.ts +9 -1
- package/dist/types/openapi/references.d.ts +14 -7
- package/dist/types/seo/x-handle.d.ts +3 -2
- package/docs/advanced/api-reference.mdx +8 -6
- package/docs/configuration/ai.mdx +26 -8
- package/docs/configuration/search.mdx +2 -0
- package/docs/configuration/seo.mdx +1 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/meta.mdx +2 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +35 -1
- package/docs/content/sources.mdx +1 -1
- package/docs/content/versioning.mdx +106 -0
- package/docs/reference/cli.mdx +1 -0
- package/docs/reference/frontmatter.mdx +3 -0
- package/package.json +13 -1
- package/skills/blume-migrate/SKILL.md +2 -2
- package/skills/blume-migrate/references/docusaurus.md +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +1 -1
- package/src/ai/agent-readability.ts +40 -12
- package/src/ai/api-catalog.ts +2 -2
- package/src/ai/ask-context.ts +49 -12
- package/src/ai/ask.ts +10 -1
- package/src/ai/component-markdown.ts +80 -43
- package/src/ai/llms.ts +42 -17
- package/src/ai/mcp/data.ts +48 -12
- package/src/ai/mcp/discovery.ts +52 -16
- package/src/ai/mcp/server.ts +280 -125
- package/src/ai/mcp/tools.ts +3 -3
- package/src/ai/skills.ts +32 -9
- package/src/ai/tar.ts +29 -70
- package/src/ai/visibility.ts +2 -2
- package/src/astro/component-slots.ts +2 -0
- package/src/astro/examples.ts +13 -5
- package/src/astro/generate.ts +113 -63
- package/src/astro/integration.ts +13 -2
- package/src/astro/islands.ts +23 -12
- package/src/astro/templates.ts +185 -41
- package/src/audit/agent.ts +16 -31
- package/src/audit/checks/content.ts +26 -11
- package/src/audit/checks/dns-aid.ts +3 -0
- package/src/audit/checks/indexability.ts +24 -6
- package/src/audit/checks/llms.ts +9 -4
- package/src/audit/checks/network.ts +2 -0
- package/src/audit/checks/social.ts +18 -10
- package/src/audit/crawl.ts +78 -25
- package/src/audit/report.ts +20 -19
- package/src/audit/run.ts +15 -5
- package/src/audit/snapshot.ts +29 -6
- package/src/audit/types.ts +25 -3
- package/src/blume-modules.d.ts +5 -1
- package/src/cli/commands/audit.ts +21 -21
- package/src/cli/commands/build.ts +30 -16
- package/src/cli/commands/dev.ts +15 -15
- package/src/cli/commands/doctor.ts +2 -0
- package/src/cli/commands/eject.ts +4 -4
- package/src/cli/commands/eval.ts +24 -30
- package/src/cli/commands/init.ts +9 -9
- package/src/cli/commands/mcp-stdio.ts +3 -0
- package/src/cli/commands/translate.ts +14 -3
- package/src/cli/commands/version.ts +85 -0
- package/src/cli/dev-lock.ts +31 -10
- package/src/cli/eject-scripts.ts +17 -2
- package/src/cli/env.ts +13 -30
- package/src/cli/index.ts +2 -0
- package/src/cli/init/questions.ts +1 -1
- package/src/cli/init/scaffold.ts +41 -13
- package/src/cli/internal-error.ts +1 -0
- package/src/cli/report-format.ts +22 -0
- package/src/components/content/AccordionItem.astro +2 -9
- package/src/components/content/ColorItem.astro +5 -13
- package/src/components/content/Component.astro +12 -8
- package/src/components/content/Frame.astro +2 -12
- package/src/components/content/Prompt.astro +12 -31
- package/src/components/content/Tab.astro +2 -9
- package/src/components/content/Tooltip.astro +1 -9
- package/src/components/content/Update.astro +2 -9
- package/src/components/content/auto-type-table.ts +3 -0
- package/src/components/content/diff.ts +9 -5
- package/src/components/content/github-info.ts +2 -0
- package/src/components/content/inline-markdown.ts +28 -0
- package/src/components/copy-feedback.ts +96 -0
- package/src/components/islands/ask-ai.tsx +111 -34
- package/src/components/islands/hooks.ts +5 -1
- package/src/components/islands/webmcp.ts +49 -12
- package/src/components/layout/Header.astro +25 -1
- package/src/components/layout/NavSelector.astro +11 -2
- package/src/components/layout/NavTree.astro +4 -2
- package/src/components/layout/PageActions.astro +20 -32
- package/src/components/layout/PageLayout.astro +8 -28
- package/src/components/layout/RootLayout.astro +24 -48
- package/src/components/layout/Search.astro +133 -22
- package/src/components/layout/VersionBanner.astro +39 -0
- package/src/components/layout/analytics-client.ts +8 -5
- package/src/components/layout/drawer-inert.ts +31 -0
- package/src/components/layout/hydration-hint.ts +1 -1
- package/src/components/layout/nav-utils.ts +1 -4
- package/src/components/layout/overrides.ts +25 -12
- package/src/components/layout/search/algolia.ts +18 -5
- package/src/components/layout/search/endpoint.ts +3 -0
- package/src/components/layout/search/flexsearch.ts +23 -7
- package/src/components/layout/search/orama-cloud.ts +1 -1
- package/src/components/layout/search/orama.ts +4 -1
- package/src/components/layout/search/pagefind.ts +8 -5
- package/src/components/layout/search/types.ts +45 -1
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/ApiOverview.astro +32 -6
- package/src/components/openapi/AsyncApiOperation.astro +237 -0
- package/src/components/openapi/Bindings.astro +89 -0
- package/src/components/openapi/MethodBadge.astro +3 -0
- package/src/components/openapi/Operation.astro +7 -2
- package/src/components/openapi/PanelTabs.astro +131 -0
- package/src/components/openapi/ParametersTable.astro +2 -0
- package/src/components/openapi/RequestPanel.astro +12 -119
- package/src/components/openapi/async-snippets.ts +174 -0
- package/src/components/openapi/async.ts +348 -0
- package/src/components/openapi/helpers.ts +52 -20
- package/src/components/openapi/panel.ts +11 -8
- package/src/components/openapi/security.ts +102 -29
- package/src/components/openapi/snippets.ts +11 -11
- package/src/components/raf-throttle.ts +21 -0
- package/src/components/slug.ts +14 -0
- package/src/core/base-path.ts +18 -1
- package/src/core/component-overrides.ts +28 -23
- package/src/core/config-input.ts +96 -27
- package/src/core/config.ts +20 -7
- package/src/core/content.ts +3 -1
- package/src/core/data.ts +16 -1
- package/src/core/define-components.ts +5 -0
- package/src/core/diagnostics.ts +46 -38
- package/src/core/frontmatter.ts +74 -4
- package/src/core/graph.ts +137 -53
- package/src/core/i18n-ui.ts +15 -0
- package/src/core/i18n.ts +16 -8
- package/src/core/load-module.ts +1 -0
- package/src/core/manifest.ts +92 -3
- package/src/core/meta.ts +44 -14
- package/src/core/nav-diagnostics.ts +3 -3
- package/src/core/navigation.ts +247 -67
- package/src/core/probe.ts +7 -19
- package/src/core/project-graph.ts +27 -4
- package/src/core/schema.ts +219 -67
- package/src/core/site-url.ts +27 -0
- package/src/core/sources/assets.ts +2 -0
- package/src/core/sources/cache.ts +16 -8
- package/src/core/sources/github-releases.ts +39 -11
- package/src/core/sources/mdx-remote.ts +4 -0
- package/src/core/sources/normalize.ts +93 -22
- package/src/core/sources/notion.ts +76 -22
- package/src/core/sources/portable-text.ts +48 -12
- package/src/core/sources/resolve.ts +1 -0
- package/src/core/sources/sanity.ts +68 -14
- package/src/core/sources/types.ts +17 -1
- package/src/core/sources/watch.ts +1 -1
- package/src/core/standard-schema.ts +9 -3
- package/src/core/text-width.ts +26 -0
- package/src/core/tsconfig-aliases.ts +9 -5
- package/src/core/types.ts +45 -2
- package/src/core/ui-packs/index.ts +9 -1
- package/src/core/version-cut.ts +301 -0
- package/src/core/version.ts +2 -0
- package/src/core/versions.ts +170 -0
- package/src/deploy/adapter-output.ts +5 -2
- package/src/deploy/cloudflare-negotiation.ts +40 -11
- package/src/deploy/robots.ts +2 -1
- package/src/deploy/rss.ts +2 -1
- package/src/deploy/sitemap.ts +89 -8
- package/src/deploy/vercel-negotiation.ts +11 -4
- package/src/eval/agents.ts +13 -10
- package/src/eval/report.ts +5 -18
- package/src/eval/run.ts +2 -2
- package/src/eval/schema.ts +1 -1
- package/src/markdown/base-links.ts +6 -6
- package/src/markdown/directives.ts +7 -1
- package/src/markdown/heading-anchors.ts +17 -6
- package/src/markdown/index.ts +73 -24
- package/src/markdown/inline-code.ts +14 -2
- package/src/markdown/language-icon.ts +6 -2
- package/src/markdown/mdast.ts +18 -4
- package/src/markdown/package-commands.ts +63 -58
- package/src/markdown/table-wrap.ts +4 -1
- package/src/markdown/twoslash.ts +2 -0
- package/src/og/card.ts +50 -33
- package/src/og/derive.ts +43 -27
- package/src/openapi/asyncapi.ts +366 -0
- package/src/openapi/model.ts +135 -66
- package/src/openapi/parse.ts +166 -33
- package/src/openapi/references.ts +47 -22
- package/src/openapi/render-mdx.ts +137 -59
- package/src/openapi/scalar.ts +8 -10
- package/src/openapi/source.ts +126 -29
- package/src/registry/eject.ts +7 -2
- package/src/search/documents.ts +103 -39
- package/src/search/facets.ts +7 -5
- package/src/search/orama-index.ts +117 -32
- package/src/search/popular.ts +10 -5
- package/src/search/providers.ts +2 -2
- package/src/search/sync/index.ts +2 -0
- package/src/search/sync/typesense.ts +4 -2
- package/src/seo/jsonld.ts +24 -6
- package/src/seo/x-handle.ts +8 -3
- package/src/theme/chrome-icons.ts +7 -2
- package/src/theme/fonts.ts +8 -4
- package/src/theme/icons.ts +4 -2
- package/src/theme/palette.ts +27 -15
- package/src/translate/ledger.ts +4 -2
- package/src/translate/meta.ts +15 -6
- package/src/translate/report.ts +10 -19
- package/src/translate/run.ts +29 -38
- package/src/translate/validate.ts +52 -17
- package/src/translate/work-list.ts +0 -0
- package/src/cli/coalesce.ts +0 -43
|
@@ -20,7 +20,6 @@ export interface SecuritySchemeLike {
|
|
|
20
20
|
scheme?: string;
|
|
21
21
|
/** `http` bearer: a hint at the token format, e.g. `JWT`. */
|
|
22
22
|
bearerFormat?: string;
|
|
23
|
-
[key: string]: unknown;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
/** One security requirement: scheme name -> required scopes (empty outside OAuth). */
|
|
@@ -85,42 +84,115 @@ export const resolveSecurity = (
|
|
|
85
84
|
return { alternatives, optional };
|
|
86
85
|
};
|
|
87
86
|
|
|
87
|
+
/**
|
|
88
|
+
* One AsyncAPI 3.x security entry: a `$ref` into
|
|
89
|
+
* `components.securitySchemes`, or an inline scheme object (the shape the
|
|
90
|
+
* official 2.x converter emits for scoped requirements, `scopes` on the
|
|
91
|
+
* scheme itself).
|
|
92
|
+
*/
|
|
93
|
+
export interface AsyncApiSecurityEntryLike {
|
|
94
|
+
$ref?: string;
|
|
95
|
+
type?: string;
|
|
96
|
+
scopes?: unknown;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const SECURITY_SCHEME_REF = /^#\/components\/securitySchemes\/(?<name>[^/]+)$/u;
|
|
100
|
+
|
|
101
|
+
/** Runtime string check for spec fields a hand-written document may corrupt. */
|
|
102
|
+
const isSpecString = (value: string | undefined): value is string =>
|
|
103
|
+
typeof value === "string";
|
|
104
|
+
|
|
105
|
+
/** Parsed YAML can put anything in a security list; keep only real objects. */
|
|
106
|
+
const isSecurityEntryObject = (
|
|
107
|
+
entry: AsyncApiSecurityEntryLike
|
|
108
|
+
): entry is AsyncApiSecurityEntryLike =>
|
|
109
|
+
typeof entry === "object" && entry !== null;
|
|
110
|
+
|
|
111
|
+
/** Decode a JSON-pointer token: `kafka~1sasl` -> `kafka/sasl`. */
|
|
112
|
+
const unescapePointer = (token: string): string =>
|
|
113
|
+
token.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve an AsyncAPI 3.x security list. Unlike OpenAPI's requirement maps,
|
|
117
|
+
* each entry names a single scheme and any one entry satisfies the operation
|
|
118
|
+
* — so every entry becomes its own one-scheme "or" alternative. A `$ref`
|
|
119
|
+
* pointing at an undeclared scheme is kept (with `scheme` undefined),
|
|
120
|
+
* matching {@link resolveSecurity}'s render-don't-drop rule.
|
|
121
|
+
*/
|
|
122
|
+
export const resolveAsyncApiSecurity = (
|
|
123
|
+
entries: AsyncApiSecurityEntryLike[],
|
|
124
|
+
schemes: Record<string, SecuritySchemeLike> | undefined
|
|
125
|
+
): OperationSecurity => {
|
|
126
|
+
const alternatives: ResolvedScheme[][] = [];
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
if (!isSecurityEntryObject(entry)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const pointer = SECURITY_SCHEME_REF.exec(entry.$ref ?? "")?.groups?.name;
|
|
132
|
+
const name = pointer === undefined ? undefined : unescapePointer(pointer);
|
|
133
|
+
const inline = name === undefined && !isSpecString(entry.$ref);
|
|
134
|
+
const scheme = inline ? entry : schemes?.[name ?? ""];
|
|
135
|
+
alternatives.push([
|
|
136
|
+
{
|
|
137
|
+
key:
|
|
138
|
+
name ??
|
|
139
|
+
(isSpecString(entry.type) && entry.type !== ""
|
|
140
|
+
? entry.type
|
|
141
|
+
: "security"),
|
|
142
|
+
scheme,
|
|
143
|
+
scopes:
|
|
144
|
+
inline && Array.isArray(entry.scopes)
|
|
145
|
+
? entry.scopes.filter(
|
|
146
|
+
(scope): scope is string => typeof scope === "string"
|
|
147
|
+
)
|
|
148
|
+
: [],
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
return { alternatives, optional: false };
|
|
153
|
+
};
|
|
154
|
+
|
|
88
155
|
const capitalize = (text: string): string =>
|
|
89
156
|
text.charAt(0).toUpperCase() + text.slice(1);
|
|
90
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Fixed labels for scheme types with no per-scheme variation. Covers both
|
|
160
|
+
* OpenAPI's types and the broker-auth types AsyncAPI adds; `http` is handled
|
|
161
|
+
* separately (its label depends on the scheme/bearerFormat fields).
|
|
162
|
+
*/
|
|
163
|
+
const TYPE_LABELS = new Map<string, string>([
|
|
164
|
+
["X509", "X.509 certificate"],
|
|
165
|
+
["apiKey", "API key"],
|
|
166
|
+
["asymmetricEncryption", "Asymmetric encryption"],
|
|
167
|
+
["gssapi", "SASL/GSSAPI"],
|
|
168
|
+
["httpApiKey", "API key"],
|
|
169
|
+
["mutualTLS", "Mutual TLS"],
|
|
170
|
+
["oauth2", "OAuth2 access token"],
|
|
171
|
+
["openIdConnect", "OpenID Connect token"],
|
|
172
|
+
["plain", "SASL/PLAIN"],
|
|
173
|
+
["scramSha256", "SASL/SCRAM-SHA-256"],
|
|
174
|
+
["scramSha512", "SASL/SCRAM-SHA-512"],
|
|
175
|
+
["symmetricEncryption", "Symmetric encryption"],
|
|
176
|
+
["userPassword", "Username & password"],
|
|
177
|
+
]);
|
|
178
|
+
|
|
91
179
|
/** A short human label for a scheme row, e.g. `Bearer token` or `API key`. */
|
|
92
180
|
export const schemeLabel = (resolved: ResolvedScheme): string => {
|
|
93
181
|
const { scheme } = resolved;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
: "Bearer token";
|
|
101
|
-
}
|
|
102
|
-
if (kind === "basic") {
|
|
103
|
-
return "Basic auth";
|
|
104
|
-
}
|
|
105
|
-
return kind ? `HTTP ${kind}` : "HTTP auth";
|
|
106
|
-
}
|
|
107
|
-
case "apiKey": {
|
|
108
|
-
return "API key";
|
|
182
|
+
if (scheme?.type === "http") {
|
|
183
|
+
const kind = (scheme.scheme ?? "").toLowerCase();
|
|
184
|
+
if (kind === "bearer") {
|
|
185
|
+
return scheme.bearerFormat
|
|
186
|
+
? `Bearer token (${scheme.bearerFormat})`
|
|
187
|
+
: "Bearer token";
|
|
109
188
|
}
|
|
110
|
-
|
|
111
|
-
return "
|
|
112
|
-
}
|
|
113
|
-
case "openIdConnect": {
|
|
114
|
-
return "OpenID Connect token";
|
|
115
|
-
}
|
|
116
|
-
case "mutualTLS": {
|
|
117
|
-
return "Mutual TLS";
|
|
118
|
-
}
|
|
119
|
-
default: {
|
|
120
|
-
// Unknown scheme ref: the component name is the best label available.
|
|
121
|
-
return resolved.key;
|
|
189
|
+
if (kind === "basic") {
|
|
190
|
+
return "Basic auth";
|
|
122
191
|
}
|
|
192
|
+
return kind ? `HTTP ${kind}` : "HTTP auth";
|
|
123
193
|
}
|
|
194
|
+
// Unknown scheme ref: the component name is the best label available.
|
|
195
|
+
return TYPE_LABELS.get(scheme?.type ?? "") ?? resolved.key;
|
|
124
196
|
};
|
|
125
197
|
|
|
126
198
|
/**
|
|
@@ -138,7 +210,8 @@ export const schemeCarrier = (
|
|
|
138
210
|
case "openIdConnect": {
|
|
139
211
|
return { in: "header", name: "Authorization" };
|
|
140
212
|
}
|
|
141
|
-
case "apiKey":
|
|
213
|
+
case "apiKey":
|
|
214
|
+
case "httpApiKey": {
|
|
142
215
|
return { in: scheme.in ?? "header", name: scheme.name ?? resolved.key };
|
|
143
216
|
}
|
|
144
217
|
default: {
|
|
@@ -87,8 +87,8 @@ const headerValues = (
|
|
|
87
87
|
params: ParamLike[],
|
|
88
88
|
schemas: Record<string, SchemaLike>,
|
|
89
89
|
auth: SampleAuth | undefined
|
|
90
|
-
)
|
|
91
|
-
const headers
|
|
90
|
+
) => {
|
|
91
|
+
const headers = { ...auth?.headers };
|
|
92
92
|
for (const param of params) {
|
|
93
93
|
if (param.in === "header" && param.required && param.name) {
|
|
94
94
|
headers[param.name] = String(
|
|
@@ -230,14 +230,14 @@ const LANGUAGES: SampleLanguage[] = [
|
|
|
230
230
|
{ build: pythonSnippet, id: "python", label: "Python", lang: "python" },
|
|
231
231
|
];
|
|
232
232
|
|
|
233
|
-
const ALIASES
|
|
234
|
-
bash
|
|
235
|
-
javascript
|
|
236
|
-
node
|
|
237
|
-
py
|
|
238
|
-
shell
|
|
239
|
-
typescript
|
|
240
|
-
|
|
233
|
+
const ALIASES = new Map([
|
|
234
|
+
["bash", "curl"],
|
|
235
|
+
["javascript", "js"],
|
|
236
|
+
["node", "js"],
|
|
237
|
+
["py", "python"],
|
|
238
|
+
["shell", "curl"],
|
|
239
|
+
["typescript", "js"],
|
|
240
|
+
]);
|
|
241
241
|
|
|
242
242
|
/** The sample languages to render, resolved from config ids (unknown ids dropped). */
|
|
243
243
|
export const sampleLanguages = (ids: string[]): SampleLanguage[] => {
|
|
@@ -246,7 +246,7 @@ export const sampleLanguages = (ids: string[]): SampleLanguage[] => {
|
|
|
246
246
|
const out: SampleLanguage[] = [];
|
|
247
247
|
const seen = new Set<string>();
|
|
248
248
|
for (const raw of wanted) {
|
|
249
|
-
const id = ALIASES
|
|
249
|
+
const id = ALIASES.get(raw.toLowerCase()) ?? raw.toLowerCase();
|
|
250
250
|
const language = byId.get(id);
|
|
251
251
|
if (language && !seen.has(id)) {
|
|
252
252
|
seen.add(id);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coalesce a high-frequency event handler (resize, scroll) into at most one
|
|
3
|
+
* call per animation frame — the toc-element scroll pattern, shared. Calls
|
|
4
|
+
* landing while a frame is pending are dropped; the handler runs once on the
|
|
5
|
+
* next frame with the latest state. Layout reads inside `fn` then happen once
|
|
6
|
+
* per frame instead of once per event, without the settle lag a debounce
|
|
7
|
+
* would add to position-tracking handlers.
|
|
8
|
+
*/
|
|
9
|
+
export const rafThrottle = (fn: () => void): (() => void) => {
|
|
10
|
+
let ticking = false;
|
|
11
|
+
return () => {
|
|
12
|
+
if (ticking) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
ticking = true;
|
|
16
|
+
requestAnimationFrame(() => {
|
|
17
|
+
ticking = false;
|
|
18
|
+
fn();
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { slug } from "github-slugger";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Slug a component's title into a DOM id (accordions, tabs, changelog
|
|
5
|
+
* updates), replacing three identical per-component copies. github-slugger —
|
|
6
|
+
* already what heading anchors use, both here and in Satteri's own
|
|
7
|
+
* heading-ids — so a component id slugs exactly like a heading with the same
|
|
8
|
+
* text (unicode letters kept, `user_id` keeps its underscore, `C#` keeps
|
|
9
|
+
* nothing extra dropped). Stateless on purpose: components render across many
|
|
10
|
+
* pages in one build process, so a stateful slugger would leak duplicate
|
|
11
|
+
* suffixes between pages — same-page duplicates are de-duplicated client-side
|
|
12
|
+
* by each component's own script.
|
|
13
|
+
*/
|
|
14
|
+
export const componentSlug = (value: string): string => slug(value);
|
package/src/core/base-path.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `components/islands/base-path.ts` and serves `deployment.base` via `BASE_URL`.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { trimEnd } from "./trim.ts";
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* Canonicalize a configured base path to either `""` (none) or `/seg[/seg…]`
|
|
17
19
|
* (leading slash, no trailing slash, collapsed inner slashes). A blank value or
|
|
@@ -33,10 +35,25 @@ export const normalizeBasePath = (input?: string): string => {
|
|
|
33
35
|
* `/docs` and `/docs/` as the same page) and collapse an empty path to `/`.
|
|
34
36
|
*/
|
|
35
37
|
export const normalizePath = (path: string): string => {
|
|
36
|
-
const trimmed = path
|
|
38
|
+
const trimmed = trimEnd(path, "/");
|
|
37
39
|
return trimmed === "" ? "/" : trimmed;
|
|
38
40
|
};
|
|
39
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Canonicalize a route-ish string (a configured route, a page path, an agent-
|
|
44
|
+
* supplied route) to `/` or `/seg[/seg…]`: trimmed, exactly one leading slash,
|
|
45
|
+
* no trailing slash. The shared spelling of what openapi/references,
|
|
46
|
+
* ai/ask-context, and ai/mcp/server each hand-rolled with slightly different
|
|
47
|
+
* regexes.
|
|
48
|
+
*/
|
|
49
|
+
export const normalizeRoute = (input: string): string => {
|
|
50
|
+
const trimmed = trimEnd(input.trim(), "/");
|
|
51
|
+
if (trimmed === "") {
|
|
52
|
+
return "/";
|
|
53
|
+
}
|
|
54
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
40
57
|
/**
|
|
41
58
|
* Whether a link target is a root-relative internal path (`/x`) — the only
|
|
42
59
|
* shape a base path applies to. Protocol-relative (`//host`), absolute URLs,
|
|
@@ -65,18 +65,20 @@ const GROUPS = ["mdx", "layout", "islands"] as const;
|
|
|
65
65
|
const GROUP_SET = new Set<string>(GROUPS);
|
|
66
66
|
type Group = (typeof GROUPS)[number];
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const isGroup = (name: string): name is Group => GROUP_SET.has(name);
|
|
69
|
+
|
|
70
|
+
const FRAMEWORK_BY_EXT = new Map<string, OverrideFramework>([
|
|
71
|
+
["jsx", "react"],
|
|
72
|
+
["svelte", "svelte"],
|
|
73
|
+
["tsx", "react"],
|
|
74
|
+
["vue", "vue"],
|
|
75
|
+
]);
|
|
74
76
|
|
|
75
|
-
const FRAMEWORK_LABEL
|
|
77
|
+
const FRAMEWORK_LABEL = {
|
|
76
78
|
react: "React",
|
|
77
79
|
svelte: "Svelte",
|
|
78
80
|
vue: "Vue",
|
|
79
|
-
}
|
|
81
|
+
} satisfies Record<OverrideFramework, string>;
|
|
80
82
|
|
|
81
83
|
/** Extensions probed (in order) when a specifier omits one. */
|
|
82
84
|
const COMPONENT_EXTS = [
|
|
@@ -90,7 +92,7 @@ const COMPONENT_EXTS = [
|
|
|
90
92
|
"svelte",
|
|
91
93
|
];
|
|
92
94
|
|
|
93
|
-
const HYDRATION_MODES = new Set<HydrationMode>([
|
|
95
|
+
const HYDRATION_MODES: ReadonlySet<string> = new Set<HydrationMode>([
|
|
94
96
|
"idle",
|
|
95
97
|
"load",
|
|
96
98
|
"media",
|
|
@@ -98,6 +100,9 @@ const HYDRATION_MODES = new Set<HydrationMode>([
|
|
|
98
100
|
"visible",
|
|
99
101
|
]);
|
|
100
102
|
|
|
103
|
+
const isHydrationMode = (value: string): value is HydrationMode =>
|
|
104
|
+
HYDRATION_MODES.has(value);
|
|
105
|
+
|
|
101
106
|
interface ImportBinding {
|
|
102
107
|
/** Exported name: `"default"` or a named export. */
|
|
103
108
|
imported: string;
|
|
@@ -226,7 +231,7 @@ const toImport = (
|
|
|
226
231
|
}
|
|
227
232
|
}
|
|
228
233
|
return {
|
|
229
|
-
framework: FRAMEWORK_BY_EXT
|
|
234
|
+
framework: FRAMEWORK_BY_EXT.get(extension) ?? null,
|
|
230
235
|
name: imported,
|
|
231
236
|
path,
|
|
232
237
|
};
|
|
@@ -270,9 +275,9 @@ const applyDescriptorProperty = (
|
|
|
270
275
|
} else if (
|
|
271
276
|
name === "client" &&
|
|
272
277
|
ts.isStringLiteral(init) &&
|
|
273
|
-
|
|
278
|
+
isHydrationMode(init.text)
|
|
274
279
|
) {
|
|
275
|
-
descriptor.client = init.text
|
|
280
|
+
descriptor.client = init.text;
|
|
276
281
|
} else if (name === "media" && ts.isStringLiteral(init)) {
|
|
277
282
|
descriptor.media = init.text;
|
|
278
283
|
}
|
|
@@ -334,13 +339,14 @@ const finalize = (
|
|
|
334
339
|
);
|
|
335
340
|
}
|
|
336
341
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
342
|
+
const normalized: NormalizedOverride = { identifier, key, source };
|
|
343
|
+
if (client) {
|
|
344
|
+
normalized.client = client;
|
|
345
|
+
}
|
|
346
|
+
if (media) {
|
|
347
|
+
normalized.media = media;
|
|
348
|
+
}
|
|
349
|
+
return normalized;
|
|
344
350
|
};
|
|
345
351
|
|
|
346
352
|
const normalizeEntry = (
|
|
@@ -446,22 +452,21 @@ const collectGroupOverrides = (
|
|
|
446
452
|
}
|
|
447
453
|
const name = propName(property.name);
|
|
448
454
|
if (
|
|
449
|
-
!(name &&
|
|
455
|
+
!(name && isGroup(name)) ||
|
|
450
456
|
!ts.isObjectLiteralExpression(property.initializer)
|
|
451
457
|
) {
|
|
452
458
|
return;
|
|
453
459
|
}
|
|
454
|
-
const group = name as Group;
|
|
455
460
|
for (const entry of property.initializer.properties) {
|
|
456
461
|
const normalized = normalizeEntry(
|
|
457
462
|
entry,
|
|
458
|
-
|
|
463
|
+
name,
|
|
459
464
|
imports,
|
|
460
465
|
dir,
|
|
461
466
|
result.warnings
|
|
462
467
|
);
|
|
463
468
|
if (normalized) {
|
|
464
|
-
result[
|
|
469
|
+
result[name].push(normalized);
|
|
465
470
|
}
|
|
466
471
|
}
|
|
467
472
|
};
|
package/src/core/config-input.ts
CHANGED
|
@@ -205,6 +205,8 @@ export interface SanitySource {
|
|
|
205
205
|
/** A Notion database; pages become entries, blocks become MDX. */
|
|
206
206
|
export interface NotionSource {
|
|
207
207
|
type: "notion";
|
|
208
|
+
/** Max concurrent Notion API requests; default 3 (Notion's per-integration pace). */
|
|
209
|
+
concurrency?: number;
|
|
208
210
|
/** Notion database id. */
|
|
209
211
|
database: string;
|
|
210
212
|
/** Opt-in dev polling interval (seconds); omit to freeze for the session. */
|
|
@@ -653,6 +655,12 @@ export interface AskConfig {
|
|
|
653
655
|
* limiting, and streaming. Accepts an absolute URL or root-relative path.
|
|
654
656
|
*/
|
|
655
657
|
endpoint?: string;
|
|
658
|
+
/**
|
|
659
|
+
* Extra system-prompt text appended to the built-in instructions — use it
|
|
660
|
+
* for identity, language, or tone. The built-in grounding behavior (answer
|
|
661
|
+
* from the retrieved excerpts, cite pages as Markdown links) is preserved.
|
|
662
|
+
*/
|
|
663
|
+
instructions?: string;
|
|
656
664
|
/** Model id to use. Defaults to `openai/gpt-5.5`. */
|
|
657
665
|
model?: string;
|
|
658
666
|
/** Which backend routes the request. Defaults to `gateway`. */
|
|
@@ -753,6 +761,7 @@ export interface AiConfig {
|
|
|
753
761
|
/** Web Bot Auth signature directory. Off until at least one key is listed. */
|
|
754
762
|
export interface WebBotAuthConfig {
|
|
755
763
|
/** Public JWKs to publish (e.g. an Ed25519 key: `kty: "OKP"`, `crv: "Ed25519"`, `x: …`). */
|
|
764
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- mirrors the schema's `z.record(z.unknown())` (the drift guard requires it); JWK parameters are validated at parse time, not typed.
|
|
756
765
|
keys?: Record<string, unknown>[];
|
|
757
766
|
}
|
|
758
767
|
|
|
@@ -830,6 +839,59 @@ export interface I18nConfig {
|
|
|
830
839
|
ui?: Record<string, Record<string, Record<string, string>>>;
|
|
831
840
|
}
|
|
832
841
|
|
|
842
|
+
// ---------------------------------------------------------------------------
|
|
843
|
+
// Versions
|
|
844
|
+
// ---------------------------------------------------------------------------
|
|
845
|
+
|
|
846
|
+
/** A frozen documentation snapshot: a directory under the content root. */
|
|
847
|
+
export interface ArchivedVersionInput {
|
|
848
|
+
/**
|
|
849
|
+
* The "you're viewing an old version" notice: `true` (default) for the
|
|
850
|
+
* built-in message, a string for custom copy, `false` to hide it.
|
|
851
|
+
*/
|
|
852
|
+
banner?: boolean | string;
|
|
853
|
+
/**
|
|
854
|
+
* Where this version's pages point their canonical URL. `latest` (default)
|
|
855
|
+
* targets the same page in the current docs when it still exists (self
|
|
856
|
+
* otherwise); `self` keeps every page authoritative.
|
|
857
|
+
*/
|
|
858
|
+
canonical?: "latest" | "self";
|
|
859
|
+
/**
|
|
860
|
+
* Directory name under the content root, and the URL segment. Must start
|
|
861
|
+
* with a letter (e.g. `v1.0`).
|
|
862
|
+
*/
|
|
863
|
+
id: string;
|
|
864
|
+
/** Switcher label; defaults to the id. */
|
|
865
|
+
label?: string;
|
|
866
|
+
/** Emit `noindex` on every page of this version. Defaults to `false`. */
|
|
867
|
+
noindex?: boolean;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Docs versioning. Opt-in: the latest docs live at the content root with
|
|
872
|
+
* unprefixed URLs, and each archived version is a frozen snapshot directory
|
|
873
|
+
* (`content/docs/<id>/`) cut with `blume version <id>`. Archived means frozen:
|
|
874
|
+
* snapshots carry their own translations and are never retranslated.
|
|
875
|
+
*/
|
|
876
|
+
export interface VersionsConfig {
|
|
877
|
+
/** Frozen snapshots, newest first — this order is the switcher order. */
|
|
878
|
+
archived?: ArchivedVersionInput[];
|
|
879
|
+
/** Labels the unprefixed tree (the latest docs) in the switcher. */
|
|
880
|
+
current: {
|
|
881
|
+
/** Small tag rendered next to the label (e.g. `Latest`). */
|
|
882
|
+
badge?: string;
|
|
883
|
+
label: string;
|
|
884
|
+
};
|
|
885
|
+
switcher?: {
|
|
886
|
+
/**
|
|
887
|
+
* Where switching lands when the page has no equivalent in the target
|
|
888
|
+
* version: `same-page` (default) goes to the equivalent when it exists
|
|
889
|
+
* (version root otherwise); `root` always goes to the version root.
|
|
890
|
+
*/
|
|
891
|
+
redirect?: "same-page" | "root";
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
833
895
|
// ---------------------------------------------------------------------------
|
|
834
896
|
// Deployment & redirects
|
|
835
897
|
// ---------------------------------------------------------------------------
|
|
@@ -1104,28 +1166,24 @@ export interface ReactConfig {
|
|
|
1104
1166
|
// ---------------------------------------------------------------------------
|
|
1105
1167
|
|
|
1106
1168
|
/**
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
* SPA (a single self-contained route).
|
|
1169
|
+
* The shared shape of both API-reference blocks (`openapi`, `asyncapi`). Only
|
|
1170
|
+
* the per-block defaults differ; those are documented on the extending
|
|
1171
|
+
* interfaces.
|
|
1111
1172
|
*/
|
|
1112
|
-
|
|
1113
|
-
/** Code-sample languages shown per operation (Blume renderer). */
|
|
1114
|
-
codeSamples?: string[];
|
|
1173
|
+
interface ReferenceConfig {
|
|
1115
1174
|
/** Turn the reference on. Defaults to `false`. */
|
|
1116
1175
|
enabled?: boolean;
|
|
1117
1176
|
/** Start nested schema rows expanded (Blume renderer). Defaults to `false`. */
|
|
1118
1177
|
expandSchemas?: boolean;
|
|
1119
1178
|
/** Who renders the reference. Defaults to `blume`. */
|
|
1120
1179
|
renderer?: "blume" | "scalar";
|
|
1121
|
-
/** Where the reference mounts. Defaults to `/reference`. */
|
|
1122
|
-
route?: string;
|
|
1123
1180
|
/**
|
|
1124
1181
|
* Extra Scalar options forwarded verbatim to the embedded `<ScalarComponent>`
|
|
1125
1182
|
* (Scalar renderer only) — e.g. `localization`, `agent`,
|
|
1126
1183
|
* `hideTestRequestButton`, `orderSchemaPropertiesBy`. These win over Blume's
|
|
1127
1184
|
* derived spec/theme config, so it's a full escape hatch to Scalar's API.
|
|
1128
1185
|
*/
|
|
1186
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- mirrors the schema's `z.record(z.unknown())` (the drift guard requires it); the values are Scalar's own API surface, deliberately unmodeled.
|
|
1129
1187
|
scalar?: Record<string, unknown>;
|
|
1130
1188
|
/** One or more specs; each renders on its own route by default. */
|
|
1131
1189
|
sources?: OpenApiSource[];
|
|
@@ -1136,27 +1194,36 @@ export interface OpenApiConfig {
|
|
|
1136
1194
|
}
|
|
1137
1195
|
|
|
1138
1196
|
/**
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
* `
|
|
1197
|
+
* OpenAPI reference. By default (`renderer: "blume"`) Blume renders its own UI:
|
|
1198
|
+
* one real page per operation, grouped by tag in the sidebar and included in
|
|
1199
|
+
* search, llms.txt, and OG. Set `renderer: "scalar"` for the embedded Scalar
|
|
1200
|
+
* SPA (a single self-contained route).
|
|
1142
1201
|
*/
|
|
1143
|
-
export interface
|
|
1144
|
-
/**
|
|
1145
|
-
|
|
1146
|
-
|
|
1202
|
+
export interface OpenApiConfig extends ReferenceConfig {
|
|
1203
|
+
/**
|
|
1204
|
+
* Code-sample languages shown per operation (Blume renderer). Defaults to
|
|
1205
|
+
* `["curl", "js", "python"]`.
|
|
1206
|
+
*/
|
|
1207
|
+
codeSamples?: string[];
|
|
1208
|
+
/** Where the reference mounts. Defaults to `/reference`. */
|
|
1147
1209
|
route?: string;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* AsyncAPI reference. Same shape as {@link OpenApiConfig}: by default
|
|
1214
|
+
* (`renderer: "blume"`) Blume normalizes the spec to AsyncAPI 3.x and renders
|
|
1215
|
+
* its own UI — one real page per operation, grouped by tag (or channel) in the
|
|
1216
|
+
* sidebar and included in search, llms.txt, and OG. Set `renderer: "scalar"`
|
|
1217
|
+
* for the embedded Scalar SPA (a single self-contained route).
|
|
1218
|
+
*/
|
|
1219
|
+
export interface AsyncApiConfig extends ReferenceConfig {
|
|
1148
1220
|
/**
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
* Scalar's API.
|
|
1221
|
+
* Code-sample tools shown per operation (Blume renderer). Defaults to every
|
|
1222
|
+
* tool appropriate to the operation's protocol binding.
|
|
1152
1223
|
*/
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
|
|
1156
|
-
/** Shorthand for a single source. */
|
|
1157
|
-
spec?: string;
|
|
1158
|
-
/** Scalar theme name. */
|
|
1159
|
-
theme?: string;
|
|
1224
|
+
codeSamples?: string[];
|
|
1225
|
+
/** Where the reference mounts. Defaults to `/events`. */
|
|
1226
|
+
route?: string;
|
|
1160
1227
|
}
|
|
1161
1228
|
|
|
1162
1229
|
// ---------------------------------------------------------------------------
|
|
@@ -1294,7 +1361,7 @@ export interface BlumeConfig {
|
|
|
1294
1361
|
ai?: AiConfig;
|
|
1295
1362
|
/** Analytics providers (PostHog, Vercel, or arbitrary scripts). */
|
|
1296
1363
|
analytics?: AnalyticsConfig;
|
|
1297
|
-
/** AsyncAPI reference (
|
|
1364
|
+
/** AsyncAPI reference (native renderer by default, Scalar opt-out). */
|
|
1298
1365
|
asyncapi?: AsyncApiConfig;
|
|
1299
1366
|
/** Site-wide announcement banner shown above the header. */
|
|
1300
1367
|
banner?: BannerConfig;
|
|
@@ -1367,6 +1434,8 @@ export interface BlumeConfig {
|
|
|
1367
1434
|
title?: string;
|
|
1368
1435
|
/** On-page table of contents. Defaults to on (H2–H3). */
|
|
1369
1436
|
toc?: TocConfig;
|
|
1437
|
+
/** Docs versioning (opt-in frozen snapshots with a version switcher). */
|
|
1438
|
+
versions?: VersionsConfig;
|
|
1370
1439
|
}
|
|
1371
1440
|
|
|
1372
1441
|
// ---------------------------------------------------------------------------
|
package/src/core/config.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
3
5
|
import type { BlumeConfig } from "./config-input.ts";
|
|
4
6
|
import { applyDeploymentEnv } from "./deployment-env.ts";
|
|
5
7
|
import { BlumeError, diagnosticsFromZod } from "./diagnostics.ts";
|
|
@@ -68,7 +70,8 @@ import type { Diagnostic } from "./types.ts";
|
|
|
68
70
|
* **Reference docs**
|
|
69
71
|
* - `openapi` — native OpenAPI reference: one real page per operation, woven
|
|
70
72
|
* into the sidebar and search. Point `sources`/`spec` at your spec.
|
|
71
|
-
* - `asyncapi` — AsyncAPI reference
|
|
73
|
+
* - `asyncapi` — native AsyncAPI reference with the same treatment; 2.x specs
|
|
74
|
+
* are normalized to 3.x automatically.
|
|
72
75
|
*
|
|
73
76
|
* **Search & AI**
|
|
74
77
|
* - `search` — search backend `provider` (`orama` by default; `pagefind`,
|
|
@@ -161,6 +164,15 @@ export interface ConfigLoadResult {
|
|
|
161
164
|
|
|
162
165
|
const importConfigModule = createModuleLoader();
|
|
163
166
|
|
|
167
|
+
/**
|
|
168
|
+
* The slice of a user config module probed before schema defaults apply:
|
|
169
|
+
* whether `theme.fonts` was actually set. `looseObject` keeps every other key
|
|
170
|
+
* out of scope; a non-object at either level simply fails the probe.
|
|
171
|
+
*/
|
|
172
|
+
const themeFontsProbeSchema = z.looseObject({
|
|
173
|
+
theme: z.looseObject({ fonts: z.unknown() }).optional(),
|
|
174
|
+
});
|
|
175
|
+
|
|
164
176
|
/**
|
|
165
177
|
* Load and validate the project config. When no config file exists, schema
|
|
166
178
|
* defaults produce a fully resolved config so the zero-boilerplate path works.
|
|
@@ -176,11 +188,14 @@ export const loadConfig = async (
|
|
|
176
188
|
): Promise<ConfigLoadResult> => {
|
|
177
189
|
const configFile = findConfigFile(root);
|
|
178
190
|
|
|
179
|
-
let raw: unknown
|
|
191
|
+
let raw: unknown;
|
|
180
192
|
if (configFile) {
|
|
181
193
|
try {
|
|
182
194
|
raw = await importConfigModule(configFile);
|
|
183
195
|
} catch (error) {
|
|
196
|
+
// SAFETY: the module loader rejects with the thrown load/parse failure,
|
|
197
|
+
// which Node surfaces as an Error; a non-Error rejection only degrades
|
|
198
|
+
// the interpolated message.
|
|
184
199
|
throw new BlumeError({
|
|
185
200
|
code: "BLUME_CONFIG_LOAD_FAILED",
|
|
186
201
|
file: configFile,
|
|
@@ -191,11 +206,9 @@ export const loadConfig = async (
|
|
|
191
206
|
}
|
|
192
207
|
|
|
193
208
|
// Read before parsing: schema defaults erase the set-vs-defaulted distinction.
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
(raw as { theme?: { fonts?: unknown } }).theme?.fonts !== undefined
|
|
198
|
-
);
|
|
209
|
+
const probe = themeFontsProbeSchema.safeParse(raw);
|
|
210
|
+
const themeFontsConfigured =
|
|
211
|
+
probe.success && probe.data.theme?.fonts !== undefined;
|
|
199
212
|
|
|
200
213
|
const parsed = blumeConfigSchema.safeParse(raw ?? {});
|
|
201
214
|
if (!parsed.success) {
|
package/src/core/content.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ResolvedI18nConfig } from "./schema.ts";
|
|
1
|
+
import type { ResolvedI18nConfig, ResolvedVersionsConfig } from "./schema.ts";
|
|
2
2
|
import { filesystemSource } from "./sources/filesystem.ts";
|
|
3
3
|
import { normalizeEntry } from "./sources/normalize.ts";
|
|
4
4
|
import type { Diagnostic, PageRecord } from "./types.ts";
|
|
@@ -24,6 +24,7 @@ export const discoverContent = async (options: {
|
|
|
24
24
|
defaultType: string;
|
|
25
25
|
basePath?: string;
|
|
26
26
|
i18n?: ResolvedI18nConfig;
|
|
27
|
+
versions?: ResolvedVersionsConfig;
|
|
27
28
|
}): Promise<{ pages: PageRecord[]; diagnostics: Diagnostic[] }> => {
|
|
28
29
|
const source = filesystemSource({
|
|
29
30
|
exclude: options.exclude,
|
|
@@ -43,6 +44,7 @@ export const discoverContent = async (options: {
|
|
|
43
44
|
defaultType: options.defaultType,
|
|
44
45
|
i18n: options.i18n,
|
|
45
46
|
source: { name: source.name, prefix: source.prefix, staged: false },
|
|
47
|
+
versions: options.versions,
|
|
46
48
|
});
|
|
47
49
|
pages.push(...normalized.pages);
|
|
48
50
|
diagnostics.push(...normalized.diagnostics);
|