blume 0.3.0 → 0.4.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 (81) hide show
  1. package/dist/cli/index.js +747 -471
  2. package/dist/cli/index.js.map +45 -38
  3. package/dist/types/core/schema.d.ts +289 -278
  4. package/dist/types/migrate/mintlify/assets.d.ts +8 -0
  5. package/docs/01-quickstart.mdx +5 -16
  6. package/docs/02-deployment.mdx +21 -54
  7. package/docs/advanced/api-reference.mdx +10 -37
  8. package/docs/advanced/blog.mdx +9 -25
  9. package/docs/advanced/changelog.mdx +10 -33
  10. package/docs/advanced/custom-pages.mdx +21 -78
  11. package/docs/configuration/ai.mdx +42 -103
  12. package/docs/configuration/analytics.mdx +20 -38
  13. package/docs/configuration/customization.mdx +40 -73
  14. package/docs/configuration/export.mdx +9 -34
  15. package/docs/configuration/index.mdx +67 -87
  16. package/docs/configuration/search.mdx +17 -54
  17. package/docs/configuration/seo.mdx +17 -48
  18. package/docs/configuration/theming.mdx +20 -42
  19. package/docs/content/components.mdx +42 -101
  20. package/docs/content/i18n.mdx +21 -72
  21. package/docs/content/index.mdx +18 -48
  22. package/docs/content/islands.mdx +25 -52
  23. package/docs/content/meta.mdx +23 -50
  24. package/docs/content/navigation.mdx +23 -62
  25. package/docs/content/sources.mdx +20 -83
  26. package/docs/content/syntax.mdx +37 -105
  27. package/docs/index.mdx +11 -40
  28. package/docs/reference/cli.mdx +18 -29
  29. package/docs/reference/frontmatter.mdx +2 -5
  30. package/package.json +1 -1
  31. package/src/astro/integration.ts +26 -3
  32. package/src/astro/islands.ts +6 -2
  33. package/src/astro/markdown-negotiation.ts +17 -3
  34. package/src/astro/pages.ts +6 -1
  35. package/src/astro/static-assets.ts +117 -0
  36. package/src/astro/templates.ts +48 -26
  37. package/src/cli/args.ts +23 -0
  38. package/src/cli/commands/build.ts +23 -0
  39. package/src/cli/commands/dev.ts +11 -2
  40. package/src/cli/commands/doctor.ts +10 -1
  41. package/src/cli/commands/eject.ts +3 -1
  42. package/src/cli/commands/init.ts +21 -1
  43. package/src/cli/commands/preview.ts +2 -1
  44. package/src/cli/commands/validate.ts +12 -1
  45. package/src/cli/dev-lock.ts +84 -0
  46. package/src/cli/log.ts +11 -0
  47. package/src/components/BlumePage.astro +2 -0
  48. package/src/components/content/YouTube.astro +35 -0
  49. package/src/components/content/youtube.ts +46 -0
  50. package/src/components/islands/ask-ai.tsx +14 -14
  51. package/src/components/props.ts +3 -0
  52. package/src/core/assets.ts +31 -0
  53. package/src/core/bridge.ts +10 -0
  54. package/src/core/builtin-tags.ts +1 -0
  55. package/src/core/diagnostics.ts +6 -1
  56. package/src/core/gitignore.ts +30 -0
  57. package/src/core/links.ts +60 -19
  58. package/src/core/schema.ts +7 -0
  59. package/src/core/sources/mdx-remote.ts +54 -8
  60. package/src/core/sources/normalize.ts +6 -1
  61. package/src/core/sources/notion.ts +49 -5
  62. package/src/core/sources/sanity.ts +5 -1
  63. package/src/deploy/rss.ts +1 -8
  64. package/src/deploy/sitemap.ts +20 -1
  65. package/src/deploy/xml.ts +8 -0
  66. package/src/markdown/directives.ts +15 -7
  67. package/src/markdown/package-commands.ts +26 -4
  68. package/src/migrate/fumadocs/content.ts +14 -1
  69. package/src/migrate/fumadocs/groups.ts +7 -0
  70. package/src/migrate/fumadocs/index.ts +5 -2
  71. package/src/migrate/mintlify/assets.ts +46 -0
  72. package/src/migrate/mintlify/index.ts +53 -45
  73. package/src/migrate/shared.ts +12 -27
  74. package/src/og/card.ts +14 -2
  75. package/src/registry/eject.ts +13 -3
  76. package/src/registry/registry.ts +6 -0
  77. package/src/registry/rewrite-imports.ts +31 -19
  78. package/src/search/documents.ts +23 -5
  79. package/src/search/sync/algolia.ts +5 -1
  80. package/src/search/sync/typesense.ts +24 -16
  81. package/src/theme/palette.ts +26 -7
@@ -3,19 +3,11 @@ title: Syntax
3
3
  description: Every Markdown and MDX feature Blume renders — formatting, lists, tables, callouts, code blocks, package installs, and math.
4
4
  ---
5
5
 
6
- Blume renders standard Markdown and MDX with a curated, GitHub-flavored feature
7
- set — no imports, no configuration. Write content the way you already do; this
8
- page shows everything that's supported, with a live preview and the source for
9
- each.
6
+ Blume renders standard Markdown and MDX with a curated, GitHub-flavored feature set — no imports, no configuration. Write content the way you already do; this page shows everything that's supported, with a live preview and the source for each.
10
7
 
11
8
  ## Headings
12
9
 
13
- Structure a page with headings. Blume renders your frontmatter `title` as the
14
- page heading, so start your content at `##` — `##` and `###` become entries
15
- in the table of contents. Every `##`–`######` heading is also wrapped in a link
16
- to its own anchor, so readers can click a heading to copy, bookmark, or share a
17
- permalink straight to that section (hover to reveal the `#`). Turn this off with
18
- `markdown: { headingAnchors: false }` in `blume.config.ts`.
10
+ Structure a page with headings. Blume renders your frontmatter `title` as the page heading, so start your content at `##` — `##` and `###` become entries in the table of contents. Every `##`–`######` heading is also wrapped in a link to its own anchor, so readers can click a heading to copy, bookmark, or share a permalink straight to that section (hover to reveal the `#`). Turn this off with `markdown: { headingAnchors: false }` in `blume.config.ts`.
19
11
 
20
12
  ```md
21
13
  ## Section
@@ -27,8 +19,7 @@ permalink straight to that section (hover to reveal the `#`). Turn this off with
27
19
 
28
20
  ## Emphasis
29
21
 
30
- Inline formatting for stressing words, marking deletions, and showing code or
31
- keystrokes mid-sentence.
22
+ Inline formatting for stressing words, marking deletions, and showing code or keystrokes mid-sentence.
32
23
 
33
24
  **Bold**, _italic_, ~~strikethrough~~, and `inline code`.
34
25
 
@@ -48,8 +39,7 @@ E = mc^2^ and H~2~O.
48
39
 
49
40
  ## Blockquotes
50
41
 
51
- Set off a quotation, callout aside, or an editorial note from the surrounding
52
- text.
42
+ Set off a quotation, callout aside, or an editorial note from the surrounding text.
53
43
 
54
44
  > Documentation that's fast, AI-ready, and zero-config — down to the template.
55
45
 
@@ -59,8 +49,7 @@ text.
59
49
 
60
50
  ## Lists
61
51
 
62
- Use unordered lists for unordered sets, ordered lists for sequences, and task
63
- lists for checklists and roadmaps.
52
+ Use unordered lists for unordered sets, ordered lists for sequences, and task lists for checklists and roadmaps.
64
53
 
65
54
  - Markdown-first authoring
66
55
  - Static by default
@@ -90,8 +79,7 @@ lists for checklists and roadmaps.
90
79
 
91
80
  ## Tables
92
81
 
93
- Tabulate structured data — config options, comparison matrices, parameter lists.
94
- Use colons in the divider row to align columns.
82
+ Tabulate structured data — config options, comparison matrices, parameter lists. Use colons in the divider row to align columns.
95
83
 
96
84
  | Command | Description | Output |
97
85
  | ------------- | --------------------- | :-----: |
@@ -107,8 +95,7 @@ Use colons in the divider row to align columns.
107
95
 
108
96
  ## Links and images
109
97
 
110
- Link to other pages or external sites. Images accept any path under `public/` or
111
- a remote URL.
98
+ Link to other pages or external sites. Images accept any path under `public/` (or another directory listed in [`content.assets`](/docs/configuration#content)) or a remote URL.
112
99
 
113
100
  Read the [quickstart](/docs/quickstart) to get started.
114
101
 
@@ -118,9 +105,7 @@ Read the [quickstart](/docs/quickstart) to get started.
118
105
  ![Alt text](/screenshot.png)
119
106
  ```
120
107
 
121
- Content images are click-to-zoom by default — readers can click any image to open
122
- it in a lightbox. Turn this off with `markdown: { imageZoom: false }` in
123
- `blume.config.ts`, or opt a single image out with `data-no-zoom`.
108
+ Content images are click-to-zoom by default — readers can click any image to open it in a lightbox. Turn this off with `markdown: { imageZoom: false }` in `blume.config.ts`, or opt a single image out with `data-no-zoom`.
124
109
 
125
110
  ## Horizontal rule
126
111
 
@@ -134,10 +119,7 @@ Separate major shifts in topic within a long page.
134
119
 
135
120
  ## Code blocks
136
121
 
137
- Fenced code blocks are syntax-highlighted with a header showing the language —
138
- with a brand icon for recognized languages — and a copy button. Add a **title**
139
- after the language — typically a filename — and it replaces the language label in
140
- the header.
122
+ Fenced code blocks are syntax-highlighted with a header showing the language — with a brand icon for recognized languages — and a copy button. Add a **title** after the language — typically a filename — and it replaces the language label in the header.
141
123
 
142
124
  ```ts blume.config.ts
143
125
  import { defineConfig } from "blume";
@@ -157,14 +139,11 @@ export default defineConfig({
157
139
  ```
158
140
  ````
159
141
 
160
- Inline code can be highlighted too: add a `{:lang}` marker inside a backtick span
161
- and it's colored like a tiny code block — `useState(){:js}` or
162
- `T extends object{:ts}`. Turn it on with `markdown: { code: { inline: true } }`.
142
+ Inline code can be highlighted too: add a `{:lang}` marker inside a backtick span and it's colored like a tiny code block — `useState(){:js}` or `T extends object{:ts}`. Turn it on with `markdown: { code: { inline: true } }`.
163
143
 
164
144
  ### Line numbers
165
145
 
166
- Append `lineNumbers` to render a line-number gutter — on its own or alongside a
167
- title:
146
+ Append `lineNumbers` to render a line-number gutter — on its own or alongside a title:
168
147
 
169
148
  ```ts server.ts lineNumbers
170
149
  import { serve } from "blume";
@@ -182,9 +161,7 @@ serve({ port: 3000 });
182
161
 
183
162
  ### Highlighting
184
163
 
185
- Annotate code with GitHub-style comments to draw attention to lines, words, and
186
- changes. The comments are stripped from the rendered output, so the code stays
187
- copy-paste clean. All four are on by default — no configuration.
164
+ Annotate code with GitHub-style comments to draw attention to lines, words, and changes. The comments are stripped from the rendered output, so the code stays copy-paste clean. All four are on by default — no configuration.
188
165
 
189
166
  Mark a line with `// [!code highlight]` to give it a highlighted background:
190
167
 
@@ -194,8 +171,7 @@ const config = defineConfig({
194
171
  });
195
172
  ```
196
173
 
197
- Show changes with `// [!code ++]` for additions and `// [!code --]` for
198
- removals, rendered as a green/red diff:
174
+ Show changes with `// [!code ++]` for additions and `// [!code --]` for removals, rendered as a green/red diff:
199
175
 
200
176
  ```ts
201
177
  export default defineConfig({
@@ -212,8 +188,7 @@ import { serve } from "blume"; // [!code word:serve]
212
188
  serve({ port: 3000 });
213
189
  ```
214
190
 
215
- Dim everything except the lines you mark with `// [!code focus]` (the rest
216
- sharpens on hover):
191
+ Dim everything except the lines you mark with `// [!code focus]` (the rest sharpens on hover):
217
192
 
218
193
  ```ts
219
194
  export default defineConfig({
@@ -222,9 +197,7 @@ export default defineConfig({
222
197
  });
223
198
  ```
224
199
 
225
- Or highlight lines by **number** instead of comments — useful when you can't edit
226
- the code. Put a brace range after the language; single lines, comma lists, and
227
- `start-end` spans all work:
200
+ Or highlight lines by **number** instead of comments — useful when you can't edit the code. Put a brace range after the language; single lines, comma lists, and `start-end` spans all work:
228
201
 
229
202
  ```ts {1,4-5}
230
203
  import { defineConfig } from "blume";
@@ -248,10 +221,7 @@ export default defineConfig({
248
221
 
249
222
  ### Display types
250
223
 
251
- Mark a TypeScript block `twoslash` to display real types straight from the
252
- compiler — powered by [Twoslash](https://shiki.style/packages/twoslash). Hover
253
- any token to see its inferred type, and add an inline `^?` query to pin a type
254
- below the line.
224
+ Mark a TypeScript block `twoslash` to display real types straight from the compiler — powered by [Twoslash](https://shiki.style/packages/twoslash). Hover any token to see its inferred type, and add an inline `^?` query to pin a type below the line.
255
225
 
256
226
  ```ts twoslash
257
227
  const config = {
@@ -272,15 +242,11 @@ config.title;
272
242
  ```
273
243
  ````
274
244
 
275
- :::note
276
- Hide the language icons or wrap long lines instead of scrolling with
277
- `markdown: { code: { icons: false, wrap: true } }` in `blume.config.ts`.
278
- :::
245
+ :::note Hide the language icons or wrap long lines instead of scrolling with `markdown: { code: { icons: false, wrap: true } }` in `blume.config.ts`. :::
279
246
 
280
247
  ## Package install
281
248
 
282
- A `package-install` block turns a single install command into a tabbed snippet
283
- for npm, pnpm, yarn, and bun — so readers copy the one that matches their setup.
249
+ A `package-install` block turns a single install command into a tabbed snippet for npm, pnpm, yarn, and bun — so readers copy the one that matches their setup.
284
250
 
285
251
  ```package-install
286
252
  npm i blume
@@ -294,9 +260,7 @@ npm i blume
294
260
 
295
261
  ## Diagrams
296
262
 
297
- A `mermaid` block renders a [Mermaid](https://mermaid.js.org) diagram — flowcharts,
298
- sequence diagrams, and more — straight from text. Diagrams follow the active color
299
- theme and re-render when it changes.
263
+ A `mermaid` block renders a [Mermaid](https://mermaid.js.org) diagram — flowcharts, sequence diagrams, and more — straight from text. Diagrams follow the active color theme and re-render when it changes.
300
264
 
301
265
  ```mermaid
302
266
  flowchart LR
@@ -314,105 +278,77 @@ flowchart LR
314
278
  ```
315
279
  ````
316
280
 
317
- Diagrams render on the client, so this is an MDX-only feature, and the Mermaid
318
- library loads only on pages that include one.
281
+ Diagrams render on the client, so this is an MDX-only feature, and the Mermaid library loads only on pages that include one.
319
282
 
320
283
  ## Callouts
321
284
 
322
- Callouts pull a reader's attention to context, advice, or risk. Write them as
323
- `:::type` directives; add a title in brackets, like `:::warning[Heads up]`.
285
+ Callouts pull a reader's attention to context, advice, or risk. Write them as `:::type` directives; add a title in brackets, like `:::warning[Heads up]`.
324
286
 
325
287
  ### Note
326
288
 
327
289
  Neutral, supporting context the reader should keep in mind.
328
290
 
329
- :::note
330
- Blume regenerates `.blume/` on every run — never edit it by hand.
331
- :::
291
+ :::note Blume regenerates `.blume/` on every run — never edit it by hand. :::
332
292
 
333
293
  ```md
334
- :::note
335
- Blume regenerates `.blume/` on every run — never edit it by hand.
336
- :::
294
+ :::note Blume regenerates `.blume/` on every run — never edit it by hand. :::
337
295
  ```
338
296
 
339
297
  ### Tip
340
298
 
341
299
  A helpful shortcut or best practice that isn't required but makes life easier.
342
300
 
343
- :::tip
344
- Set `deployment.site` so sitemaps and Open Graph images use absolute URLs.
345
- :::
301
+ :::tip Set `deployment.site` so sitemaps and Open Graph images use absolute URLs. :::
346
302
 
347
303
  ```md
348
- :::tip
349
- Set `deployment.site` so sitemaps and Open Graph images use absolute URLs.
350
- :::
304
+ :::tip Set `deployment.site` so sitemaps and Open Graph images use absolute URLs. :::
351
305
  ```
352
306
 
353
307
  ### Success
354
308
 
355
309
  Confirm a positive outcome or that a step completed as expected.
356
310
 
357
- :::success
358
- Your docs built successfully and are ready to deploy.
359
- :::
311
+ :::success Your docs built successfully and are ready to deploy. :::
360
312
 
361
313
  ```md
362
- :::success
363
- Your docs built successfully and are ready to deploy.
364
- :::
314
+ :::success Your docs built successfully and are ready to deploy. :::
365
315
  ```
366
316
 
367
317
  ### Warning
368
318
 
369
319
  Flag something that needs care to avoid a mistake or surprising behavior.
370
320
 
371
- :::warning[Heads up]
372
- Switching to `output: "server"` requires an adapter before you can deploy.
373
- :::
321
+ :::warning[Heads up] Switching to `output: "server"` requires an adapter before you can deploy. :::
374
322
 
375
323
  ```md
376
- :::warning[Heads up]
377
- Switching to `output: "server"` requires an adapter before you can deploy.
378
- :::
324
+ :::warning[Heads up] Switching to `output: "server"` requires an adapter before you can deploy. :::
379
325
  ```
380
326
 
381
327
  ### Danger
382
328
 
383
329
  Call out a destructive or breaking action that can't easily be undone.
384
330
 
385
- :::danger
386
- `blume eject` is a one-way step — the generated Astro project becomes yours.
387
- :::
331
+ :::danger `blume eject` is a one-way step — the generated Astro project becomes yours. :::
388
332
 
389
333
  ```md
390
- :::danger
391
- `blume eject` is a one-way step — the generated Astro project becomes yours.
392
- :::
334
+ :::danger `blume eject` is a one-way step — the generated Astro project becomes yours. :::
393
335
  ```
394
336
 
395
337
  ### Info
396
338
 
397
339
  An informational aside; an alias-friendly default that reads as neutral.
398
340
 
399
- :::info
400
- The core theme ships zero client JavaScript.
401
- :::
341
+ :::info The core theme ships zero client JavaScript. :::
402
342
 
403
343
  ```md
404
- :::info
405
- The core theme ships zero client JavaScript.
406
- :::
344
+ :::info The core theme ships zero client JavaScript. :::
407
345
  ```
408
346
 
409
- The names `caution`, `error`, `important`, and `warn` are accepted as aliases for
410
- `warning`, `danger`, `note`, and `warning` respectively.
347
+ The names `caution`, `error`, `important`, and `warn` are accepted as aliases for `warning`, `danger`, `note`, and `warning` respectively.
411
348
 
412
349
  ## Math
413
350
 
414
- Render LaTeX with KaTeX for formulas in prose or as centered blocks — useful for
415
- math-heavy or scientific docs. Inline math goes in `$…$`; block math in `$$…$$`.
351
+ Render LaTeX with KaTeX for formulas in prose or as centered blocks — useful for math-heavy or scientific docs. Inline math goes in `$…$`; block math in `$$…$$`.
416
352
 
417
353
  The Pythagorean theorem is $a^2 + b^2 = c^2$.
418
354
 
@@ -428,15 +364,11 @@ $$
428
364
  $$
429
365
  ```
430
366
 
431
- :::note
432
- Math is opt-in because `$` is common in prose and code. Enable it with
433
- `markdown: { math: true }` in `blume.config.ts`.
434
- :::
367
+ :::note Math is opt-in because `$` is common in prose and code. Enable it with `markdown: { math: true }` in `blume.config.ts`. :::
435
368
 
436
369
  ## Smart punctuation
437
370
 
438
- Blume converts straight quotes and dashes to typographic equivalents as you
439
- write, so prose reads like it was typeset — no special characters required.
371
+ Blume converts straight quotes and dashes to typographic equivalents as you write, so prose reads like it was typeset — no special characters required.
440
372
 
441
373
  "Quotes" become curly, -- becomes an en dash, --- an em dash, and ... an ellipsis.
442
374
 
package/docs/index.mdx CHANGED
@@ -6,9 +6,7 @@ sidebar:
6
6
  order: 0
7
7
  ---
8
8
 
9
- Drop Markdown or MDX into a folder, run `blume dev`, and get a production-grade
10
- docs site — navigation, search, theming, Open Graph images, and a rich component
11
- library — with no app boilerplate to write or maintain.
9
+ Drop Markdown or MDX into a folder, run `blume dev`, and get a production-grade docs site — navigation, search, theming, Open Graph images, and a rich component library — with no app boilerplate to write or maintain.
12
10
 
13
11
  <CardGroup cols={2}>
14
12
  <Card title="Quickstart" href="/docs/quickstart" icon="rocket">
@@ -29,61 +27,34 @@ Blume takes the best of both worlds. The framework is the template, so the only
29
27
 
30
28
  ### Fast by default
31
29
 
32
- Blume builds on Astro and Vite and renders static HTML by default — fast,
33
- cacheable, and cheap to host. The core theme is React-free and ships **zero
34
- client JavaScript**, so pages score well on Core Web Vitals out of the box. Dev
35
- startup and hot reload feel Vite-native, and you opt into server features only
36
- 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 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.
37
31
 
38
32
  ### AI-ready out of the box
39
33
 
40
- Every Blume site speaks fluent machine. It emits [`llms.txt` and
41
- `llms-full.txt`](/docs/configuration/ai), serves any page's raw Markdown by
42
- appending `.md` to its URL, and gives readers **Copy as Markdown** and **Open in
43
- chat** actions on every page. Add an optional in-page **Ask AI** assistant, or
44
- host an [**MCP server**](/docs/configuration/ai#mcp-server) so coding agents like
45
- Claude Code and Cursor can search and read your docs directly — no scraping, no
46
- hosted service. Your Markdown is the source of truth for both humans and models.
34
+ Every Blume site speaks fluent machine. It emits [`llms.txt` and `llms-full.txt`](/docs/configuration/ai), serves any page's raw Markdown by appending `.md` to its URL, and gives readers **Copy as Markdown** and **Open in chat** actions on every page. Add an optional in-page **Ask AI** assistant, or host an [**MCP server**](/docs/configuration/ai#mcp-server) so coding agents like Claude Code and Cursor can search and read your docs directly — no scraping, no hosted service. Your Markdown is the source of truth for both humans and models.
47
35
 
48
36
  ### Zero configuration — even the template
49
37
 
50
- A folder of docs is a complete project. There's no starter to clone, no Astro or
51
- Tailwind to set up, and no template to maintain. Navigation is inferred from your
52
- files, [search](/docs/configuration/search) works in dev and production without a hosted
53
- service, and theming is a handful of tokens. Everything has a sensible default;
54
- configuration is something you reach for, not something you start with.
38
+ A folder of docs is a complete project. There's no starter to clone, no Astro or Tailwind to set up, and no template to maintain. Navigation is inferred from your files, [search](/docs/configuration/search) works in dev and production without a hosted service, and theming is a handful of tokens. Everything has a sensible default; configuration is something you reach for, not something you start with.
55
39
 
56
40
  ### Type-safe to the core
57
41
 
58
- Your [`blume.config.ts`](/docs/configuration) and every
59
- [`meta.ts`](/docs/content/meta) are real TypeScript — validated by a schema and
60
- authored with `defineConfig` and `defineMeta`. Your editor autocompletes every
61
- option and catches typos, invalid values, and missing fields as you type, long
62
- before a build. Configuration is code you can refactor, compute, and trust — not
63
- loosely-typed YAML.
42
+ Your [`blume.config.ts`](/docs/configuration) and every [`meta.ts`](/docs/content/meta) are real TypeScript — validated by a schema and authored with `defineConfig` and `defineMeta`. Your editor autocompletes every option and catches typos, invalid values, and missing fields as you type, long before a build. Configuration is code you can refactor, compute, and trust — not loosely-typed YAML.
64
43
 
65
44
  ## Everything included
66
45
 
67
- - **Components** — callouts, cards, steps, tabs, accordions, badges, file trees,
68
- and parameter tables, usable in MDX with [no imports](/docs/content/components).
69
- - **Local search** — Orama works in dev and production; Pagefind is one flag away
70
- for large sites. No hosted index.
71
- - **AI** — [`llms.txt`, raw Markdown URLs, Copy as Markdown, Open in chat, an
72
- Ask AI assistant, and a hosted MCP server](/docs/configuration/ai).
46
+ - **Components** — callouts, cards, steps, tabs, accordions, badges, file trees, and parameter tables, usable in MDX with [no imports](/docs/content/components).
47
+ - **Local search** Orama works in dev and production; Pagefind is one flag away for large sites. No hosted index.
48
+ - **AI** — [`llms.txt`, raw Markdown URLs, Copy as Markdown, Open in chat, an Ask AI assistant, and a hosted MCP server](/docs/configuration/ai).
73
49
  - **Navigation** — inferred from files, refined with `meta.ts` or config.
74
50
  - **SEO** — metadata, Open Graph images, RSS feeds, and JSON-LD, [built in](/docs/configuration/seo).
75
- - **Customization** — component overrides, React islands, custom pages, theme
76
- tokens, and a source-component registry via `blume add`.
51
+ - **Customization** — component overrides, React islands, custom pages, theme tokens, and a source-component registry via `blume add`.
77
52
  - **Migration** — `blume migrate mintlify | starlight | nextra | fumadocs`.
78
- - **Eject** — `blume eject` produces a standalone Astro project that still uses
79
- the `blume` package.
53
+ - **Eject** — `blume eject` produces a standalone Astro project that still uses the `blume` package.
80
54
 
81
55
  ## How it works
82
56
 
83
- The Blume CLI discovers your content, builds a content graph, and generates a
84
- hidden Astro project under `.blume/` that it drives for dev and build. The
85
- generated runtime is an implementation detail — you write Markdown, Blume handles
86
- the rest — until you choose to eject and own it.
57
+ The Blume CLI discovers your content, builds a content graph, and generates a hidden Astro project under `.blume/` that it drives for dev and build. The generated runtime is an implementation detail — you write Markdown, Blume handles the rest — until you choose to eject and own it.
87
58
 
88
59
  ## Next steps
89
60
 
@@ -9,19 +9,19 @@ blume <command> [options]
9
9
 
10
10
  ## Commands
11
11
 
12
- | Command | Description |
13
- | ---------------------- | ------------------------------------------------------ |
14
- | `blume init` | Scaffold a minimal project. |
15
- | `blume dev` | Start the dev server with hot reload. |
16
- | `blume build` | Build the static (or server) site. |
17
- | `blume preview` | Preview the last build. |
18
- | `blume add <item>` | Install a source component from the registry. |
12
+ | Command | Description |
13
+ | --- | --- |
14
+ | `blume init` | Scaffold a minimal project. |
15
+ | `blume dev` | Start the dev server with hot reload. |
16
+ | `blume build` | Build the static (or server) site. |
17
+ | `blume preview` | Preview the last build. |
18
+ | `blume add <item>` | Install a source component from the registry. |
19
19
  | `blume migrate <tool>` | Migrate from Mintlify, Starlight, Nextra, or Fumadocs. |
20
- | `blume sync` | Re-fetch remote content sources and regenerate. |
21
- | `blume eject` | Promote the runtime into a standalone Astro app. |
22
- | `blume check` | Type-check the site with `astro check`. |
23
- | `blume doctor` | Diagnose config and content problems. |
24
- | `blume validate` | Validate links across your content. |
20
+ | `blume sync` | Re-fetch remote content sources and regenerate. |
21
+ | `blume eject` | Promote the runtime into a standalone Astro app. |
22
+ | `blume check` | Type-check the site with `astro check`. |
23
+ | `blume doctor` | Diagnose config and content problems. |
24
+ | `blume validate` | Validate links across your content. |
25
25
 
26
26
  ## Common flags
27
27
 
@@ -45,17 +45,11 @@ blume <command> [options]
45
45
  - `blume eject --yes` — skip the confirmation prompt.
46
46
  - `blume validate --external` — also check external links over the network.
47
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.
48
+ - `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.
51
49
 
52
50
  ## Type-checking
53
51
 
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:
52
+ `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:
59
53
 
60
54
  ```json title="package.json"
61
55
  {
@@ -65,8 +59,7 @@ are errors, so it works as a `typecheck` step in CI:
65
59
  }
66
60
  ```
67
61
 
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`:
62
+ Add a `tsconfig.json` extending Astro's config to your project root so authored pages resolve `blume/*` imports and virtual modules like `blume:data`:
70
63
 
71
64
  ```json title="tsconfig.json"
72
65
  {
@@ -81,11 +74,7 @@ Without a project `tsconfig.json`, only the generated runtime is checked.
81
74
 
82
75
  `blume validate` checks every link discovered in your content:
83
76
 
84
- - **Internal page links** (`/guides/intro`, `./sibling`) must resolve to a real
85
- page — broken ones are reported as errors.
86
- - **Anchor links** (`#section`, `/guides/intro#setup`) must match a heading on
87
- the target page — misses are warnings.
77
+ - **Internal page links** (`/guides/intro`, `./sibling`) must resolve to a real page — broken ones are reported as errors.
78
+ - **Anchor links** (`#section`, `/guides/intro#setup`) must match a heading on the target page — misses are warnings.
88
79
  - **Asset links** (`/logo.png`) are checked against the `public/` directory.
89
- - **External links** are only checked with `--external` (off by default since it
90
- requires the network); dead links (404/410/unreachable) are errors, while
91
- rate-limited or transient responses (403/429/5xx/timeout) are warnings.
80
+ - **External links** are only checked with `--external` (off by default since it requires the network); dead links (404/410/unreachable) are errors, while rate-limited or transient responses (403/429/5xx/timeout) are warnings.
@@ -64,8 +64,7 @@ search:
64
64
 
65
65
  ## Changelog
66
66
 
67
- Changelog entries (`type: changelog`) accept an optional `changelog` object for
68
- richer feed and display metadata:
67
+ Changelog entries (`type: changelog`) accept an optional `changelog` object for richer feed and display metadata:
69
68
 
70
69
  ```yaml lineNumbers
71
70
  type: changelog
@@ -75,8 +74,6 @@ changelog:
75
74
  category: Features
76
75
  ```
77
76
 
78
- `date` may live here or at the top level — both feed the
79
- [changelog RSS feed](/docs/content#feeds). See [Changelog](/docs/advanced/changelog) for the
80
- generated timeline page and feed.
77
+ `date` may live here or at the top level — both feed the [changelog RSS feed](/docs/content#feeds). See [Changelog](/docs/advanced/changelog) for the generated timeline page and feed.
81
78
 
82
79
  Schemas are exported from `blume/schema` for editor and migration tooling.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -1,10 +1,13 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import { fileURLToPath } from "node:url";
2
3
 
3
4
  import type { AstroIntegration } from "astro";
4
5
 
6
+ import type { AssetMount } from "../core/assets.ts";
5
7
  import { enrichDiagnostic } from "../core/diagnostics.ts";
6
8
  import type { Diagnostic } from "../core/types.ts";
7
9
  import { markdownVariantUrl, prefersMarkdown } from "./markdown-negotiation.ts";
10
+ import { copyAssetMounts, serveAssetMounts } from "./static-assets.ts";
8
11
 
9
12
  /** The dev server's HMR channel — either `.ws` (Vite ≤5) or `.hot` (Vite 6+). */
10
13
  interface OverlayChannel {
@@ -72,6 +75,13 @@ export interface BlumeIntegrationOptions {
72
75
  pages: BlumePageRoute[];
73
76
  /** Page routes that have a raw-Markdown variant (the content manifest). */
74
77
  contentRoutes: string[];
78
+ /** Configured `deployment.base`, stripped from dev URLs before matching. */
79
+ base?: string;
80
+ /**
81
+ * `content.assets` mounts: top-level dirs served at the site root in dev and
82
+ * copied into the build output, so root-served assets need no relocation.
83
+ */
84
+ assets?: AssetMount[];
75
85
  }
76
86
 
77
87
  /**
@@ -83,13 +93,13 @@ export interface BlumeIntegrationOptions {
83
93
  * keep serving HTML.
84
94
  */
85
95
  const negotiateMarkdown =
86
- (routes: ReadonlySet<string>) =>
96
+ (routes: ReadonlySet<string>, base?: string) =>
87
97
  (req: IncomingMessage, res: ServerResponse, next: () => void): void => {
88
98
  if (
89
99
  (req.method === "GET" || req.method === "HEAD") &&
90
100
  prefersMarkdown(req.headers.accept)
91
101
  ) {
92
- const variant = markdownVariantUrl(req.url, routes);
102
+ const variant = markdownVariantUrl(req.url, routes, base);
93
103
  if (variant) {
94
104
  res.setHeader("Vary", "Accept");
95
105
  req.url = variant;
@@ -108,6 +118,12 @@ export const blumeIntegration = (
108
118
  options: BlumeIntegrationOptions
109
119
  ): AstroIntegration => ({
110
120
  hooks: {
121
+ "astro:build:done": async ({ dir }) => {
122
+ // Copy in-place asset mounts into the build output, mirroring publicDir.
123
+ if (options.assets?.length) {
124
+ await copyAssetMounts(options.assets, fileURLToPath(dir));
125
+ }
126
+ },
111
127
  "astro:config:setup": ({ injectRoute }) => {
112
128
  for (const page of options.pages) {
113
129
  injectRoute({
@@ -124,9 +140,16 @@ export const blumeIntegration = (
124
140
  // Prepend so the rewrite happens before Astro's own request handler,
125
141
  // letting the rewritten URL resolve to the `.md` endpoint.
126
142
  server.middlewares.stack.unshift({
127
- handle: negotiateMarkdown(new Set(options.contentRoutes)),
143
+ handle: negotiateMarkdown(new Set(options.contentRoutes), options.base),
128
144
  route: "",
129
145
  });
146
+ // Serve `content.assets` mounts (Astro only serves publicDir in dev).
147
+ if (options.assets?.length) {
148
+ server.middlewares.stack.unshift({
149
+ handle: serveAssetMounts(options.assets),
150
+ route: "",
151
+ });
152
+ }
130
153
  },
131
154
  },
132
155
  name: "blume",
@@ -105,9 +105,13 @@ export const discoverIslands = async (
105
105
  continue;
106
106
  }
107
107
  const name = base.replace(ISLAND_FILE, "");
108
- if (!/^[A-Z]/u.test(name)) {
108
+ // The name is used verbatim as both an MDX tag and an unquoted object key
109
+ // in the generated island map, so it must be a plain PascalCase identifier
110
+ // — a `-`, `.`, or space (e.g. `Time-Picker.tsx`) would otherwise emit a
111
+ // syntax-error module and fail the whole build with no pointer to the file.
112
+ if (!/^[A-Z][A-Za-z0-9_]*$/u.test(name)) {
109
113
  warnings.push(
110
- `Island "${file}" must have a PascalCase filename to be used in MDX (e.g. Counter.tsx → <Counter />); skipping it.`
114
+ `Island "${file}" must have a PascalCase identifier filename to be used in MDX (letters, digits, and underscores only, e.g. Counter.tsx → <Counter />); skipping it.`
111
115
  );
112
116
  continue;
113
117
  }
@@ -50,7 +50,8 @@ export const prefersMarkdown = (accept: string | null | undefined): boolean => {
50
50
  */
51
51
  export const markdownVariantUrl = (
52
52
  rawUrl: string | null | undefined,
53
- routes: ReadonlySet<string>
53
+ routes: ReadonlySet<string>,
54
+ base?: string
54
55
  ): string | null => {
55
56
  if (!rawUrl) {
56
57
  return null;
@@ -58,11 +59,24 @@ export const markdownVariantUrl = (
58
59
  const queryIndex = rawUrl.indexOf("?");
59
60
  const query = queryIndex === -1 ? "" : rawUrl.slice(queryIndex);
60
61
  const rawPath = queryIndex === -1 ? rawUrl : rawUrl.slice(0, queryIndex);
62
+
63
+ // A non-root `deployment.base` prefixes the dev-server URL but not the logical
64
+ // content routes, so strip it before matching and re-add it to the variant.
65
+ const prefix = base && base !== "/" ? base.replace(/\/$/u, "") : "";
66
+ let path = rawPath;
67
+ if (prefix) {
68
+ if (path === prefix || path.startsWith(`${prefix}/`)) {
69
+ path = path.slice(prefix.length) || "/";
70
+ } else {
71
+ return null;
72
+ }
73
+ }
74
+
61
75
  const pathname =
62
- rawPath !== "/" && rawPath.endsWith("/") ? rawPath.slice(0, -1) : rawPath;
76
+ path !== "/" && path.endsWith("/") ? path.slice(0, -1) : path;
63
77
  if (!routes.has(pathname)) {
64
78
  return null;
65
79
  }
66
80
  const target = pathname === "/" ? "/index" : pathname;
67
- return `${target}.md${query}`;
81
+ return `${prefix}${target}.md${query}`;
68
82
  };
@@ -21,7 +21,12 @@ export const discoverPages = async (
21
21
  return files.map((file) => {
22
22
  const rel = relative(pagesRoot, file);
23
23
  const withoutExt = rel.slice(0, rel.length - extname(rel).length);
24
- const parts = withoutExt.split("/").filter((part) => part !== "index");
24
+ const parts = withoutExt.split("/");
25
+ // Only a trailing `index` maps to its parent dir; a folder literally named
26
+ // `index` (e.g. `index/foo.astro`) must keep its segment.
27
+ if (parts.at(-1) === "index") {
28
+ parts.pop();
29
+ }
25
30
  const pattern = parts.length === 0 ? "/" : `/${parts.join("/")}`;
26
31
  return { entrypoint: file, pattern };
27
32
  });