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
|
@@ -114,9 +114,24 @@ export interface PageRecord {
|
|
|
114
114
|
locale: string;
|
|
115
115
|
/**
|
|
116
116
|
* Locale-agnostic logical route shared by every translation (e.g.
|
|
117
|
-
* `/guides/x`
|
|
117
|
+
* `/guides/x`, or `/v1.0/guides/x` under versioning — the key is
|
|
118
|
+
* version-specific, so translations group within their version).
|
|
119
|
+
* Pages with the same key are translations of each other.
|
|
118
120
|
*/
|
|
119
121
|
translationKey: string;
|
|
122
|
+
/**
|
|
123
|
+
* Resolved docs version: an archived id (`v1.0`) for snapshot pages, `""`
|
|
124
|
+
* for the current (unprefixed) docs — including every page of an
|
|
125
|
+
* unversioned project.
|
|
126
|
+
*/
|
|
127
|
+
version: string;
|
|
128
|
+
/**
|
|
129
|
+
* Version- and locale-agnostic logical route (e.g. `/guides/x` for
|
|
130
|
+
* `/v1.0/guides/x`). Pages with the same key and locale are the same
|
|
131
|
+
* logical page across versions — this drives the switcher's same-page
|
|
132
|
+
* navigation and the canonical-to-latest lookup.
|
|
133
|
+
*/
|
|
134
|
+
versionKey: string;
|
|
120
135
|
/**
|
|
121
136
|
* True for entries filled in from the fallback locale to pad a locale's
|
|
122
137
|
* navigation for pages it hasn't translated yet. The record's content —
|
|
@@ -170,7 +185,12 @@ export type NavNode = {
|
|
|
170
185
|
label: string;
|
|
171
186
|
badge?: string;
|
|
172
187
|
directory?: DirectoryMode;
|
|
173
|
-
|
|
188
|
+
/**
|
|
189
|
+
* The group's resolved render mode. Always stamped by both builders
|
|
190
|
+
* (generated and explicit-config sidebars), so the renderer can rely
|
|
191
|
+
* on it per node.
|
|
192
|
+
*/
|
|
193
|
+
display: SidebarDisplay;
|
|
174
194
|
icon?: string;
|
|
175
195
|
route?: string;
|
|
176
196
|
/**
|
|
@@ -249,6 +269,12 @@ export interface ContentGraph {
|
|
|
249
269
|
navigation: Navigation;
|
|
250
270
|
/** Navigation per locale; one entry per configured locale under i18n. */
|
|
251
271
|
navigationByLocale: Record<string, Navigation>;
|
|
272
|
+
/**
|
|
273
|
+
* Navigation per archived version, keyed by version id and then locale code
|
|
274
|
+
* (`""` on a single-locale site). The current version's trees are
|
|
275
|
+
* `navigation`/`navigationByLocale`; empty when versioning is off.
|
|
276
|
+
*/
|
|
277
|
+
navigationByVersion: Record<string, Record<string, Navigation>>;
|
|
252
278
|
/** Map of route -> pageId for fast lookup and duplicate detection. */
|
|
253
279
|
routes: Map<string, string>;
|
|
254
280
|
diagnostics: Diagnostic[];
|
|
@@ -269,6 +295,12 @@ export interface LocaleSwitchOption {
|
|
|
269
295
|
/** True when this locale has no real translation (renders fallback content). */
|
|
270
296
|
untranslated: boolean;
|
|
271
297
|
}
|
|
298
|
+
/** A docs version a logical page exists in, for the switcher and canonicals. */
|
|
299
|
+
export interface VersionAlternate {
|
|
300
|
+
/** Version id; `""` for the current (unprefixed) docs. */
|
|
301
|
+
version: string;
|
|
302
|
+
path: string;
|
|
303
|
+
}
|
|
272
304
|
/** A route entry written to `blume.manifest.json`. */
|
|
273
305
|
export interface RouteManifestEntry {
|
|
274
306
|
id: string;
|
|
@@ -296,6 +328,15 @@ export interface RouteManifestEntry {
|
|
|
296
328
|
locale: string;
|
|
297
329
|
/** Locales this logical page is genuinely translated into (excludes fallbacks). */
|
|
298
330
|
alternates: RouteAlternate[];
|
|
331
|
+
/** Resolved docs version (`""` for the current docs; see `PageRecord.version`). */
|
|
332
|
+
version: string;
|
|
333
|
+
/**
|
|
334
|
+
* Versions this logical page exists in within this route's locale — the
|
|
335
|
+
* current version first, then archived versions in configured order. Drives
|
|
336
|
+
* the switcher's same-page navigation and the canonical-to-latest lookup.
|
|
337
|
+
* Empty when versioning is off.
|
|
338
|
+
*/
|
|
339
|
+
versionAlternates: VersionAlternate[];
|
|
299
340
|
/** True when this route renders fallback content for a missing translation. */
|
|
300
341
|
fallback?: boolean;
|
|
301
342
|
/** Resolved "last updated" ISO date, when the feature is enabled. */
|
|
@@ -10,4 +10,12 @@ import type { UIStringsOverride } from "../i18n-ui.ts";
|
|
|
10
10
|
* `zh-TW`). A locale's pack is merged onto English by {@link resolveUIStrings},
|
|
11
11
|
* so a pack only needs the keys it translates.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Starter packs keyed by locale code. Open-keyed on purpose: resolvers look
|
|
15
|
+
* packs up by whatever locale a project configures, not only the codes here.
|
|
16
|
+
*/
|
|
17
|
+
interface UiPackRegistry {
|
|
18
|
+
[locale: string]: UIStringsOverride;
|
|
19
|
+
}
|
|
20
|
+
export declare const UI_PACKS: UiPackRegistry;
|
|
21
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
2
|
+
export { normalizeRoute } from "../core/base-path.ts";
|
|
2
3
|
/**
|
|
3
4
|
* Pure resolution of the configured API reference blocks into concrete routes,
|
|
4
5
|
* labels, and a renderer choice — no file IO, so the content source, the
|
|
@@ -43,9 +44,10 @@ export interface ReferenceSource {
|
|
|
43
44
|
theme?: string;
|
|
44
45
|
/**
|
|
45
46
|
* Arbitrary Scalar config forwarded to `<ScalarComponent>` (Scalar renderer
|
|
46
|
-
* only). Takes precedence over Blume's derived spec/theme config.
|
|
47
|
+
* only). Takes precedence over Blume's derived spec/theme config. Typed off
|
|
48
|
+
* the config schema so the two can never drift.
|
|
47
49
|
*/
|
|
48
|
-
scalar?:
|
|
50
|
+
scalar?: ResolvedConfig["openapi"]["scalar"];
|
|
49
51
|
/** Display options carried through to the Blume renderer. */
|
|
50
52
|
display: ReferenceDisplay;
|
|
51
53
|
/**
|
|
@@ -54,12 +56,17 @@ export interface ReferenceSource {
|
|
|
54
56
|
*/
|
|
55
57
|
collisions?: string[];
|
|
56
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Lowercase, hyphen-separated slug: `Add a Pet!` -> `add-a-pet`. Unicode
|
|
61
|
+
* letters are kept (`Größe` -> `größe`), NFC-normalized so canonically
|
|
62
|
+
* equivalent spellings (NFD input from macOS tooling) land on one slug.
|
|
63
|
+
* Non-ASCII slugs rely on the emitter percent-encoding the URL where a raw
|
|
64
|
+
* URI is required (sitemap, canonical).
|
|
65
|
+
*/
|
|
57
66
|
export declare const slugify: (text: string) => string;
|
|
58
|
-
/** Normalize a configured route to a single leading slash, no trailing slash. */
|
|
59
|
-
export declare const normalizeRoute: (route: string) => string;
|
|
60
67
|
/**
|
|
61
|
-
* Resolve every enabled reference.
|
|
62
|
-
* by default
|
|
68
|
+
* Resolve every enabled reference. Both blocks honor their `renderer` —
|
|
69
|
+
* Blume's own UI by default, with the embedded Scalar SPA as the opt-out.
|
|
63
70
|
*/
|
|
64
71
|
export declare const resolveReferences: (config: ResolvedConfig) => ReferenceSource[];
|
|
65
72
|
/**
|
|
@@ -70,7 +77,7 @@ export declare const resolveReferences: (config: ResolvedConfig) => ReferenceSou
|
|
|
70
77
|
* targets so such a tab doesn't read as a broken link.
|
|
71
78
|
*/
|
|
72
79
|
export declare const referenceRoutes: (config: ResolvedConfig) => string[];
|
|
73
|
-
/** Blume-rendered
|
|
80
|
+
/** Blume-rendered references (both kinds), deduped by route (first wins). */
|
|
74
81
|
export declare const blumeReferences: (config: ResolvedConfig) => ReferenceSource[];
|
|
75
82
|
/** Whether any reference is Scalar-rendered (gates the `@scalar/astro` dep + pages). */
|
|
76
83
|
export declare const hasScalarReferences: (config: ResolvedConfig) => boolean;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Astro's collections carry no schema here, so a page's `seo.x.creator` reaches
|
|
8
8
|
* them as raw frontmatter, and the schema's own transform never runs on it.
|
|
9
9
|
* (Blume's page pipeline does reject a non-string `creator` before the page is
|
|
10
|
-
* built, so
|
|
10
|
+
* built, so the string guard is defense in depth rather than the expected
|
|
11
|
+
* path.)
|
|
11
12
|
*/
|
|
12
|
-
export declare const normalizeXHandle: (value:
|
|
13
|
+
export declare const normalizeXHandle: <Value>(value: Value) => string | undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: OpenAPI / AsyncAPI
|
|
3
|
-
description: Drop in an OpenAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
|
|
3
|
+
description: Drop in an OpenAPI or AsyncAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, and generated code samples. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. The config below points Blume at the public Petstore spec as an example.
|
|
6
|
+
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, and generated code samples. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. Event-driven APIs get the [same treatment from an AsyncAPI spec](#asyncapi). The config below points Blume at the public Petstore spec as an example.
|
|
7
7
|
|
|
8
8
|
```ts blume.config.ts lineNumbers
|
|
9
9
|
openapi: {
|
|
@@ -153,7 +153,7 @@ Blume's own [`i18n`](/docs/content/i18n) translates the docs chrome, but Scalar
|
|
|
153
153
|
|
|
154
154
|
## AsyncAPI
|
|
155
155
|
|
|
156
|
-
Event-driven APIs use a sibling `asyncapi` block with the same shape.
|
|
156
|
+
Event-driven APIs use a sibling `asyncapi` block with the same shape — and the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, and an Authorization section derived from the spec's `securitySchemes` (server-level and operation-level, alternatives as "or" groups). Only the default route differs (`/events`):
|
|
157
157
|
|
|
158
158
|
```ts blume.config.ts lineNumbers
|
|
159
159
|
asyncapi: {
|
|
@@ -162,6 +162,8 @@ asyncapi: {
|
|
|
162
162
|
}
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
AsyncAPI **2.x specs are normalized to 3.x automatically** with the official AsyncAPI converter, so `publish`/`subscribe` channels map onto `send`/`receive` operation pages with stable URLs — later upgrading the spec file itself through the converter moves nothing. Operations group by tag; untagged operations group under their channel address.
|
|
166
|
+
|
|
167
|
+
Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on the `openapi` block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
|
|
168
|
+
|
|
169
|
+
Everything documented above carries over unchanged: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags, and search indexing by operation summary and tag. Setting `renderer: "scalar"` opts back into the embedded Scalar SPA — Scalar auto-detects the document type and renders channels, operations, messages, and a Models section (no interactive playground for events) — where, as with OpenAPI, only `noindex` applies.
|
|
@@ -136,6 +136,22 @@ ai: {
|
|
|
136
136
|
|
|
137
137
|
`label` is the question that gets asked; `icon` is optional. Leave `suggestions` unset (or empty) and the panel opens to a plain input.
|
|
138
138
|
|
|
139
|
+
### Custom instructions
|
|
140
|
+
|
|
141
|
+
Add your own system-prompt text with `instructions` — identity, language, tone, or anything else the assistant should keep in mind:
|
|
142
|
+
|
|
143
|
+
```ts blume.config.ts lineNumbers
|
|
144
|
+
ai: {
|
|
145
|
+
ask: {
|
|
146
|
+
enabled: true,
|
|
147
|
+
instructions:
|
|
148
|
+
"You are Bloomy, the Acme docs assistant. Answer in the language the question was asked in, and keep answers under three paragraphs.",
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Your text is **appended to** the built-in instructions rather than replacing them: the built-in part carries the [grounding](#grounding) contract — answer only from the retrieved pages, cite them as Markdown links — that the chat panel's citations depend on, so it stays intact whatever you add.
|
|
154
|
+
|
|
139
155
|
### Grounding
|
|
140
156
|
|
|
141
157
|
Ask AI is **grounded in your docs**. For each question it retrieves the most relevant pages — using the same lexical [Orama](/docs/configuration/search) index that powers on-page search — and injects them into the model's system prompt, so answers come from your content instead of the model's own knowledge. The assistant is told to answer only from the retrieved pages, to say when something isn't covered, and to cite the pages it drew from.
|
|
@@ -185,15 +201,17 @@ A static build with Ask AI enabled and no external `endpoint` fails fast with a
|
|
|
185
201
|
|
|
186
202
|
By default Ask AI routes through the **Vercel AI Gateway**: `model` is a `provider/model` string, so you switch models by changing it (`openai/gpt-5.5`, `anthropic/claude-sonnet-4-5`, and so on) with no provider SDK to install. The gateway reads `AI_GATEWAY_API_KEY` from your environment and is wired up automatically when you deploy on Vercel.
|
|
187
203
|
|
|
188
|
-
Set `provider` to point Ask AI somewhere else. Each backend reads its API key from an environment variable
|
|
204
|
+
Set `provider` to point Ask AI somewhere else. Each backend reads its API key from an environment variable and streams through a provider SDK you install in your project — only the one you use:
|
|
205
|
+
|
|
206
|
+
| `provider` | `model` | API key env var | SDK to install |
|
|
207
|
+
| --- | --- | --- | --- |
|
|
208
|
+
| `gateway` (default) | a `provider/model` string via the AI Gateway | `AI_GATEWAY_API_KEY` | none — ships with Blume |
|
|
209
|
+
| `openrouter` | any [OpenRouter](https://openrouter.ai) model | `OPENROUTER_API_KEY` | `@openrouter/ai-sdk-provider` |
|
|
210
|
+
| `llmgateway` | any [LLMGateway](https://llmgateway.io) model | `LLMGATEWAY_API_KEY` | `@ai-sdk/openai-compatible` |
|
|
211
|
+
| `inkeep` | an [Inkeep](https://inkeep.com) QA model | `INKEEP_API_KEY` | `@ai-sdk/openai-compatible` |
|
|
212
|
+
| `openai-compatible` | whatever your endpoint serves | set with `apiKeyEnv` | `@ai-sdk/openai-compatible` |
|
|
189
213
|
|
|
190
|
-
|
|
191
|
-
| --- | --- | --- |
|
|
192
|
-
| `gateway` (default) | a `provider/model` string via the AI Gateway | `AI_GATEWAY_API_KEY` |
|
|
193
|
-
| `openrouter` | any [OpenRouter](https://openrouter.ai) model | `OPENROUTER_API_KEY` |
|
|
194
|
-
| `llmgateway` | any [LLMGateway](https://llmgateway.io) model | `LLMGATEWAY_API_KEY` |
|
|
195
|
-
| `inkeep` | an [Inkeep](https://inkeep.com) QA model | `INKEEP_API_KEY` |
|
|
196
|
-
| `openai-compatible` | whatever your endpoint serves | set with `apiKeyEnv` |
|
|
214
|
+
The SDKs are optional peer dependencies, so add the one your backend needs to your project (e.g. `npm install @openrouter/ai-sdk-provider`). If it's missing, the build warns with the exact package name before Vite would fail to resolve the import.
|
|
197
215
|
|
|
198
216
|
For example, to use OpenRouter:
|
|
199
217
|
|
|
@@ -42,6 +42,8 @@ Write `href` as if the site were mounted at the root — a `basePath` is applied
|
|
|
42
42
|
|
|
43
43
|
For every indexable page, Blume indexes its title, description, and body reduced to plain text — code blocks, images, and markup are stripped, so results stay relevant. The index is built from your source files, so it's identical in dev and production.
|
|
44
44
|
|
|
45
|
+
On a [versioned](/docs/content/versioning) site, results default to the version being viewed, with an "All versions" toggle in the dialog footer (remembered per reader). Cross-version hits name their version on the row. Orama, FlexSearch, Algolia, and Typesense honor the scoping — hosted records carry a `version` facet, with the current docs uploaded as `"current"` — while Pagefind stays unscoped, matching its locale behavior.
|
|
46
|
+
|
|
45
47
|
## Tags
|
|
46
48
|
|
|
47
49
|
Add `search.tags` to a page's frontmatter to group it under a filter in the search dialog — readers can narrow results to a tag with a click. Tags also become a facet on the hosted providers.
|
|
@@ -249,7 +249,7 @@ URLs are absolute when `deployment.site` is set. Pages marked `seo.noindex` are
|
|
|
249
249
|
|
|
250
250
|
## Sitemap
|
|
251
251
|
|
|
252
|
-
Blume writes a `sitemap.xml` of every indexable page at build time. It needs an absolute [`deployment.site`](/docs/deployment) and lists every page except drafts, hidden, and `noindex` pages. On by default:
|
|
252
|
+
Blume writes a `sitemap.xml` of every indexable page at build time. It needs an absolute [`deployment.site`](/docs/deployment) and lists every page except drafts, hidden, and `noindex` pages. On a [versioned](/docs/content/versioning) site, archived pages whose canonical points at their live equivalent are left out too — the live page is the one to index. On by default:
|
|
253
253
|
|
|
254
254
|
```ts blume.config.ts lineNumbers
|
|
255
255
|
seo: {
|
package/docs/content/i18n.mdx
CHANGED
|
@@ -3,7 +3,7 @@ title: Internationalization
|
|
|
3
3
|
description: Serve your docs in multiple languages with locale-aware routing, per-language navigation, translated UI, and SEO — all convention-first.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Blume serves one project in many languages. Drop translated files into the right place and Blume wires up routing, the language switcher, per-locale navigation, and SEO for you — there's no separate routing layer to maintain. It's opt-in: without an `i18n` block, your site stays single-language exactly as before.
|
|
6
|
+
Blume serves one project in many languages. Drop translated files into the right place and Blume wires up routing, the language switcher, per-locale navigation, and SEO for you — there's no separate routing layer to maintain. It's opt-in: without an `i18n` block, your site stays single-language exactly as before. It also composes with [versioning](/docs/content/versioning) — a frozen snapshot keeps its translations, and locale fallback works within each version.
|
|
7
7
|
|
|
8
8
|
## Enable it
|
|
9
9
|
|
package/docs/content/meta.mdx
CHANGED
|
@@ -31,11 +31,12 @@ Every field is optional — set only what you want to override.
|
|
|
31
31
|
| `icon` | `string` | Icon shown next to the label. |
|
|
32
32
|
| `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
|
|
33
33
|
| `collapsed` | `boolean` | Under the [`group` display mode](/docs/content/navigation#display-modes), whether the group starts collapsed. |
|
|
34
|
+
| `display` | `"flat" \| "group" \| "page"` | Render mode for this group; overrides the global [`navigation.sidebar.display`](/docs/content/navigation#display-modes). |
|
|
34
35
|
| `pages` | `string[]` | Explicit order for the group's children, by slug. |
|
|
35
36
|
|
|
36
37
|
The `pages` array lists children by slug — the folder or file name with its numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is `"quickstart"`). Children you leave out still appear, after the listed ones.
|
|
37
38
|
|
|
38
|
-
How groups render — flat headers, collapsible disclosures, or drill-in panels —
|
|
39
|
+
How groups render — flat headers, collapsible disclosures, or drill-in panels — defaults to the sidebar-wide `navigation.sidebar.display`; set `display` here to override it for this group alone. A folder's `index` page can also set it from frontmatter, which wins over `meta.ts` — see [per-group overrides](/docs/content/navigation#per-group-overrides).
|
|
39
40
|
|
|
40
41
|
## Computed meta
|
|
41
42
|
|
package/docs/content/meta.ts
CHANGED
|
@@ -69,7 +69,39 @@ navigation: {
|
|
|
69
69
|
`page` mode keeps deep sections tidy — reach for it when groups have many children and you'd rather drill into them than scroll past them.
|
|
70
70
|
:::
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
### Per-group overrides
|
|
73
|
+
|
|
74
|
+
Any generated group can opt out of the global mode — no explicit sidebar required. Set `display` in the folder's [`meta.ts`](/docs/content/meta), or — when the folder has an `index` page — under `sidebar` in that page's frontmatter, and only that group changes:
|
|
75
|
+
|
|
76
|
+
```ts meta.ts
|
|
77
|
+
import { defineMeta } from "blume";
|
|
78
|
+
|
|
79
|
+
export default defineMeta({
|
|
80
|
+
title: "Client SDKs",
|
|
81
|
+
display: "page",
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```yaml index.mdx
|
|
86
|
+
---
|
|
87
|
+
title: Client SDKs
|
|
88
|
+
sidebar:
|
|
89
|
+
display: page
|
|
90
|
+
---
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A generated group's effective mode resolves highest priority first:
|
|
94
|
+
|
|
95
|
+
1. `sidebar.display` in the group's own `index` page frontmatter
|
|
96
|
+
2. `display` in the folder's `meta.ts`
|
|
97
|
+
3. The global `navigation.sidebar.display`
|
|
98
|
+
4. The Blume default (`flat`)
|
|
99
|
+
|
|
100
|
+
A group's `display` applies to that group only — nested subgroups resolve their own value through the same chain. A `page`-mode group with an index page still drills into its sub-panel: the index page lists as the panel's first item, and landing on its URL opens the panel directly.
|
|
101
|
+
|
|
102
|
+
`sidebar.display` means nothing anywhere else — on a non-index page, on the content root's own `index` page (the root isn't a group; use `navigation.sidebar.display`), or on any page when an [explicit sidebar](#explicit-sidebar) is configured (its items own each group's mode) — so Blume reports a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning instead of silently dropping it. `collapsed` remains specific to `group` mode; it's inert when a group resolves to `flat` or `page`.
|
|
103
|
+
|
|
104
|
+
A group in an [explicit sidebar](#explicit-sidebar) overrides the global mode with its own `display`, exactly as before.
|
|
73
105
|
|
|
74
106
|
## Ordering
|
|
75
107
|
|
|
@@ -171,6 +203,8 @@ navigation: {
|
|
|
171
203
|
|
|
172
204
|
Each item takes a `label`, a `path`, and optional `icon`, `description`, and `tag`. `kind` (`dropdown`, `product`, `version`, or `language`) is a hint for how the selector is used; all render the same dropdown.
|
|
173
205
|
|
|
206
|
+
With [versioning](/docs/content/versioning) configured, Blume renders a version selector automatically — declaring your own `kind: "version"` selector here replaces the automatic one, so hand-rolled setups keep working.
|
|
207
|
+
|
|
174
208
|
## Featured links
|
|
175
209
|
|
|
176
210
|
Pin links to the top of the sidebar, above every section — a blog, a changelog, a contact or support page that should always be one click away. Unlike the generated tree, featured links are **not scoped by tab**: they show on every route, on every breakpoint.
|
package/docs/content/sources.mdx
CHANGED
|
@@ -146,7 +146,7 @@ export default defineConfig({
|
|
|
146
146
|
});
|
|
147
147
|
```
|
|
148
148
|
|
|
149
|
-
The integration token comes from the `NOTION_TOKEN` environment variable (share the database with your integration). By default every page is imported; set `publishedValue` to make the `Status` property a publish gate — any other value then maps to `draft: true`, which production builds drop. **Notion image URLs are signed and expire**, so the adapter downloads them at build time into the site's assets and rewrites the references — a CMS image never rots a static build.
|
|
149
|
+
The integration token comes from the `NOTION_TOKEN` environment variable (share the database with your integration). By default every page is imported; set `publishedValue` to make the `Status` property a publish gate — any other value then maps to `draft: true`, which production builds drop. **Notion image URLs are signed and expire**, so the adapter downloads them at build time into the site's assets and rewrites the references — a CMS image never rots a static build. API calls are paced through a small request pool (3 at a time, matching Notion's per-integration rate limit) so databases with hundreds of pages import without tripping `429` responses; set `concurrency` on the source to tune it.
|
|
150
150
|
|
|
151
151
|
## Preview and sync
|
|
152
152
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Versioning
|
|
3
|
+
description: Freeze snapshots of your docs per release with a version switcher, canonical-to-latest SEO, version-scoped search, and a version-aware agent surface.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Blume versions your docs the way releases actually work: the latest documentation lives at your content root with clean, unprefixed URLs, and each past version is a frozen snapshot in its own folder. Cut a snapshot when you ship, and Blume wires up the switcher, the "old version" notice, search scoping, SEO, and the agent surface for you. It's opt-in: without a `versions` block, nothing changes.
|
|
7
|
+
|
|
8
|
+
## Enable it
|
|
9
|
+
|
|
10
|
+
Add a `versions` block naming the current docs and any archived snapshots:
|
|
11
|
+
|
|
12
|
+
```ts blume.config.ts lineNumbers
|
|
13
|
+
versions: {
|
|
14
|
+
current: { label: "v2.0", badge: "Latest" },
|
|
15
|
+
archived: [
|
|
16
|
+
{ id: "v1.0" },
|
|
17
|
+
{ id: "v0.9", label: "0.9 (legacy)" },
|
|
18
|
+
],
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`current` labels the unprefixed tree in the switcher (with an optional `badge`). Each archived entry's `id` is both the snapshot's directory name and its URL segment — ids must start with a letter (`v1.0`, not `1.0`) so they can never collide with [numeric ordering prefixes](/docs/content/navigation#ordering). List archived versions newest first; that order is the switcher order.
|
|
23
|
+
|
|
24
|
+
## Cut a version
|
|
25
|
+
|
|
26
|
+
When you release, freeze the current docs with one command:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
blume version v1.0
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This copies your content tree into `docs/v1.0/` (existing snapshots excluded), rewrites root-absolute links inside the copy so they stay within the snapshot (`/guides/x` becomes `/v1.0/guides/x`, fenced and inline code untouched), and registers the id in `blume.config.ts` — or prints the entry to paste when your config is shaped in a way it won't touch. Links to pages that aren't part of the copied tree — generated API references, remote sources like a changelog — keep pointing at the live pages, since the snapshot has no copy of them. Run `blume version` with no id to list the configured versions.
|
|
33
|
+
|
|
34
|
+
Review and commit the new directory like any other content. Restart `blume dev` to pick it up.
|
|
35
|
+
|
|
36
|
+
```txt
|
|
37
|
+
docs/
|
|
38
|
+
index.mdx -> / (latest)
|
|
39
|
+
guides/quickstart.mdx -> /guides/quickstart
|
|
40
|
+
v1.0/
|
|
41
|
+
index.mdx -> /v1.0 (frozen)
|
|
42
|
+
guides/quickstart.mdx -> /v1.0/guides/quickstart
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Archived means frozen.** Future edits belong in the live tree; a snapshot is the docs as they were. Blume leans on that: snapshots keep their own folder meta and translations, [`blume translate`](/docs/reference/translate) never retranslates them, and a configured explicit sidebar applies only to the current docs — a snapshot's sidebar always comes from its own files.
|
|
46
|
+
|
|
47
|
+
## The switcher and the notice
|
|
48
|
+
|
|
49
|
+
With versions configured, the header grows a version dropdown automatically. Switching lands on the same page in the target version when it exists, and on that version's root when it doesn't (set `switcher.redirect: "root"` to always land on the root). If you declare your own `kind: "version"` selector in [`navigation.selectors`](/docs/content/navigation#selectors), it replaces the automatic one.
|
|
50
|
+
|
|
51
|
+
Every archived page also shows a non-dismissible notice with a "Go to latest" link pointing at the page's live equivalent. Customize or disable it per version:
|
|
52
|
+
|
|
53
|
+
```ts blume.config.ts
|
|
54
|
+
archived: [
|
|
55
|
+
{ id: "v1.0", banner: "These docs cover the 1.x SDK." },
|
|
56
|
+
{ id: "v0.9", banner: false },
|
|
57
|
+
];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## SEO
|
|
61
|
+
|
|
62
|
+
Old docs are search engines' favorite trap: the stale page outranks the live one, or both compete. Blume defaults to the answer SEO guides recommend and no other docs framework automates — archived pages stay indexable but declare the **latest equivalent as their canonical**, so the live page is authoritative while version-only content (a page that no longer exists in the latest docs) remains findable with a self-canonical.
|
|
63
|
+
|
|
64
|
+
Per version you can pick a different treatment:
|
|
65
|
+
|
|
66
|
+
```ts blume.config.ts
|
|
67
|
+
archived: [
|
|
68
|
+
{ id: "v1.0" }, // canonical → latest (default)
|
|
69
|
+
{ id: "v0.9", canonical: "self" }, // every page authoritative
|
|
70
|
+
{ id: "v0.8", noindex: true }, // deindexed entirely
|
|
71
|
+
];
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The sitemap follows suit: archived pages whose canonical points at a live equivalent are left out, `noindex` versions are left out wholesale, and version-only pages stay listed. A page's own `seo.canonical` frontmatter always wins.
|
|
75
|
+
|
|
76
|
+
## Search
|
|
77
|
+
|
|
78
|
+
The search dialog scopes results to the version being viewed, with an "All versions" toggle (remembered per reader) beside the language one. Cross-version hits name their version on the result row. Orama (the default), FlexSearch, Algolia, and Typesense all honor the scoping — hosted records carry a `version` facet, with the current docs uploaded as `"current"` — while Pagefind stays unscoped, matching its locale behavior.
|
|
79
|
+
|
|
80
|
+
## Agents
|
|
81
|
+
|
|
82
|
+
The agent surface is version-aware — something no other docs framework does:
|
|
83
|
+
|
|
84
|
+
- The MCP `search_docs` and `list_pages` tools default to the current docs and accept `version`: an archived id (`"v1.0"`) or `"all"`. `get_navigation` returns an archived snapshot's tree on request.
|
|
85
|
+
- `llms.txt` sections archived versions after the current docs, labeled `1.0 (archived)`, so an agent reading the index knows which docs are frozen.
|
|
86
|
+
- `llms-full.txt` stays current-only — the flat dump never interleaves frozen copies of the same page.
|
|
87
|
+
- Raw Markdown mirrors (`.md` URLs) exist for every version's pages, as for any route.
|
|
88
|
+
|
|
89
|
+
## With i18n
|
|
90
|
+
|
|
91
|
+
Versioning composes with [internationalization](/docs/content/i18n). On disk the version folder is outermost — a snapshot naturally contains its locale folders — while in URLs the locale stays outermost, matching the rest of the site:
|
|
92
|
+
|
|
93
|
+
```txt
|
|
94
|
+
docs/
|
|
95
|
+
guides/x.mdx -> /guides/x
|
|
96
|
+
fr/guides/x.mdx -> /fr/guides/x
|
|
97
|
+
v1.0/
|
|
98
|
+
guides/x.mdx -> /v1.0/guides/x
|
|
99
|
+
fr/guides/x.mdx -> /fr/v1.0/guides/x
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Locale fallback works within each version: an untranslated snapshot page renders the fallback locale's content at the localized URL, and `hreflang` alternates group per version. A version id may not collide with a configured locale code — Blume rejects that config outright.
|
|
103
|
+
|
|
104
|
+
## What stays unversioned
|
|
105
|
+
|
|
106
|
+
Versioning covers the docs content tree. The blog, changelog, API references generated from OpenAPI specs, and custom pages are always current. Two more behaviors worth knowing: header tabs are defined against the current docs, so inside an archived tree the sidebar renders unscoped by tabs; and large sites should note each snapshot is a full copy — content, search index entries, and navigation data all grow per version.
|
package/docs/reference/cli.mdx
CHANGED
|
@@ -24,6 +24,7 @@ blume <command> [options]
|
|
|
24
24
|
| `blume audit` | Audit the built site for SEO and health issues. |
|
|
25
25
|
| `blume eval` | Test the docs: an agent answers your questions using only the documentation. |
|
|
26
26
|
| `blume translate` | Translate docs into the configured locales with a local agent CLI. |
|
|
27
|
+
| `blume version [id]` | Freeze the current docs as an [archived version](/docs/content/versioning) (no id lists configured versions). |
|
|
27
28
|
|
|
28
29
|
## Common flags
|
|
29
30
|
|
|
@@ -46,8 +46,11 @@ sidebar:
|
|
|
46
46
|
icon: download
|
|
47
47
|
badge: New
|
|
48
48
|
hidden: false
|
|
49
|
+
display: page
|
|
49
50
|
```
|
|
50
51
|
|
|
52
|
+
`display` sets the render mode of the page's folder group ([per-group overrides](/docs/content/navigation#per-group-overrides)) and is only meaningful on a folder's `index` page under the generated sidebar — anywhere else (a non-index page, the content root's own `index` page, or any page under an explicit `navigation.sidebar`) it has no group to configure, and Blume warns with `BLUME_SIDEBAR_DISPLAY_IGNORED`.
|
|
53
|
+
|
|
51
54
|
## SEO
|
|
52
55
|
|
|
53
56
|
```yaml lineNumbers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blume",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Documentation that's fast, AI-ready, and zero-config.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"@astrojs/node": "^11.0.0",
|
|
73
73
|
"@astrojs/react": "^6.0.0",
|
|
74
74
|
"@astrojs/vercel": "^11.0.3",
|
|
75
|
+
"@asyncapi/converter": "^2.0.2",
|
|
75
76
|
"@clack/prompts": "^1.7.0",
|
|
76
77
|
"@iconify-json/lucide": "^1.2.115",
|
|
77
78
|
"@iconify/types": "^2.0.0",
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
"@shikijs/twoslash": "^4.2.0",
|
|
87
88
|
"@tailwindcss/typography": "^0.5.20",
|
|
88
89
|
"@tailwindcss/vite": "^4",
|
|
90
|
+
"@types/mdast": "^4.0.4",
|
|
89
91
|
"@vercel/analytics": "^2.0.1",
|
|
90
92
|
"ai": "^7.0.42",
|
|
91
93
|
"astro": "^7.1.0",
|
|
@@ -93,6 +95,7 @@
|
|
|
93
95
|
"chokidar": "^5.0.0",
|
|
94
96
|
"citty": "^0.1.6",
|
|
95
97
|
"consola": "^3.4.0",
|
|
98
|
+
"cross-spawn": "^7.0.6",
|
|
96
99
|
"dompurify": "^3.4.13",
|
|
97
100
|
"dotenv": "^17.4.2",
|
|
98
101
|
"epub-gen-memory": "^1.1.2",
|
|
@@ -113,10 +116,16 @@
|
|
|
113
116
|
"medium-zoom": "^1.1.0",
|
|
114
117
|
"mermaid": "^11.16.1",
|
|
115
118
|
"micromark-extension-gfm": "^3.0.0",
|
|
119
|
+
"nanotar": "^0.3.0",
|
|
116
120
|
"node-html-parser": "^9.0.0",
|
|
117
121
|
"openapi-sampler": "^1.7.4",
|
|
122
|
+
"p-limit": "^7.3.1",
|
|
123
|
+
"p-map": "^7.0.6",
|
|
124
|
+
"p-retry": "^8.0.0",
|
|
125
|
+
"package-manager-detector": "^1.8.0",
|
|
118
126
|
"pagefind": "^1.3.0",
|
|
119
127
|
"pathe": "^2.0.0",
|
|
128
|
+
"perfect-debounce": "^2.1.0",
|
|
120
129
|
"picomatch": "^4.0.5",
|
|
121
130
|
"react": "^19.0.0",
|
|
122
131
|
"react-dom": "^19.0.0",
|
|
@@ -126,16 +135,19 @@
|
|
|
126
135
|
"sharp": "^0.35.3",
|
|
127
136
|
"shiki": "^4.2.0",
|
|
128
137
|
"simple-icons": "^13.0.0",
|
|
138
|
+
"string-width": "^8.1.0",
|
|
129
139
|
"tailwindcss": "^4.3.3",
|
|
130
140
|
"takumi-js": "^2.2.1",
|
|
131
141
|
"tinyglobby": "^0.2.10",
|
|
132
142
|
"twoslash": "^0.3.9",
|
|
133
143
|
"typescript": "^6.0.3",
|
|
144
|
+
"ufo": "^1.6.4",
|
|
134
145
|
"undici": "^8.9.0",
|
|
135
146
|
"write-file-atomic": "^8.0.0",
|
|
136
147
|
"zod": "^4.3.6"
|
|
137
148
|
},
|
|
138
149
|
"devDependencies": {
|
|
150
|
+
"@types/cross-spawn": "^6.0.6",
|
|
139
151
|
"@types/html-escaper": "^3.0.4",
|
|
140
152
|
"@types/js-yaml": "^4.0.9",
|
|
141
153
|
"@types/node": "^22.10.0",
|
|
@@ -44,8 +44,8 @@ The single biggest shift for most sources — especially Mintlify — is that **
|
|
|
44
44
|
|
|
45
45
|
- **Folders become groups, files become pages.** A page's sidebar label is its frontmatter `title`; a group's label is the humanized folder name.
|
|
46
46
|
- **Ordering resolves highest-priority-first:** an explicit `navigation.sidebar` (replaces the whole tree) → a folder's `meta.ts` `pages` array → a page's frontmatter `sidebar.order` → the filesystem (`index` first, then numeric filename prefix like `01-`, then alphabetical).
|
|
47
|
-
- **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
|
|
48
|
-
- **Sidebar render mode
|
|
47
|
+
- **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order.
|
|
48
|
+
- **Sidebar render mode: a global default with per-folder overrides.** `navigation.sidebar.display` in `blume.config.ts` is `"flat"` (default), `"group"` (collapsible), or `"page"` (drill-in sub-panel) and sets the mode for every group at once. A folder can override its **own** group: `display` in its `meta.ts`, or — sugar when the folder has an `index` page — `sidebar.display` in that index page's frontmatter. Precedence: index frontmatter → `meta.ts` → global config → `flat`; an override applies to that one group only (nested subgroups resolve their own chain). So a source's per-category collapse/drill-in modes migrate per folder — only reach for the global mode when the whole sidebar changes. Under an explicit `navigation.sidebar`, the config item's own `display` field is the only per-group control (frontmatter/meta `display` is ignored there, with a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning).
|
|
49
49
|
- **An explicit `navigation.sidebar` replaces filesystem generation entirely.** Use it only for a nav shape files can't express. Its items are a page route string, a group (`{ label, items }`), or a link (`{ label, href }`).
|
|
50
50
|
- **Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently.** When a source (Mintlify `groups`, Nextra `_meta`, a Docusaurus sidebar…) declares a nested group, its pages usually sit **flat in one folder** and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting you must **either** move those pages into a real subfolder (`meta.ts` for label/`collapsed`) — which changes their URLs, so add `redirects` — **or** declare the group in an explicit `navigation.sidebar`, which nests the existing routes without moving a file. Walk config `pages`/nav arrays **recursively** during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost.
|
|
51
51
|
|
|
@@ -41,7 +41,7 @@ Every Docusaurus repo serves **`static/`** at the site root (`static/img/foo.png
|
|
|
41
41
|
## Navigation: `sidebars.js` + `_category_.json`
|
|
42
42
|
|
|
43
43
|
- **Autogenerated sidebar** (`{ type: 'autogenerated', dirName: '...' }`) → Blume's default filesystem navigation. Docusaurus strips numeric prefixes (`01-`) exactly like Blume, so the convention round-trips; no config needed.
|
|
44
|
-
- **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed
|
|
44
|
+
- **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed`, and collapsible rendering → that folder's `meta.ts` `display: "group"` (or `navigation.sidebar.display: "group"` once in config when every category collapses).
|
|
45
45
|
- **`_category_.json`** (also `.yml`) (`{ label, position, collapsed, collapsible, link, className, customProps }`) → a folder `meta.ts`: `label`→`title`, `position`→`order`, `collapsed`→`collapsed`. `link.type: 'generated-index'` → an `index` page in the folder — **and the old URL was `/docs/category/<slug>`**, so add a redirect and rewrite inbound links. `link.type: 'doc'` → make that doc the folder's `index`. `collapsible`/`className`/`customProps` → drop (report).
|
|
46
46
|
- **`src/pages/` — inventory, don't nuke.** Nearly every repo has a React landing page (`src/pages/index.tsx`) and often extra Markdown pages. Markdown pages → content pages; the React home page → rebuild as a docs index or a custom `.astro` page under `content.pages` — report either way.
|
|
47
47
|
|
|
@@ -39,7 +39,7 @@ Everything else is `defineConfig({ title })`.
|
|
|
39
39
|
| `description` | **drop** (folders have no description) |
|
|
40
40
|
| `collapsible: false` | **drop** (report — no per-folder equivalent) |
|
|
41
41
|
|
|
42
|
-
`meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`
|
|
42
|
+
`meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`, `display`. Render mode is per-folder or global: a folder that needs collapsible rendering sets its own `meta.ts` `display: "group"` (drill-in is `"page"`); when the whole sidebar should collapse, set `navigation.sidebar.display: "group"` once in `blume.config.ts` instead of repeating it per folder.
|
|
43
43
|
|
|
44
44
|
**`root: true` folders are Fumadocs' tab mechanism** — Fumadocs UI renders them as layout tabs and scopes the sidebar to the active one. That is exactly Blume's `navigation.tabs`: add `{ label, path, icon? }` per root folder (label/icon from its `meta.json`), pointing at the folder's route. Blume then scopes the sidebar by URL prefix the same way. Don't try to model root folders inside `meta.ts`.
|
|
45
45
|
|
|
@@ -83,7 +83,7 @@ The codemod touches **only frontmatter**. Icons in MDX **body** (`<Icon icon="
|
|
|
83
83
|
|
|
84
84
|
Mintlify's `navigation` object (`tabs`/`anchors`/`dropdowns`/`products`/`versions`/`languages`/`groups`/`pages`) is fully config-declared. **Prefer restructuring content into folders**, not porting the config verbatim:
|
|
85
85
|
|
|
86
|
-
- **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page —
|
|
86
|
+
- **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages, display })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page — defaults to the global `navigation.sidebar.display` in `blume.config.ts`; a folder overrides its own group with `meta.ts` `display`, or `sidebar.display` in its `index` page's frontmatter.)
|
|
87
87
|
- **Config-only nested groups don't exist on disk — you must materialize them, or they flatten silently.** A nested `{ group, pages }` almost never has a matching subfolder: its pages sit **flat in the parent directory** (e.g. `platform/analytics/getting-started.mdx`, `…/quick-reference.mdx`) and the grouping lives **only** in the `docs.json` `pages` array. If you leave the files where they are, filesystem-derived nav sees one flat folder and the inner group vanishes — Mintlify's `Analytics → Reference → {…}` becomes a flat `Analytics → {…}`. To preserve it you must **either** move those pages into a real subfolder (`platform/analytics/reference/`, with a `meta.ts` for the label/`collapsed`), **or** declare the shape in an explicit `navigation.sidebar`. Walk **every** `pages` array recursively and treat any nested `group` object as a folder-move to plan, not files already in place. When inventorying the nav (workflow step 2), record the config nesting depth separately from the on-disk depth — they diverge exactly here.
|
|
88
88
|
- **This is the canonical case for `navigation.sidebar` over folders.** Materializing a purely-presentational nested group as a subfolder changes URLs (`/platform/analytics/getting-started` → `/platform/analytics/reference/getting-started`) for no reason other than a visual grouping, forcing a `redirects` entry per page. When you want to keep the nesting **and** the URLs, an explicit `navigation.sidebar` group (`{ label, items }`) is the better trade — it nests the existing routes without moving any file. Pick per group; don't reflexively flatten.
|
|
89
89
|
- **`pages`** entries are page refs (paths without extension) → files at the corresponding path. An entry that's `"GET /path"` is an OpenAPI endpoint stub → **delete it** (Blume generates these; see OpenAPI).
|