@docubook/flame 1.0.0-beta.40 → 1.0.0-beta.60

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.
@@ -23,6 +23,7 @@ import {
23
23
  PROJECT_ROOT,
24
24
  loadDocuConfig,
25
25
  } from "./paths";
26
+ import { htmlShell as createHtmlShell } from "./html";
26
27
  import { generateSearchIndex } from "./search-indexer";
27
28
  import { buildClientBundle } from "./hydrate";
28
29
  import { logger } from "./logger";
@@ -101,22 +102,14 @@ let assetManifest = { js: "client.js", css: "client.css" };
101
102
 
102
103
  function htmlShell(title: string, description: string, body: string): string {
103
104
  const favicon = docuConfig.meta?.favicon || "/favicon.ico";
104
- return `<!DOCTYPE html>
105
- <html lang="en">
106
- <head>
107
- <meta charset="UTF-8">
108
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
109
- <title>${Bun.escapeHTML(title)}</title>
110
- <meta name="description" content="${Bun.escapeHTML(description)}">
111
- <link rel="icon" type="image/x-icon" href="${Bun.escapeHTML(favicon)}">
112
- <link rel="stylesheet" href="/assets/${assetManifest.css}">
113
- <script>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}</script>
114
- </head>
115
- <body>
116
- <div id="root">${body}</div>
117
- <script src="/assets/${assetManifest.js}"></script>
118
- </body>
119
- </html>`;
105
+ return createHtmlShell({
106
+ title,
107
+ description,
108
+ body,
109
+ favicon,
110
+ css: assetManifest.css,
111
+ js: assetManifest.js,
112
+ });
120
113
  }
121
114
 
122
115
  async function renderDocsPage(slug: string, rawMdx: string, filePath: string): Promise<string> {
@@ -142,12 +135,17 @@ async function renderDocsPage(slug: string, rawMdx: string, filePath: string): P
142
135
  throw new Error(`MDX Error in: docs/${slug}.mdx\n${msg}`, { cause: err });
143
136
  }
144
137
 
145
- const content = React.createElement(MDXRemote, { compiledSource, components });
138
+ const content = React.createElement(MDXRemote, {
139
+ compiledSource,
140
+ scope: {},
141
+ frontmatter: {},
142
+ components,
143
+ });
146
144
 
147
- const title = frontmatter.title || slug;
145
+ const title = frontmatter.title || slug || "Docs";
148
146
  const description = frontmatter.description || "";
149
147
  const date = frontmatter.date || (await getGitLastModified(filePath));
150
- const slugParts = slug.split("/");
148
+ const slugParts = slug ? slug.split("/") : [];
151
149
 
152
150
  const page = React.createElement(
153
151
  DocsLayout,
@@ -227,7 +225,7 @@ async function build() {
227
225
  logger.spinner.start("Building pages...");
228
226
  t = performance.now();
229
227
 
230
- const CONCURRENCY = 10;
228
+ const CONCURRENCY = parseInt(process.env.BUILD_CONCURRENCY || "10", 10);
231
229
  const buildTasks = [];
232
230
  const errors: string[] = [];
233
231
 
@@ -287,48 +285,18 @@ async function build() {
287
285
  await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));
288
286
  }
289
287
 
290
- const indexMdxPath = join(DOCS_DIR, "index.mdx");
291
- const indexRaw = await readFile(indexMdxPath, "utf-8");
292
- const indexTocs = extractTocsFromRawMdx(indexRaw);
293
- const { frontmatter: indexFm, strippedContent: indexStripped } = extractFrontmatterWithContent<{
294
- title?: string;
295
- description?: string;
296
- date?: string;
297
- }>(indexRaw);
298
- const indexSerialized = await serialize(indexStripped, {
299
- mdxOptions: {
300
- rehypePlugins: createDefaultRehypePlugins(),
301
- remarkPlugins: createDefaultRemarkPlugins(),
302
- },
303
- });
304
- const indexContent = React.createElement(MDXRemote, {
305
- compiledSource: indexSerialized.compiledSource,
306
- components: createMdxComponents(),
307
- });
308
- const indexRelPath = indexMdxPath.replace(PROJECT_ROOT + "/", "");
309
- const indexDate = indexFm.date || (await getGitLastModified(indexRelPath)) || undefined;
310
- const indexPageEl = React.createElement(
311
- DocsLayout,
312
- { repoUrl: docuConfig.repo?.url },
313
- React.createElement(DocsPage, {
314
- slug: [],
315
- title: indexFm.title || "Docs",
316
- description: indexFm.description || "",
317
- date: indexDate,
318
- content: indexContent,
319
- tocs: indexTocs,
320
- filePath: indexRelPath,
321
- repoUrl: docuConfig.repo?.url,
322
- compiledSource: indexSerialized.compiledSource,
323
- })
324
- );
325
- const indexHtml = htmlShell(
326
- indexFm.title || docuConfig.meta?.title || "DocuBook",
327
- indexFm.description || docuConfig.meta?.description || "",
328
- renderToString(indexPageEl)
329
- );
330
- await mkdir(join(DIST_DIR, "docs"), { recursive: true });
331
- await writeFile(join(DIST_DIR, "docs", "index.html"), indexHtml);
288
+ try {
289
+ const indexMdxPath = join(DOCS_DIR, "index.mdx");
290
+ const indexRaw = await readFile(indexMdxPath, "utf-8");
291
+ const indexRelPath = indexMdxPath.replace(PROJECT_ROOT + "/", "");
292
+ const indexHtml = await renderDocsPage("", indexRaw, indexRelPath);
293
+ await mkdir(join(DIST_DIR, "docs"), { recursive: true });
294
+ await writeFile(join(DIST_DIR, "docs", "index.html"), indexHtml);
295
+ } catch (err) {
296
+ const msg = err instanceof Error ? err.message : String(err);
297
+ errors.push(`index.mdx: ${msg}`);
298
+ console.error(`\n\u274C Failed to build index: ${msg}\n`);
299
+ }
332
300
 
333
301
  const landingPage = React.createElement(IndexPage);
334
302
  const landingHtml = htmlShell(
@@ -358,12 +326,12 @@ async function build() {
358
326
  logger.routes();
359
327
  console.log("");
360
328
 
329
+ await writeCache(cache);
330
+
361
331
  if (errors.length > 0) {
362
332
  console.error(`\n\u274C Build completed with ${errors.length} error(s)\n`);
363
333
  process.exit(1);
364
334
  }
365
-
366
- await writeCache(cache);
367
335
  }
368
336
 
369
337
  initSentry()
@@ -15,43 +15,55 @@ function safeParseTocs(raw: string | undefined): TocItem[] {
15
15
  }
16
16
  }
17
17
 
18
- function mountIslands() {
19
- const sidebarEl = document.getElementById("sidebar-island");
20
- if (sidebarEl) {
21
- const tocs: TocItem[] = safeParseTocs(sidebarEl.dataset.tocs);
22
- const title = sidebarEl.dataset.title || "";
23
- const repoUrl = sidebarEl.dataset.repo || "";
24
- const el = React.createElement(Sidebar, { tocs, title, repoUrl });
25
- if (sidebarEl.childElementCount > 0) {
26
- hydrateRoot(sidebarEl, el);
27
- } else {
28
- createRoot(sidebarEl).render(el);
29
- }
18
+ function mountIsland(
19
+ id: string,
20
+ render: (el: HTMLElement) => React.ReactElement,
21
+ forceCreate = false
22
+ ) {
23
+ const el = document.getElementById(id);
24
+ if (!el) return;
25
+ const node = render(el);
26
+ if (!forceCreate && el.childElementCount > 0) {
27
+ hydrateRoot(el, node);
28
+ } else {
29
+ el.innerHTML = "";
30
+ createRoot(el).render(node);
30
31
  }
32
+ }
31
33
 
32
- const mobileBarEl = document.getElementById("mobile-bar-island");
33
- if (mobileBarEl) {
34
- const tocs: TocItem[] = safeParseTocs(mobileBarEl.dataset.tocs);
35
- const title = mobileBarEl.dataset.title || "";
36
- const repoUrl = mobileBarEl.dataset.repo || "";
37
- createRoot(mobileBarEl).render(React.createElement(MobileBar, { tocs, title, repoUrl }));
38
- }
34
+ function mountIslands() {
35
+ mountIsland(
36
+ "sidebar-island",
37
+ (el) => {
38
+ const tocs: TocItem[] = safeParseTocs(el.dataset.tocs);
39
+ return React.createElement(Sidebar, {
40
+ tocs,
41
+ title: el.dataset.title || "",
42
+ repoUrl: el.dataset.repo || "",
43
+ });
44
+ },
45
+ true
46
+ );
39
47
 
40
- const tocEl = document.getElementById("toc-island");
41
- if (tocEl) {
42
- const tocs: TocItem[] = safeParseTocs(tocEl.dataset.tocs);
43
- const el = React.createElement(Toc, { tocs });
44
- if (tocEl.childElementCount > 0) {
45
- hydrateRoot(tocEl, el);
46
- } else {
47
- createRoot(tocEl).render(el);
48
- }
49
- }
48
+ mountIsland(
49
+ "mobile-bar-island",
50
+ (el) => {
51
+ const tocs: TocItem[] = safeParseTocs(el.dataset.tocs);
52
+ return React.createElement(MobileBar, {
53
+ tocs,
54
+ title: el.dataset.title || "",
55
+ repoUrl: el.dataset.repo || "",
56
+ });
57
+ },
58
+ true
59
+ );
50
60
 
51
- const themeEl = document.getElementById("theme-island");
52
- if (themeEl) {
53
- createRoot(themeEl).render(React.createElement(ThemeToggle));
54
- }
61
+ mountIsland("toc-island", (el) => {
62
+ const tocs: TocItem[] = safeParseTocs(el.dataset.tocs);
63
+ return React.createElement(Toc, { tocs });
64
+ });
65
+
66
+ mountIsland("theme-island", () => React.createElement(ThemeToggle));
55
67
 
56
68
  hydrateMdxContent();
57
69
  }
@@ -64,7 +76,9 @@ function hydrateMdxContent() {
64
76
  try {
65
77
  const compiledSource = JSON.parse(sourceEl.textContent || "");
66
78
  const components = createMdxComponents();
67
- createRoot(island).render(React.createElement(MDXRemote, { compiledSource, components }));
79
+ createRoot(island).render(
80
+ React.createElement(MDXRemote, { compiledSource, scope: {}, frontmatter: {}, components })
81
+ );
68
82
  } catch (e) {
69
83
  console.error("[mdx-hydrate]", e);
70
84
  }
@@ -62,7 +62,7 @@ function scanDir(dirPath: string, docsRoot: string): FileNode[] {
62
62
  }
63
63
 
64
64
  for (const entry of entries) {
65
- if (entry.startsWith(".")) continue;
65
+ if (entry.startsWith(".") || entry === "assets") continue;
66
66
 
67
67
  const absPath = join(dirPath, entry);
68
68
 
@@ -107,7 +107,7 @@ function fileNodesToRoutes(nodes: FileNode[], parentHref = ""): DocuRoute[] {
107
107
  if (isIndexFile) continue;
108
108
 
109
109
  const segment = node.relPath.split("/").pop()!;
110
- const href = parentHref ? `/${segment}` : `/${node.relPath}`;
110
+ const href = `/${segment}`;
111
111
 
112
112
  routes.push({
113
113
  title: toTitleCase(baseName),
@@ -116,10 +116,9 @@ function fileNodesToRoutes(nodes: FileNode[], parentHref = ""): DocuRoute[] {
116
116
  } else {
117
117
  const dirTitle = toTitleCase(node.name);
118
118
  const segment = node.relPath.split("/").pop()!;
119
- const dirHref = parentHref ? `/${segment}` : `/${node.relPath}`;
119
+ const dirHref = `/${segment}`;
120
120
 
121
- const fullDirHref = parentHref ? `${parentHref}/${segment}` : `/${node.relPath}`;
122
- const children = fileNodesToRoutes(node.children || [], fullDirHref);
121
+ const children = fileNodesToRoutes(node.children || [], dirHref);
123
122
 
124
123
  if (children.length === 0) continue;
125
124
 
@@ -0,0 +1,31 @@
1
+ export interface HtmlShellOptions {
2
+ title: string;
3
+ description: string;
4
+ body: string;
5
+ favicon: string;
6
+ css: string;
7
+ js: string;
8
+ nonce?: string;
9
+ extraScripts?: string;
10
+ }
11
+
12
+ export function htmlShell(opts: HtmlShellOptions): string {
13
+ const { title, description, body, favicon, css, js, nonce, extraScripts } = opts;
14
+ const nonceAttr = nonce ? ` nonce="${Bun.escapeHTML(nonce)}"` : "";
15
+ return `<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>${Bun.escapeHTML(title)}</title>
21
+ <meta name="description" content="${Bun.escapeHTML(description)}">
22
+ <link rel="icon" type="image/x-icon" href="${Bun.escapeHTML(favicon)}">
23
+ <link rel="stylesheet" href="/assets/${Bun.escapeHTML(css)}">
24
+ <script${nonceAttr}>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}</script>
25
+ </head>
26
+ <body>
27
+ <div id="root">${body}</div>
28
+ <script${nonceAttr} src="/assets/${Bun.escapeHTML(js)}"></script>${extraScripts ? `\n ${extraScripts}` : ""}
29
+ </body>
30
+ </html>`;
31
+ }
@@ -70,7 +70,10 @@ export async function buildClientBundle(): Promise<{ js: string; css: string }>
70
70
  throw new Error("Client bundle failed");
71
71
  }
72
72
 
73
- const jsFile = result.outputs[0]?.path.split("/").pop() || "client.js";
73
+ if (!result.outputs[0]) {
74
+ throw new Error("Client bundle produced no output files");
75
+ }
76
+ const jsFile = result.outputs[0].path.split("/").pop()!;
74
77
  const tmpCss = join(ASSETS_DIR, "_tmp.css");
75
78
  const proc = Bun.spawn(
76
79
  [
@@ -1,5 +1,6 @@
1
1
  import { resolve, join } from "node:path";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import type { DocuConfig } from "./types";
3
4
 
4
5
  /**
5
6
  * FRAMEWORK_ROOT: Where the package code lives (.docu/components, .docu/pages, .docu/styles, .docu/lib)
@@ -26,9 +27,9 @@ export const DOCS_ASSETS_DIR = join(PROJECT_ROOT, "docs/assets");
26
27
  export const DOCU_CONFIG_PATH = join(PROJECT_ROOT, "docu.json");
27
28
 
28
29
  // Config singleton
29
- let _config: Record<string, unknown> | null = null;
30
+ let _config: DocuConfig | null = null;
30
31
 
31
- export function loadDocuConfig(): Record<string, unknown> {
32
+ export function loadDocuConfig(): DocuConfig {
32
33
  if (_config) return _config;
33
34
  if (!existsSync(DOCU_CONFIG_PATH)) {
34
35
  throw new Error(`docu.json not found at ${DOCU_CONFIG_PATH}`);
@@ -200,7 +200,11 @@ async function scanMdxFiles(dir: string, base = ""): Promise<{ path: string; abs
200
200
  files.push(...(await scanMdxFiles(fullPath, relPath)));
201
201
  } else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
202
202
  if (entry.name === "index.mdx" && !base) continue;
203
- files.push({ path: relPath.replace(/\.(mdx|md)$/, ""), absPath: fullPath });
203
+ let path = relPath.replace(/\.(mdx|md)$/, "");
204
+ if (/\/index$/.test(path)) {
205
+ path = path.replace(/\/index$/, "");
206
+ }
207
+ files.push({ path, absPath: fullPath });
204
208
  }
205
209
  }
206
210
  return files;
@@ -212,13 +216,13 @@ export async function generateSearchIndex(docsDir?: string, outputDir?: string):
212
216
  await mkdir(dist, { recursive: true });
213
217
 
214
218
  const mdxFiles = await scanMdxFiles(docs);
215
- const allRecords: SearchRecord[] = [];
216
-
217
- for (const file of mdxFiles) {
218
- const raw = await readFile(file.absPath, "utf-8");
219
- const records = extractRecords(file.path, raw);
220
- allRecords.push(...records);
221
- }
219
+ const results = await Promise.all(
220
+ mdxFiles.map(async (file) => {
221
+ const raw = await readFile(file.absPath, "utf-8");
222
+ return extractRecords(file.path, raw);
223
+ })
224
+ );
225
+ const allRecords = results.flat();
222
226
 
223
227
  await writeFile(join(dist, "search-index.json"), JSON.stringify(allRecords));
224
228
  return allRecords.length;
@@ -24,6 +24,7 @@ import { generateSearchIndex } from "./search-indexer";
24
24
  import { logger } from "./logger";
25
25
  import { initSentry, captureException } from "./sentry";
26
26
  import { SECURITY_HEADERS, generateNonce, htmlResponse } from "./security";
27
+ import { htmlShell as createHtmlShell } from "./html";
27
28
 
28
29
  const docuConfig = loadDocuConfig();
29
30
 
@@ -58,7 +59,7 @@ try {
58
59
  const hmrClients = new Set<ReadableStreamDefaultController>();
59
60
 
60
61
  function hmrScript(nonce: string): string {
61
- return `<script nonce="${nonce}">
62
+ return `<script nonce="${Bun.escapeHTML(nonce)}">
62
63
  (function(){
63
64
  const es = new EventSource("/__hmr");
64
65
  es.onmessage = function(e) {
@@ -93,27 +94,6 @@ process.on("SIGTERM", () => {
93
94
  process.exit(0);
94
95
  });
95
96
 
96
- function htmlShell(title: string, description: string, body: string, nonce: string): string {
97
- const favicon = docuConfig.meta?.favicon || "/favicon.ico";
98
- return `<!DOCTYPE html>
99
- <html lang="en">
100
- <head>
101
- <meta charset="UTF-8">
102
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
103
- <title>${Bun.escapeHTML(title)}</title>
104
- <meta name="description" content="${Bun.escapeHTML(description)}">
105
- <link rel="icon" type="image/x-icon" href="${Bun.escapeHTML(favicon)}">
106
- <link rel="stylesheet" href="/assets/${assetManifest.css}">
107
- <script nonce="${nonce}">try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}</script>
108
- </head>
109
- <body>
110
- <div id="root">${body}</div>
111
- <script nonce="${nonce}" src="/assets/${assetManifest.js}"></script>
112
- ${hmrScript(nonce)}
113
- </body>
114
- </html>`;
115
- }
116
-
117
97
  function createHtmlResponse(
118
98
  title: string,
119
99
  description: string,
@@ -121,7 +101,17 @@ function createHtmlResponse(
121
101
  status = 200
122
102
  ): Response {
123
103
  const nonce = generateNonce();
124
- const html = htmlShell(title, description, body, nonce);
104
+ const favicon = docuConfig.meta?.favicon || "/favicon.ico";
105
+ const html = createHtmlShell({
106
+ title,
107
+ description,
108
+ body,
109
+ favicon,
110
+ css: assetManifest.css,
111
+ js: assetManifest.js,
112
+ nonce,
113
+ extraScripts: hmrScript(nonce),
114
+ });
125
115
  return htmlResponse(html, nonce, status);
126
116
  }
127
117
 
@@ -165,6 +155,8 @@ async function getDocsForSlug(slug: string) {
165
155
  });
166
156
  const content = React.createElement(MDXRemote, {
167
157
  compiledSource: serialized.compiledSource,
158
+ scope: {},
159
+ frontmatter: {},
168
160
  components,
169
161
  });
170
162
 
package/bin/cli.js CHANGED
@@ -2,7 +2,9 @@
2
2
  /* global process, console */
3
3
 
4
4
  import { resolve, join } from "node:path";
5
- import { cpSync, existsSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { cpSync, existsSync, readFileSync, writeFileSync, renameSync } from "node:fs";
6
+
7
+ const __dirname = import.meta.dirname;
6
8
 
7
9
  const COMMAND_MAP = {
8
10
  dev: "server.ts",
@@ -16,7 +18,7 @@ const command = process.argv[2];
16
18
 
17
19
  if (!command || command === "--help" || command === "-h") {
18
20
  console.log(`
19
- @docubook/flame — A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.,
21
+ @docubook/flame — A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.
20
22
 
21
23
  Usage: flame <command>
22
24
 
@@ -35,26 +37,40 @@ if (!command || command === "--help" || command === "-h") {
35
37
  }
36
38
 
37
39
  if (command === "init") {
38
- const scaffoldDir = resolve(import.meta.dirname, "../template");
39
- const targetDir = process.cwd();
40
+ try {
41
+ const scaffoldDir = resolve(__dirname, "../template");
42
+ const targetDir = process.cwd();
40
43
 
41
- if (existsSync(join(targetDir, "docu.json"))) {
42
- console.error("docu.json already exists. Aborting to avoid overwriting.");
43
- process.exit(1);
44
- }
44
+ if (!existsSync(scaffoldDir)) {
45
+ console.error("Template directory not found. Package may be corrupted.");
46
+ process.exit(1);
47
+ }
45
48
 
46
- cpSync(scaffoldDir, targetDir, { recursive: true });
49
+ if (existsSync(join(targetDir, "docu.json"))) {
50
+ console.error("docu.json already exists. Aborting to avoid overwriting.");
51
+ process.exit(1);
52
+ }
47
53
 
48
- const pkgPath = join(targetDir, "package.json");
49
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
50
- const flamePkg = JSON.parse(
51
- readFileSync(resolve(import.meta.dirname, "../package.json"), "utf-8")
52
- );
53
- pkg.dependencies["@docubook/flame"] = `^${flamePkg.version}`;
54
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
54
+ cpSync(scaffoldDir, targetDir, { recursive: true });
55
55
 
56
- console.log(`\n ✓ Project scaffolded!\n\n Next steps:\n bun install\n bun run dev\n`);
57
- process.exit(0);
56
+ const gitignoreSrc = join(targetDir, "gitignore");
57
+ if (existsSync(gitignoreSrc)) {
58
+ renameSync(gitignoreSrc, join(targetDir, ".gitignore"));
59
+ }
60
+
61
+ const pkgPath = join(targetDir, "package.json");
62
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
63
+ const flamePkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf-8"));
64
+ pkg.dependencies = pkg.dependencies || {};
65
+ pkg.dependencies["@docubook/flame"] = `^${flamePkg.version}`;
66
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
67
+
68
+ console.log(`\n ✓ Project scaffolded!\n\n Next steps:\n bun install\n bun run dev\n`);
69
+ process.exit(0);
70
+ } catch (err) {
71
+ console.error(`Failed to scaffold project: ${err.message}`);
72
+ process.exit(1);
73
+ }
58
74
  }
59
75
 
60
76
  if (!(command in COMMAND_MAP)) {
@@ -62,5 +78,5 @@ if (!(command in COMMAND_MAP)) {
62
78
  process.exit(1);
63
79
  }
64
80
 
65
- const scriptPath = resolve(import.meta.dirname, "../.docu/lib", COMMAND_MAP[command]);
81
+ const scriptPath = resolve(__dirname, "../.docu/lib", COMMAND_MAP[command]);
66
82
  await import(scriptPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docubook/flame",
3
- "version": "1.0.0-beta.40",
3
+ "version": "1.0.0-beta.60",
4
4
  "description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ .docu/dist
3
+ .docu/build-cache.json
4
+ .env
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "my-docs",
2
+ "name": "flame",
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "scripts": {