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.
Files changed (71) hide show
  1. package/dist/cli/index.js +1921 -560
  2. package/dist/cli/index.js.map +36 -24
  3. package/dist/types/core/data.d.ts +16 -0
  4. package/dist/types/core/define-components.d.ts +9 -2
  5. package/dist/types/core/diagnostics.d.ts +5 -0
  6. package/dist/types/core/schema.d.ts +26 -502
  7. package/dist/types/core/types.d.ts +2 -2
  8. package/docs/02-deployment.mdx +21 -2
  9. package/docs/advanced/custom-pages.mdx +63 -1
  10. package/docs/configuration/ai.mdx +20 -3
  11. package/docs/configuration/customization.mdx +103 -5
  12. package/docs/configuration/index.mdx +13 -0
  13. package/docs/configuration/seo.mdx +5 -0
  14. package/docs/content/islands.mdx +73 -0
  15. package/docs/content/navigation.mdx +25 -0
  16. package/docs/index.mdx +3 -12
  17. package/docs/reference/cli.mdx +42 -0
  18. package/package.json +3 -1
  19. package/src/ai/ask-context.ts +131 -0
  20. package/src/ai/ask-data.ts +25 -0
  21. package/src/astro/component-slots.ts +165 -0
  22. package/src/astro/generate.ts +132 -13
  23. package/src/astro/integration.ts +59 -0
  24. package/src/astro/pages.ts +5 -12
  25. package/src/astro/templates.ts +92 -44
  26. package/src/blume-modules.d.ts +25 -0
  27. package/src/cli/commands/build.ts +186 -1
  28. package/src/cli/commands/check.ts +62 -0
  29. package/src/cli/commands/dev.ts +21 -1
  30. package/src/cli/commands/doctor.ts +23 -6
  31. package/src/cli/commands/init.ts +163 -15
  32. package/src/cli/commands/validate.ts +16 -2
  33. package/src/cli/index.ts +15 -0
  34. package/src/cli/internal-error.ts +63 -0
  35. package/src/cli/log.ts +30 -1
  36. package/src/cli/prepare.ts +17 -3
  37. package/src/cli/required-secrets.ts +44 -0
  38. package/src/components/BlumePage.astro +107 -0
  39. package/src/components/index.ts +3 -3
  40. package/src/components/islands/ask-ai.tsx +15 -1
  41. package/src/components/islands/hooks.ts +188 -0
  42. package/src/components/layout/Empty.astro +6 -0
  43. package/src/components/layout/Header.astro +24 -39
  44. package/src/components/layout/Logo.astro +50 -0
  45. package/src/components/layout/NavSelector.astro +75 -0
  46. package/src/components/layout/PageLayout.astro +38 -2
  47. package/src/components/layout/RootLayout.astro +70 -4
  48. package/src/components/layout/hydration-hint.ts +30 -0
  49. package/src/components/layout/overrides.ts +6 -4
  50. package/src/components/props.ts +68 -0
  51. package/src/core/builtin-tags.ts +39 -0
  52. package/src/core/component-diagnostics.ts +44 -0
  53. package/src/core/component-overrides.ts +478 -0
  54. package/src/core/config.ts +8 -0
  55. package/src/core/data.ts +14 -0
  56. package/src/core/define-components.ts +9 -2
  57. package/src/core/diagnostics.ts +90 -1
  58. package/src/core/graph.ts +7 -0
  59. package/src/core/nav-diagnostics.ts +205 -0
  60. package/src/core/project-graph.ts +40 -1
  61. package/src/core/schema.ts +28 -96
  62. package/src/core/sources/normalize.ts +51 -0
  63. package/src/core/types.ts +2 -2
  64. package/src/deploy/redirects.ts +43 -0
  65. package/src/migrate/mintlify/config.ts +1 -176
  66. package/src/migrate/starlight/config.ts +0 -4
  67. package/src/og/card.ts +163 -38
  68. package/src/registry/eject.ts +39 -9
  69. package/src/registry/registry.ts +166 -0
  70. package/src/runtime/index.ts +61 -0
  71. 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 {
@@ -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`). Static builds
122
- emit redirect pages; server builds handle redirects at request time.
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 — rather than from this site's pages. The other backends
155
- are plain model passthroughs: the endpoint streams the reader's messages straight
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 widget, or any built-in layout slot you want to customize the
70
- header, sidebar, breadcrumbs, table of contents, or pagination:
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
- exactly like the built-in until you change it. `blume add` prints the
78
- `defineComponents` snippet to register it under the matching layout slot.
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
@@ -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
- Blume is the answer to a problem I kept trying to solve at Vercel: documentation
25
- should be _fast_, _AI-ready_, and require _zero configuration_ — down to not
26
- needing a starter template at all.
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
 
@@ -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.2.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",