@docubook/flame 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docu/node/build.ts +6 -1
- package/.docu/node/html.ts +4 -2
- package/.docu/node/hydrate.ts +65 -1
- package/.docu/node/server.ts +4 -1
- package/.docu/node/types.ts +5 -0
- package/README.md +93 -0
- package/bin/cli.js +8 -1
- package/docu.schema.json +29 -0
- package/package.json +4 -3
- package/template/.env.example +5 -0
- package/template/docu.json +3 -0
package/.docu/node/build.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "./paths";
|
|
17
17
|
import { htmlShell as createHtmlShell } from "./html";
|
|
18
18
|
import { generateSearchIndex } from "./search-indexer";
|
|
19
|
-
import { buildClientBundle } from "./hydrate";
|
|
19
|
+
import { buildClientBundle, computeInlineThemeCss } from "./hydrate";
|
|
20
20
|
import { logger } from "./logger";
|
|
21
21
|
import { initSentry, captureException } from "./sentry";
|
|
22
22
|
import type { BuildCache, CliArgs } from "./types";
|
|
@@ -95,6 +95,8 @@ export function shouldRebuild(path: string, mtime: number, cache: BuildCache): b
|
|
|
95
95
|
|
|
96
96
|
let assetManifest = { js: "client.js", css: "client.css" };
|
|
97
97
|
|
|
98
|
+
let inlineThemeCss: string | undefined;
|
|
99
|
+
|
|
98
100
|
function htmlShell(title: string, description: string, body: string): string {
|
|
99
101
|
const favicon = docuConfig.meta?.favicon || "/favicon.ico";
|
|
100
102
|
return createHtmlShell({
|
|
@@ -104,6 +106,7 @@ function htmlShell(title: string, description: string, body: string): string {
|
|
|
104
106
|
favicon,
|
|
105
107
|
css: assetManifest.css,
|
|
106
108
|
js: assetManifest.js,
|
|
109
|
+
themeCss: inlineThemeCss,
|
|
107
110
|
});
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -189,6 +192,8 @@ async function build() {
|
|
|
189
192
|
assetManifest = await buildClientBundle();
|
|
190
193
|
logger.bundleDone(Math.round(performance.now() - t));
|
|
191
194
|
|
|
195
|
+
inlineThemeCss = computeInlineThemeCss();
|
|
196
|
+
|
|
192
197
|
const lastManifest = cache["__assets__"];
|
|
193
198
|
const assetsChanged =
|
|
194
199
|
!lastManifest || lastManifest.hash !== `${assetManifest.js}:${assetManifest.css}`;
|
package/.docu/node/html.ts
CHANGED
|
@@ -7,11 +7,13 @@ export interface HtmlShellOptions {
|
|
|
7
7
|
js: string;
|
|
8
8
|
nonce?: string;
|
|
9
9
|
extraScripts?: string;
|
|
10
|
+
themeCss?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function htmlShell(opts: HtmlShellOptions): string {
|
|
13
|
-
const { title, description, body, favicon, css, js, nonce, extraScripts } = opts;
|
|
14
|
+
const { title, description, body, favicon, css, js, nonce, extraScripts, themeCss } = opts;
|
|
14
15
|
const nonceAttr = nonce ? ` nonce="${Bun.escapeHTML(nonce)}"` : "";
|
|
16
|
+
const themeStyle = themeCss ? `\n <style${nonceAttr}>${Bun.escapeHTML(themeCss)}</style>` : "";
|
|
15
17
|
return `<!DOCTYPE html>
|
|
16
18
|
<html lang="en">
|
|
17
19
|
<head>
|
|
@@ -19,7 +21,7 @@ export function htmlShell(opts: HtmlShellOptions): string {
|
|
|
19
21
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
20
22
|
<title>${Bun.escapeHTML(title)}</title>
|
|
21
23
|
<meta name="description" content="${Bun.escapeHTML(description)}">
|
|
22
|
-
<link rel="icon" type="image/x-icon" href="${Bun.escapeHTML(favicon)}"
|
|
24
|
+
<link rel="icon" type="image/x-icon" href="${Bun.escapeHTML(favicon)}">${themeStyle}
|
|
23
25
|
<link rel="stylesheet" href="/assets/${Bun.escapeHTML(css)}">
|
|
24
26
|
<script${nonceAttr}>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}</script>
|
|
25
27
|
</head>
|
package/.docu/node/hydrate.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { mkdir, readdir, unlink } from "node:fs/promises";
|
|
3
|
+
import { resolveTheme, generateThemeCss, presetRegistry } from "@docubook/themes-colors";
|
|
3
4
|
import { ASSETS_DIR, LIB_DIR, STYLES_DIR, loadDocuConfig } from "./paths";
|
|
4
5
|
import { resolveRoutes } from "./fs-scanner";
|
|
5
6
|
import type { DocuRoute } from "./types";
|
|
7
|
+
import type { ThemeConfig } from "@docubook/themes-colors";
|
|
8
|
+
|
|
9
|
+
const themeRegistry = presetRegistry;
|
|
6
10
|
|
|
7
11
|
async function cleanOldBundles() {
|
|
8
12
|
try {
|
|
@@ -19,6 +23,53 @@ async function cleanOldBundles() {
|
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Read the effective theme config with this priority:
|
|
28
|
+
* 1. FLAME_THEME env var (CLI --theme flag)
|
|
29
|
+
* 2. docu.json theme.colors field
|
|
30
|
+
*/
|
|
31
|
+
export function getThemeConfig(): ThemeConfig | undefined {
|
|
32
|
+
if (process.env.FLAME_THEME) {
|
|
33
|
+
return process.env.FLAME_THEME;
|
|
34
|
+
}
|
|
35
|
+
const config = loadDocuConfig();
|
|
36
|
+
return config.themes?.colors;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Append theme CSS to compiled Tailwind output based on theme config.
|
|
41
|
+
*/
|
|
42
|
+
export function buildThemeCss(baseCss: string, themeConfig: unknown): string {
|
|
43
|
+
try {
|
|
44
|
+
const resolved = resolveTheme(themeConfig as ThemeConfig | undefined | null, themeRegistry);
|
|
45
|
+
return baseCss + "\n" + generateThemeCss(resolved);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.warn(
|
|
48
|
+
`[flame] Failed to resolve theme CSS: ${err instanceof Error ? err.message : String(err)}`
|
|
49
|
+
);
|
|
50
|
+
return baseCss;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Compute inline theme CSS for FOUC prevention.
|
|
56
|
+
* Returns undefined if no theme is configured or on error.
|
|
57
|
+
*/
|
|
58
|
+
export function computeInlineThemeCss(): string | undefined {
|
|
59
|
+
try {
|
|
60
|
+
const themeColors = getThemeConfig();
|
|
61
|
+
if (themeColors) {
|
|
62
|
+
const resolved = resolveTheme(themeColors, themeRegistry);
|
|
63
|
+
return generateThemeCss(resolved);
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`[flame] Failed to compute inline theme CSS: ${err instanceof Error ? err.message : String(err)}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
22
73
|
export async function buildClientBundle(): Promise<{ js: string; css: string }> {
|
|
23
74
|
await mkdir(ASSETS_DIR, { recursive: true });
|
|
24
75
|
await cleanOldBundles();
|
|
@@ -93,7 +144,20 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
|
|
|
93
144
|
const err = await new Response(proc.stderr).text();
|
|
94
145
|
throw new Error(`Tailwind CSS build failed:\n${err}`);
|
|
95
146
|
}
|
|
96
|
-
|
|
147
|
+
|
|
148
|
+
let cssContent = await Bun.file(tmpCss).text();
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const themeColors = getThemeConfig();
|
|
152
|
+
if (themeColors) {
|
|
153
|
+
cssContent = buildThemeCss(cssContent, themeColors);
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`[flame] Failed to resolve theme config, falling back to globals.css only: ${err instanceof Error ? err.message : String(err)}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
97
161
|
const cssHash = new Bun.CryptoHasher("md5").update(cssContent).digest("hex").slice(0, 8);
|
|
98
162
|
const cssFile = `client-${cssHash}.css`;
|
|
99
163
|
await Bun.write(join(ASSETS_DIR, cssFile), cssContent);
|
package/.docu/node/server.ts
CHANGED
|
@@ -11,7 +11,7 @@ import DocsPage from "../pages/docs/[[...slug]]";
|
|
|
11
11
|
import NotFoundPage from "../pages/404";
|
|
12
12
|
import IndexPage from "../pages/index";
|
|
13
13
|
import { DocsLayout } from "../components/DocsLayout";
|
|
14
|
-
import { buildClientBundle } from "./hydrate";
|
|
14
|
+
import { buildClientBundle, computeInlineThemeCss } from "./hydrate";
|
|
15
15
|
import { generateSearchIndex } from "./search-indexer";
|
|
16
16
|
import { logger } from "./logger";
|
|
17
17
|
import { initSentry, captureException } from "./sentry";
|
|
@@ -31,6 +31,8 @@ let t = performance.now();
|
|
|
31
31
|
const assetManifest = await buildClientBundle();
|
|
32
32
|
logger.bundleDone(Math.round(performance.now() - t));
|
|
33
33
|
|
|
34
|
+
const inlineThemeCss = computeInlineThemeCss();
|
|
35
|
+
|
|
34
36
|
logger.indexStart();
|
|
35
37
|
t = performance.now();
|
|
36
38
|
const records = await generateSearchIndex();
|
|
@@ -91,6 +93,7 @@ function createHtmlResponse(
|
|
|
91
93
|
js: assetManifest.js,
|
|
92
94
|
nonce,
|
|
93
95
|
extraScripts: hmrScript(nonce),
|
|
96
|
+
themeCss: inlineThemeCss,
|
|
94
97
|
});
|
|
95
98
|
return htmlResponse(html, nonce, status);
|
|
96
99
|
}
|
package/.docu/node/types.ts
CHANGED
|
@@ -66,6 +66,8 @@ export interface HomeConfig {
|
|
|
66
66
|
features?: HomeFeature[];
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
import type { ThemeConfig } from "@docubook/themes-colors";
|
|
70
|
+
|
|
69
71
|
export interface DocuConfig {
|
|
70
72
|
meta: DocuMeta;
|
|
71
73
|
home?: HomeConfig;
|
|
@@ -73,6 +75,9 @@ export interface DocuConfig {
|
|
|
73
75
|
footer: DocuFooter;
|
|
74
76
|
repo: RepoConfig;
|
|
75
77
|
routes: DocuRoute[];
|
|
78
|
+
themes?: {
|
|
79
|
+
colors: ThemeConfig;
|
|
80
|
+
};
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
export interface BuildCache {
|
package/README.md
CHANGED
|
@@ -124,6 +124,94 @@ The `home` section configures your landing page with a hero section and feature
|
|
|
124
124
|
| `hero.actions` | Array of CTA buttons with `text`, `link`, `theme`, `icon` |
|
|
125
125
|
| `features` | Array of feature cards with `icon`, `title`, `description`, `link` |
|
|
126
126
|
|
|
127
|
+
### Theme Colors
|
|
128
|
+
|
|
129
|
+
Flame uses `@docubook/themes-colors` for its config-driven color system. Configure via `docu.json`:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"themes": {
|
|
134
|
+
"colors": "default"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Preset Themes
|
|
140
|
+
|
|
141
|
+
Use a preset name as a string. Three built-in presets are available:
|
|
142
|
+
|
|
143
|
+
| Name | Description | Hue |
|
|
144
|
+
| ------------- | ----------------- | ------ |
|
|
145
|
+
| `"default"` | Modern Blue theme | ~210 |
|
|
146
|
+
| `"freshlime"` | Fresh Lime theme | ~85 |
|
|
147
|
+
| `"coffee"` | Rich Coffee theme | ~25–35 |
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"themes": {
|
|
152
|
+
"colors": "freshlime"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Custom Hex Colors
|
|
158
|
+
|
|
159
|
+
Define a custom primary color as a hex value. The full 24-variable palette is auto-generated from it:
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"themes": {
|
|
164
|
+
"colors": {
|
|
165
|
+
"primary": "#FF5733"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
| Property | Type | Required | Description |
|
|
172
|
+
| --------- | -------------- | -------- | ----------------------------------------------------------------- |
|
|
173
|
+
| `primary` | `string` (hex) | ✅ Yes | Primary brand color. A full light + dark scale is auto-generated. |
|
|
174
|
+
|
|
175
|
+
##### What gets auto-generated from `primary`
|
|
176
|
+
|
|
177
|
+
A single hex color generates **24 CSS variables × 2 modes** (light `:root` + dark `.dark`) plus **12 syntax highlighting tokens × 2 modes** — all derived from the primary color.
|
|
178
|
+
|
|
179
|
+
| Token | Description |
|
|
180
|
+
| -------------------------------------------- | -------------------------- |
|
|
181
|
+
| `--background` / `--foreground` | Page background & text |
|
|
182
|
+
| `--card` / `--card-foreground` | Card surface & text |
|
|
183
|
+
| `--popover` / `--popover-foreground` | Popover surface & text |
|
|
184
|
+
| `--primary` / `--primary-foreground` | Primary brand color & text |
|
|
185
|
+
| `--secondary` / `--secondary-foreground` | Secondary color & text |
|
|
186
|
+
| `--muted` / `--muted-foreground` | Muted surface & text |
|
|
187
|
+
| `--accent` / `--accent-foreground` | Accent color & text |
|
|
188
|
+
| `--destructive` / `--destructive-foreground` | Destructive action & text |
|
|
189
|
+
| `--border` | Border color |
|
|
190
|
+
| `--input` | Input field border |
|
|
191
|
+
| `--ring` | Focus ring color |
|
|
192
|
+
| `--radius` | Border radius value |
|
|
193
|
+
| `--base-100` / `--base-200` / `--base-300` | DaisyUI surface layers |
|
|
194
|
+
| `--base-content` | DaisyUI content color |
|
|
195
|
+
|
|
196
|
+
#### CLI Override
|
|
197
|
+
|
|
198
|
+
Override any theme without editing `docu.json`:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
flame dev --theme freshlime
|
|
202
|
+
flame build --theme coffee
|
|
203
|
+
flame preview --theme default
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### Priority
|
|
207
|
+
|
|
208
|
+
Theme resolution follows this order (first match wins):
|
|
209
|
+
1. `--theme` CLI flag (e.g., `flame dev --theme coffee`)
|
|
210
|
+
2. `docu.json` → `themes.colors`
|
|
211
|
+
3. Falls back to `default` preset
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
127
215
|
### Routes
|
|
128
216
|
|
|
129
217
|
When `routes` is an empty array `[]`, Flame automatically scans your `docs/` folder at build-time and generates the sidebar navigation from the directory structure. Folders become collapsible sections, and `.mdx`/`.md` files become links — sorted alphabetically.
|
|
@@ -249,6 +337,11 @@ Copy `.env.example` to `.env` to customize:
|
|
|
249
337
|
# Server port (default: 3000)
|
|
250
338
|
PORT=3000
|
|
251
339
|
|
|
340
|
+
# Theme preset (optional — overrides docu.json themes.colors)
|
|
341
|
+
# FLAME_THEME=default
|
|
342
|
+
# FLAME_THEME=freshlime
|
|
343
|
+
# FLAME_THEME=coffee
|
|
344
|
+
|
|
252
345
|
# Error Monitoring (optional)
|
|
253
346
|
SENTRY_DSN=https://your-dsn@sentry.io/project-id
|
|
254
347
|
```
|
package/bin/cli.js
CHANGED
|
@@ -16,6 +16,12 @@ const COMMAND_MAP = {
|
|
|
16
16
|
|
|
17
17
|
const command = process.argv[2];
|
|
18
18
|
|
|
19
|
+
// Parse --theme flag: set env before importing build script
|
|
20
|
+
const themeIndex = process.argv.indexOf("--theme");
|
|
21
|
+
if (themeIndex !== -1 && themeIndex + 1 < process.argv.length) {
|
|
22
|
+
process.env.FLAME_THEME = process.argv[themeIndex + 1];
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
if (!command || command === "--help" || command === "-h") {
|
|
20
26
|
console.log(`
|
|
21
27
|
@docubook/flame — A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.
|
|
@@ -31,7 +37,8 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
31
37
|
init Scaffold a new project in current directory
|
|
32
38
|
|
|
33
39
|
Options:
|
|
34
|
-
--help
|
|
40
|
+
--help Show this help message
|
|
41
|
+
--theme <name> Override theme preset (e.g. freshlime, coffee). Works with dev, build, preview.
|
|
35
42
|
`);
|
|
36
43
|
process.exit(0);
|
|
37
44
|
}
|
package/docu.schema.json
CHANGED
|
@@ -117,6 +117,35 @@
|
|
|
117
117
|
"type": "array",
|
|
118
118
|
"description": "Documentation navigation routes. Leave empty for auto-detection from docs/ folder.",
|
|
119
119
|
"items": { "$ref": "#/$defs/route" }
|
|
120
|
+
},
|
|
121
|
+
"themes": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"description": "Color themes configuration. Use preset name or custom hex values.",
|
|
124
|
+
"additionalProperties": false,
|
|
125
|
+
"properties": {
|
|
126
|
+
"colors": {
|
|
127
|
+
"description": "Theme colors — preset name (e.g. \"default\", \"freshlime\", \"coffee\") or custom hex object",
|
|
128
|
+
"oneOf": [
|
|
129
|
+
{
|
|
130
|
+
"type": "string",
|
|
131
|
+
"enum": ["default", "freshlime", "coffee"]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"type": "object",
|
|
135
|
+
"additionalProperties": false,
|
|
136
|
+
"properties": {
|
|
137
|
+
"primary": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$",
|
|
140
|
+
"description": "Primary brand color in hex format"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"required": ["primary"]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"required": ["colors"]
|
|
120
149
|
}
|
|
121
150
|
},
|
|
122
151
|
"$defs": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docubook/flame",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,9 +51,10 @@
|
|
|
51
51
|
"lucide-react": "^1.14.0",
|
|
52
52
|
"react": "^19.0.0",
|
|
53
53
|
"react-dom": "^19.0.0",
|
|
54
|
-
"@docubook/
|
|
54
|
+
"@docubook/core": "^1.7.0",
|
|
55
55
|
"@docubook/mdx-content": "^3.2.1",
|
|
56
|
-
"@docubook/
|
|
56
|
+
"@docubook/ui-react": "^0.1.3",
|
|
57
|
+
"@docubook/themes-colors": "^0.10.0"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
60
|
"@sentry/bun": "^9.0.0"
|
package/template/.env.example
CHANGED