blume 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1921 -560
- package/dist/cli/index.js.map +36 -24
- package/dist/types/core/data.d.ts +16 -0
- package/dist/types/core/define-components.d.ts +9 -2
- package/dist/types/core/diagnostics.d.ts +5 -0
- package/dist/types/core/schema.d.ts +26 -502
- package/dist/types/core/types.d.ts +2 -2
- package/docs/02-deployment.mdx +21 -2
- package/docs/advanced/custom-pages.mdx +63 -1
- package/docs/configuration/ai.mdx +20 -3
- package/docs/configuration/customization.mdx +103 -5
- package/docs/configuration/index.mdx +13 -0
- package/docs/configuration/seo.mdx +5 -0
- package/docs/content/islands.mdx +73 -0
- package/docs/content/navigation.mdx +25 -0
- package/docs/index.mdx +3 -12
- package/docs/reference/cli.mdx +42 -0
- package/package.json +3 -1
- package/src/ai/ask-context.ts +131 -0
- package/src/ai/ask-data.ts +25 -0
- package/src/astro/component-slots.ts +165 -0
- package/src/astro/generate.ts +132 -13
- package/src/astro/integration.ts +59 -0
- package/src/astro/pages.ts +5 -12
- package/src/astro/templates.ts +92 -44
- package/src/blume-modules.d.ts +25 -0
- package/src/cli/commands/build.ts +186 -1
- package/src/cli/commands/check.ts +62 -0
- package/src/cli/commands/dev.ts +21 -1
- package/src/cli/commands/doctor.ts +23 -6
- package/src/cli/commands/init.ts +163 -15
- package/src/cli/commands/validate.ts +16 -2
- package/src/cli/index.ts +15 -0
- package/src/cli/internal-error.ts +63 -0
- package/src/cli/log.ts +30 -1
- package/src/cli/prepare.ts +17 -3
- package/src/cli/required-secrets.ts +44 -0
- package/src/components/BlumePage.astro +107 -0
- package/src/components/index.ts +3 -3
- package/src/components/islands/ask-ai.tsx +15 -1
- package/src/components/islands/hooks.ts +188 -0
- package/src/components/layout/Empty.astro +6 -0
- package/src/components/layout/Header.astro +24 -39
- package/src/components/layout/Logo.astro +50 -0
- package/src/components/layout/NavSelector.astro +75 -0
- package/src/components/layout/PageLayout.astro +38 -2
- package/src/components/layout/RootLayout.astro +70 -4
- package/src/components/layout/hydration-hint.ts +30 -0
- package/src/components/layout/overrides.ts +6 -4
- package/src/components/props.ts +68 -0
- package/src/core/builtin-tags.ts +39 -0
- package/src/core/component-diagnostics.ts +44 -0
- package/src/core/component-overrides.ts +478 -0
- package/src/core/config.ts +8 -0
- package/src/core/data.ts +14 -0
- package/src/core/define-components.ts +9 -2
- package/src/core/diagnostics.ts +90 -1
- package/src/core/graph.ts +7 -0
- package/src/core/nav-diagnostics.ts +205 -0
- package/src/core/project-graph.ts +40 -1
- package/src/core/schema.ts +28 -96
- package/src/core/sources/normalize.ts +51 -0
- package/src/core/types.ts +2 -2
- package/src/deploy/redirects.ts +43 -0
- package/src/migrate/mintlify/config.ts +1 -176
- package/src/migrate/starlight/config.ts +0 -4
- package/src/og/card.ts +163 -38
- package/src/registry/eject.ts +39 -9
- package/src/registry/registry.ts +166 -0
- package/src/runtime/index.ts +61 -0
- package/src/vite-env.d.ts +14 -0
|
@@ -108,6 +108,8 @@ export interface PageRecord {
|
|
|
108
108
|
format: "md" | "mdx";
|
|
109
109
|
/** Internal/asset links discovered in the page (for validation). */
|
|
110
110
|
links: PageLink[];
|
|
111
|
+
/** Capitalized JSX component tags used in the body (`.mdx` only). */
|
|
112
|
+
componentsUsed?: string[];
|
|
111
113
|
/** Resolved "last updated" ISO date, when the feature is enabled. */
|
|
112
114
|
lastModified?: string;
|
|
113
115
|
}
|
|
@@ -168,8 +170,6 @@ export interface NavSidebarVariant {
|
|
|
168
170
|
export interface NavChromeVariant {
|
|
169
171
|
path: string;
|
|
170
172
|
banner?: ResolvedConfig["banner"];
|
|
171
|
-
footer?: ResolvedConfig["footer"];
|
|
172
|
-
navbar?: ResolvedConfig["navbar"];
|
|
173
173
|
}
|
|
174
174
|
/** The complete navigation model derived from the content graph. */
|
|
175
175
|
export interface Navigation {
|
package/docs/02-deployment.mdx
CHANGED
|
@@ -118,8 +118,27 @@ Map old URLs to new ones in `blume.config.ts`:
|
|
|
118
118
|
redirects: [{ from: "/old", to: "/new", status: 301 }];
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
`status` accepts `301`, `302`, `307`, or `308` (default `301`).
|
|
122
|
-
|
|
121
|
+
`status` accepts `301`, `302`, `307`, or `308` (default `301`). Server builds
|
|
122
|
+
handle redirects at request time. Static builds emit redirect pages **and**
|
|
123
|
+
platform files so your host issues a real HTTP redirect: `_redirects` (Netlify,
|
|
124
|
+
Cloudflare Pages), `vercel.json` (Vercel), and `blume-redirects.json` — a
|
|
125
|
+
structured manifest for anything else (nginx/Apache rules, an edge worker). A
|
|
126
|
+
`_redirects` or `vercel.json` you ship in `public/` is left untouched.
|
|
127
|
+
|
|
128
|
+
## Environment variables
|
|
129
|
+
|
|
130
|
+
When a feature needs a runtime secret, Blume warns at `blume dev`/`build` if it's
|
|
131
|
+
missing — so the problem surfaces early instead of at the first request:
|
|
132
|
+
|
|
133
|
+
| Feature | Variable |
|
|
134
|
+
| --------------------------------------- | ------------------------------------- |
|
|
135
|
+
| Ask AI (AI Gateway) | `AI_GATEWAY_API_KEY` (or Vercel OIDC) |
|
|
136
|
+
| Ask AI (OpenRouter / OpenAI-compatible) | the provider's `apiKeyEnv` |
|
|
137
|
+
| Mixedbread search | `MIXEDBREAD_API_KEY` |
|
|
138
|
+
|
|
139
|
+
Set them in `.env.local` for local dev and in your host's environment for
|
|
140
|
+
production. Build-time secrets for search-index sync (Algolia, Orama Cloud,
|
|
141
|
+
Typesense) are warned about separately during the sync step.
|
|
123
142
|
|
|
124
143
|
## Build summary
|
|
125
144
|
|
|
@@ -167,6 +167,51 @@ const posts = (await getCollection("docs"))
|
|
|
167
167
|
</ul>
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Runtime helpers
|
|
171
|
+
|
|
172
|
+
`blume/runtime` bundles the common data patterns so you don't reach into
|
|
173
|
+
`blume:data` internals.
|
|
174
|
+
|
|
175
|
+
**`getBlumeCollection(data, query?)`** selects content routes — filtered by
|
|
176
|
+
collection, locale, or path prefix, with drafts and hidden pages excluded and the
|
|
177
|
+
result sorted by path — which is exactly what a custom index needs:
|
|
178
|
+
|
|
179
|
+
```astro pages/blog/index.astro lineNumbers
|
|
180
|
+
---
|
|
181
|
+
import data from "blume:data";
|
|
182
|
+
import { getBlumeCollection } from "blume/runtime";
|
|
183
|
+
|
|
184
|
+
const posts = getBlumeCollection(data, { prefix: "/blog" });
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
<ul>
|
|
188
|
+
{posts.map((post) => (
|
|
189
|
+
<li><a href={post.path}>{post.title}</a></li>
|
|
190
|
+
))}
|
|
191
|
+
</ul>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**`<BlumePage>`** renders a content entry's body inside a custom page, with
|
|
195
|
+
Blume's built-in MDX components (callouts, cards, steps…) already wired in — for
|
|
196
|
+
featuring a doc on a landing page or building a bespoke index that shows real
|
|
197
|
+
content:
|
|
198
|
+
|
|
199
|
+
```astro pages/index.astro lineNumbers
|
|
200
|
+
---
|
|
201
|
+
import BlumePage from "blume/components/BlumePage.astro";
|
|
202
|
+
import data from "blume:data";
|
|
203
|
+
import { getBlumeCollection } from "blume/runtime";
|
|
204
|
+
|
|
205
|
+
const [intro] = getBlumeCollection(data, { prefix: "/docs" });
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
{intro && <BlumePage id={intro.entryId} />}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Pass `components` to add your own overrides or islands (which live in the
|
|
212
|
+
generated runtime and aren't imported by default), and `collection` to read from
|
|
213
|
+
a collection other than `"docs"`.
|
|
214
|
+
|
|
170
215
|
## Using the site layout
|
|
171
216
|
|
|
172
217
|
`RootLayout` gives a custom page the full docs chrome — header, sidebar, search,
|
|
@@ -211,7 +256,24 @@ generated `og:image` automatically: Blume renders an Open Graph card for every
|
|
|
211
256
|
static custom page — the home included, the most-shared URL — served at
|
|
212
257
|
`/og/<route>.png` (`/og/index.png` for `/`). The home card uses the site title
|
|
213
258
|
with the description as its eyebrow; a deeper page is titled from its last path
|
|
214
|
-
segment. Set `ogImage` or `canonical` explicitly to override.
|
|
259
|
+
segment. Set `ogImage` or `canonical` explicitly to override either. `ogImage`
|
|
260
|
+
takes a root-relative path — a file in `public/`, resolved against
|
|
261
|
+
[`deployment.site`](/docs/deployment) to the absolute URL crawlers need — or an
|
|
262
|
+
external URL, which passes through untouched:
|
|
263
|
+
|
|
264
|
+
```astro pages/index.astro lineNumbers
|
|
265
|
+
<PageLayout
|
|
266
|
+
siteUrl={config.site}
|
|
267
|
+
ogEnabled={config.og.enabled}
|
|
268
|
+
ogImage="/opengraph-image.png"
|
|
269
|
+
page={{ title: config.title }}
|
|
270
|
+
>
|
|
271
|
+
<!-- page content -->
|
|
272
|
+
</PageLayout>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Only this page changes — every other route keeps its generated card — so it's how
|
|
276
|
+
you give the home page alone a bespoke share image.
|
|
215
277
|
|
|
216
278
|
`page.title` is used verbatim as the document title (no `- siteTitle` suffix),
|
|
217
279
|
since marketing pages usually set their own. To give a custom page the full docs
|
|
@@ -84,6 +84,24 @@ ai: {
|
|
|
84
84
|
}
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### Grounding
|
|
88
|
+
|
|
89
|
+
Ask AI is **grounded in your docs**. For each question it retrieves the most
|
|
90
|
+
relevant pages — using the same lexical [Orama](/docs/configuration/search) index
|
|
91
|
+
that powers on-page search — and injects them into the model's system prompt, so
|
|
92
|
+
answers come from your content instead of the model's own knowledge. The
|
|
93
|
+
assistant is told to answer only from the retrieved pages, to say when something
|
|
94
|
+
isn't covered, and to cite the pages it drew from.
|
|
95
|
+
|
|
96
|
+
The page the reader is currently on is added to the context first and used to
|
|
97
|
+
scope retrieval to that page's language, so answers stay relevant to where they
|
|
98
|
+
are in the docs. Retrieval runs at request time from a snapshot baked into the
|
|
99
|
+
build, so it works regardless of your [search](/docs/configuration/search)
|
|
100
|
+
provider — even when search is set to `none` — and needs no configuration.
|
|
101
|
+
|
|
102
|
+
Grounding is on for every backend except **[Inkeep](#backends)**, which runs its
|
|
103
|
+
own retrieval over the content you've indexed in its dashboard.
|
|
104
|
+
|
|
87
105
|
### Server output required
|
|
88
106
|
|
|
89
107
|
Ask AI is a server route (`POST /api/ask`), so it can't run on a static build.
|
|
@@ -151,9 +169,8 @@ at a different env var or proxy.
|
|
|
151
169
|
|
|
152
170
|
:::note
|
|
153
171
|
**Inkeep** answers from the content you've indexed in the Inkeep dashboard — it
|
|
154
|
-
runs its own retrieval —
|
|
155
|
-
|
|
156
|
-
to the model.
|
|
172
|
+
runs its own retrieval — so Blume leaves it ungrounded. Every other backend is
|
|
173
|
+
[grounded](#grounding) in this site's pages.
|
|
157
174
|
:::
|
|
158
175
|
|
|
159
176
|
Keys are read with `process.env`, which covers the Node, Vercel, and Netlify
|
|
@@ -25,6 +25,101 @@ export default defineComponents({
|
|
|
25
25
|
Keys are the names you write in MDX (`<Callout>`, `<Pricing>`). Use the `.tsx`
|
|
26
26
|
filename when you import React components.
|
|
27
27
|
|
|
28
|
+
### Reference form
|
|
29
|
+
|
|
30
|
+
Every override — in `mdx`, `layout`, or `islands` — accepts three forms:
|
|
31
|
+
|
|
32
|
+
```ts components.ts
|
|
33
|
+
import { defineComponents } from "blume";
|
|
34
|
+
import Callout from "./components/Callout.astro";
|
|
35
|
+
|
|
36
|
+
export default defineComponents({
|
|
37
|
+
mdx: {
|
|
38
|
+
Callout, // 1. an imported component
|
|
39
|
+
Note: "./components/Note.astro", // 2. a path string (resolved from the project root)
|
|
40
|
+
Chart: { component: "./components/Chart.tsx", client: "load" }, // 3. a descriptor
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The **descriptor** form adds a hydration mode so an interactive
|
|
46
|
+
React/Vue/Svelte component ships its JavaScript and comes alive on the client.
|
|
47
|
+
Without a `client` mode a framework component renders as static HTML — Blume
|
|
48
|
+
prints a build warning when it spots one, since that's usually a mistake.
|
|
49
|
+
|
|
50
|
+
| `client` | Hydrates |
|
|
51
|
+
| ----------- | ---------------------------------------------------------------- |
|
|
52
|
+
| `"load"` | Immediately on page load |
|
|
53
|
+
| `"idle"` | When the main thread is idle |
|
|
54
|
+
| `"visible"` | When scrolled into view |
|
|
55
|
+
| `"media"` | When a `media` query matches (add `media: "(min-width: 40rem)"`) |
|
|
56
|
+
| `"only"` | Client only, never server-rendered |
|
|
57
|
+
|
|
58
|
+
For interactive components you use across many pages, the [`islands`
|
|
59
|
+
group](/docs/content/islands#registering-islands-in-componentsts) is a shorthand
|
|
60
|
+
for the descriptor form with `client: "visible"`.
|
|
61
|
+
|
|
62
|
+
### Typing an override
|
|
63
|
+
|
|
64
|
+
When you replace a built-in, import its prop type from `blume/components` so your
|
|
65
|
+
component matches the contract — the types are derived from the components
|
|
66
|
+
themselves, so they never drift:
|
|
67
|
+
|
|
68
|
+
```tsx components/Callout.tsx
|
|
69
|
+
import type { CalloutProps } from "blume/components";
|
|
70
|
+
|
|
71
|
+
export default function Callout(props: CalloutProps) {
|
|
72
|
+
// …your own callout, same props as the built-in
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Prop types are exported for the content components (`CalloutProps`, `CardProps`,
|
|
77
|
+
`TabsProps`, `StepsProps`, `BadgeProps`, and more).
|
|
78
|
+
|
|
79
|
+
## Layout slots
|
|
80
|
+
|
|
81
|
+
The `layout` map replaces a piece of Blume's chrome with your own component. Each
|
|
82
|
+
override receives the same props as the built-in it replaces, so you can wrap the
|
|
83
|
+
default or start from scratch.
|
|
84
|
+
|
|
85
|
+
```ts components.ts
|
|
86
|
+
import { defineComponents } from "blume";
|
|
87
|
+
import Footer from "./components/Footer.astro";
|
|
88
|
+
import Logo from "./components/Logo.astro";
|
|
89
|
+
|
|
90
|
+
export default defineComponents({
|
|
91
|
+
layout: {
|
|
92
|
+
Logo, // brand mark + title in the header
|
|
93
|
+
Footer, // site-wide footer (no built-in — renders only when set)
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Wired slots:
|
|
99
|
+
|
|
100
|
+
| Slot | Replaces | Props |
|
|
101
|
+
| ----------------- | -------------------------------------------------------- | -------------------------------------------------------------- |
|
|
102
|
+
| `Layout` | The entire page shell (`RootLayout`) | Everything the built-in layout receives, plus the `layout` map |
|
|
103
|
+
| `Header` | The top navigation bar | `site`, `logo`, `navigation`, `route`, `searchEnabled`, … |
|
|
104
|
+
| `Logo` | The brand link (mark + title) in the header | `site`, `logo` |
|
|
105
|
+
| `Search` | The header search trigger + modal | `navigation`, `strings`, `locale`, `askEnabled` |
|
|
106
|
+
| `Sidebar` | The primary navigation tree | `items`, `currentRoute` |
|
|
107
|
+
| `MobileNav` | The nav inside the mobile drawer (defaults to `Sidebar`) | `items`, `currentRoute` |
|
|
108
|
+
| `Breadcrumbs` | The breadcrumb trail | `crumbs` |
|
|
109
|
+
| `TableOfContents` | The on-this-page outline | `headings`, `title`, `variant` |
|
|
110
|
+
| `Pagination` | The prev/next footer links | `prev`, `next`, `strings` |
|
|
111
|
+
| `PageHeader` | An injection point above the article (no built-in) | `page`, `headings`, `route` |
|
|
112
|
+
| `PageFooter` | An injection point below the article (no built-in) | `page`, `headings`, `route` |
|
|
113
|
+
| `Footer` | A site-wide footer after the content grid (no built-in) | `site`, `navigation`, `ui` |
|
|
114
|
+
|
|
115
|
+
`PageHeader`, `PageFooter`, and `Footer` have no built-in component — they render
|
|
116
|
+
nothing until you set them, which makes them handy injection points for a
|
|
117
|
+
promo banner, a "last updated" note, or a marketing footer.
|
|
118
|
+
|
|
119
|
+
Layout slots accept the same [three reference forms](#reference-form) as MDX
|
|
120
|
+
overrides, so a slot can be a path string or a hydrated descriptor
|
|
121
|
+
(`{ component, client }`) when you want an interactive header or footer.
|
|
122
|
+
|
|
28
123
|
## Interactive islands
|
|
29
124
|
|
|
30
125
|
For interactive UI (React, Vue, or Svelte), drop a component into an `islands/`
|
|
@@ -66,16 +161,19 @@ available:
|
|
|
66
161
|
blume add
|
|
67
162
|
```
|
|
68
163
|
|
|
69
|
-
Install a
|
|
70
|
-
|
|
164
|
+
Install a layout slot (header, sidebar, breadcrumbs, table of contents,
|
|
165
|
+
pagination, or feedback) or any content component (callout, card, tabs, steps,
|
|
166
|
+
accordion, and more):
|
|
71
167
|
|
|
72
168
|
```bash
|
|
169
|
+
blume add callout
|
|
73
170
|
blume add pagination
|
|
74
171
|
```
|
|
75
172
|
|
|
76
|
-
The copy imports the rest of the framework from `blume/*`, so it renders
|
|
77
|
-
|
|
78
|
-
|
|
173
|
+
The copy imports the rest of the framework from `blume/*`, so it renders exactly
|
|
174
|
+
like the built-in until you change it. `blume add` prints the `defineComponents`
|
|
175
|
+
snippet to register it — content components under `mdx`, layout pieces under
|
|
176
|
+
`layout`.
|
|
79
177
|
|
|
80
178
|
## Eject
|
|
81
179
|
|
|
@@ -259,6 +259,19 @@ seo: {
|
|
|
259
259
|
|
|
260
260
|
These work best with an absolute [`deployment.site`](/docs/deployment) for full URLs.
|
|
261
261
|
|
|
262
|
+
## Table of contents
|
|
263
|
+
|
|
264
|
+
The on-this-page outline is on by default and lists `H2`–`H3` headings. Turn it
|
|
265
|
+
off, or change the heading range, with `toc`:
|
|
266
|
+
|
|
267
|
+
```ts blume.config.ts
|
|
268
|
+
export default defineConfig({
|
|
269
|
+
toc: false, // hide it everywhere
|
|
270
|
+
// …or narrow the range:
|
|
271
|
+
toc: { minHeadingLevel: 2, maxHeadingLevel: 4 },
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
262
275
|
## Feature options
|
|
263
276
|
|
|
264
277
|
Each of these has its own guide. The config field is the entry point:
|
|
@@ -114,6 +114,11 @@ OG rendering uses hex internally, so an `oklch` custom accent falls back to the
|
|
|
114
114
|
default. Use a named accent (`blue`, `teal`, …) or a hex value for custom cards.
|
|
115
115
|
:::
|
|
116
116
|
|
|
117
|
+
`seo.image` is frontmatter, so it only covers Markdown and MDX content. To give a
|
|
118
|
+
custom [`.astro` page](/docs/advanced/custom-pages) its own social image — a
|
|
119
|
+
marketing home or landing page, and the way to give the home page alone a bespoke
|
|
120
|
+
share image — pass the `ogImage` prop to `PageLayout`.
|
|
121
|
+
|
|
117
122
|
## RSS feeds
|
|
118
123
|
|
|
119
124
|
Blume builds an RSS feed for each content type in `rss.types` — `blog` and
|
package/docs/content/islands.mdx
CHANGED
|
@@ -36,6 +36,35 @@ pages (a styled callout, a pricing table), use an [MDX
|
|
|
36
36
|
override](/docs/configuration/customization) instead — it ships no JavaScript.
|
|
37
37
|
:::
|
|
38
38
|
|
|
39
|
+
## Registering islands in `components.ts`
|
|
40
|
+
|
|
41
|
+
If you'd rather keep islands next to the rest of your components — or give them a
|
|
42
|
+
different name than the file — register them with `defineComponents`. The
|
|
43
|
+
`islands` group is exactly like the `islands/` folder: each entry is available in
|
|
44
|
+
every MDX page and hydrates (defaulting to `client: "visible"`).
|
|
45
|
+
|
|
46
|
+
```ts components.ts
|
|
47
|
+
import { defineComponents } from "blume";
|
|
48
|
+
import Counter from "./widgets/Counter.tsx";
|
|
49
|
+
|
|
50
|
+
export default defineComponents({
|
|
51
|
+
islands: {
|
|
52
|
+
Counter, // <Counter /> in any MDX page, hydrated
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Reference the component by import or by a path string, and set a hydration mode
|
|
58
|
+
per island with the descriptor form:
|
|
59
|
+
|
|
60
|
+
```ts components.ts
|
|
61
|
+
export default defineComponents({
|
|
62
|
+
islands: {
|
|
63
|
+
Chart: { component: "./widgets/Chart.tsx", client: "only" },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
39
68
|
## Hydration
|
|
40
69
|
|
|
41
70
|
By default an island uses `client:visible`: it hydrates when the reader scrolls
|
|
@@ -92,3 +121,47 @@ and children (`<Counter>label</Counter>`) arrive as the default slot.
|
|
|
92
121
|
Islands hydrate on the client, so anything you pass as a prop must be
|
|
93
122
|
serializable — strings, numbers, plain objects, not functions.
|
|
94
123
|
:::
|
|
124
|
+
|
|
125
|
+
## Hooks
|
|
126
|
+
|
|
127
|
+
Islands hydrate on their own, so there's no React context to thread project data
|
|
128
|
+
through. Instead, `blume/hooks` reads a small snapshot the layout serializes into
|
|
129
|
+
the page — no props to drill:
|
|
130
|
+
|
|
131
|
+
```tsx islands/PageInfo.tsx lineNumbers
|
|
132
|
+
import { useBlume, usePage } from "blume/hooks";
|
|
133
|
+
|
|
134
|
+
export default function PageInfo() {
|
|
135
|
+
const blume = useBlume();
|
|
136
|
+
const page = usePage();
|
|
137
|
+
if (!(blume && page)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return (
|
|
141
|
+
<p>
|
|
142
|
+
You're reading <strong>{page.title}</strong> on {blume.config.title}.
|
|
143
|
+
</p>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Hook | Returns |
|
|
149
|
+
| ------------- | --------------------------------------------------------------------- |
|
|
150
|
+
| `useBlume()` | `{ config, navigation }` for the site, or `null` before mount |
|
|
151
|
+
| `usePage()` | `{ route, title }` for the current page, or `null` before mount |
|
|
152
|
+
| `useSearch()` | `{ search, results, loading }` — query the configured search provider |
|
|
153
|
+
| `useAskAI()` | `{ ask, messages, loading, reset }` — stream from the Ask AI endpoint |
|
|
154
|
+
|
|
155
|
+
`useBlume()` and `usePage()` return `null` until the island mounts (so server and
|
|
156
|
+
client render the same first frame) — guard for it. The snapshot is emitted only
|
|
157
|
+
on pages that ship React, so a fully static site pays nothing.
|
|
158
|
+
|
|
159
|
+
On [custom pages](/docs/advanced/custom-pages) built with `PageLayout`, pass
|
|
160
|
+
`clientData` so islands there can read it:
|
|
161
|
+
|
|
162
|
+
```astro
|
|
163
|
+
<PageLayout
|
|
164
|
+
clientData={{ config: data.config, navigation: data.navigation, page: { route: "/", title: "Home" } }}
|
|
165
|
+
{/* …other props… */}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
@@ -113,6 +113,31 @@ your loose top-level pages while the sectioned content stays behind its tab,
|
|
|
113
113
|
mirroring Fumadocs' root folders. If a route has no pages of its own to show
|
|
114
114
|
this way, the full tree is shown instead, so the sidebar is never left blank.
|
|
115
115
|
|
|
116
|
+
## Selectors
|
|
117
|
+
|
|
118
|
+
For switching between whole partitions of a site — a product, a version, or any
|
|
119
|
+
grouped set of destinations — add a `selector`. Each renders as a dropdown in the
|
|
120
|
+
header, showing the option whose `path` matches the current route:
|
|
121
|
+
|
|
122
|
+
```ts blume.config.ts lineNumbers
|
|
123
|
+
navigation: {
|
|
124
|
+
selectors: [
|
|
125
|
+
{
|
|
126
|
+
kind: "version",
|
|
127
|
+
label: "Version",
|
|
128
|
+
items: [
|
|
129
|
+
{ label: "v2 (latest)", path: "/v2", icon: "rocket" },
|
|
130
|
+
{ label: "v1", path: "/v1" },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Each item takes a `label`, a `path`, and optional `icon`, `description`, and
|
|
138
|
+
`tag`. `kind` (`dropdown`, `product`, `version`, or `language`) is a hint for how
|
|
139
|
+
the selector is used; all render the same dropdown.
|
|
140
|
+
|
|
116
141
|
## Explicit sidebar
|
|
117
142
|
|
|
118
143
|
For full control, define `navigation.sidebar` in config. When it's set, Blume
|
package/docs/index.mdx
CHANGED
|
@@ -21,18 +21,9 @@ library — with no app boilerplate to write or maintain.
|
|
|
21
21
|
|
|
22
22
|
## Why Blume exists
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Most docs tools hand you a project to own before you've written a word: an app to
|
|
29
|
-
scaffold, a framework to learn, a template to keep in sync with upstream. Blume
|
|
30
|
-
flips that around. The framework _is_ the template, so the only thing you ever
|
|
31
|
-
touch is your content. When you outgrow the defaults, you add configuration one
|
|
32
|
-
file at a time — and you can `blume eject` to a plain Astro project the day you
|
|
33
|
-
want full control.
|
|
34
|
-
|
|
35
|
-
— Hayden Bleasel
|
|
24
|
+
Docs should be fast, AI-ready and require zero configuration — down to not needing a starter template at all. Some docs tools hand you an entire codebase to maintain before you've written a word. Others build the template around your content, but lock you in to their managed service.
|
|
25
|
+
|
|
26
|
+
Blume takes the best of both worlds. The framework is the template, so the only thing you ever touch is your content. When you want to customize, you can start replacing the built-in components, modifying the single configuration file or even ejecting if you want the Astro site directly.
|
|
36
27
|
|
|
37
28
|
## What makes Blume different
|
|
38
29
|
|
package/docs/reference/cli.mdx
CHANGED
|
@@ -19,21 +19,63 @@ blume <command> [options]
|
|
|
19
19
|
| `blume migrate <tool>` | Migrate from Mintlify, Starlight, Nextra, or Fumadocs. |
|
|
20
20
|
| `blume sync` | Re-fetch remote content sources and regenerate. |
|
|
21
21
|
| `blume eject` | Promote the runtime into a standalone Astro app. |
|
|
22
|
+
| `blume check` | Type-check the site with `astro check`. |
|
|
22
23
|
| `blume doctor` | Diagnose config and content problems. |
|
|
23
24
|
| `blume validate` | Validate links across your content. |
|
|
24
25
|
|
|
25
26
|
## Common flags
|
|
26
27
|
|
|
27
28
|
- `blume init --content-dir <dir> --yes` — set the content folder (default `docs`) and skip prompts.
|
|
29
|
+
- `blume init --template docs|api|sdk|changelog` — scaffold from a starter (API reference, SDK, or changelog instead of the plain docs seed).
|
|
30
|
+
- `blume init --package-manager npm|pnpm|yarn|bun` — tailor the printed next-steps to your package manager.
|
|
31
|
+
- `blume init --eject` — scaffold, then eject to a standalone Astro project (falls back to guiding you through `blume eject` when dependencies aren't installed yet).
|
|
28
32
|
- `blume dev --host --port <n> --open`
|
|
33
|
+
- `blume dev --content-dir <dir>` — scan a different content folder without editing `blume.config.ts`.
|
|
34
|
+
- `blume dev --debug` — verbose Astro/Vite logging for troubleshooting.
|
|
29
35
|
- `blume dev --preview` / `blume build --preview` — include drafts and unpublished CMS content.
|
|
30
36
|
- `blume build --strict` — fail the build on diagnostic errors (also works on `blume dev`).
|
|
37
|
+
- `blume build --output static|server --adapter vercel|node|netlify|cloudflare --base /docs` — override the deployment output, adapter, and base path from `blume.config.ts`.
|
|
38
|
+
- `blume build --analyze` — print the client JavaScript bundle sizes (largest first) after the build.
|
|
39
|
+
- `blume build --budget-js <kb> --budget-css <kb>` — fail the build when total client JavaScript/CSS exceeds the budget, turning a performance target into a CI gate.
|
|
31
40
|
- `blume preview --host --port <n>` — bind the preview server.
|
|
32
41
|
- `blume sync --force` — re-fetch remote sources, dropping the cached snapshot first.
|
|
33
42
|
- `blume add <item> --force` — overwrite files that already exist.
|
|
43
|
+
- `blume check --preview` — include drafts and unpublished CMS content when checking.
|
|
44
|
+
- `blume check --strict` — fail on content diagnostics as well as type errors.
|
|
34
45
|
- `blume eject --yes` — skip the confirmation prompt.
|
|
35
46
|
- `blume validate --external` — also check external links over the network.
|
|
36
47
|
- `blume validate --strict` — exit non-zero on warnings too.
|
|
48
|
+
- `blume validate --json` / `blume doctor --json` — emit diagnostics as JSON on
|
|
49
|
+
stdout (with `code`, `severity`, `file`, `line`/`column`, and `docsUrl`) for CI
|
|
50
|
+
and editor integrations.
|
|
51
|
+
|
|
52
|
+
## Type-checking
|
|
53
|
+
|
|
54
|
+
`blume check` runs [`astro check`](https://docs.astro.build/en/reference/cli-reference/#astro-check)
|
|
55
|
+
over your project. It regenerates the `.blume` runtime, syncs Astro's content
|
|
56
|
+
types, then reports any TypeScript errors — in your `blume.config.ts`, in custom
|
|
57
|
+
`.astro` pages, and in the components they import. It exits non-zero when there
|
|
58
|
+
are errors, so it works as a `typecheck` step in CI:
|
|
59
|
+
|
|
60
|
+
```json title="package.json"
|
|
61
|
+
{
|
|
62
|
+
"scripts": {
|
|
63
|
+
"typecheck": "blume check"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Add a `tsconfig.json` extending Astro's config to your project root so authored
|
|
69
|
+
pages resolve `blume/*` imports and virtual modules like `blume:data`:
|
|
70
|
+
|
|
71
|
+
```json title="tsconfig.json"
|
|
72
|
+
{
|
|
73
|
+
"extends": "astro/tsconfigs/strict",
|
|
74
|
+
"include": [".blume/.astro/types.d.ts", ".blume/src/env.d.ts", "**/*"]
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Without a project `tsconfig.json`, only the generated runtime is checked.
|
|
37
79
|
|
|
38
80
|
## Validating links
|
|
39
81
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blume",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Documentation that's fast, AI-ready, and zero-config.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"default": "./src/core/schema.ts"
|
|
44
44
|
},
|
|
45
45
|
"./runtime": "./src/runtime/index.ts",
|
|
46
|
+
"./hooks": "./src/components/islands/hooks.ts",
|
|
46
47
|
"./astro": "./src/astro/index.ts",
|
|
47
48
|
"./og": "./src/og/index.ts",
|
|
48
49
|
"./markdown": "./src/markdown/index.ts",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"typecheck": "tsgo --noEmit && tsgo -p test/tsconfig.json --noEmit"
|
|
63
64
|
},
|
|
64
65
|
"dependencies": {
|
|
66
|
+
"@astrojs/check": "^0.9.0",
|
|
65
67
|
"@astrojs/markdown-satteri": "^0.3.2",
|
|
66
68
|
"@astrojs/mdx": "^7.0.0",
|
|
67
69
|
"@astrojs/node": "^11.0.0",
|