@docubook/flame 2.0.0-beta.3 → 2.0.0-beta.4
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/Pagination.tsx +12 -1
- package/.docu/lib/build.deno.js +1 -1
- package/.docu/lib/{build.impl-Bz_p421t.js → build.impl-CT-YLyLn.js} +22 -21
- package/.docu/lib/build.impl-CT-YLyLn.js.map +1 -0
- package/.docu/lib/build.impl-cJPJeQ8r.js +2 -0
- package/.docu/lib/build.node.js +1 -1
- package/.docu/lib/clean.js +1 -1
- package/.docu/lib/deploy.deno.js +1 -1
- package/.docu/lib/deploy.node.js +1 -1
- package/.docu/lib/{deploy.shared-fk8eM-r4.js → deploy.shared-CmOzm3b5.js} +2 -2
- package/.docu/lib/{deploy.shared-fk8eM-r4.js.map → deploy.shared-CmOzm3b5.js.map} +1 -1
- package/.docu/lib/{html.shared-FwgbE1WG.js → html.shared-WwQnhDty.js} +30 -24
- package/.docu/lib/html.shared-WwQnhDty.js.map +1 -0
- package/.docu/lib/{logger-CQyNTE6L.js → logger-Cz0CPMi8.js} +2 -2
- package/.docu/lib/{logger-CQyNTE6L.js.map → logger-Cz0CPMi8.js.map} +1 -1
- package/.docu/lib/preview.deno.js +1 -1
- package/.docu/lib/{preview.impl-CnsbLmDA.js → preview.impl-BaYhTRBW.js} +3 -3
- package/.docu/lib/{preview.impl-CnsbLmDA.js.map → preview.impl-BaYhTRBW.js.map} +1 -1
- package/.docu/lib/preview.node.js +1 -1
- package/.docu/lib/server.deno.js +1 -1
- package/.docu/lib/{server.impl-CXTzYqbF.js → server.impl-l8hXfV_0.js} +6 -6
- package/.docu/lib/server.impl-l8hXfV_0.js.map +1 -0
- package/.docu/lib/server.node.js +1 -1
- package/.docu/lib/{utils-DA17MyQG.js → utils-B_CoyKie.js} +28 -6
- package/.docu/lib/utils-B_CoyKie.js.map +1 -0
- package/.docu/node/build.impl.ts +42 -35
- package/.docu/node/build.ts +57 -41
- package/.docu/node/cache-key.ts +1 -1
- package/.docu/node/html.shared.ts +2 -1
- package/.docu/node/html.ts +2 -1
- package/.docu/node/plugin-builder.ts +10 -1
- package/.docu/node/route.ts +19 -21
- package/.docu/node/security.ts +18 -1
- package/.docu/node/server-routes.ts +3 -3
- package/.docu/node/utils.ts +22 -2
- package/package.json +6 -6
- package/.docu/lib/build.impl-Bz_p421t.js.map +0 -1
- package/.docu/lib/build.impl-CtPrlYAE.js +0 -2
- package/.docu/lib/html.shared-FwgbE1WG.js.map +0 -1
- package/.docu/lib/server.impl-CXTzYqbF.js.map +0 -1
- package/.docu/lib/utils-DA17MyQG.js.map +0 -1
package/.docu/node/build.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { logger } from "./logger";
|
|
|
30
30
|
import { initSentry, captureException } from "./sentry";
|
|
31
31
|
import { loadPlugins } from "./plugin-loader";
|
|
32
32
|
import { BuildPluginBuilder } from "./plugin-builder";
|
|
33
|
-
import { scanMdxFiles } from "./utils";
|
|
33
|
+
import { scanMdxFiles, resolveDocsIndexSource, DEFAULT_FAVICON } from "./utils";
|
|
34
34
|
import type { BuildCache, BuildCacheMeta, CliArgs } from "./types";
|
|
35
35
|
import { isCacheEntry } from "./types";
|
|
36
36
|
import {
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
runtimeStamp,
|
|
42
42
|
} from "./cache-key";
|
|
43
43
|
import { clearDerivedPageCaches } from "./mdx";
|
|
44
|
-
import { generateNonce } from "./security";
|
|
44
|
+
import { generateNonce, cspHeader } from "./security";
|
|
45
45
|
import type { PageMeta, PageContext } from "./plugin";
|
|
46
46
|
import { buildSeoMeta } from "./seo";
|
|
47
47
|
import DocsPage from "../pages/docs/[[...slug]]";
|
|
@@ -109,12 +109,11 @@ type RebuildDecision = "yes" | "hash_check" | "no";
|
|
|
109
109
|
export function shouldRebuild(path: string, mtime: number, cache: BuildCache): RebuildDecision {
|
|
110
110
|
const cached = cache[path];
|
|
111
111
|
if (!isCacheEntry(cached)) return "yes";
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
if (mtime !== cached.mtime && mtime > cached.mtime) return "hash_check";
|
|
112
|
+
// Any mtime drift vs the recorded one — a newer edit, or mtime moving
|
|
113
|
+
// backwards (rsync -a, cp -p, snapshot restore) — falls through to the
|
|
114
|
+
// hash check: hashing an unchanged file is cheap, serving a stale page
|
|
115
|
+
// is not. Only an untouched mtime skips without reading the file.
|
|
116
|
+
if (mtime !== cached.mtime) return "hash_check";
|
|
118
117
|
return "no";
|
|
119
118
|
}
|
|
120
119
|
|
|
@@ -227,14 +226,18 @@ async function renderDocsPage(
|
|
|
227
226
|
const bodyExtra = builder?.collectBody(ctx);
|
|
228
227
|
|
|
229
228
|
const depth = slug ? slug.split("/").length : 1;
|
|
230
|
-
const favicon = docuConfig.meta?.favicon ||
|
|
229
|
+
const favicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
231
230
|
const seo = buildSeoMeta(docuConfig, frontmatter, slug || "");
|
|
231
|
+
// Parity with build.impl.ts: static hosts without header control (GitHub
|
|
232
|
+
// Pages) rely on the <meta> CSP for the per-page script policy.
|
|
233
|
+
const csp = nonce ? cspHeader(nonce) : undefined;
|
|
232
234
|
let html = htmlShell({
|
|
233
235
|
title,
|
|
234
236
|
description,
|
|
235
237
|
body,
|
|
236
238
|
favicon,
|
|
237
239
|
seo,
|
|
240
|
+
csp,
|
|
238
241
|
css: assetManifest.css,
|
|
239
242
|
js: assetManifest.js,
|
|
240
243
|
nonce,
|
|
@@ -337,15 +340,16 @@ async function build() {
|
|
|
337
340
|
});
|
|
338
341
|
await Promise.all(prePassTasks);
|
|
339
342
|
|
|
340
|
-
// The docs root (index.mdx) renders with slug "" — mirror that
|
|
341
|
-
// index page hydrates too. Its render has its own try/catch; skip
|
|
342
|
-
|
|
343
|
-
|
|
343
|
+
// The docs root (index.mdx, or index.md) renders with slug "" — mirror that
|
|
344
|
+
// key so the index page hydrates too. Its render has its own try/catch; skip
|
|
345
|
+
// on error.
|
|
346
|
+
const indexSource = resolveDocsIndexSource(DOCS_DIR);
|
|
347
|
+
if (indexSource) {
|
|
344
348
|
try {
|
|
345
|
-
const indexRaw = await readFile(
|
|
349
|
+
const indexRaw = await readFile(indexSource, "utf-8");
|
|
346
350
|
let indexContent = indexRaw;
|
|
347
351
|
if (builder) {
|
|
348
|
-
const relPath =
|
|
352
|
+
const relPath = indexSource.replace(PROJECT_ROOT + "/", "");
|
|
349
353
|
const transformed = await builder.runOnLoad(relPath, indexContent);
|
|
350
354
|
if (transformed?.contents) indexContent = transformed.contents;
|
|
351
355
|
}
|
|
@@ -394,9 +398,8 @@ async function build() {
|
|
|
394
398
|
|
|
395
399
|
const allRelPaths = mdxFiles.map((f) => f.absPath.replace(PROJECT_ROOT + "/", ""));
|
|
396
400
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
allRelPaths.push(indexMdxFull.replace(PROJECT_ROOT + "/", ""));
|
|
401
|
+
if (indexSource) {
|
|
402
|
+
allRelPaths.push(indexSource.replace(PROJECT_ROOT + "/", ""));
|
|
400
403
|
}
|
|
401
404
|
const gitDates = await getGitLastModifiedBatch(allRelPaths);
|
|
402
405
|
|
|
@@ -476,38 +479,49 @@ async function build() {
|
|
|
476
479
|
await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));
|
|
477
480
|
}
|
|
478
481
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
482
|
+
if (indexSource) {
|
|
483
|
+
try {
|
|
484
|
+
const indexRaw = await readFile(indexSource, "utf-8");
|
|
485
|
+
const indexRelPath = indexSource.replace(PROJECT_ROOT + "/", "");
|
|
486
|
+
const indexHtml = await renderDocsPage(
|
|
487
|
+
"",
|
|
488
|
+
indexRaw,
|
|
489
|
+
indexRelPath,
|
|
490
|
+
gitDates,
|
|
491
|
+
builder,
|
|
492
|
+
generateNonce()
|
|
493
|
+
);
|
|
494
|
+
await mkdir(join(DIST_DIR, "docs"), { recursive: true });
|
|
495
|
+
await writeFile(join(DIST_DIR, "docs", "index.html"), indexHtml);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
498
|
+
errors.push(`index: ${msg}`);
|
|
499
|
+
console.error(`\n❌ Failed to build index: ${msg}\n`);
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
const msg = "docs root index: docs/index.mdx (or docs/index.md) not found";
|
|
503
|
+
errors.push(`index: ${msg}`);
|
|
504
|
+
console.error(`\n❌ Failed to build index: ${msg}\n`);
|
|
497
505
|
}
|
|
498
506
|
|
|
499
507
|
const landingPage = React.createElement(IndexPage);
|
|
500
|
-
const landingFavicon = docuConfig.meta?.favicon ||
|
|
501
|
-
const landingSeo = buildSeoMeta(
|
|
508
|
+
const landingFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
509
|
+
const landingSeo = buildSeoMeta(
|
|
510
|
+
docuConfig,
|
|
511
|
+
docuConfig.meta as unknown as Record<string, unknown>,
|
|
512
|
+
""
|
|
513
|
+
);
|
|
514
|
+
const landingNonce = generateNonce();
|
|
502
515
|
const landingHtml = htmlShell({
|
|
503
516
|
title: docuConfig.meta?.title || "DocuBook",
|
|
504
517
|
description: docuConfig.meta?.description || "",
|
|
505
518
|
body: renderToString(landingPage),
|
|
506
519
|
favicon: landingFavicon,
|
|
507
520
|
seo: landingSeo,
|
|
521
|
+
csp: cspHeader(landingNonce),
|
|
508
522
|
css: assetManifest.css,
|
|
509
523
|
js: assetManifest.js,
|
|
510
|
-
nonce:
|
|
524
|
+
nonce: landingNonce,
|
|
511
525
|
themeCss: inlineThemeCss,
|
|
512
526
|
});
|
|
513
527
|
await writeFile(join(DIST_DIR, "index.html"), landingHtml);
|
|
@@ -517,16 +531,18 @@ async function build() {
|
|
|
517
531
|
{ repoUrl: docuConfig.repo?.url },
|
|
518
532
|
React.createElement(NotFoundPage)
|
|
519
533
|
);
|
|
520
|
-
const notFoundFavicon = docuConfig.meta?.favicon ||
|
|
534
|
+
const notFoundFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
535
|
+
const notFoundNonce = generateNonce();
|
|
521
536
|
const notFoundHtml = htmlShell({
|
|
522
537
|
title: "404 - Not Found",
|
|
523
538
|
description: "",
|
|
524
539
|
body: renderToString(notFoundPage),
|
|
525
540
|
favicon: notFoundFavicon,
|
|
526
541
|
headExtra: ['<meta name="robots" content="noindex,follow">'],
|
|
542
|
+
csp: cspHeader(notFoundNonce),
|
|
527
543
|
css: assetManifest.css,
|
|
528
544
|
js: assetManifest.js,
|
|
529
|
-
nonce:
|
|
545
|
+
nonce: notFoundNonce,
|
|
530
546
|
themeCss: inlineThemeCss,
|
|
531
547
|
// Served as the static-host fallback at ANY requested path — relative
|
|
532
548
|
// depth can never be right there, so use root-absolute asset URLs.
|
package/.docu/node/cache-key.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { FRAMEWORK_ROOT, STYLES_DIR, resolveProjectFile } from "./paths";
|
|
|
8
8
|
* (e.g. Bun.build barrel optimization, Tailwind CLI upgrade). Old caches
|
|
9
9
|
* with a mismatched version are discarded on read (see build.ts readCache).
|
|
10
10
|
*/
|
|
11
|
-
export const BUILD_CACHE_VERSION =
|
|
11
|
+
export const BUILD_CACHE_VERSION = 4;
|
|
12
12
|
|
|
13
13
|
/** Toolchain fingerprint: Bun version on Bun, Deno version on Deno, Node elsewhere. */
|
|
14
14
|
export function runtimeStamp(): string {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { escapeHtml } from "./escapeHtml";
|
|
10
|
+
import { cspMeta } from "./security";
|
|
10
11
|
|
|
11
12
|
import type { SeoMeta } from "./seo";
|
|
12
13
|
|
|
@@ -86,7 +87,7 @@ export function htmlShell(opts: HtmlShellOptions): string {
|
|
|
86
87
|
${favicon ? `<link rel="icon" type="image/x-icon" href="${escapeHtml(resolvePath(favicon))}">` : ""}${themeStyle}
|
|
87
88
|
<link rel="preload" href="${escapeHtml(assetPrefix + css)}" as="style">
|
|
88
89
|
<link rel="stylesheet" href="${escapeHtml(assetPrefix + css)}">
|
|
89
|
-
${csp ? `<meta http-equiv="Content-Security-Policy" content="${escapeHtml(csp)}">` : ""}
|
|
90
|
+
${csp ? `<meta http-equiv="Content-Security-Policy" content="${escapeHtml(cspMeta(csp))}">` : ""}
|
|
90
91
|
${seoTags}
|
|
91
92
|
<script${nonceAttr}>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}</script>${headInjection}
|
|
92
93
|
</head>
|
package/.docu/node/html.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HtmlShellOptions } from "./html.shared";
|
|
2
2
|
export type { HtmlShellOptions };
|
|
3
|
+
import { cspMeta } from "./security";
|
|
3
4
|
|
|
4
5
|
export function htmlShell(opts: HtmlShellOptions): string {
|
|
5
6
|
const {
|
|
@@ -48,7 +49,7 @@ export function htmlShell(opts: HtmlShellOptions): string {
|
|
|
48
49
|
${favicon ? `<link rel="icon" type="image/x-icon" href="${Bun.escapeHTML(resolvePath(favicon))}">` : ""}${themeStyle}
|
|
49
50
|
<link rel="preload" href="${Bun.escapeHTML(assetPrefix + css)}" as="style">
|
|
50
51
|
<link rel="stylesheet" href="${Bun.escapeHTML(assetPrefix + css)}">
|
|
51
|
-
${csp ? `<meta http-equiv="Content-Security-Policy" content="${Bun.escapeHTML(csp)}">` : ""}
|
|
52
|
+
${csp ? `<meta http-equiv="Content-Security-Policy" content="${Bun.escapeHTML(cspMeta(csp))}">` : ""}
|
|
52
53
|
${seoTags}
|
|
53
54
|
<script${nonceAttr}>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}</script>${headInjection}
|
|
54
55
|
</head>
|
|
@@ -408,6 +408,8 @@ export class BuildPluginBuilder implements PluginBuilder {
|
|
|
408
408
|
* Execute the transformHtml chain in pipeline pattern.
|
|
409
409
|
* Each callback receives the **previous** callback's return value (or the
|
|
410
410
|
* original HTML for the first). Every callback **must** return a string.
|
|
411
|
+
* Callbacks returning a non-string (e.g. `undefined`) are skipped with a
|
|
412
|
+
* warning — the current HTML passes through unchanged for that step.
|
|
411
413
|
* Errors inside individual callbacks are caught and logged — the current
|
|
412
414
|
* HTML passes through unchanged for that step.
|
|
413
415
|
*
|
|
@@ -419,7 +421,14 @@ export class BuildPluginBuilder implements PluginBuilder {
|
|
|
419
421
|
let result = html;
|
|
420
422
|
for (let i = 0; i < this._transformHtml.length; i++) {
|
|
421
423
|
try {
|
|
422
|
-
|
|
424
|
+
const next = await this._transformHtml[i](result, context);
|
|
425
|
+
if (typeof next === "string") {
|
|
426
|
+
result = next;
|
|
427
|
+
} else {
|
|
428
|
+
console.warn(
|
|
429
|
+
`[plugin] transformHtml callback #${i + 1} returned invalid type (expected a string), keeping previous HTML`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
423
432
|
} catch (err) {
|
|
424
433
|
console.error(
|
|
425
434
|
`[plugin] transformHtml callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`
|
package/.docu/node/route.ts
CHANGED
|
@@ -47,6 +47,22 @@ export function getRouteMap(): Map<string, string> {
|
|
|
47
47
|
return map;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Single pagination entry builder (DRY) — one `readPageFrontmatter` call
|
|
52
|
+
* serves both prev + next from the parse-once registry, so no file is
|
|
53
|
+
* re-read or re-parsed. `description` rides along on prev too; the UI
|
|
54
|
+
* keeps the paired prev minimal by design and only renders the rich
|
|
55
|
+
* title + description when prev stands alone (last page, no next).
|
|
56
|
+
*/
|
|
57
|
+
function toPaginationEntry(href: string, routeMap: Map<string, string>) {
|
|
58
|
+
const fm = readPageFrontmatter(href);
|
|
59
|
+
return {
|
|
60
|
+
href,
|
|
61
|
+
title: fm.title || routeMap.get(href) || "",
|
|
62
|
+
description: fm.description || "",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
/**
|
|
51
67
|
* Frontmatter for a page — read from the parse-once registry (populated
|
|
52
68
|
* during compilation) instead of re-reading + re-parsing the file. Falls
|
|
@@ -86,15 +102,7 @@ export function getPreviousNext(pathname: string) {
|
|
|
86
102
|
const routeMap = getRouteMap();
|
|
87
103
|
const first = paths[0];
|
|
88
104
|
if (!first) return { prev: null, next: null };
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
prev: null,
|
|
92
|
-
next: {
|
|
93
|
-
href: first,
|
|
94
|
-
title: fm.title || routeMap.get(first) || "",
|
|
95
|
-
description: fm.description || "",
|
|
96
|
-
},
|
|
97
|
-
};
|
|
105
|
+
return { prev: null, next: toPaginationEntry(first, routeMap) };
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
const paths = flattenRoutes();
|
|
@@ -109,19 +117,9 @@ export function getPreviousNext(pathname: string) {
|
|
|
109
117
|
const prevHref = index > 0 ? paths[index - 1] : null;
|
|
110
118
|
const nextHref = index < paths.length - 1 ? paths[index + 1] : null;
|
|
111
119
|
|
|
112
|
-
const prevFm = prevHref ? readPageFrontmatter(prevHref) : null;
|
|
113
|
-
const nextFm = nextHref ? readPageFrontmatter(nextHref) : null;
|
|
114
120
|
return {
|
|
115
|
-
prev: prevHref
|
|
116
|
-
|
|
117
|
-
: null,
|
|
118
|
-
next: nextHref
|
|
119
|
-
? {
|
|
120
|
-
href: nextHref,
|
|
121
|
-
title: nextFm?.title || routeMap.get(nextHref) || "",
|
|
122
|
-
description: nextFm?.description || "",
|
|
123
|
-
}
|
|
124
|
-
: null,
|
|
121
|
+
prev: prevHref ? toPaginationEntry(prevHref, routeMap) : null,
|
|
122
|
+
next: nextHref ? toPaginationEntry(nextHref, routeMap) : null,
|
|
125
123
|
};
|
|
126
124
|
}
|
|
127
125
|
|
package/.docu/node/security.ts
CHANGED
|
@@ -30,6 +30,15 @@ export function cspHeader(nonce: string, allowEval = false): string {
|
|
|
30
30
|
].join("; ");
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Strip `frame-ancestors` for `<meta http-equiv="Content-Security-Policy">`.
|
|
35
|
+
* Browsers ignore `frame-ancestors` in meta CSP (header-only directive)
|
|
36
|
+
* and log a console warning. HTTP header from `cspHeader()` keeps it.
|
|
37
|
+
*/
|
|
38
|
+
export function cspMeta(csp: string): string {
|
|
39
|
+
return csp.replace(/;\s*frame-ancestors 'none'/, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function isPathSafe(pathname: string, baseDir: string): boolean {
|
|
34
43
|
const decoded = decodeURIComponent(pathname);
|
|
35
44
|
const resolved = resolve(baseDir, decoded.slice(1));
|
|
@@ -87,12 +96,20 @@ export function normalizeImporterPath(importer: string): string {
|
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
export function injectNonce(html: string, nonce: string): string {
|
|
90
|
-
|
|
99
|
+
const scripts = html.replace(/<script\b(?![^>]*\bsrc\s*=)([^>]*)>/gi, (match) => {
|
|
91
100
|
if (/nonce\s*=/i.test(match)) {
|
|
92
101
|
return match.replace(/nonce="[^"]*"/i, `nonce="${nonce}"`);
|
|
93
102
|
}
|
|
94
103
|
return match.replace(/>$/, ` nonce="${nonce}">`);
|
|
95
104
|
});
|
|
105
|
+
// Keep the <meta> CSP nonce in sync: browsers intersect the meta policy
|
|
106
|
+
// with the response-header policy, so a stale build-time nonce would block
|
|
107
|
+
// the very scripts re-tagged above.
|
|
108
|
+
return scripts.replace(/<meta\b[^>]*Content-Security-Policy[^>]*>/gi, (tag) =>
|
|
109
|
+
tag.replace(/'nonce-[^']*'|&#(?:x27;|39;)nonce-[^&]*(?:&#(?:x27;|39;))/i, (match) =>
|
|
110
|
+
match.startsWith("&#") ? `'nonce-${nonce}'` : `'nonce-${nonce}'`
|
|
111
|
+
)
|
|
112
|
+
);
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
export interface PluginResponseLike {
|
|
@@ -4,7 +4,7 @@ import { readFileSync, statSync } from "node:fs";
|
|
|
4
4
|
import React, { type ReactNode } from "react";
|
|
5
5
|
import { renderToString } from "react-dom/server";
|
|
6
6
|
import { compileMdx, frontmatterField } from "./mdx";
|
|
7
|
-
import { getContentType } from "./utils";
|
|
7
|
+
import { DEFAULT_FAVICON, getContentType } from "./utils";
|
|
8
8
|
import { DOCS_DIR, DIST_DIR, PROJECT_ROOT } from "./paths";
|
|
9
9
|
import { BuildPluginBuilder } from "./plugin-builder";
|
|
10
10
|
import type { PageContext } from "./plugin";
|
|
@@ -32,7 +32,7 @@ function createHtmlResponse(
|
|
|
32
32
|
depth = 0
|
|
33
33
|
): Response {
|
|
34
34
|
const nonce = generateNonce();
|
|
35
|
-
const favicon = state.docuConfig.meta?.favicon ||
|
|
35
|
+
const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
36
36
|
const html = createHtmlShell({
|
|
37
37
|
title,
|
|
38
38
|
description,
|
|
@@ -187,7 +187,7 @@ async function renderDocsServerPage(
|
|
|
187
187
|
const headExtra = state.builder.collectHead(ctx);
|
|
188
188
|
const bodyExtra = state.builder.collectBody(ctx);
|
|
189
189
|
const nonce = generateNonce();
|
|
190
|
-
const favicon = state.docuConfig.meta?.favicon ||
|
|
190
|
+
const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
191
191
|
let html = createHtmlShell({
|
|
192
192
|
title,
|
|
193
193
|
description,
|
package/.docu/node/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { cn, parseDate, formatDate, formatDate2 } from "@docubook/core";
|
|
2
2
|
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
3
4
|
import { readdir, stat } from "node:fs/promises";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
|
|
@@ -11,7 +12,8 @@ export interface ScannedMdxFile {
|
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Scan a directory recursively for MDX/MD files.
|
|
14
|
-
* Skips "assets" directories, hidden directories (dot-prefixed), and root-level
|
|
15
|
+
* Skips "assets" directories, hidden directories (dot-prefixed), and root-level
|
|
16
|
+
* index.mdx/index.md (the docs root renders separately in build).
|
|
15
17
|
* Shared between build.ts and search-indexer.ts.
|
|
16
18
|
*/
|
|
17
19
|
export async function scanMdxFiles(dir: string, baseDir = ""): Promise<ScannedMdxFile[]> {
|
|
@@ -26,7 +28,9 @@ export async function scanMdxFiles(dir: string, baseDir = ""): Promise<ScannedMd
|
|
|
26
28
|
if (entry.name === "assets" || entry.name.startsWith(".")) continue;
|
|
27
29
|
files.push(...(await scanMdxFiles(fullPath, relativePath)));
|
|
28
30
|
} else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
|
|
29
|
-
|
|
31
|
+
// Root index renders separately (either extension) — scanning it would
|
|
32
|
+
// produce a duplicate "index" page colliding with dist/docs/index.html.
|
|
33
|
+
if (!baseDir && (entry.name === "index.mdx" || entry.name === "index.md")) continue;
|
|
30
34
|
const stats = await stat(fullPath);
|
|
31
35
|
let path = relativePath.replace(/\.(mdx|md)$/, "");
|
|
32
36
|
|
|
@@ -44,6 +48,22 @@ export function isExternalUrl(url: string): boolean {
|
|
|
44
48
|
return /^(https?:\/\/|\/\/)/.test(url);
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the docs root index source — docs/index.mdx preferred, docs/index.md
|
|
53
|
+
* as fallback (mirrors the dev server's getDocsForSlug extension handling).
|
|
54
|
+
*/
|
|
55
|
+
export function resolveDocsIndexSource(docsDir: string): string | undefined {
|
|
56
|
+
for (const ext of [".mdx", ".md"]) {
|
|
57
|
+
const candidate = join(docsDir, `index${ext}`);
|
|
58
|
+
if (existsSync(candidate)) return candidate;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Default favicon — resolves in both build output (`docs/assets/` is copied
|
|
64
|
+
* to `dist/docs/assets/`) and dev (served from `docs/assets/` via fallback). */
|
|
65
|
+
export const DEFAULT_FAVICON = "/docs/assets/images/favicon.ico";
|
|
66
|
+
|
|
47
67
|
/** Suffix an internal docs link with `.html` to match the flat static build output. */
|
|
48
68
|
export function docsHtmlHref(path: string): string {
|
|
49
69
|
return `${path}.html`;
|
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.4",
|
|
4
4
|
"description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"author-url": "https://wildan.dev",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
+
"@docubook/core": "^2.0.0-beta.4",
|
|
49
|
+
"@docubook/markdown": "^2.0.0-beta.4",
|
|
50
|
+
"@docubook/themes-colors": "^2.0.0-beta.4",
|
|
51
|
+
"@docubook/ui-react": "^2.0.0-beta.4",
|
|
48
52
|
"@mdx-js/react": "^3.0.1",
|
|
49
53
|
"@tailwindcss/cli": "4.3.0",
|
|
50
54
|
"@tailwindcss/typography": "0.5.16",
|
|
@@ -55,11 +59,7 @@
|
|
|
55
59
|
"react-dom": "^19.2.7",
|
|
56
60
|
"unified": "^11.0.0",
|
|
57
61
|
"vite": "^8.2.1",
|
|
58
|
-
"zod": "^4.4.3"
|
|
59
|
-
"@docubook/core": "^2.0.0-beta.3",
|
|
60
|
-
"@docubook/markdown": "^2.0.0-beta.3",
|
|
61
|
-
"@docubook/themes-colors": "^2.0.0-beta.3",
|
|
62
|
-
"@docubook/ui-react": "^2.0.0-beta.3"
|
|
62
|
+
"zod": "^4.4.3"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"@sentry/bun": "^10.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"build.impl-Bz_p421t.js","names":[],"sources":["../node/types.ts","../node/seo.ts","../node/build.impl.ts"],"sourcesContent":["import type { ThemeConfig } from \"@docubook/themes-colors\";\nimport type { PluginEntry } from \"./plugin\";\n\nexport interface DocuRouteContext {\n title?: string;\n icon?: string;\n description?: string;\n}\n\nexport interface DocuRoute {\n title: string;\n href: string;\n noLink?: boolean;\n items?: DocuRoute[];\n context?: DocuRouteContext;\n}\n\nexport interface DocuMeta {\n title: string;\n description: string;\n baseURL: string;\n favicon?: string;\n /** Default OG image path (e.g. /docs/assets/images/og.png). Used when page frontmatter has no image. */\n ogImage?: string;\n}\n\nexport interface SocialLink {\n name: string;\n url: string;\n}\n\nexport interface DocuNavbar {\n logoText: string;\n logo?: { src?: string; alt?: string };\n menu: { title: string; href: string }[];\n}\n\nexport interface DocuFooter {\n social: SocialLink[];\n}\n\nexport interface DocuSidebar {\n /** How context sections are displayed.\n * \"dropdown\" — context switcher dropdown (default)\n * \"separator\" — inline group headers in sidebar */\n context?: \"dropdown\" | \"separator\";\n}\n\nexport interface RepoConfig {\n url: string;\n path: string;\n edit: boolean;\n}\n\nexport interface HeroAction {\n text: string;\n link: string;\n theme?: \"primary\" | \"secondary\" | \"ghost\";\n icon?: string;\n}\n\nexport interface Hero {\n tagline?: string;\n headline: string;\n description?: string;\n actions?: HeroAction[];\n}\n\nexport interface HomeFeature {\n icon?: string;\n title: string;\n description: string;\n link?: string;\n}\n\nexport interface HomeConfig {\n hero?: Hero;\n features?: HomeFeature[];\n}\n\nexport interface DocuConfig {\n meta: DocuMeta;\n home?: HomeConfig;\n navbar: DocuNavbar;\n footer: DocuFooter;\n repo: RepoConfig;\n sidebar?: DocuSidebar;\n routes: DocuRoute[];\n themes?: {\n colors: ThemeConfig;\n };\n /** List of DocuBook plugins to load. Empty by default — no-op when absent. */\n plugins?: PluginEntry[];\n}\n\nexport interface BuildCacheEntry {\n hash: string;\n mtime: number;\n builtAt: number;\n}\n\n/** Version stamp stored as `__meta__` — extends entry so the index signature stays valid. */\nexport interface BuildCacheMeta extends BuildCacheEntry {\n version: number;\n runtime: string;\n}\n\nexport interface BuildCache {\n [path: string]: BuildCacheEntry;\n __meta__?: BuildCacheMeta;\n __assets__?: BuildCacheEntry;\n __bundle__?: BuildCacheEntry;\n}\n\n/** Narrow a cache slot to a real page/asset entry (guards `__meta__` shape). */\nexport function isCacheEntry(value: unknown): value is BuildCacheEntry {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.hash === \"string\" && typeof v.mtime === \"number\" && typeof v.builtAt === \"number\";\n}\n\nexport interface CliArgs {\n force?: boolean;\n clean?: boolean;\n ssr?: boolean;\n}\n\nexport interface ParsedDoc {\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n raw: string;\n scope?: Record<string, unknown>;\n}\n\nexport type { TocItem } from \"@docubook/core\";\n","import type { DocuConfig } from \"./types\";\nimport { frontmatterField } from \"./mdx\";\n\nexport interface SeoMeta {\n /** Absolute canonical URL */\n url: string;\n /** Site name for og:site_name */\n siteName: string;\n /** Absolute OG image URL (from frontmatter.image, if set) */\n image?: string;\n}\n\n/**\n * Build SEO metadata from config and per-page frontmatter.\n * All fields are derived from existing data — no extra config required.\n */\nexport function buildSeoMeta(\n config: DocuConfig,\n frontmatter: Record<string, unknown>,\n slug: string\n): SeoMeta {\n const baseURL = config.meta?.baseURL?.replace(/\\/+$/, \"\") || \"\";\n const url = slug ? `${baseURL}/docs/${slug}` : `${baseURL}/`;\n\n const result: SeoMeta = {\n url,\n siteName: config.meta?.title || \"\",\n };\n\n // Per-page image from frontmatter, fallback to global default from config\n const image = frontmatterField(frontmatter, \"image\") || config.meta?.ogImage;\n if (image) {\n // Resolve using URL constructor — handles absolute, root-relative, and relative paths\n try {\n result.image = new URL(image, image.startsWith(\"/\") ? baseURL : `${baseURL}/docs/`).href;\n } catch {\n result.image = image;\n }\n }\n\n return result;\n}\n","/**\n * Runtime-neutral static build — mirror of `build.ts` (Bun-only via its\n * transitive imports, protected) with the Bun-coupled modules swapped for\n * their neutral counterparts: `html.shared` (pure escaping) and\n * `hydrate.node` (esbuild client bundling). Everything else is identical.\n * The Node/Deno build entries call `runBuildCli()`.\n */\n\nimport { readFile, writeFile, mkdir, readdir, copyFile, rename, unlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { join, dirname } from \"node:path\";\nimport React from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport {\n compileMdx,\n compileMdxModule,\n frontmatterField,\n getGitLastModifiedBatch,\n getPageContent,\n getPageFrontmatter,\n getPageStripped,\n registerPageContent,\n} from \"./mdx\";\nimport {\n DOCS_DIR,\n DIST_DIR,\n ASSETS_DIR,\n CACHE_FILE,\n DOCS_ASSETS_DIR,\n PROJECT_ROOT,\n loadDocuConfig,\n} from \"./paths\";\nimport { htmlShell } from \"./html.shared\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { scanMdxFiles } from \"./utils\";\nimport type { BuildCache, BuildCacheMeta, CliArgs } from \"./types\";\nimport { isCacheEntry } from \"./types\";\nimport {\n BUILD_CACHE_VERSION,\n atomicWriteFile,\n hashMdxSources,\n hookMemoryPressure,\n runtimeStamp,\n} from \"./cache-key\";\nimport { clearDerivedPageCaches } from \"./mdx\";\nimport { generateNonce, cspHeader } from \"./security\";\nimport type { PageMeta, PageContext } from \"./plugin\";\nimport { buildSeoMeta } from \"./seo\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport IndexPage from \"../pages/index\";\nimport NotFoundPage from \"../pages/404\";\nimport { DocsLayout } from \"../components/DocsLayout\";\n\nfunction parseArgs(): CliArgs {\n const args = process.argv.slice(2);\n return {\n force: args.includes(\"--force\") || args.includes(\"-f\"),\n clean: args.includes(\"--clean\") || args.includes(\"-c\"),\n };\n}\n\nfunction hashContent(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nasync function readCache(): Promise<BuildCache> {\n try {\n if (existsSync(CACHE_FILE)) {\n const data = await readFile(CACHE_FILE, \"utf-8\");\n const parsed = JSON.parse(data) as BuildCache;\n const meta = parsed.__meta__ as BuildCacheMeta | undefined;\n if (!meta || meta.version !== BUILD_CACHE_VERSION || meta.runtime !== runtimeStamp()) {\n return {};\n }\n return parsed;\n }\n } catch (err) {\n console.error(\"Failed to load build cache:\", (err as Error).message);\n }\n return {};\n}\n\nfunction stampCache(cache: BuildCache): void {\n cache.__meta__ = {\n hash: `${BUILD_CACHE_VERSION}:${runtimeStamp()}`,\n mtime: 0,\n builtAt: Date.now(),\n version: BUILD_CACHE_VERSION,\n runtime: runtimeStamp(),\n };\n}\n\nasync function writeCache(cache: BuildCache): Promise<void> {\n stampCache(cache);\n await atomicWriteFile(writeFile, rename, unlink, CACHE_FILE, JSON.stringify(cache, null, 2));\n}\n\nfunction parseConcurrency(): number {\n return Math.max(1, parseInt(process.env.BUILD_CONCURRENCY || \"4\", 10) || 4);\n}\n\ntype RebuildDecision = \"yes\" | \"hash_check\" | \"no\";\n\nfunction shouldRebuild(path: string, mtime: number, cache: BuildCache): RebuildDecision {\n const cached = cache[path];\n if (!isCacheEntry(cached)) return \"yes\";\n if (mtime > cached.builtAt + 2000) return \"hash_check\";\n if (Math.abs(mtime - cached.builtAt) <= 2000 && mtime !== cached.mtime) return \"hash_check\";\n if (mtime !== cached.mtime && mtime > cached.mtime) return \"hash_check\";\n return \"no\";\n}\n\nlet assetManifest = { js: \"client.js\", css: \"client.css\" };\n\n/**\n * Reuse manifest.json on bundle cache hit; fall back to a full rebuild\n * when the manifest is missing or malformed.\n */\nasync function resolveAssetManifest(\n bundleHit: boolean,\n mdxSources: Record<string, string>\n): Promise<{ js: string; css: string }> {\n if (!bundleHit) return buildClientBundle(mdxSources);\n try {\n const manifest = JSON.parse(await readFile(join(ASSETS_DIR, \"manifest.json\"), \"utf-8\")) as {\n js?: string;\n css?: string;\n };\n if (typeof manifest.js === \"string\" && typeof manifest.css === \"string\") {\n return { js: manifest.js, css: manifest.css };\n }\n } catch {\n // corrupt/missing manifest — rebuild below\n }\n return buildClientBundle(mdxSources);\n}\n\nlet inlineThemeCss: string | undefined;\n\nasync function renderDocsPage(\n docuConfig: ReturnType<typeof loadDocuConfig>,\n slug: string,\n rawMdx: string,\n filePath: string,\n gitDates?: Map<string, string>,\n builder?: BuildPluginBuilder | null,\n nonce?: string\n): Promise<string> {\n let content = rawMdx;\n if (builder) {\n const transformed = await builder.runOnLoad(filePath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n let result;\n try {\n const remarkPlugins = builder?.collectRemarkPlugins();\n const rehypePlugins = builder?.collectRehypePlugins();\n // Parse-once: reuse the prePass frontmatter + stripped content so the\n // SSR phase does not re-extract them. Only when the prePass actually\n // registered them — otherwise compileMdx does its own extraction.\n const preFm = getPageFrontmatter(`/${slug}`);\n const preStripped = getPageStripped(`/${slug}`);\n const pre =\n preFm !== undefined && preStripped !== undefined\n ? { frontmatter: preFm, strippedContent: preStripped }\n : undefined;\n result = await compileMdx(\n content,\n filePath,\n gitDates,\n remarkPlugins,\n rehypePlugins,\n undefined,\n pre\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown MDX error\";\n throw new Error(`MDX Error in: docs/${slug}.mdx\\n${msg}`, { cause: err });\n }\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (builder) {\n frontmatter = await builder.runTransformFrontmatterChain(frontmatter, {\n slug,\n filePath,\n content,\n });\n }\n\n const title = frontmatterField(frontmatter, \"title\") || slug || \"Docs\";\n const description = frontmatterField(frontmatter, \"description\");\n const slugParts = slug ? slug.split(\"/\") : [];\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: docuConfig.repo?.url },\n React.createElement(DocsPage, {\n slug: slugParts,\n title,\n description,\n date: frontmatterField(frontmatter, \"date\") || undefined,\n // Render MDX content as its own root: client hydrates the island as a\n // separate root, so SSR must be root-relative too or useId-based ids\n // (mdx-compiler components) mismatch during hydration.\n content: renderToString(result.content),\n tocs: result.tocs,\n filePath,\n repoUrl: docuConfig.repo?.url,\n mdxSlug: slug,\n })\n );\n\n const body = renderToString(page);\n\n const ctx: PageContext = { slug, filePath, frontmatter, content, config: docuConfig };\n const headExtra = builder?.collectHead(ctx);\n const bodyExtra = builder?.collectBody(ctx);\n\n const depth = slug ? slug.split(\"/\").length : 1;\n const favicon = docuConfig.meta?.favicon || \"/docs/assets/images/favicon.ico\";\n const seo = buildSeoMeta(docuConfig, frontmatter, slug || \"\");\n // MDX content hydrates from the bundled ESM module (mdx-hydrate), not\n // new Function — no 'unsafe-eval' needed in the CSP.\n const csp = nonce ? cspHeader(nonce) : undefined;\n let html = htmlShell({\n title,\n description,\n body,\n favicon,\n seo,\n csp,\n css: assetManifest.css,\n js: assetManifest.js,\n nonce,\n themeCss: inlineThemeCss,\n depth,\n headExtra,\n bodyExtra,\n });\n\n if (builder) {\n html = await builder.runTransformHtmlChain(html, ctx);\n }\n\n return html;\n}\n\nasync function copyDirectoryRecursive(src: string, dest: string): Promise<void> {\n if (!existsSync(src)) return;\n await mkdir(dest, { recursive: true });\n const entries = await readdir(src, { withFileTypes: true });\n for (const entry of entries) {\n const srcPath = join(src, entry.name);\n const destPath = join(dest, entry.name);\n if (entry.isDirectory()) {\n await copyDirectoryRecursive(srcPath, destPath);\n } else {\n await copyFile(srcPath, destPath);\n }\n }\n}\n\nexport async function runBuild(): Promise<void> {\n const docuConfig = loadDocuConfig();\n const args = parseArgs();\n\n hookMemoryPressure(clearDerivedPageCaches);\n\n logger.buildStart();\n\n if (args.clean) {\n const { rm } = await import(\"node:fs/promises\");\n try {\n await rm(DIST_DIR, { recursive: true, force: true });\n } catch (err) {\n console.error(\"Failed to clean dist directory:\", (err as Error).message);\n }\n }\n\n await mkdir(DIST_DIR, { recursive: true });\n await mkdir(ASSETS_DIR, { recursive: true });\n\n await copyDirectoryRecursive(DOCS_ASSETS_DIR, join(DIST_DIR, \"docs\", \"assets\"));\n\n const mdxFiles = await scanMdxFiles(DOCS_DIR);\n const cache = args.force ? {} : await readCache();\n let built = 0;\n let skipped = 0;\n\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n await builder.runOnStart();\n }\n\n // Pre-compile every page's MDX to an ESM module (program format) so the\n // client bundle can hydrate the content island statically — no new Function.\n // Mirrors the page loop's transform + plugin chain so SSR and client trees\n // match. Runs for all files regardless of cache; the bundle is shared by\n // every page, so a content change invalidates the page cache anyway.\n const mdxSources: Record<string, string> = {};\n const prePassTasks = mdxFiles.map(async (file) => {\n let raw: string;\n try {\n raw = await readFile(file.absPath, \"utf-8\");\n } catch {\n return;\n }\n // Cache the original content so the page loop does not re-read the file\n // (one disk read per file — the frontmatter is parsed once here too).\n registerPageContent(`/${file.path}`, raw);\n let content = raw;\n if (builder) {\n const relPath = file.absPath.replace(PROJECT_ROOT + \"/\", \"\");\n const transformed = await builder.runOnLoad(relPath, content);\n if (transformed?.contents) content = transformed.contents;\n }\n const remarkPlugins = builder?.collectRemarkPlugins();\n const rehypePlugins = builder?.collectRehypePlugins();\n mdxSources[file.path] = await compileMdxModule(\n content,\n remarkPlugins,\n rehypePlugins,\n `/${file.path}`\n );\n });\n await Promise.all(prePassTasks);\n\n // The docs root (index.mdx) renders with slug \"\" — mirror that key so the\n // index page hydrates too. Its render has its own try/catch; skip on error.\n const indexMdxPath = join(DOCS_DIR, \"index.mdx\");\n if (existsSync(indexMdxPath)) {\n try {\n const indexRaw = await readFile(indexMdxPath, \"utf-8\");\n let indexContent = indexRaw;\n if (builder) {\n const relPath = indexMdxPath.replace(PROJECT_ROOT + \"/\", \"\");\n const transformed = await builder.runOnLoad(relPath, indexContent);\n if (transformed?.contents) indexContent = transformed.contents;\n }\n mdxSources[\"\"] = await compileMdxModule(\n indexContent,\n builder?.collectRemarkPlugins(),\n builder?.collectRehypePlugins()\n );\n } catch {\n // ignore — the index render reports its own error\n }\n }\n\n logger.bundleStart();\n let t = performance.now();\n const bundleHash = hashMdxSources(mdxSources);\n const lastBundle = cache[\"__bundle__\"];\n const bundleHit =\n isCacheEntry(lastBundle) &&\n lastBundle.hash === bundleHash &&\n existsSync(join(ASSETS_DIR, \"manifest.json\"));\n assetManifest = await resolveAssetManifest(bundleHit, mdxSources);\n logger.bundleDone(Math.round(performance.now() - t));\n\n inlineThemeCss = computeInlineThemeCss();\n\n const lastManifest = cache[\"__assets__\"];\n const assetsChanged =\n !isCacheEntry(lastManifest) || lastManifest.hash !== `${assetManifest.js}:${assetManifest.css}`;\n if (assetsChanged) {\n cache[\"__assets__\"] = {\n hash: `${assetManifest.js}:${assetManifest.css}`,\n mtime: 0,\n builtAt: Date.now(),\n };\n }\n if (!bundleHit) {\n cache[\"__bundle__\"] = { hash: bundleHash, mtime: 0, builtAt: Date.now() };\n }\n\n logger.spinner.start(\"Building pages...\");\n t = performance.now();\n\n const allRelPaths = mdxFiles.map((f) => f.absPath.replace(PROJECT_ROOT + \"/\", \"\"));\n\n const indexMdxFull = join(DOCS_DIR, \"index.mdx\");\n if (existsSync(indexMdxFull)) {\n allRelPaths.push(indexMdxFull.replace(PROJECT_ROOT + \"/\", \"\"));\n }\n const gitDates = await getGitLastModifiedBatch(allRelPaths);\n\n const CONCURRENCY = parseConcurrency();\n const buildTasks = [];\n const errors: string[] = [];\n\n for (const file of mdxFiles) {\n const rebuildDecision = shouldRebuild(file.path, file.mtime, cache);\n\n if (rebuildDecision === \"no\") {\n const outputPath = join(DIST_DIR, \"docs\", `${file.path}.html`);\n if (existsSync(outputPath) && !assetsChanged) {\n skipped++;\n continue;\n }\n }\n\n let rawMdx = getPageContent(`/${file.path}`);\n if (rawMdx === undefined) {\n try {\n rawMdx = await readFile(file.absPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n continue;\n }\n }\n\n if (rebuildDecision === \"hash_check\") {\n const contentHash = hashContent(rawMdx);\n const cached = cache[file.path];\n if (isCacheEntry(cached) && cached.hash === contentHash) {\n if (!assetsChanged) {\n const outputPath = join(DIST_DIR, \"docs\", `${file.path}.html`);\n if (existsSync(outputPath)) {\n cache[file.path] = { ...cached, mtime: file.mtime, builtAt: Date.now() };\n skipped++;\n continue;\n }\n }\n }\n }\n\n const relPath = file.absPath.replace(PROJECT_ROOT + \"/\", \"\");\n const capturedRawMdx = rawMdx;\n const capturedFile = file;\n\n buildTasks.push(async () => {\n try {\n const pageNonce = generateNonce();\n const html = await renderDocsPage(\n docuConfig,\n capturedFile.path,\n capturedRawMdx,\n relPath,\n gitDates,\n builder,\n pageNonce\n );\n const outputPath = join(DIST_DIR, \"docs\", `${capturedFile.path}.html`);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, html);\n cache[capturedFile.path] = {\n hash: hashContent(capturedRawMdx),\n mtime: capturedFile.mtime,\n builtAt: Date.now(),\n };\n built++;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push(msg);\n console.error(`\\n\\u274C ${msg}\\n`);\n }\n });\n }\n\n for (let i = 0; i < buildTasks.length; i += CONCURRENCY) {\n await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));\n }\n\n try {\n const indexMdxPath = join(DOCS_DIR, \"index.mdx\");\n const indexRaw = await readFile(indexMdxPath, \"utf-8\");\n const indexRelPath = indexMdxPath.replace(PROJECT_ROOT + \"/\", \"\");\n const indexHtml = await renderDocsPage(\n docuConfig,\n \"\",\n indexRaw,\n indexRelPath,\n gitDates,\n builder,\n generateNonce()\n );\n await mkdir(join(DIST_DIR, \"docs\"), { recursive: true });\n await writeFile(join(DIST_DIR, \"docs\", \"index.html\"), indexHtml);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push(`index.mdx: ${msg}`);\n console.error(`\\n\\u274C Failed to build index: ${msg}\\n`);\n }\n\n const landingPage = React.createElement(IndexPage);\n const landingFavicon = docuConfig.meta?.favicon || \"/docs/assets/images/favicon.ico\";\n const landingSeo = buildSeoMeta(\n docuConfig,\n docuConfig.meta as unknown as Record<string, unknown>,\n \"\"\n );\n const landingNonce = generateNonce();\n const landingHtml = htmlShell({\n title: docuConfig.meta?.title || \"DocuBook\",\n description: docuConfig.meta?.description || \"\",\n body: renderToString(landingPage),\n favicon: landingFavicon,\n seo: landingSeo,\n csp: cspHeader(landingNonce),\n css: assetManifest.css,\n js: assetManifest.js,\n nonce: landingNonce,\n themeCss: inlineThemeCss,\n });\n await writeFile(join(DIST_DIR, \"index.html\"), landingHtml);\n\n const notFoundPage = React.createElement(\n DocsLayout,\n { repoUrl: docuConfig.repo?.url },\n React.createElement(NotFoundPage)\n );\n const notFoundFavicon = docuConfig.meta?.favicon || \"/docs/assets/images/favicon.ico\";\n const notFoundNonce = generateNonce();\n const notFoundHtml = htmlShell({\n title: \"404 - Not Found\",\n description: \"\",\n body: renderToString(notFoundPage),\n favicon: notFoundFavicon,\n headExtra: ['<meta name=\"robots\" content=\"noindex,follow\">'],\n csp: cspHeader(notFoundNonce),\n css: assetManifest.css,\n js: assetManifest.js,\n nonce: notFoundNonce,\n themeCss: inlineThemeCss,\n // Served as the static-host fallback at ANY requested path — relative\n // depth can never be right there, so use root-absolute asset URLs.\n absoluteAssets: true,\n });\n await writeFile(join(DIST_DIR, \"404.html\"), notFoundHtml);\n\n logger.spinner.stop(\n `Built ${built} pages (${skipped} cached) \\x1b[90m(${Math.round(performance.now() - t)}ms)\\x1b[0m`\n );\n\n if (builder) {\n const pages: PageMeta[] = mdxFiles.map((f) => ({\n slug: f.path,\n title: f.path.split(\"/\").pop() || f.path,\n filePath: join(DOCS_DIR, f.path),\n outputPath: join(DIST_DIR, \"docs\", `${f.path}.html`),\n }));\n await builder.runOnEnd(pages);\n }\n\n logger.indexStart();\n t = performance.now();\n // No content changed (all pages cached) → the aggregated index is\n // unchanged too; reuse the existing file instead of regenerating it.\n const indexSkipped = built === 0 && existsSync(join(ASSETS_DIR, \"search-index.json\"));\n const indexCount = indexSkipped ? 0 : await generateSearchIndex();\n logger.indexDone(indexCount, Math.round(performance.now() - t), indexSkipped);\n\n logger.routes();\n\n await writeCache(cache);\n\n if (errors.length > 0) {\n console.error(`\\n\\u274C Build completed with ${errors.length} error(s)\\n`);\n process.exit(1);\n }\n}\n\nexport async function runBuildCli(): Promise<void> {\n try {\n await initSentry();\n await runBuild();\n } catch (err) {\n captureException(err);\n console.error(\"Build failed:\", err);\n process.exit(1);\n }\n}\n"],"mappings":"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"}
|