blume 0.4.0 → 0.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.
Files changed (67) hide show
  1. package/dist/cli/index.js +1137 -722
  2. package/dist/cli/index.js.map +28 -23
  3. package/dist/types/core/data.d.ts +2 -0
  4. package/dist/types/core/project.d.ts +12 -2
  5. package/dist/types/core/schema.d.ts +154 -15
  6. package/dist/types/core/types.d.ts +7 -0
  7. package/docs/advanced/api-reference.mdx +33 -23
  8. package/docs/advanced/bridge.mdx +74 -0
  9. package/docs/advanced/meta.ts +8 -1
  10. package/docs/advanced/migrate.mdx +119 -0
  11. package/docs/configuration/index.mdx +1 -1
  12. package/docs/content/components.mdx +55 -2
  13. package/docs/content/i18n.mdx +1 -1
  14. package/docs/content/syntax.mdx +2 -2
  15. package/docs/index.mdx +2 -2
  16. package/docs/reference/cli.mdx +29 -1
  17. package/docs/reference/frontmatter.mdx +5 -0
  18. package/package.json +11 -1
  19. package/src/astro/generate.ts +18 -8
  20. package/src/astro/templates.ts +28 -4
  21. package/src/cli/commands/build.ts +107 -63
  22. package/src/cli/commands/check.ts +20 -0
  23. package/src/cli/dev-lock.ts +13 -5
  24. package/src/cli/prepare.ts +3 -0
  25. package/src/components/BlumePage.astro +6 -0
  26. package/src/components/Icon.astro +13 -10
  27. package/src/components/content/ApiField.astro +75 -0
  28. package/src/components/content/ParamField.astro +39 -0
  29. package/src/components/content/RequestField.astro +23 -0
  30. package/src/components/content/ResponseField.astro +23 -0
  31. package/src/components/content/Step.astro +1 -1
  32. package/src/components/layout/Breadcrumbs.astro +7 -2
  33. package/src/components/layout/NavTree.astro +24 -8
  34. package/src/components/layout/RootLayout.astro +56 -34
  35. package/src/components/layout/Search.astro +1 -1
  36. package/src/components/openapi/ApiOverview.astro +84 -0
  37. package/src/components/openapi/MethodBadge.astro +28 -0
  38. package/src/components/openapi/Operation.astro +140 -0
  39. package/src/components/openapi/ParametersTable.astro +97 -0
  40. package/src/components/openapi/RequestBody.astro +58 -0
  41. package/src/components/openapi/RequestPanel.astro +169 -0
  42. package/src/components/openapi/Responses.astro +91 -0
  43. package/src/components/openapi/SchemaProperty.astro +118 -0
  44. package/src/components/openapi/SchemaTable.astro +86 -0
  45. package/src/components/openapi/helpers.ts +238 -0
  46. package/src/components/openapi/panel.ts +59 -0
  47. package/src/components/openapi/snippets.ts +201 -0
  48. package/src/core/builtin-tags.ts +5 -0
  49. package/src/core/data.ts +2 -0
  50. package/src/core/project-graph.ts +5 -1
  51. package/src/core/project.ts +25 -3
  52. package/src/core/schema.ts +47 -6
  53. package/src/core/sources/mintlify.ts +1 -1
  54. package/src/core/sources/resolve.ts +28 -6
  55. package/src/core/types.ts +7 -0
  56. package/src/migrate/mintlify/config.ts +153 -1
  57. package/src/migrate/mintlify/content.ts +8 -2
  58. package/src/migrate/mintlify/index.ts +58 -1
  59. package/src/openapi/model.ts +174 -0
  60. package/src/openapi/parse.ts +48 -0
  61. package/src/openapi/references.ts +164 -0
  62. package/src/openapi/render-mdx.ts +76 -0
  63. package/src/openapi/scalar.ts +15 -103
  64. package/src/openapi/source.ts +140 -0
  65. package/src/registry/eject.ts +15 -2
  66. package/src/theme/chrome-icons.ts +22 -0
  67. package/src/theme/icons.ts +151 -161
@@ -0,0 +1,119 @@
1
+ ---
2
+ title: Migrate
3
+ description: Convert a Mintlify, Fumadocs, Nextra, or Starlight project to Blume in one command — config, content, navigation, and assets translated to idiomatic Blume.
4
+ ---
5
+
6
+ Already have a docs site somewhere else? `blume migrate` is a one-shot codemod that converts a project from another docs framework into Blume — translating the config, rewriting each page to idiomatic Blume MDX, converting navigation files, and relocating assets, all in place. When it finishes you have a `blume.config.ts` and a tree that `blume dev` can serve.
7
+
8
+ ```bash
9
+ npx blume migrate <tool>
10
+ ```
11
+
12
+ The `<tool>` is the framework you're coming from:
13
+
14
+ | Tool | Detects | Command |
15
+ | --- | --- | --- |
16
+ | [Mintlify](#mintlify) | `docs.json` / `mint.json` | `blume migrate mintlify` |
17
+ | [Fumadocs](#fumadocs) | `content/docs` + `meta.json` | `blume migrate fumadocs` |
18
+ | [Nextra](#nextra) | `content/` or `pages/` + `_meta` | `blume migrate nextra` |
19
+ | [Starlight](#starlight) | `src/content/docs` + `astro.config.*` | `blume migrate starlight` |
20
+
21
+ :::warning A migration **rewrites files in place** — pages, config, navigation, and assets. Commit (or stash) your work first so you can review the diff and roll back cleanly. :::
22
+
23
+ ## What a migration does
24
+
25
+ Every migrator runs the same shape of work, tuned to the source framework:
26
+
27
+ <Steps>
28
+ <Step title="Translate the config">
29
+ The framework's config — `docs.json`, a `starlight({...})` block, and so on — becomes a `blume.config.ts` at your project root, with navigation, theme, and site chrome mapped across.
30
+ </Step>
31
+ <Step title="Rewrite every page">
32
+ Each `.md`/`.mdx` file is rewritten to idiomatic Blume markup: framework callouts become [`:::` directives](/docs/content/syntax), components are renamed or converted, and frontmatter is mapped to Blume's [page schema](/docs/reference/frontmatter). Keys Blume doesn't recognize are dropped and reported.
33
+ </Step>
34
+ <Step title="Convert navigation">
35
+ Navigation files (`_meta.{js,ts,json}`, `meta.json`) become typed [`meta.ts`](/docs/content/meta) files, preserving order and titles.
36
+ </Step>
37
+ <Step title="Relocate assets and scripts">
38
+ Referenced asset folders are kept in place and served via [`content.assets`](/docs/content/sources); loose top-level files move under `public/`. Your `dev`/`build`/`start` npm scripts are repointed at the matching `blume` commands.
39
+ </Step>
40
+ </Steps>
41
+
42
+ When it's done the CLI prints how many files it touched, a list of **warnings** — anything that needs a human eye, like a component with no Blume equivalent or a dropped frontmatter key — and a reminder to review the generated config:
43
+
44
+ ```txt
45
+ ✔ Migrated 42 content file(s).
46
+ ⚠ Components without a Blume equivalent need manual review: <Frame>.
47
+ ⚠ Dropped unsupported page frontmatter keys: mode, "og:image".
48
+ ▶ Review blume.config.ts and run `blume dev`.
49
+ ```
50
+
51
+ ## Tools
52
+
53
+ ### Mintlify
54
+
55
+ Reads `docs.json` (or legacy `mint.json`) and rewrites every page **in place** — content stays at the project root (`content.root` is `"."`).
56
+
57
+ - Snippets under `/snippets` are inlined as includes; component snippets (e.g. `.jsx`) are kept and their imports rewritten to relative paths.
58
+ - A Mintlify `openapi` spec — declared top-level or on a nav group (a path, URL, or `{ source, directory }`) — maps to Blume's [native OpenAPI reference](/docs/advanced/api-reference) (`openapi.sources`), which renders one real page per operation. A group's `directory` becomes the reference's route; endpoint refs like `GET /users` are dropped since Blume generates them from the spec.
59
+ - `docs.json` `variables` are inlined into content — Blume has no runtime `{{variable}}` substitution.
60
+ - Multi-language projects map to [`i18n.locales`](/docs/content/i18n); the language nav selector is dropped in favor of Blume's locale switching.
61
+ - Icons resolve against the real bundled libraries — Font Awesome (free), Lucide, and Tabler. The migrator sets [`icons.library: fontawesome`](/docs/content/components#default-library) (Mintlify's default), so Font Awesome names (`shield-halved`, `gauge-high`, `layer-group`, …) and `iconType` styles render unchanged. Pro-only FA styles (`light`/`thin`/`duotone`/`sharp-solid`) fall back to solid.
62
+ - Fonts map to [`theme.fonts`](/docs/configuration/theming) when the family is one of Blume's curated Google Fonts (`fonts.family`, or a `heading`/`body` split); a family outside that set is warned about, not guessed. Header links (`navbar.links`/`navbar.primary`) and footer socials (`footer.socials`) have no `blume.config` equivalent yet, so they're reported as warnings rather than dropped silently — re-add them with [`navigation.tabs`](/docs/content/navigation) or a Header/Footer [layout override](/docs/advanced/custom-pages). The contextual page menu and last-updated timestamp are already Blume defaults.
63
+ - Field components — [`<ParamField>`, `<ResponseField>`, `<RequestField>`](/docs/content/components#api-fields) — render natively via Blume's compat components, so hand-written CLI/SDK/endpoint field docs carry over unchanged. Any remaining component with no Blume equivalent (e.g. `<Update>`) is flagged for manual review.
64
+
65
+ :::tip Want to preview Blume against a Mintlify codebase **without** rewriting anything? Run `blume dev` with no config and Blume serves `docs.json` as-is in [Bridge mode](/docs/advanced/bridge). Migrate when you're ready to commit. :::
66
+
67
+ ### Fumadocs
68
+
69
+ Reads `content/docs` + `meta.json`. Moves pages into `docs/`, preserving the `/docs` route prefix, and rewrites Fumadocs MDX to Blume markup: callouts, `<Cards>`/`<Accordions>`/`<Files>`, `<Tabs items>`, and `<include>` directives are all converted. Each `meta.json` becomes a typed `meta.ts`.
70
+
71
+ ### Nextra
72
+
73
+ Reads `content/` or `pages/` plus `_meta` files. Moves pages into `docs/`, rewrites `<Callout>`s to [directives](/docs/content/syntax), and converts every `_meta.{js,ts,json}` into a typed `meta.ts` — navigation order and titles preserved.
74
+
75
+ ### Starlight
76
+
77
+ Reads `src/content/docs` + your `astro.config.*`. Translates the `starlight({...})` options into `blume.config.ts` and rewrites each page **in place** (content stays under `src/content/docs`): asides become directives, components are renamed, and frontmatter is mapped across.
78
+
79
+ ## After migrating
80
+
81
+ <Steps>
82
+ <Step title="Review the config">
83
+ Open `blume.config.ts` and check the mapped navigation, theme, and site
84
+ settings. The migrator is thorough but conservative — some source options
85
+ have no Blume equivalent and are left out.
86
+ </Step>
87
+ <Step title="Read the warnings">
88
+ Each warning points at something the codemod couldn't fully translate. Work
89
+ through them before shipping.
90
+ </Step>
91
+ <Step title="Run the dev server">
92
+ Serve the migrated site. Blume [validates your content](/docs/reference/cli#validating-links) as it serves it, so broken links, missing anchors, and schema errors surface immediately.
93
+
94
+ ```bash
95
+ blume dev
96
+ ```
97
+
98
+ </Step>
99
+ <Step title="Delete leftovers">
100
+ Old framework files (lockfile entries, `astro.config`, `theme.config`,
101
+ framework dependencies) are left untouched so nothing is lost. Remove them
102
+ by hand once you've confirmed the site builds.
103
+ </Step>
104
+ </Steps>
105
+
106
+ <CardGroup cols={2}>
107
+ <Card title="Bridge mode" href="/docs/advanced/bridge" icon="cable">
108
+ Serve a Mintlify project with `blume dev` — no migration, no file changes.
109
+ </Card>
110
+ <Card title="CLI reference" href="/docs/reference/cli" icon="terminal">
111
+ Every command and flag, including `blume migrate`.
112
+ </Card>
113
+ <Card title="Content sources" href="/docs/content/sources" icon="folder-tree">
114
+ How Blume scans content, assets, and remote sources into a site.
115
+ </Card>
116
+ <Card title="Frontmatter" href="/docs/reference/frontmatter" icon="file-text">
117
+ The page schema your migrated frontmatter is mapped onto.
118
+ </Card>
119
+ </CardGroup>
@@ -187,7 +187,7 @@ content: {
187
187
  }
188
188
  ```
189
189
 
190
- This is mainly useful after migrating from a tool that serves assets from the project root (like Mintlify), so references such as `![](/images/create.png)` keep resolving without relocating every file.
190
+ This is mainly useful after [migrating](/docs/advanced/migrate) from a tool that serves assets from the project root (like Mintlify), so references such as `![](/images/create.png)` keep resolving without relocating every file. [Bridge mode](/docs/advanced/bridge) uses the same mechanism to serve a Mintlify project's assets in place, untouched.
191
191
 
192
192
  ## Last modified
193
193
 
@@ -125,7 +125,7 @@ A negative or breaking state, such as a deprecation.
125
125
 
126
126
  ## Icon
127
127
 
128
- Render an icon from Blume's built-in set by name — the same set the `icon` props on cards, tiles, tabs, and sidebar entries draw from. Names are lowercase and kebab-cased (`rocket`, `book-open`, `chevron-right`).
128
+ Render an icon by name — the same `icon` props power cards, steps, tiles, tabs, and sidebar entries. Names come from three bundled libraries: [Lucide](https://lucide.dev) (the default), [Font Awesome](https://fontawesome.com/icons) (its free set), and [Tabler](https://tabler.io/icons). Names are lowercase and kebab-cased (`rocket`, `gauge-high`, `book-open`).
129
129
 
130
130
  <Icon icon="rocket" size={20} />
131
131
 
@@ -133,7 +133,26 @@ Render an icon from Blume's built-in set by name — the same set the `icon` pro
133
133
  <Icon icon="rocket" size={20} />
134
134
  ```
135
135
 
136
- `icon` is the icon name; `size` sets the pixel size (default `16`) and `color` tints it (any CSS color; defaults to `currentColor`). Pass a raw `<svg>` string, an image URL, or a local image path in place of a name to render your own art, and add a `label` to expose it to assistive tech — without one, the icon is decorative.
136
+ A bare name resolves against your project's [`icons.library`](#default-library) (Lucide unless you change it). Two ways to reach another library for a single icon:
137
+
138
+ - **`iconType`** selects a Font Awesome style — `solid` (default), `regular`, or `brands`: `<Icon icon="github" iconType="brands" />`. Font Awesome Pro styles (`light`, `thin`, `duotone`, `sharp-solid`) aren't in the bundled free data and fall back to solid.
139
+ - **A `library:name` prefix** overrides the default per icon: `fa6-solid:gauge-high`, `fa6-brands:github`, `lucide:rocket`, `tabler:heart`.
140
+
141
+ `size` sets the pixel size (default `16`) and `color` tints it (any CSS color; defaults to `currentColor`). Pass a raw `<svg>` string, an image URL, or a local image path in place of a name to render your own art, and add a `label` to expose it to assistive tech — without one, the icon is decorative.
142
+
143
+ Icons resolve at build time and inline as zero-JS SVG — nothing is fetched at runtime.
144
+
145
+ ### Default library
146
+
147
+ Set which library bare icon names resolve against:
148
+
149
+ ```ts title="blume.config.ts"
150
+ export default defineConfig({
151
+ icons: { library: "fontawesome" }, // "lucide" (default) | "fontawesome" | "tabler"
152
+ });
153
+ ```
154
+
155
+ A `library:name` prefix always wins over this, so you can mix libraries on any default. Migrating from Mintlify sets this to `fontawesome` for you (Mintlify's default), so your existing icon names keep working.
137
156
 
138
157
  ## File tree
139
158
 
@@ -438,6 +457,40 @@ Show or hide content by audience. `for="web"` renders only on the site; `for="ag
438
457
  <Visibility for="agents">Shown only in the generated Markdown.</Visibility>
439
458
  ```
440
459
 
460
+ ## API fields
461
+
462
+ Document a single request/response field — a CLI flag, an SDK argument, an endpoint parameter — with its name, type, and description. `ParamField`, `ResponseField`, and `RequestField` are Mintlify-compatible, so a migrated site's field markup renders unchanged. Each takes a `type`, plus `required`, `deprecated`, and `default`; the description is the element's body and may hold rich MDX, including a nested [Expandable](#expandable) for sub-properties.
463
+
464
+ `ParamField` names the field through the attribute that marks its location — `path`, `query`, `header`, or `body` (or a plain `name`); the location shows as a small label.
465
+
466
+ <ParamField path="userId" type="string" required>
467
+ The unique identifier of the user.
468
+ </ParamField>
469
+
470
+ <ParamField query="limit" type="integer" default="20">
471
+ Maximum number of results to return per page.
472
+ </ParamField>
473
+
474
+ <ResponseField name="createdAt" type="string">
475
+ ISO 8601 timestamp for when the record was created.
476
+ </ResponseField>
477
+
478
+ ```mdx
479
+ <ParamField path="userId" type="string" required>
480
+ The unique identifier of the user.
481
+ </ParamField>
482
+
483
+ <ParamField query="limit" type="integer" default="20">
484
+ Maximum number of results to return per page.
485
+ </ParamField>
486
+
487
+ <ResponseField name="createdAt" type="string">
488
+ ISO 8601 timestamp for when the record was created.
489
+ </ResponseField>
490
+ ```
491
+
492
+ For a full spec-driven API reference — one page per operation with generated schemas — use the [OpenAPI reference](/docs/advanced/api-reference) instead.
493
+
441
494
  ## Type tables
442
495
 
443
496
  Tables for documenting an object's properties — its props, types, and defaults. Write the rows by hand with `TypeTable`, or generate them straight from a TypeScript interface or type alias with `AutoTypeTable`.
@@ -22,7 +22,7 @@ i18n: {
22
22
 
23
23
  Each locale has a `code` (used in URLs), a `label` (shown in the language switcher), and an optional `dir` for right-to-left scripts (`"ltr"` by default).
24
24
 
25
- :::tip Migrating from Mintlify? `blume migrate mintlify` reads `navigation.languages[]` and writes the `i18n` block for you — the `default: true` language becomes `defaultLocale`, and translated folders already match Blume's layout. :::
25
+ :::tip Migrating from Mintlify? [`blume migrate mintlify`](/docs/advanced/migrate) reads `navigation.languages[]` and writes the `i18n` block for you — the `default: true` language becomes `defaultLocale`, and translated folders already match Blume's layout. :::
26
26
 
27
27
  ## Organize translated content
28
28
 
@@ -338,10 +338,10 @@ Call out a destructive or breaking action that can't easily be undone.
338
338
 
339
339
  An informational aside; an alias-friendly default that reads as neutral.
340
340
 
341
- :::info The core theme ships zero client JavaScript. :::
341
+ :::info The core theme ships no client framework JS. :::
342
342
 
343
343
  ```md
344
- :::info The core theme ships zero client JavaScript. :::
344
+ :::info The core theme ships no client framework JS. :::
345
345
  ```
346
346
 
347
347
  The names `caution`, `error`, `important`, and `warn` are accepted as aliases for `warning`, `danger`, `note`, and `warning` respectively.
package/docs/index.mdx CHANGED
@@ -27,7 +27,7 @@ Blume takes the best of both worlds. The framework is the template, so the only
27
27
 
28
28
  ### Fast by default
29
29
 
30
- Blume builds on Astro and Vite and renders static HTML by default — fast, cacheable, and cheap to host. The core theme is React-free and ships **zero client JavaScript**, so pages score well on Core Web Vitals out of the box. Dev startup and hot reload feel Vite-native, and you opt into server features only when you need them.
30
+ Blume builds on Astro and Vite and renders static HTML by default — fast, cacheable, and cheap to host. The core theme ships no client framework JS so pages score well on Core Web Vitals out of the box. Dev startup and hot reload feel Vite-native, and you opt into server features only when you need them.
31
31
 
32
32
  ### AI-ready out of the box
33
33
 
@@ -49,7 +49,7 @@ Your [`blume.config.ts`](/docs/configuration) and every [`meta.ts`](/docs/conten
49
49
  - **Navigation** — inferred from files, refined with `meta.ts` or config.
50
50
  - **SEO** — metadata, Open Graph images, RSS feeds, and JSON-LD, [built in](/docs/configuration/seo).
51
51
  - **Customization** — component overrides, React islands, custom pages, theme tokens, and a source-component registry via `blume add`.
52
- - **Migration** — `blume migrate mintlify | starlight | nextra | fumadocs`.
52
+ - **Migration** — [`blume migrate mintlify | starlight | nextra | fumadocs`](/docs/advanced/migrate), or run `blume dev` on a Mintlify project as-is with [bridge mode](/docs/advanced/bridge).
53
53
  - **Eject** — `blume eject` produces a standalone Astro project that still uses the `blume` package.
54
54
 
55
55
  ## How it works
@@ -16,7 +16,7 @@ blume <command> [options]
16
16
  | `blume build` | Build the static (or server) site. |
17
17
  | `blume preview` | Preview the last build. |
18
18
  | `blume add <item>` | Install a source component from the registry. |
19
- | `blume migrate <tool>` | Migrate from Mintlify, Starlight, Nextra, or Fumadocs. |
19
+ | [`blume migrate <tool>`](/docs/advanced/migrate) | 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
22
  | `blume check` | Type-check the site with `astro check`. |
@@ -37,16 +37,44 @@ blume <command> [options]
37
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
38
  - `blume build --analyze` — print the client JavaScript bundle sizes (largest first) after the build.
39
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.
40
+ - `blume build --isolated` — build into a throwaway `.blume-verify/` runtime (and its own `dist/`) instead of `.blume/`, so a running `blume dev` server and your real `dist/` are left untouched. See [Verifying while the dev server runs](#verifying-while-the-dev-server-runs).
40
41
  - `blume preview --host --port <n>` — bind the preview server.
41
42
  - `blume sync --force` — re-fetch remote sources, dropping the cached snapshot first.
42
43
  - `blume add <item> --force` — overwrite files that already exist.
43
44
  - `blume check --preview` — include drafts and unpublished CMS content when checking.
44
45
  - `blume check --strict` — fail on content diagnostics as well as type errors.
46
+ - `blume check --isolated` — type-check in a throwaway `.blume-verify/` runtime so a running `blume dev` server is left untouched. See [Verifying while the dev server runs](#verifying-while-the-dev-server-runs).
45
47
  - `blume eject --yes` — skip the confirmation prompt.
46
48
  - `blume validate --external` — also check external links over the network.
47
49
  - `blume validate --strict` — exit non-zero on warnings too.
48
50
  - `blume validate --json` / `blume doctor --json` — emit diagnostics as JSON on stdout (with `code`, `severity`, `file`, `line`/`column`, and `docsUrl`) for CI and editor integrations.
49
51
 
52
+ ## Verifying while the dev server runs
53
+
54
+ `blume dev` serves a live Astro server rooted at the generated `.blume/` runtime and regenerates it on every change. `blume build` and `blume check` regenerate the _same_ `.blume/`, so running either while the dev server is live would corrupt it — both refuse with an error and exit non-zero:
55
+
56
+ ```
57
+ A `blume dev` server is running against .blume; building would corrupt it.
58
+ Stop the dev server, or re-run with --isolated to build/verify against
59
+ .blume-verify without touching it.
60
+ ```
61
+
62
+ The `--isolated` flag is the escape hatch. It relocates the entire generated runtime (and, for `build`, its output `dist/`) to a sibling `.blume-verify/` directory, so the verification never writes anything the dev server — or your real `dist/` — depends on:
63
+
64
+ ```bash
65
+ # In a second terminal, while `blume dev` is running:
66
+ blume check --isolated # fast: type-check the .astro/config changes
67
+ blume build --isolated # thorough: full production render into .blume-verify/dist
68
+ ```
69
+
70
+ `check --isolated` is the quick path (Astro type + template diagnostics, no `dist/`); `build --isolated` is the heavier one that also catches runtime render errors. Isolated builds skip the deploy post-steps (search index, hosted-provider sync, `llms.txt`, sitemap/robots, redirects) — a verify only needs to confirm the site compiles and renders, not publish it. Blume adds `.blume-verify/` to your `.gitignore` automatically.
71
+
72
+ This is especially useful when a coding agent needs to verify changes while you keep the dev server open. To make plain `blume build`/`blume check` isolate without the flag — for example in an agent's shell — set `BLUME_RUNTIME_DIR` to the runtime directory to use:
73
+
74
+ ```bash
75
+ export BLUME_RUNTIME_DIR=.blume-verify
76
+ ```
77
+
50
78
  ## Type-checking
51
79
 
52
80
  `blume check` runs [`astro check`](https://docs.astro.build/en/reference/cli-reference/#astro-check) over your project. It regenerates the `.blume` runtime, syncs Astro's content types, then reports any TypeScript errors — in your `blume.config.ts`, in custom `.astro` pages, and in the components they import. It exits non-zero when there are errors, so it works as a `typecheck` step in CI:
@@ -18,6 +18,11 @@ Every page accepts the following frontmatter. All fields are optional.
18
18
  type: "string",
19
19
  description: "Publish date for blog/changelog feeds (ISO or YAML date).",
20
20
  },
21
+ authors: {
22
+ type: "string | string[] | object[]",
23
+ description:
24
+ "Post author(s) for blog/changelog content — a name, or objects with a name plus optional avatar/url and any extra fields. Preserved as-is.",
25
+ },
21
26
  slug: { type: "string", description: "Override the generated slug." },
22
27
  draft: {
23
28
  type: "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -50,6 +50,7 @@
50
50
  "./components": "./src/components/index.ts",
51
51
  "./components/*": "./src/components/*",
52
52
  "./core/*": "./src/core/*",
53
+ "./openapi/*": "./src/openapi/*",
53
54
  "./sources/*": "./src/core/sources/*",
54
55
  "./theme/*": "./src/theme/*",
55
56
  "./package.json": "./package.json"
@@ -69,10 +70,19 @@
69
70
  "@astrojs/node": "^11.0.0",
70
71
  "@astrojs/react": "^6.0.0",
71
72
  "@astrojs/vercel": "^11.0.0",
73
+ "@iconify-json/fa6-brands": "^1.2.6",
74
+ "@iconify-json/fa6-regular": "^1.2.4",
75
+ "@iconify-json/fa6-solid": "^1.2.4",
76
+ "@iconify-json/lucide": "^1.2.115",
77
+ "@iconify-json/tabler": "^1.2.35",
78
+ "@iconify/types": "^2.0.0",
79
+ "@iconify/utils": "^3.1.3",
72
80
  "@modelcontextprotocol/sdk": "^1.29.0",
73
81
  "@orama/orama": "^3.1.18",
74
82
  "@pierre/diffs": "^1.2.11",
75
83
  "@scalar/astro": "^0.4.5",
84
+ "@scalar/openapi-parser": "^0.28.8",
85
+ "@scalar/openapi-types": "^0.9.1",
76
86
  "@shikijs/transformers": "^4.2.0",
77
87
  "@shikijs/twoslash": "^4.2.0",
78
88
  "@tailwindcss/typography": "^0.5.20",
@@ -36,11 +36,9 @@ import type { ResolvedConfig } from "../core/schema.ts";
36
36
  import { resolveTsconfigAliases } from "../core/tsconfig-aliases.ts";
37
37
  import type { Navigation } from "../core/types.ts";
38
38
  import { buildRssFeeds, renderRssFeed } from "../deploy/rss.ts";
39
- import {
40
- buildReferenceFiles,
41
- hasReferences,
42
- referenceTabs,
43
- } from "../openapi/scalar.ts";
39
+ import { hasScalarReferences, referenceTabs } from "../openapi/references.ts";
40
+ import { buildReferenceFiles } from "../openapi/scalar.ts";
41
+ import { isOpenApiSource } from "../openapi/source.ts";
44
42
  import { registry } from "../registry/registry.ts";
45
43
  import { buildSearchDocuments } from "../search/documents.ts";
46
44
  import { searchProviderMeta, servesStaticIndex } from "../search/providers.ts";
@@ -582,8 +580,9 @@ export const buildRuntimeData = (project: BlumeProject): string => {
582
580
 
583
581
  const { i18n } = config;
584
582
 
585
- // API reference routes (Scalar) surface as header tabs alongside the
586
- // content-derived ones, so the reference stays discoverable in every locale.
583
+ // API reference routes surface as header tabs alongside the content-derived
584
+ // ones (Blume-rendered references also own a tab-scoped sidebar of operations),
585
+ // so the reference stays discoverable in every locale.
587
586
  const withReferenceTabs = (nav: Navigation): Navigation => ({
588
587
  ...nav,
589
588
  repoUrl: config.navigation.repo && repoUrl ? repoUrl : null,
@@ -650,6 +649,7 @@ export const buildRuntimeData = (project: BlumeProject): string => {
650
649
  })),
651
650
  }
652
651
  : null,
652
+ icons: config.icons,
653
653
  imageZoom: config.markdown.imageZoom,
654
654
  logo: resolveLogo(project),
655
655
  mcp: config.mcp.enabled
@@ -912,6 +912,7 @@ export const generateRuntime = async (
912
912
  const themePath = join(srcDir, "generated", "app.css");
913
913
  const searchClientPath = join(srcDir, "generated", "search-client.ts");
914
914
  const examplesPath = join(srcDir, "generated", "examples.ts");
915
+ const openapiPath = join(srcDir, "generated", "openapi.json");
915
916
 
916
917
  // Record every file this pass writes so orphans (from a now-disabled feature)
917
918
  // can be pruned afterwards. `write` wraps the atomic writer and tracks paths.
@@ -987,6 +988,7 @@ export const generateRuntime = async (
987
988
  needsReact,
988
989
  needsSvelte,
989
990
  needsVue,
991
+ openapiPath,
990
992
  pages,
991
993
  searchClientPath,
992
994
  themePath,
@@ -1235,7 +1237,7 @@ export const generateRuntime = async (
1235
1237
  // React ships with Blume; Vue/Svelte islands need their Astro integration
1236
1238
  // installed by the project. Warn early rather than let Vite fail to resolve it.
1237
1239
  warnings.push(...islandFrameworkWarnings(frameworks, context.root));
1238
- if (hasReferences(config)) {
1240
+ if (hasScalarReferences(config)) {
1239
1241
  const references = await buildReferenceFiles({
1240
1242
  config,
1241
1243
  contentRoutes: new Set(project.graph.pages.map((page) => page.route)),
@@ -1254,6 +1256,14 @@ export const generateRuntime = async (
1254
1256
  join(srcDir, "generated", "data.json"),
1255
1257
  buildRuntimeData(project)
1256
1258
  );
1259
+ // The parsed OpenAPI specs behind the `blume:openapi` alias. Always written
1260
+ // (even as `{}`) so the alias resolves whether or not a reference is enabled;
1261
+ // the source parsed the specs during the scan, so this is just serialization.
1262
+ const openApiSource = project.sources.find(isOpenApiSource);
1263
+ await write(
1264
+ openapiPath,
1265
+ `${JSON.stringify(openApiSource ? openApiSource.openApiData() : {})}\n`
1266
+ );
1257
1267
  await write(
1258
1268
  join(out, "blume.manifest.json"),
1259
1269
  `${JSON.stringify(project.manifest, null, 2)}\n`
@@ -7,6 +7,7 @@ import type { AskBackend } from "../ai/ask.ts";
7
7
  import { resolveAssetMounts } from "../core/assets.ts";
8
8
  import type { ResolvedConfig } from "../core/schema.ts";
9
9
  import type { ProjectContext } from "../core/types.ts";
10
+ import { hasScalarReferences } from "../openapi/references.ts";
10
11
  import { searchProviderMeta } from "../search/providers.ts";
11
12
  import { buildFontEntries } from "../theme/fonts.ts";
12
13
  import type { ExampleSpec } from "./examples.ts";
@@ -99,9 +100,10 @@ export const runtimeDependencies = (options: {
99
100
  if (needsSvelte) {
100
101
  deps.push("@astrojs/svelte");
101
102
  }
102
- // The Scalar integration is only declared when an API reference is configured,
103
- // so projects that don't use it never pull it into the runtime.
104
- if (config.openapi.enabled || config.asyncapi.enabled) {
103
+ // The Scalar integration is only declared for a Scalar-rendered reference
104
+ // (the `renderer: "scalar"` fallback, or AsyncAPI). Blume-rendered OpenAPI
105
+ // parses at generate time and needs no runtime Scalar dependency.
106
+ if (hasScalarReferences(config)) {
105
107
  deps.push("@scalar/astro");
106
108
  }
107
109
  // Only the configured search provider's SDK is declared, so a project pulls in
@@ -178,6 +180,10 @@ const renderUserAliases = (
178
180
  )
179
181
  .join("");
180
182
 
183
+ /** Astro's build output dir: the runtime's own `distDir`, else `<root>/dist`. */
184
+ const astroOutDir = (context: ProjectContext): string =>
185
+ context.distDir ?? `${context.root}/dist`;
186
+
181
187
  export const astroConfigTemplate = (options: {
182
188
  context: ProjectContext;
183
189
  config: ResolvedConfig;
@@ -190,6 +196,7 @@ export const astroConfigTemplate = (options: {
190
196
  examplesPath: string;
191
197
  themePath: string;
192
198
  searchClientPath: string;
199
+ openapiPath: string;
193
200
  /** Project tsconfig path aliases (`find` -> absolute dir), e.g. `@` -> src. */
194
201
  aliases?: Record<string, string>;
195
202
  }): string => {
@@ -199,6 +206,7 @@ export const astroConfigTemplate = (options: {
199
206
  examplesPath,
200
207
  needsSvelte,
201
208
  needsVue,
209
+ openapiPath,
202
210
  searchClientPath,
203
211
  } = options;
204
212
  const { deployment } = config;
@@ -322,7 +330,7 @@ ${twoslashImport}${reactImport}${vueImport}${svelteImport}${blumeImport}${adapte
322
330
  export default defineConfig({
323
331
  root: ${JSON.stringify(context.outDir)},
324
332
  srcDir: ${JSON.stringify(`${context.outDir}/src`)},
325
- outDir: ${JSON.stringify(`${context.root}/dist`)},
333
+ outDir: ${JSON.stringify(astroOutDir(context))},
326
334
  publicDir: ${JSON.stringify(`${context.root}/public`)},
327
335
  output: ${JSON.stringify(deployment.output)},${adapterOption}${siteOption}${baseOption}${redirectsOption}${i18nOption}${fontsOption}
328
336
  integrations: [${integrations.join(", ")}],
@@ -357,6 +365,7 @@ export default defineConfig({
357
365
  alias: {
358
366
  "blume:data": ${JSON.stringify(dataPath)},
359
367
  "blume:examples": ${JSON.stringify(examplesPath)},
368
+ "blume:openapi": ${JSON.stringify(openapiPath)},
360
369
  "blume:search-client": ${JSON.stringify(searchClientPath)},
361
370
  "blume:theme": ${JSON.stringify(themePath)},${userAliasLines}
362
371
  },
@@ -913,7 +922,10 @@ import FileTree from "blume/components/content/FileTree.astro";
913
922
  import Frame from "blume/components/content/Frame.astro";
914
923
  import GithubInfo from "blume/components/content/GithubInfo.astro";
915
924
  import Panel from "blume/components/content/Panel.astro";
925
+ import ParamField from "blume/components/content/ParamField.astro";
916
926
  import Prompt from "blume/components/content/Prompt.astro";
927
+ import RequestField from "blume/components/content/RequestField.astro";
928
+ import ResponseField from "blume/components/content/ResponseField.astro";
917
929
  import Step from "blume/components/content/Step.astro";
918
930
  import Steps from "blume/components/content/Steps.astro";
919
931
  import Tab from "blume/components/content/Tab.astro";
@@ -927,6 +939,8 @@ import TypeTable from "blume/components/content/TypeTable.astro";
927
939
  import Visibility from "blume/components/content/Visibility.astro";
928
940
  import YouTube from "blume/components/content/YouTube.astro";
929
941
  import Icon from "blume/components/Icon.astro";
942
+ import ApiOverview from "blume/components/openapi/ApiOverview.astro";
943
+ import Operation from "blume/components/openapi/Operation.astro";
930
944
  ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts";
931
945
  import { islandComponents } from "../generated/islands.ts";
932
946
  import data from "../generated/data.json";
@@ -943,6 +957,7 @@ export const prerender = true;
943
957
  const components = {
944
958
  Accordion,
945
959
  AccordionItem,
960
+ ApiOverview,
946
961
  AutoTypeTable,
947
962
  Badge,
948
963
  Callout,
@@ -960,8 +975,12 @@ const components = {
960
975
  Frame,
961
976
  GithubInfo,
962
977
  Icon,
978
+ Operation,
963
979
  Panel,
980
+ ParamField,
964
981
  Prompt,
982
+ RequestField,
983
+ ResponseField,
965
984
  Step,
966
985
  Steps,
967
986
  Tab,
@@ -1448,6 +1467,11 @@ declare module "blume:data" {
1448
1467
  export default data;
1449
1468
  }
1450
1469
 
1470
+ declare module "blume:openapi" {
1471
+ const specs: import("blume/openapi/model.ts").OpenApiData;
1472
+ export default specs;
1473
+ }
1474
+
1451
1475
  declare module "blume:search-client" {
1452
1476
  export const createSearch: () =>
1453
1477
  | import("blume/components/layout/search/types.ts").SearchFn