@docubook/flame 2.0.0-beta.0 → 2.0.0-beta.2
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/components/ScrollTo.tsx +17 -11
- package/.docu/components/Toc.tsx +32 -7
- package/.docu/lib/build.deno.js +6 -9
- package/.docu/lib/build.deno.js.map +1 -0
- package/.docu/lib/build.impl-BJsi28mD.js +377 -0
- package/.docu/lib/build.impl-BJsi28mD.js.map +1 -0
- package/.docu/lib/build.impl-yVLaa1eQ.js +2 -0
- package/.docu/lib/build.node.js +6 -9
- package/.docu/lib/build.node.js.map +1 -0
- package/.docu/lib/clean.js +25 -25
- package/.docu/lib/clean.js.map +1 -0
- package/.docu/lib/deno-DmEaKggj.js +20 -0
- package/.docu/lib/deno-DmEaKggj.js.map +1 -0
- package/.docu/lib/deploy.deno.js +9 -9
- package/.docu/lib/deploy.deno.js.map +1 -0
- package/.docu/lib/deploy.node.js +8 -8
- package/.docu/lib/deploy.node.js.map +1 -0
- package/.docu/lib/deploy.shared-D3UWafa6.js +295 -0
- package/.docu/lib/deploy.shared-D3UWafa6.js.map +1 -0
- package/.docu/lib/html.shared-DSn-7_6t.js +2467 -0
- package/.docu/lib/html.shared-DSn-7_6t.js.map +1 -0
- package/.docu/lib/logger-CycvLCrQ.js +312 -0
- package/.docu/lib/logger-CycvLCrQ.js.map +1 -0
- package/.docu/lib/node-DPTySdwG.js +80 -0
- package/.docu/lib/node-DPTySdwG.js.map +1 -0
- package/.docu/lib/paths-BtOIPBQ9.js +52 -0
- package/.docu/lib/paths-BtOIPBQ9.js.map +1 -0
- package/.docu/lib/preview.deno.js +7 -11
- package/.docu/lib/preview.deno.js.map +1 -0
- package/.docu/lib/preview.impl-CXJS2tQS.js +77 -0
- package/.docu/lib/preview.impl-CXJS2tQS.js.map +1 -0
- package/.docu/lib/preview.node.js +7 -11
- package/.docu/lib/preview.node.js.map +1 -0
- package/.docu/lib/server.deno.js +7 -12
- package/.docu/lib/server.deno.js.map +1 -0
- package/.docu/lib/server.impl-CJuWimfN.js +358 -0
- package/.docu/lib/server.impl-CJuWimfN.js.map +1 -0
- package/.docu/lib/server.node.js +7 -12
- package/.docu/lib/server.node.js.map +1 -0
- package/.docu/lib/utils-DA17MyQG.js +167 -0
- package/.docu/lib/utils-DA17MyQG.js.map +1 -0
- package/.docu/node/hydrate.node.ts +116 -166
- package/bin/compile-lib.mjs +30 -28
- package/package.json +6 -6
- package/.docu/lib/build.impl-S4DS5XPH.js +0 -12
- package/.docu/lib/chunk-4IQXHHPF.js +0 -62
- package/.docu/lib/chunk-C3RCUTUE.js +0 -470
- package/.docu/lib/chunk-D3QTSBR4.js +0 -434
- package/.docu/lib/chunk-EOK6KATZ.js +0 -191
- package/.docu/lib/chunk-IVM5UM44.js +0 -301
- package/.docu/lib/chunk-JMQI3FKV.js +0 -92
- package/.docu/lib/chunk-LXJLSXQI.js +0 -2691
- package/.docu/lib/chunk-O326MYJE.js +0 -330
- package/.docu/lib/chunk-UISOJ4RW.js +0 -114
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-DA17MyQG.js","names":[],"sources":["../node/security.ts","../node/utils.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { resolve } from \"node:path\";\nimport { realpathSync } from \"node:fs\";\n\nexport const SECURITY_HEADERS: Record<string, string> = {\n \"Strict-Transport-Security\": \"max-age=63072000; includeSubDomains; preload\",\n \"X-Frame-Options\": \"DENY\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n \"Permissions-Policy\": \"camera=(), microphone=(), geolocation=()\",\n};\n\nexport function generateNonce(): string {\n return randomBytes(16).toString(\"base64\");\n}\n\nexport function cspHeader(nonce: string, allowEval = false): string {\n const scriptSrc = allowEval\n ? `script-src 'self' 'nonce-${nonce}' 'unsafe-eval'`\n : `script-src 'self' 'nonce-${nonce}'`;\n return [\n \"default-src 'self'\",\n scriptSrc,\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' https: data:\",\n \"font-src 'self' data:\",\n \"connect-src 'self' https:\",\n \"frame-src https://www.youtube-nocookie.com\",\n \"frame-ancestors 'none'\",\n ].join(\"; \");\n}\n\nexport function isPathSafe(pathname: string, baseDir: string): boolean {\n const decoded = decodeURIComponent(pathname);\n const resolved = resolve(baseDir, decoded.slice(1));\n const baseDirSlash = baseDir.endsWith(\"/\") ? baseDir : baseDir + \"/\";\n if (!(resolved === baseDir || resolved.startsWith(baseDirSlash))) {\n return false;\n }\n\n if (resolved === baseDir) return true;\n try {\n const real = realpathSync(resolved);\n const realBase = realpathSync(baseDir);\n const realBaseDirSlash = realBase.endsWith(\"/\") ? realBase : realBase + \"/\";\n return real.startsWith(realBaseDirSlash);\n } catch (err) {\n const nodeErr = err as NodeJS.ErrnoException;\n if (nodeErr.code === \"ENOENT\") {\n return true;\n }\n console.error(\n `[security] isPathSafe error for pathname=\"${pathname}\": ${nodeErr.message} (code=${nodeErr.code})`\n );\n return false;\n }\n}\n\nexport function isSlugSafe(slug: string, docsDir: string): boolean {\n const resolved = resolve(docsDir, slug);\n const docsDirSlash = docsDir.endsWith(\"/\") ? docsDir : docsDir + \"/\";\n if (!(resolved === docsDir || resolved.startsWith(docsDirSlash))) {\n return false;\n }\n\n if (resolved === docsDir) return true;\n try {\n const real = realpathSync(resolved);\n const realDocsDir = realpathSync(docsDir);\n const realDocsDirSlash = realDocsDir.endsWith(\"/\") ? realDocsDir : realDocsDir + \"/\";\n return real.startsWith(realDocsDirSlash);\n } catch (err) {\n const nodeErr = err as NodeJS.ErrnoException;\n if (nodeErr.code === \"ENOENT\") {\n return true;\n }\n console.error(\n `[security] isSlugSafe error for slug=\"${slug}\": ${nodeErr.message} (code=${nodeErr.code})`\n );\n return false;\n }\n}\n\n/** Normalize an esbuild importer path to a canonical forward-slash absolute form. */\nexport function normalizeImporterPath(importer: string): string {\n return resolve(importer).replace(/\\\\/g, \"/\");\n}\n\nexport function injectNonce(html: string, nonce: string): string {\n return html.replace(/<script\\b(?![^>]*\\bsrc\\s*=)([^>]*)>/gi, (match) => {\n if (/nonce\\s*=/i.test(match)) {\n return match.replace(/nonce=\"[^\"]*\"/i, `nonce=\"${nonce}\"`);\n }\n return match.replace(/>$/, ` nonce=\"${nonce}\">`);\n });\n}\n\nexport interface PluginResponseLike {\n status: number;\n statusText?: string;\n headers: Headers;\n body: BodyInit | null;\n}\n\n/**\n * Wrap a plugin response with security headers.\n * - Fills in SECURITY_HEADERS defaults where plugin hasn't set a value\n * - Adds Content-Security-Policy for HTML responses (with optional unsafe-eval)\n * - Preserves plugin body, status, statusText unchanged\n */\nexport function wrapPluginResponse(\n pluginResponse: PluginResponseLike,\n allowEval = false\n): Response {\n const securedHeaders = new Headers(pluginResponse.headers);\n for (const [key, value] of Object.entries(SECURITY_HEADERS)) {\n if (!securedHeaders.has(key)) {\n securedHeaders.set(key, value);\n }\n }\n let body = pluginResponse.body;\n const contentType = securedHeaders.get(\"Content-Type\") || \"\";\n if (contentType.includes(\"text/html\") && !securedHeaders.has(\"Content-Security-Policy\")) {\n const nonce = generateNonce();\n securedHeaders.set(\"Content-Security-Policy\", cspHeader(nonce, allowEval));\n if (typeof body === \"string\") {\n body = injectNonce(body, nonce);\n }\n }\n return new Response(body, {\n status: pluginResponse.status,\n statusText: pluginResponse.statusText,\n headers: securedHeaders,\n });\n}\n\nexport function htmlResponse(\n html: string,\n nonce: string,\n status = 200,\n allowEval = false\n): Response {\n return new Response(html, {\n status,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": cspHeader(nonce, allowEval),\n },\n });\n}\n","export { cn, parseDate, formatDate, formatDate2 } from \"@docubook/core\";\n\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface ScannedMdxFile {\n path: string;\n absPath: string;\n mtime: number;\n}\n\n/**\n * Scan a directory recursively for MDX/MD files.\n * Skips \"assets\" directories, hidden directories (dot-prefixed), and root-level index.mdx.\n * Shared between build.ts and search-indexer.ts.\n */\nexport async function scanMdxFiles(dir: string, baseDir = \"\"): Promise<ScannedMdxFile[]> {\n const files: ScannedMdxFile[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n const relativePath = baseDir ? `${baseDir}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n if (entry.name === \"assets\" || entry.name.startsWith(\".\")) continue;\n files.push(...(await scanMdxFiles(fullPath, relativePath)));\n } else if (entry.name.endsWith(\".mdx\") || entry.name.endsWith(\".md\")) {\n if (entry.name === \"index.mdx\" && !baseDir) continue;\n const stats = await stat(fullPath);\n let path = relativePath.replace(/\\.(mdx|md)$/, \"\");\n\n if (/\\/index$/.test(path)) {\n path = path.replace(/\\/index$/, \"\");\n }\n files.push({ path, absPath: fullPath, mtime: stats.mtimeMs });\n }\n }\n\n return files;\n}\n\nexport function isExternalUrl(url: string): boolean {\n return /^(https?:\\/\\/|\\/\\/)/.test(url);\n}\n\n/** Suffix an internal docs link with `.html` to match the flat static build output. */\nexport function docsHtmlHref(path: string): string {\n return `${path}.html`;\n}\n\n/** Map a `/docs{.html,/*.html}` request back to its extensionless route (dev server).\n * Handles both `/docs/*.html` (pages) and `/docs.html` (edge case). */\nexport function stripDocsHtmlSuffix(pathname: string): string {\n if (pathname === \"/docs.html\") return \"/docs\";\n if (pathname.startsWith(\"/docs/\") && pathname.endsWith(\".html\"))\n return pathname.slice(0, -\".html\".length);\n return pathname;\n}\n\nexport function getPath(url: string): string {\n try {\n return new URL(url).pathname;\n } catch (err) {\n console.error(\"Failed to parse URL\", url, err);\n return url;\n }\n}\n\n/** Get git last modified date for a file */\nexport async function getGitLastModified(filePath: string): Promise<string | null> {\n try {\n const cleanPath = filePath.replace(/^\\//, \"\");\n if (\n !cleanPath ||\n !/^[a-zA-Z0-9\\-_/.\\s]+$/.test(cleanPath) ||\n /(^|\\/)\\.\\.($|\\/)/.test(cleanPath)\n )\n return null;\n const proc = Bun.spawn([\"git\", \"log\", \"-1\", \"--format=%cI\", \"--\", cleanPath], {\n stderr: \"ignore\",\n });\n const text = await new Response(proc.stdout).text();\n const date = text.trim();\n return date || null;\n } catch (err) {\n console.error(\"Failed to get git last modified for\", filePath, err);\n return null;\n }\n}\n\n/** Batch git last modified dates for multiple files in a single spawn */\nexport async function getGitLastModifiedBatch(filePaths: string[]): Promise<Map<string, string>> {\n const result = new Map<string, string>();\n if (filePaths.length === 0) return result;\n\n // Filter and validate paths — same guard as getGitLastModified\n const safePaths: string[] = [];\n for (const fp of filePaths) {\n const cleanPath = fp.replace(/^\\//, \"\");\n if (\n !cleanPath ||\n !/^[a-zA-Z0-9\\-_/.\\s]+$/.test(cleanPath) ||\n /(^|\\/)\\.\\.($|\\/)/.test(cleanPath)\n ) {\n console.warn(`[utils] getGitLastModifiedBatch: skipping invalid path \"${fp}\"`);\n continue;\n }\n safePaths.push(cleanPath);\n }\n\n if (safePaths.length === 0) return result;\n\n try {\n const proc = Bun.spawn(\n [\"git\", \"log\", \"--format=%cI\", \"--name-only\", \"--diff-filter=ACMR\", ...safePaths],\n { stderr: \"ignore\" }\n );\n const text = await new Response(proc.stdout).text();\n let currentDate = \"\";\n\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n if (/^\\d{4}-\\d{2}-\\d{2}T/.test(trimmed)) {\n currentDate = trimmed;\n } else if (currentDate && !result.has(trimmed)) {\n result.set(trimmed, currentDate);\n }\n }\n } catch (err) {\n console.error(\"Failed to get git last modified batch for\", filePaths, err);\n }\n\n return result;\n}\n\nconst MIME_TYPES: Record<string, string> = {\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n json: \"application/json\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n svg: \"image/svg+xml\",\n ico: \"image/x-icon\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n};\n\nexport function getContentType(pathname: string): string {\n const ext = pathname.split(\".\").pop()?.toLowerCase();\n return MIME_TYPES[ext || \"\"] || \"application/octet-stream\";\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client bundle builder for Node/Deno runtimes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Uses Vite with Rolldown plugins to produce a browser-ready client
|
|
5
5
|
* bundle (JS + CSS) from the same components Bun.build handles natively.
|
|
6
6
|
* Theme helpers (getThemeConfig, buildThemeCss, computeInlineThemeCss)
|
|
7
7
|
* are re-exported from `hydrate.ts` — that module's `buildClientBundle`
|
|
@@ -15,6 +15,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
16
|
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
17
17
|
import { createHash } from "node:crypto";
|
|
18
|
+
import { build as viteBuild } from "vite";
|
|
18
19
|
import {
|
|
19
20
|
ASSETS_DIR,
|
|
20
21
|
FRAMEWORK_ROOT,
|
|
@@ -203,180 +204,129 @@ export async function buildClientBundle(
|
|
|
203
204
|
await cleanOldBundles(new Set([`client-${twKey}.css`]));
|
|
204
205
|
|
|
205
206
|
const nodeEnv = process.env.NODE_ENV || "development";
|
|
206
|
-
const esbuild = await import("esbuild");
|
|
207
|
-
const { build } = esbuild;
|
|
208
207
|
|
|
209
208
|
const entryPath = join(LIB_DIR, "client.ts");
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
minify: nodeEnv === "production",
|
|
221
|
-
define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
|
|
222
|
-
jsx: "automatic",
|
|
223
|
-
jsxDev: nodeEnv !== "production",
|
|
224
|
-
metafile: true,
|
|
225
|
-
logLevel: "silent",
|
|
226
|
-
plugins: [
|
|
227
|
-
{
|
|
228
|
-
name: "node-builtin-stub",
|
|
229
|
-
setup(build) {
|
|
230
|
-
build.onResolve({ filter: NODE_BUILTINS_RE }, (args) => ({
|
|
231
|
-
path: args.path,
|
|
232
|
-
namespace: "node-stub",
|
|
233
|
-
}));
|
|
234
|
-
build.onLoad({ filter: /.*/, namespace: "node-stub" }, () => ({
|
|
235
|
-
contents: "module.exports = {};",
|
|
236
|
-
loader: "js",
|
|
237
|
-
}));
|
|
238
|
-
},
|
|
209
|
+
const bundle = await viteBuild({
|
|
210
|
+
configFile: false,
|
|
211
|
+
publicDir: false,
|
|
212
|
+
define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
|
|
213
|
+
plugins: [
|
|
214
|
+
{
|
|
215
|
+
name: "node-builtin-stub",
|
|
216
|
+
resolveId(id) {
|
|
217
|
+
if (NODE_BUILTINS_RE.test(id)) return `\0node-stub:${id}`;
|
|
218
|
+
return null;
|
|
239
219
|
},
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
build.onResolve({ filter: /^lucide-react$/ }, (args) => {
|
|
244
|
-
// Imports from within our virtual module go to the real package.
|
|
245
|
-
if (args.namespace === "lucide-virt") {
|
|
246
|
-
return { path: getLucideRealEntry(), namespace: "file" };
|
|
247
|
-
}
|
|
248
|
-
// markdown Icon.tsx uses namespace import for arbitrary
|
|
249
|
-
// user-provided icon names in MDX — keep full barrel there.
|
|
250
|
-
if (args.importer) {
|
|
251
|
-
const normalized = normalizeImporterPath(args.importer);
|
|
252
|
-
if (normalized.includes("/markdown/dist/")) {
|
|
253
|
-
return { path: getLucideRealEntry(), namespace: "file" };
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// All other files get tree-shaken via the virtual module.
|
|
257
|
-
// Lucide.tsx renders only config-defined icons, which are
|
|
258
|
-
// collected by extractConfigIcons() + collectAllLucideIcons().
|
|
259
|
-
return { path: args.path, namespace: "lucide-virt" };
|
|
260
|
-
});
|
|
261
|
-
build.onLoad({ filter: /.*/, namespace: "lucide-virt" }, () => {
|
|
262
|
-
const scanned = collectAllLucideIcons();
|
|
263
|
-
const configured = extractConfigIcons(loadDocuConfig());
|
|
264
|
-
const allIcons = [...new Set([...scanned, ...configured])];
|
|
265
|
-
return {
|
|
266
|
-
contents: `export { ${allIcons.join(", ")} } from "lucide-react";`,
|
|
267
|
-
loader: "js",
|
|
268
|
-
};
|
|
269
|
-
});
|
|
270
|
-
},
|
|
220
|
+
load(id) {
|
|
221
|
+
if (!id.startsWith("\0node-stub:")) return null;
|
|
222
|
+
return "export default {}; export {};";
|
|
271
223
|
},
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const config = loadDocuConfig();
|
|
283
|
-
const resolved = {
|
|
284
|
-
...config,
|
|
285
|
-
routes: resolveRoutes(config.routes as DocuRoute[] | undefined),
|
|
286
|
-
};
|
|
287
|
-
return {
|
|
288
|
-
contents: [
|
|
289
|
-
`import type { DocuRoute, DocuConfig } from "./types";`,
|
|
290
|
-
`const docuConfig = ${JSON.stringify(resolved)};`,
|
|
291
|
-
`export const routes = docuConfig.routes || [];`,
|
|
292
|
-
`export const config = docuConfig;`,
|
|
293
|
-
].join("\n"),
|
|
294
|
-
loader: "ts",
|
|
295
|
-
};
|
|
296
|
-
});
|
|
297
|
-
},
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "lucide-optimize",
|
|
227
|
+
resolveId(id, importer) {
|
|
228
|
+
if (id !== "lucide-react") return null;
|
|
229
|
+
if (importer) {
|
|
230
|
+
const normalized = normalizeImporterPath(importer);
|
|
231
|
+
if (normalized.includes("/markdown/dist/")) return null;
|
|
232
|
+
}
|
|
233
|
+
return "\0lucide-virt";
|
|
298
234
|
},
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// Compiled MDX (program format) imports the JSX runtime and the
|
|
306
|
-
// MDX provider. Virtual modules have no real directory, so esbuild
|
|
307
|
-
// cannot resolve bare specifiers from them — map them to the
|
|
308
|
-
// installed real paths explicitly (same pattern as lucide-optimize).
|
|
309
|
-
const require = createRequire(import.meta.url);
|
|
310
|
-
const mdxModuleDeps: Record<string, string> = {
|
|
311
|
-
"react/jsx-runtime": require.resolve("react/jsx-runtime"),
|
|
312
|
-
"react/jsx-dev-runtime": require.resolve("react/jsx-dev-runtime"),
|
|
313
|
-
"@mdx-js/react": require.resolve("@mdx-js/react"),
|
|
314
|
-
};
|
|
315
|
-
build.onResolve(
|
|
316
|
-
{ filter: /^(react\/jsx-runtime|react\/jsx-dev-runtime|@mdx-js\/react)$/ },
|
|
317
|
-
(args) => {
|
|
318
|
-
if (args.namespace !== "mdx-module") return;
|
|
319
|
-
return { path: mdxModuleDeps[args.path], namespace: "file" };
|
|
320
|
-
}
|
|
321
|
-
);
|
|
322
|
-
build.onResolve({ filter: /^mdx-module:/ }, (args) => ({
|
|
323
|
-
path: args.path,
|
|
324
|
-
namespace: "mdx-module",
|
|
325
|
-
}));
|
|
326
|
-
build.onLoad({ filter: /.*/, namespace: "mdx-module" }, (args) => {
|
|
327
|
-
const slug = args.path.slice("mdx-module:".length);
|
|
328
|
-
const contents = mdxSources[slug];
|
|
329
|
-
if (contents == null) {
|
|
330
|
-
return {
|
|
331
|
-
errors: [{ text: `unknown mdx module: ${slug}` }],
|
|
332
|
-
contents: "",
|
|
333
|
-
loader: "js",
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
return { contents, loader: "js" };
|
|
337
|
-
});
|
|
338
|
-
build.onResolve({ filter: /mdx-manifest$/ }, (args) => ({
|
|
339
|
-
path: args.path,
|
|
340
|
-
namespace: "mdx-manifest",
|
|
341
|
-
}));
|
|
342
|
-
build.onLoad({ filter: /.*/, namespace: "mdx-manifest" }, () => {
|
|
343
|
-
// Sort keys: the prePass fills mdxSources via Promise.all, so
|
|
344
|
-
// insertion order = resolution order (non-deterministic across
|
|
345
|
-
// processes). Stable key order keeps the bundle hash stable so
|
|
346
|
-
// the build cache (`assetsChanged`) actually hits.
|
|
347
|
-
const slugs = Object.keys(mdxSources).sort();
|
|
348
|
-
const imports = slugs
|
|
349
|
-
.map((slug, i) => {
|
|
350
|
-
const key = slug.replace(/["\\]/g, "");
|
|
351
|
-
return `import * as _mdx${i} from "mdx-module:${key}";`;
|
|
352
|
-
})
|
|
353
|
-
.join("\n");
|
|
354
|
-
const map = slugs.map((slug, i) => `${JSON.stringify(slug)}: _mdx${i}`).join(", ");
|
|
355
|
-
return {
|
|
356
|
-
contents: `${imports}\nexport const mdxModules = { ${map} };\n`,
|
|
357
|
-
loader: "js",
|
|
358
|
-
};
|
|
359
|
-
});
|
|
360
|
-
},
|
|
235
|
+
load(id) {
|
|
236
|
+
if (id !== "\0lucide-virt") return null;
|
|
237
|
+
const scanned = collectAllLucideIcons();
|
|
238
|
+
const configured = extractConfigIcons(loadDocuConfig());
|
|
239
|
+
const allIcons = [...new Set([...scanned, ...configured])];
|
|
240
|
+
return `export { ${allIcons.join(", ")} } from ${JSON.stringify(getLucideRealEntry())};`;
|
|
361
241
|
},
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "docu-config",
|
|
245
|
+
resolveId(id) {
|
|
246
|
+
if (!/client-routes$/.test(id)) return null;
|
|
247
|
+
return "\0client-routes";
|
|
248
|
+
},
|
|
249
|
+
load(id) {
|
|
250
|
+
if (id !== "\0client-routes") return null;
|
|
251
|
+
const config = loadDocuConfig();
|
|
252
|
+
const resolved = {
|
|
253
|
+
...config,
|
|
254
|
+
routes: resolveRoutes(config.routes as DocuRoute[] | undefined),
|
|
255
|
+
};
|
|
256
|
+
return [
|
|
257
|
+
`const docuConfig = ${JSON.stringify(resolved)};`,
|
|
258
|
+
`export const routes = docuConfig.routes || [];`,
|
|
259
|
+
`export const config = docuConfig;`,
|
|
260
|
+
].join("\n");
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "mdx-hydrate",
|
|
265
|
+
resolveId(id) {
|
|
266
|
+
if (/mdx-manifest$/.test(id)) return "\0mdx-manifest";
|
|
267
|
+
if (id.startsWith("mdx-module:")) return `\0${id}`;
|
|
268
|
+
return null;
|
|
269
|
+
},
|
|
270
|
+
load(id) {
|
|
271
|
+
if (id === "\0mdx-manifest") {
|
|
272
|
+
const slugs = Object.keys(mdxSources).sort();
|
|
273
|
+
const imports = slugs
|
|
274
|
+
.map((slug, i) => {
|
|
275
|
+
const key = slug.replace(/["\\]/g, "");
|
|
276
|
+
return `import * as _mdx${i} from ${JSON.stringify(`mdx-module:${key}`)};`;
|
|
277
|
+
})
|
|
278
|
+
.join("\n");
|
|
279
|
+
const map = slugs.map((slug, i) => `${JSON.stringify(slug)}: _mdx${i}`).join(", ");
|
|
280
|
+
return `${imports}\nexport const mdxModules = { ${map} };\n`;
|
|
281
|
+
}
|
|
282
|
+
if (!id.startsWith("\0mdx-module:")) return null;
|
|
283
|
+
const slug = id.slice("\0mdx-module:".length);
|
|
284
|
+
const contents = mdxSources[slug];
|
|
285
|
+
if (contents == null) {
|
|
286
|
+
throw new Error(`unknown mdx module: ${slug}`);
|
|
287
|
+
}
|
|
288
|
+
return contents;
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
build: {
|
|
293
|
+
outDir: ASSETS_DIR,
|
|
294
|
+
emptyOutDir: false,
|
|
295
|
+
sourcemap: true,
|
|
296
|
+
minify: nodeEnv === "production",
|
|
297
|
+
target: "es2020",
|
|
298
|
+
rollupOptions: {
|
|
299
|
+
input: entryPath,
|
|
300
|
+
output: {
|
|
301
|
+
format: "es",
|
|
302
|
+
entryFileNames: "client-[hash].js",
|
|
303
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
304
|
+
assetFileNames: "assets/[name]-[hash][extname]",
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
375
308
|
});
|
|
376
|
-
|
|
309
|
+
|
|
310
|
+
const outputs = Array.isArray(bundle) ? bundle : [bundle];
|
|
311
|
+
let jsFile: string | undefined;
|
|
312
|
+
for (const item of outputs) {
|
|
313
|
+
if (!("output" in item)) continue;
|
|
314
|
+
for (const output of item.output) {
|
|
315
|
+
if (
|
|
316
|
+
output.type === "chunk" &&
|
|
317
|
+
output.isEntry &&
|
|
318
|
+
output.facadeModuleId &&
|
|
319
|
+
resolve(output.facadeModuleId) === entryPath
|
|
320
|
+
) {
|
|
321
|
+
jsFile = basename(output.fileName);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (jsFile) break;
|
|
326
|
+
}
|
|
327
|
+
if (!jsFile) {
|
|
377
328
|
throw new Error("Client bundle produced no output files");
|
|
378
329
|
}
|
|
379
|
-
const jsFile = basename(jsOutput);
|
|
380
330
|
|
|
381
331
|
const { file: cssFile } = await buildTailwindCss(twKey);
|
|
382
332
|
|
package/bin/compile-lib.mjs
CHANGED
|
@@ -7,37 +7,39 @@
|
|
|
7
7
|
* The CLI routes non-Bun runtimes here (see `bin/cli.js`).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import { build, stop } from "esbuild";
|
|
10
|
+
import { build } from "vite";
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
"server.node
|
|
15
|
-
"server.deno
|
|
16
|
-
"build.node
|
|
17
|
-
"build.deno
|
|
18
|
-
"preview.node
|
|
19
|
-
"preview.deno
|
|
20
|
-
"deploy.node
|
|
21
|
-
"deploy.deno
|
|
22
|
-
"clean
|
|
12
|
+
const entries = [
|
|
13
|
+
"server.node",
|
|
14
|
+
"server.deno",
|
|
15
|
+
"build.node",
|
|
16
|
+
"build.deno",
|
|
17
|
+
"preview.node",
|
|
18
|
+
"preview.deno",
|
|
19
|
+
"deploy.node",
|
|
20
|
+
"deploy.deno",
|
|
21
|
+
"clean",
|
|
23
22
|
];
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
const entry = Object.fromEntries(entries.map((name) => [name, `.docu/node/${name}.ts`]));
|
|
26
25
|
|
|
27
26
|
await build({
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
build: {
|
|
28
|
+
outDir: ".docu/lib",
|
|
29
|
+
emptyOutDir: true,
|
|
30
|
+
ssr: true,
|
|
31
|
+
target: "node20",
|
|
32
|
+
minify: false,
|
|
33
|
+
sourcemap: true,
|
|
34
|
+
lib: {
|
|
35
|
+
entry,
|
|
36
|
+
formats: ["es"],
|
|
37
|
+
fileName: (_format, entryName) => `${entryName}.js`,
|
|
38
|
+
},
|
|
39
|
+
rollupOptions: {
|
|
40
|
+
// Keep npm and runtime dependencies resolvable by the target runtime.
|
|
41
|
+
// Relative imports are bundled from the TypeScript source tree.
|
|
42
|
+
external: (id) => !id.startsWith(".") && !id.startsWith("/") && !id.startsWith("\0"),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
38
45
|
});
|
|
39
|
-
|
|
40
|
-
// esbuild's service process keeps Deno's event loop alive after one-shot
|
|
41
|
-
// build() calls — stop it explicitly so `flame` can invoke this script
|
|
42
|
-
// under Deno. Harmless under Node.
|
|
43
|
-
await stop();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docubook/flame",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.2",
|
|
4
4
|
"description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"@tailwindcss/typography": "0.5.16",
|
|
51
51
|
"bun-plugin-tailwind": "^0.1.2",
|
|
52
52
|
"daisyui": "^5.5.19",
|
|
53
|
-
"
|
|
53
|
+
"vite": "^8.2.1",
|
|
54
54
|
"lucide-react": "^1.14.0",
|
|
55
55
|
"react": "^19.2.7",
|
|
56
56
|
"react-dom": "^19.2.7",
|
|
57
57
|
"unified": "^11.0.0",
|
|
58
58
|
"zod": "^4.4.3",
|
|
59
|
-
"@docubook/core": "^2.0.0-beta.
|
|
60
|
-
"@docubook/markdown": "^2.0.0-beta.
|
|
61
|
-
"@docubook/themes-colors": "^2.0.0-beta.
|
|
62
|
-
"@docubook/ui-react": "^2.0.0-beta.
|
|
59
|
+
"@docubook/core": "^2.0.0-beta.2",
|
|
60
|
+
"@docubook/markdown": "^2.0.0-beta.2",
|
|
61
|
+
"@docubook/themes-colors": "^2.0.0-beta.2",
|
|
62
|
+
"@docubook/ui-react": "^2.0.0-beta.2"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@sentry/bun": "^10.0.0"
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// .docu/node/paths.ts
|
|
2
|
-
import { resolve, join } from "node:path";
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { readdir, rm, unlink } from "node:fs/promises";
|
|
5
|
-
var FRAMEWORK_ROOT = resolve(import.meta.dirname, "../..");
|
|
6
|
-
var PROJECT_ROOT = process.cwd();
|
|
7
|
-
var PAGES_DIR = join(FRAMEWORK_ROOT, ".docu/pages");
|
|
8
|
-
var STYLES_DIR = join(FRAMEWORK_ROOT, ".docu/styles");
|
|
9
|
-
var nodeDir = join(FRAMEWORK_ROOT, ".docu/node");
|
|
10
|
-
var libDir = join(FRAMEWORK_ROOT, ".docu/lib");
|
|
11
|
-
var LIB_DIR = existsSync(nodeDir) ? nodeDir : libDir;
|
|
12
|
-
var DIST_DIR = join(PROJECT_ROOT, ".docu/dist");
|
|
13
|
-
var ASSETS_DIR = join(DIST_DIR, "assets");
|
|
14
|
-
var CACHE_FILE = join(PROJECT_ROOT, ".docu/build-cache.json");
|
|
15
|
-
var DOCS_DIR = join(PROJECT_ROOT, "docs");
|
|
16
|
-
var DOCS_ASSETS_DIR = join(PROJECT_ROOT, "docs/assets");
|
|
17
|
-
var DOCU_CONFIG_PATH = join(PROJECT_ROOT, "docu.json");
|
|
18
|
-
var _config = null;
|
|
19
|
-
async function cleanOldBundles(preserve) {
|
|
20
|
-
try {
|
|
21
|
-
const files = await readdir(ASSETS_DIR);
|
|
22
|
-
for (const file of files) {
|
|
23
|
-
if (preserve?.has(file)) continue;
|
|
24
|
-
if (file.startsWith("client.") || file.startsWith("client-")) {
|
|
25
|
-
await unlink(join(ASSETS_DIR, file));
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
} catch (err) {
|
|
29
|
-
if (err.code !== "ENOENT") {
|
|
30
|
-
console.error("Failed to clean old bundles:", err.message);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
await rm(join(ASSETS_DIR, "chunks"), { recursive: true, force: true });
|
|
35
|
-
} catch (err) {
|
|
36
|
-
if (err.code !== "ENOENT") {
|
|
37
|
-
console.warn("[flame] Failed to clean chunks dir:", err.message);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function loadDocuConfig() {
|
|
42
|
-
if (_config) return _config;
|
|
43
|
-
if (!existsSync(DOCU_CONFIG_PATH)) {
|
|
44
|
-
throw new Error(`docu.json not found at ${DOCU_CONFIG_PATH}`);
|
|
45
|
-
}
|
|
46
|
-
_config = JSON.parse(readFileSync(DOCU_CONFIG_PATH, "utf-8"));
|
|
47
|
-
return _config;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {
|
|
51
|
-
FRAMEWORK_ROOT,
|
|
52
|
-
PROJECT_ROOT,
|
|
53
|
-
STYLES_DIR,
|
|
54
|
-
LIB_DIR,
|
|
55
|
-
DIST_DIR,
|
|
56
|
-
ASSETS_DIR,
|
|
57
|
-
CACHE_FILE,
|
|
58
|
-
DOCS_DIR,
|
|
59
|
-
DOCS_ASSETS_DIR,
|
|
60
|
-
cleanOldBundles,
|
|
61
|
-
loadDocuConfig
|
|
62
|
-
};
|