@docubook/flame 2.0.0-beta.5 → 2.0.1
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/Theme.tsx +42 -34
- package/.docu/components/home/BackgroundBlobs.tsx +31 -0
- package/.docu/components/home/index.tsx +1 -0
- package/.docu/lib/build.deno.js +1 -1
- package/.docu/lib/build.impl--Pm9uO30.js +2 -0
- package/.docu/lib/{build.impl-BBlR3si3.js → build.impl-CsP6pPug.js} +60 -29
- package/.docu/lib/build.impl-CsP6pPug.js.map +1 -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-Dxv3lsOb.js → deploy.shared-CsEa3L1D.js} +45 -17
- package/.docu/lib/deploy.shared-CsEa3L1D.js.map +1 -0
- package/.docu/lib/{html.shared-DBMwv_Z6.js → html.shared-CsEFmy_Y.js} +181 -158
- package/.docu/lib/html.shared-CsEFmy_Y.js.map +1 -0
- package/.docu/lib/{logger-DYmvFF1d.js → logger-DLU3A-z2.js} +4 -3
- package/.docu/lib/logger-DLU3A-z2.js.map +1 -0
- package/.docu/lib/{paths-Bl2cdp9E.js → paths-Cbz-dj4s.js} +5 -9
- package/.docu/lib/{paths-Bl2cdp9E.js.map → paths-Cbz-dj4s.js.map} +1 -1
- package/.docu/lib/preview.deno.js +1 -1
- package/.docu/lib/{preview.impl-anVcZv2Z.js → preview.impl-CaZTgmnB.js} +3 -3
- package/.docu/lib/{preview.impl-anVcZv2Z.js.map → preview.impl-CaZTgmnB.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-Cvoa6Diq.js → server.impl-D-CgD1bn.js} +43 -52
- package/.docu/lib/server.impl-D-CgD1bn.js.map +1 -0
- package/.docu/lib/server.node.js +1 -1
- package/.docu/node/build.impl.ts +71 -34
- package/.docu/node/build.ts +68 -34
- package/.docu/node/cache-key.ts +8 -4
- package/.docu/node/client.ts +36 -27
- package/.docu/node/deploy.shared.ts +44 -18
- package/.docu/node/deploy.ts +5 -20
- package/.docu/node/home-client.ts +6 -0
- package/.docu/node/html.shared.ts +5 -4
- package/.docu/node/html.ts +4 -3
- package/.docu/node/hydrate.node.ts +87 -115
- package/.docu/node/hydrate.ts +74 -80
- package/.docu/node/mdx-manifest.d.ts +2 -2
- package/.docu/node/paths.ts +2 -9
- package/.docu/node/plugin-builder.ts +25 -13
- package/.docu/node/plugin.ts +33 -9
- package/.docu/node/server-routes.ts +59 -50
- package/.docu/node/server.impl.ts +4 -2
- package/.docu/node/server.ts +12 -16
- package/.docu/node/types.ts +11 -0
- package/.docu/pages/404.tsx +12 -9
- package/.docu/pages/index.tsx +8 -29
- package/.docu/styles/base.css +128 -0
- package/.docu/styles/globals.css +5 -137
- package/.docu/styles/site.css +7 -0
- package/package.json +6 -5
- package/.docu/lib/build.impl-BBlR3si3.js.map +0 -1
- package/.docu/lib/build.impl-DpoDUmei.js +0 -2
- package/.docu/lib/deploy.shared-Dxv3lsOb.js.map +0 -1
- package/.docu/lib/html.shared-DBMwv_Z6.js.map +0 -1
- package/.docu/lib/logger-DYmvFF1d.js.map +0 -1
- package/.docu/lib/server.impl-Cvoa6Diq.js.map +0 -1
|
@@ -28,7 +28,7 @@ function findProjectRoot() {
|
|
|
28
28
|
return process.cwd();
|
|
29
29
|
}
|
|
30
30
|
var PROJECT_ROOT = findProjectRoot();
|
|
31
|
-
join(FRAMEWORK_ROOT, ".docu/pages");
|
|
31
|
+
var PAGES_DIR = join(FRAMEWORK_ROOT, ".docu/pages");
|
|
32
32
|
var STYLES_DIR = join(FRAMEWORK_ROOT, ".docu/styles");
|
|
33
33
|
var nodeDir = join(FRAMEWORK_ROOT, ".docu/node");
|
|
34
34
|
var libDir = join(FRAMEWORK_ROOT, ".docu/lib");
|
|
@@ -50,18 +50,14 @@ async function cleanOldBundles(preserve) {
|
|
|
50
50
|
const files = await readdir(ASSETS_DIR);
|
|
51
51
|
for (const file of files) {
|
|
52
52
|
if (preserve?.has(file)) continue;
|
|
53
|
-
if (
|
|
53
|
+
if (/^(client|home-client|docs|site)[.-]/.test(file)) await unlink(join(ASSETS_DIR, file));
|
|
54
54
|
}
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err.code !== "ENOENT") console.error("Failed to clean old bundles:", err.message);
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
55
|
await rm(join(ASSETS_DIR, "chunks"), {
|
|
60
56
|
recursive: true,
|
|
61
57
|
force: true
|
|
62
58
|
});
|
|
63
59
|
} catch (err) {
|
|
64
|
-
if (err.code !== "ENOENT") console.
|
|
60
|
+
if (err.code !== "ENOENT") console.error("Failed to clean old bundles:", err.message);
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
function loadDocuConfig() {
|
|
@@ -71,6 +67,6 @@ function loadDocuConfig() {
|
|
|
71
67
|
return _config;
|
|
72
68
|
}
|
|
73
69
|
//#endregion
|
|
74
|
-
export { DOCS_DIR as a,
|
|
70
|
+
export { DOCS_DIR as a, PAGES_DIR as c, cleanOldBundles as d, loadDocuConfig as f, DOCS_ASSETS_DIR as i, PROJECT_ROOT as l, CACHE_FILE as n, FRAMEWORK_ROOT as o, resolveProjectFile as p, DIST_DIR as r, LIB_DIR as s, ASSETS_DIR as t, STYLES_DIR as u };
|
|
75
71
|
|
|
76
|
-
//# sourceMappingURL=paths-
|
|
72
|
+
//# sourceMappingURL=paths-Cbz-dj4s.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths-
|
|
1
|
+
{"version":3,"file":"paths-Cbz-dj4s.js","names":[],"sources":["../node/paths.ts"],"sourcesContent":["import { resolve, join } from \"node:path\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { readdir, rm, unlink } from \"node:fs/promises\";\nimport type { DocuConfig } from \"./types\";\n\n/**\n * FRAMEWORK_ROOT: Where the package code lives (.docu/components, .docu/pages, .docu/styles, .docu/node)\n * PROJECT_ROOT: Where the user's project lives (docs/, docu.json)\n */\n\n// .docu/node/paths.ts → package root is 2 levels up\nexport const FRAMEWORK_ROOT = resolve(import.meta.dirname, \"../..\");\n\n/**\n * Find the project root by walking up from cwd until docu.json is found.\n * Falls back to cwd when not found (tests, ad-hoc scripts). Bun 1.4\n * `run --parallel --filter` may change cwd per workspace task, so a bare\n * process.cwd() can point at the wrong package.\n */\nfunction findProjectRoot(): string {\n let dir = process.cwd();\n for (let i = 0; i < 6; i++) {\n try {\n if (existsSync(join(dir, \"docu.json\"))) return dir;\n } catch {\n break;\n }\n const parent = resolve(dir, \"..\");\n if (parent === dir) break;\n dir = parent;\n }\n return process.cwd();\n}\nexport const PROJECT_ROOT = findProjectRoot();\n\n// Framework paths (internal)\nexport const PAGES_DIR = join(FRAMEWORK_ROOT, \".docu/pages\");\nexport const STYLES_DIR = join(FRAMEWORK_ROOT, \".docu/styles\");\nconst nodeDir = join(FRAMEWORK_ROOT, \".docu/node\");\nconst libDir = join(FRAMEWORK_ROOT, \".docu/lib\");\nexport const LIB_DIR = existsSync(nodeDir) ? nodeDir : libDir;\n\n// Build output (user project)\nexport const DIST_DIR = join(PROJECT_ROOT, \".docu/dist\");\nexport const ASSETS_DIR = join(DIST_DIR, \"assets\");\nexport const CACHE_FILE = join(PROJECT_ROOT, \".docu/build-cache.json\");\n\n// Project paths (user content)\nexport const DOCS_DIR = join(PROJECT_ROOT, \"docs\");\nexport const DOCS_ASSETS_DIR = join(PROJECT_ROOT, \"docs/assets\");\nexport const DOCU_CONFIG_PATH = join(PROJECT_ROOT, \"docu.json\");\n\n/** Resolve a project-relative file against the discovered PROJECT_ROOT. */\nexport function resolveProjectFile(...segments: string[]): string {\n return join(PROJECT_ROOT, ...segments);\n}\n\n// Config singleton\nlet _config: DocuConfig | null = null;\n\n/** Clean stale client bundles from a previous build. */\nexport async function cleanOldBundles(preserve?: Set<string>) {\n try {\n const files = await readdir(ASSETS_DIR);\n for (const file of files) {\n if (preserve?.has(file)) continue;\n if (/^(client|home-client|docs|site)[.-]/.test(file)) {\n await unlink(join(ASSETS_DIR, file));\n }\n }\n await rm(join(ASSETS_DIR, \"chunks\"), { recursive: true, force: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n console.error(\"Failed to clean old bundles:\", (err as Error).message);\n }\n }\n}\n\nexport function loadDocuConfig(): DocuConfig {\n if (_config) return _config;\n if (!existsSync(DOCU_CONFIG_PATH)) {\n throw new Error(`docu.json not found at ${DOCU_CONFIG_PATH}`);\n }\n _config = JSON.parse(readFileSync(DOCU_CONFIG_PATH, \"utf-8\"));\n return _config!;\n}\n"],"mappings":";;;;;;;;AAWA,IAAa,iBAAiB,QAAQ,YAAY,SAAS,OAAO;;;;;;;AAQlE,SAAS,kBAA0B;CACjC,IAAI,MAAM,QAAQ,IAAI;CACtB,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,IAAI;GACF,IAAI,WAAW,KAAK,KAAK,WAAW,CAAC,GAAG,OAAO;EACjD,QAAQ;GACN;EACF;EACA,MAAM,SAAS,QAAQ,KAAK,IAAI;EAChC,IAAI,WAAW,KAAK;EACpB,MAAM;CACR;CACA,OAAO,QAAQ,IAAI;AACrB;AACA,IAAa,eAAe,gBAAgB;AAG5C,IAAa,YAAY,KAAK,gBAAgB,aAAa;AAC3D,IAAa,aAAa,KAAK,gBAAgB,cAAc;AAC7D,IAAM,UAAU,KAAK,gBAAgB,YAAY;AACjD,IAAM,SAAS,KAAK,gBAAgB,WAAW;AAC/C,IAAa,UAAU,WAAW,OAAO,IAAI,UAAU;AAGvD,IAAa,WAAW,KAAK,cAAc,YAAY;AACvD,IAAa,aAAa,KAAK,UAAU,QAAQ;AACjD,IAAa,aAAa,KAAK,cAAc,wBAAwB;AAGrE,IAAa,WAAW,KAAK,cAAc,MAAM;AACjD,IAAa,kBAAkB,KAAK,cAAc,aAAa;AAC/D,IAAa,mBAAmB,KAAK,cAAc,WAAW;;AAG9D,SAAgB,mBAAmB,GAAG,UAA4B;CAChE,OAAO,KAAK,cAAc,GAAG,QAAQ;AACvC;AAGA,IAAI,UAA6B;;AAGjC,eAAsB,gBAAgB,UAAwB;CAC5D,IAAI;EACF,MAAM,QAAQ,MAAM,QAAQ,UAAU;EACtC,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,UAAU,IAAI,IAAI,GAAG;GACzB,IAAI,sCAAsC,KAAK,IAAI,GACjD,MAAM,OAAO,KAAK,YAAY,IAAI,CAAC;EAEvC;EACA,MAAM,GAAG,KAAK,YAAY,QAAQ,GAAG;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACvE,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAC1C,QAAQ,MAAM,gCAAiC,IAAc,OAAO;CAExE;AACF;AAEA,SAAgB,iBAA6B;CAC3C,IAAI,SAAS,OAAO;CACpB,IAAI,CAAC,WAAW,gBAAgB,GAC9B,MAAM,IAAI,MAAM,0BAA0B,kBAAkB;CAE9D,UAAU,KAAK,MAAM,aAAa,kBAAkB,OAAO,CAAC;CAC5D,OAAO;AACT"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as DIST_DIR } from "./paths-
|
|
1
|
+
import { r as DIST_DIR } from "./paths-Cbz-dj4s.js";
|
|
2
2
|
import { a as getContentType, d as cspHeader, h as injectNonce, p as generateNonce, u as SECURITY_HEADERS } from "./utils-B_CoyKie.js";
|
|
3
|
-
import { t as logger } from "./logger-
|
|
3
|
+
import { t as logger } from "./logger-DLU3A-z2.js";
|
|
4
4
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
@@ -74,4 +74,4 @@ async function runPreview(adapter) {
|
|
|
74
74
|
//#endregion
|
|
75
75
|
export { runPreview as t };
|
|
76
76
|
|
|
77
|
-
//# sourceMappingURL=preview.impl-
|
|
77
|
+
//# sourceMappingURL=preview.impl-CaZTgmnB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.impl-
|
|
1
|
+
{"version":3,"file":"preview.impl-CaZTgmnB.js","names":[],"sources":["../node/preview.impl.ts"],"sourcesContent":["/**\n * Runtime-neutral preview server — mirror of `preview.ts` (Bun-only,\n * protected) driven by a `RuntimeAdapter` and `node:fs` file reads.\n */\n\nimport { existsSync, statSync, readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { logger } from \"./logger\";\nimport { DIST_DIR } from \"./paths\";\nimport { getContentType } from \"./utils\";\nimport { SECURITY_HEADERS, generateNonce, cspHeader, injectNonce } from \"./security\";\n\nfunction resolveFile(pathname: string): string | null {\n const path = pathname.slice(1);\n const isWithinDist = (p: string) => p === DIST_DIR || p.startsWith(DIST_DIR + \"/\");\n\n if (!path.includes(\".\")) {\n const withIndex = resolve(DIST_DIR, path, \"index.html\");\n if (isWithinDist(withIndex) && existsSync(withIndex)) return withIndex;\n const withHtml = resolve(DIST_DIR, path + \".html\");\n if (isWithinDist(withHtml) && existsSync(withHtml)) return withHtml;\n }\n\n const exact = resolve(DIST_DIR, path);\n if (!isWithinDist(exact)) return null;\n try {\n if (statSync(exact).isFile()) return exact;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n return null;\n}\n\nexport async function runPreview(adapter: RuntimeAdapter): Promise<ServerHandle | null> {\n const PORT = parseInt(process.env.PORT || \"4173\", 10);\n\n logger.buildStart();\n\n if (!existsSync(DIST_DIR)) {\n logger.spinner.start(\"Checking build output...\");\n logger.spinner.info(\"dist not found. Run \\x1b[1mflame build\\x1b[0m first.\");\n process.exit(0);\n }\n\n const notFoundPath = resolve(DIST_DIR, \"404.html\");\n\n const handle = await adapter.serve(\n async (req) => {\n const url = new URL(req.url);\n const pathname = decodeURIComponent(url.pathname);\n\n const filePath = resolveFile(pathname);\n\n if (filePath) {\n const contentType = getContentType(filePath);\n if (contentType === \"text/html\") {\n const nonce = generateNonce();\n const html = await readFile(filePath, \"utf-8\");\n const modified = injectNonce(html, nonce);\n return new Response(modified, {\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": cspHeader(nonce),\n },\n });\n }\n return new Response(readFileSync(filePath), {\n headers: { \"Content-Type\": contentType },\n });\n }\n\n if (existsSync(notFoundPath)) {\n const nonce = generateNonce();\n const html = await readFile(notFoundPath, \"utf-8\");\n const modified = injectNonce(html, nonce);\n return new Response(modified, {\n status: 404,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": cspHeader(nonce),\n },\n });\n }\n\n return new Response(\"404 - Not Found\", { status: 404 });\n },\n { port: PORT }\n );\n\n logger.ready(handle.port);\n return handle;\n}\n"],"mappings":";;;;;;;;;;;AAcA,SAAS,YAAY,UAAiC;CACpD,MAAM,OAAO,SAAS,MAAM,CAAC;CAC7B,MAAM,gBAAgB,MAAc,MAAM,YAAY,EAAE,WAAW,WAAW,GAAG;CAEjF,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG;EACvB,MAAM,YAAY,QAAQ,UAAU,MAAM,YAAY;EACtD,IAAI,aAAa,SAAS,KAAK,WAAW,SAAS,GAAG,OAAO;EAC7D,MAAM,WAAW,QAAQ,UAAU,OAAO,OAAO;EACjD,IAAI,aAAa,QAAQ,KAAK,WAAW,QAAQ,GAAG,OAAO;CAC7D;CAEA,MAAM,QAAQ,QAAQ,UAAU,IAAI;CACpC,IAAI,CAAC,aAAa,KAAK,GAAG,OAAO;CACjC,IAAI;EACF,IAAI,SAAS,KAAK,CAAC,CAAC,OAAO,GAAG,OAAO;CACvC,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAAU,MAAM;CAC9D;CACA,OAAO;AACT;AAEA,eAAsB,WAAW,SAAuD;CACtF,MAAM,OAAO,SAAS,QAAQ,IAAI,QAAQ,QAAQ,EAAE;CAEpD,OAAO,WAAW;CAElB,IAAI,CAAC,WAAW,QAAQ,GAAG;EACzB,OAAO,QAAQ,MAAM,0BAA0B;EAC/C,OAAO,QAAQ,KAAK,sDAAsD;EAC1E,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,eAAe,QAAQ,UAAU,UAAU;CAEjD,MAAM,SAAS,MAAM,QAAQ,MAC3B,OAAO,QAAQ;EACb,MAAM,MAAM,IAAI,IAAI,IAAI,GAAG;EAG3B,MAAM,WAAW,YAFA,mBAAmB,IAAI,QAEX,CAAQ;EAErC,IAAI,UAAU;GACZ,MAAM,cAAc,eAAe,QAAQ;GAC3C,IAAI,gBAAgB,aAAa;IAC/B,MAAM,QAAQ,cAAc;IAC5B,MAAM,OAAO,MAAM,SAAS,UAAU,OAAO;IAC7C,MAAM,WAAW,YAAY,MAAM,KAAK;IACxC,OAAO,IAAI,SAAS,UAAU,EAC5B,SAAS;KACP,gBAAgB;KAChB,GAAG;KACH,2BAA2B,UAAU,KAAK;IAC5C,EACF,CAAC;GACH;GACA,OAAO,IAAI,SAAS,aAAa,QAAQ,GAAG,EAC1C,SAAS,EAAE,gBAAgB,YAAY,EACzC,CAAC;EACH;EAEA,IAAI,WAAW,YAAY,GAAG;GAC5B,MAAM,QAAQ,cAAc;GAC5B,MAAM,OAAO,MAAM,SAAS,cAAc,OAAO;GACjD,MAAM,WAAW,YAAY,MAAM,KAAK;GACxC,OAAO,IAAI,SAAS,UAAU;IAC5B,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,GAAG;KACH,2BAA2B,UAAU,KAAK;IAC5C;GACF,CAAC;EACH;EAEA,OAAO,IAAI,SAAS,mBAAmB,EAAE,QAAQ,IAAI,CAAC;CACxD,GACA,EAAE,MAAM,KAAK,CACf;CAEA,OAAO,MAAM,OAAO,IAAI;CACxB,OAAO;AACT"}
|
package/.docu/lib/server.deno.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as DOCS_DIR, c as
|
|
2
|
-
import { D as loadPlugins, E as BuildPluginBuilder, a as IndexPage, b as buildClientBundle, c as captureException, f as compileMdx, i as DocsLayout, l as initSentry, m as frontmatterField, n as hmrScript, o as NotFoundPage, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as computeInlineThemeCss } from "./html.shared-
|
|
1
|
+
import { a as DOCS_DIR, c as PAGES_DIR, f as loadDocuConfig, l as PROJECT_ROOT, r as DIST_DIR } from "./paths-Cbz-dj4s.js";
|
|
2
|
+
import { D as loadPlugins, E as BuildPluginBuilder, a as IndexPage, b as buildClientBundle, c as captureException, f as compileMdx, i as DocsLayout, l as initSentry, m as frontmatterField, n as hmrScript, o as NotFoundPage, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as computeInlineThemeCss } from "./html.shared-CsEFmy_Y.js";
|
|
3
3
|
import { _ as isSlugSafe, a as getContentType, g as isPathSafe, l as stripDocsHtmlSuffix, m as htmlResponse, p as generateNonce, u as SECURITY_HEADERS, y as wrapPluginResponse } from "./utils-B_CoyKie.js";
|
|
4
|
-
import { t as logger } from "./logger-
|
|
4
|
+
import { t as logger } from "./logger-DLU3A-z2.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { existsSync, readFileSync, statSync, watch } from "node:fs";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -9,22 +9,32 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
9
9
|
import React from "react";
|
|
10
10
|
import { renderToString } from "react-dom/server";
|
|
11
11
|
//#region .docu/node/server-routes.ts
|
|
12
|
-
function createHtmlResponse(title, description, body, status, state, depth = 0) {
|
|
12
|
+
async function createHtmlResponse(title, description, body, status, state, pageType, page, depth = 0) {
|
|
13
13
|
const nonce = generateNonce();
|
|
14
14
|
const favicon = state.docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
15
|
-
const
|
|
15
|
+
const assets = state.assetManifest[pageType];
|
|
16
|
+
const context = {
|
|
17
|
+
...page,
|
|
18
|
+
pageType,
|
|
19
|
+
assets,
|
|
20
|
+
config: state.docuConfig
|
|
21
|
+
};
|
|
22
|
+
let html = htmlShell({
|
|
16
23
|
title,
|
|
17
24
|
description,
|
|
18
25
|
body,
|
|
19
26
|
favicon,
|
|
20
|
-
css:
|
|
21
|
-
js:
|
|
27
|
+
css: assets.css,
|
|
28
|
+
js: assets.js,
|
|
22
29
|
nonce,
|
|
23
30
|
extraScripts: hmrScript(nonce),
|
|
24
31
|
themeCss: state.inlineThemeCss,
|
|
32
|
+
headExtra: state.builder?.collectHead(context),
|
|
33
|
+
bodyExtra: state.builder?.collectBody(context),
|
|
25
34
|
depth,
|
|
26
35
|
absoluteAssets: status === 404
|
|
27
36
|
});
|
|
37
|
+
if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);
|
|
28
38
|
/** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */
|
|
29
39
|
return htmlResponse(html, nonce, status, true);
|
|
30
40
|
}
|
|
@@ -66,7 +76,7 @@ async function getDocsForSlug(slug, state) {
|
|
|
66
76
|
const result = await compileMdx(content, relPath, void 0, remarkPlugins, rehypePlugins);
|
|
67
77
|
let frontmatter = result.frontmatter;
|
|
68
78
|
if (state.builder) frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {
|
|
69
|
-
slug
|
|
79
|
+
slug,
|
|
70
80
|
filePath: relPath,
|
|
71
81
|
content
|
|
72
82
|
});
|
|
@@ -103,44 +113,19 @@ async function renderDocsServerPage(doc, slug, pathname, state) {
|
|
|
103
113
|
}));
|
|
104
114
|
const body = renderToString(page);
|
|
105
115
|
const depth = slug.length || 1;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
config: state.docuConfig
|
|
113
|
-
};
|
|
114
|
-
const headExtra = state.builder.collectHead(ctx);
|
|
115
|
-
const bodyExtra = state.builder.collectBody(ctx);
|
|
116
|
-
const nonce = generateNonce();
|
|
117
|
-
const favicon = state.docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
118
|
-
let html = htmlShell({
|
|
119
|
-
title,
|
|
120
|
-
description,
|
|
121
|
-
body,
|
|
122
|
-
favicon,
|
|
123
|
-
css: state.assetManifest.css,
|
|
124
|
-
js: state.assetManifest.js,
|
|
125
|
-
nonce,
|
|
126
|
-
extraScripts: hmrScript(nonce),
|
|
127
|
-
themeCss: state.inlineThemeCss,
|
|
128
|
-
headExtra,
|
|
129
|
-
bodyExtra,
|
|
130
|
-
depth
|
|
131
|
-
});
|
|
132
|
-
html = await state.builder.runTransformHtmlChain(html, ctx);
|
|
133
|
-
/** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */
|
|
134
|
-
return htmlResponse(html, nonce, 200, true);
|
|
135
|
-
}
|
|
136
|
-
return createHtmlResponse(title, description, body, 200, state, depth);
|
|
116
|
+
return createHtmlResponse(title, description, body, 200, state, "docs", {
|
|
117
|
+
slug: slug.join("/"),
|
|
118
|
+
filePath: doc.filePath,
|
|
119
|
+
frontmatter: doc.frontmatter,
|
|
120
|
+
content: doc.resolvedContent
|
|
121
|
+
}, depth);
|
|
137
122
|
}
|
|
138
|
-
function renderPage(Component, title, description, status, state, props = {}, depth = 0) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
123
|
+
async function renderPage(Component, title, description, status, state, props = {}, depth = 0) {
|
|
124
|
+
return createHtmlResponse(title, description, renderToString(React.createElement(Component, props)), status, state, "notFound", {
|
|
125
|
+
slug: "404",
|
|
126
|
+
filePath: resolve(PAGES_DIR, "404.tsx"),
|
|
127
|
+
frontmatter: {}
|
|
128
|
+
}, depth);
|
|
144
129
|
}
|
|
145
130
|
async function handleDocsIndex(state) {
|
|
146
131
|
const doc = await getDocsForSlug("", state);
|
|
@@ -153,12 +138,16 @@ async function handleDocsRoute(slug, state) {
|
|
|
153
138
|
if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, slug.length || 1);
|
|
154
139
|
return renderDocsServerPage(doc, slug, `/docs/${path}`, state);
|
|
155
140
|
}
|
|
156
|
-
function handleIndex(state) {
|
|
141
|
+
async function handleIndex(state) {
|
|
157
142
|
const page = React.createElement(IndexPage);
|
|
158
143
|
const body = renderToString(page);
|
|
159
|
-
return createHtmlResponse(state.docuConfig.meta?.title || "DocuBook", state.docuConfig.meta?.description || "", body, 200, state
|
|
144
|
+
return createHtmlResponse(state.docuConfig.meta?.title || "DocuBook", state.docuConfig.meta?.description || "", body, 200, state, "home", {
|
|
145
|
+
slug: "",
|
|
146
|
+
filePath: resolve(PAGES_DIR, "index.tsx"),
|
|
147
|
+
frontmatter: { ...state.docuConfig.meta }
|
|
148
|
+
});
|
|
160
149
|
}
|
|
161
|
-
function handleNotFound(state, depth = 0) {
|
|
150
|
+
async function handleNotFound(state, depth = 0) {
|
|
162
151
|
return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, depth);
|
|
163
152
|
}
|
|
164
153
|
function serveStatic(pathname) {
|
|
@@ -230,6 +219,7 @@ async function runServer(adapter) {
|
|
|
230
219
|
if (builder) {
|
|
231
220
|
const plugins = await loadPlugins(pluginsConfig);
|
|
232
221
|
for (const plugin of plugins) await plugin.setup(builder);
|
|
222
|
+
await builder.runOnStart();
|
|
233
223
|
}
|
|
234
224
|
const state = {
|
|
235
225
|
docuConfig,
|
|
@@ -298,7 +288,8 @@ async function runServer(adapter) {
|
|
|
298
288
|
if (builder) {
|
|
299
289
|
const pluginResponse = await builder.runHandleRequest(req, {
|
|
300
290
|
port: handle?.port ?? PORT,
|
|
301
|
-
hostname: handle?.hostname ?? "localhost"
|
|
291
|
+
hostname: handle?.hostname ?? "localhost",
|
|
292
|
+
assetManifest: state.assetManifest
|
|
302
293
|
});
|
|
303
294
|
if (pluginResponse) {
|
|
304
295
|
const securedResponse = wrapPluginResponse(pluginResponse, true);
|
|
@@ -328,12 +319,12 @@ async function runServer(adapter) {
|
|
|
328
319
|
if (staticRes) return staticRes;
|
|
329
320
|
}
|
|
330
321
|
let response;
|
|
331
|
-
if (pathname === "/") response = handleIndex(state);
|
|
322
|
+
if (pathname === "/") response = await handleIndex(state);
|
|
332
323
|
else if (pathname === "/docs" || pathname === "/docs/") response = await handleDocsIndex(state);
|
|
333
324
|
else if (pathname.startsWith("/docs/")) {
|
|
334
325
|
const slug = pathname.slice(6).split("/").filter(Boolean);
|
|
335
326
|
response = slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);
|
|
336
|
-
} else response = handleNotFound(state);
|
|
327
|
+
} else response = await handleNotFound(state);
|
|
337
328
|
logger.request(req.method, pathname, response.status, Math.round(performance.now() - startTime));
|
|
338
329
|
return response;
|
|
339
330
|
} catch (err) {
|
|
@@ -355,4 +346,4 @@ async function runServer(adapter) {
|
|
|
355
346
|
//#endregion
|
|
356
347
|
export { runServer as t };
|
|
357
348
|
|
|
358
|
-
//# sourceMappingURL=server.impl-
|
|
349
|
+
//# sourceMappingURL=server.impl-D-CgD1bn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.impl-D-CgD1bn.js","names":[],"sources":["../node/server-routes.ts","../node/server.impl.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport React, { type ReactNode } from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport { compileMdx, frontmatterField } from \"./mdx\";\nimport { DEFAULT_FAVICON, getContentType } from \"./utils\";\nimport { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT } from \"./paths\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport type { PageContext, PageType } from \"./plugin\";\nimport type { AssetEntry, AssetManifest, DocuConfig, TocItem } from \"./types\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport NotFoundPage from \"../pages/404\";\nimport IndexPage from \"../pages/index\";\nimport { DocsLayout } from \"../components/DocsLayout\";\nimport { generateNonce, isPathSafe, isSlugSafe, htmlResponse, SECURITY_HEADERS } from \"./security\";\nimport { htmlShell as createHtmlShell, hmrScript, errorHtml } from \"./html.shared\";\n\nexport interface ServerState {\n docuConfig: DocuConfig;\n assetManifest: AssetManifest;\n inlineThemeCss?: string;\n builder: BuildPluginBuilder | null;\n}\n\nasync function createHtmlResponse(\n title: string,\n description: string,\n body: string,\n status: number,\n state: ServerState,\n pageType: PageType,\n page: Omit<PageContext, \"pageType\" | \"assets\" | \"config\">,\n depth = 0\n): Promise<Response> {\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const assets: AssetEntry = state.assetManifest[pageType];\n const context: PageContext = {\n ...page,\n pageType,\n assets,\n config: state.docuConfig,\n };\n let html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: assets.css,\n js: assets.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n headExtra: state.builder?.collectHead(context),\n bodyExtra: state.builder?.collectBody(context),\n depth,\n // 404 pages can be requested at arbitrary depths (e.g. a noLink section\n // path typed in the address bar) — relative asset paths would resolve\n // against the wrong directory and break CSS/JS.\n absoluteAssets: status === 404,\n });\n if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);\n /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */\n return htmlResponse(html, nonce, status, true);\n}\n\n// Dev-only memo: compileMdx per request is ~70ms; repeat navigations with\n// an unchanged file (same mtime) reuse the compiled result. The dev server\n// has no other per-page cache — without this every navigation re-parses MDX.\n// ponytail: unbounded Map is fine — docs sites have a handful of pages; cap\n// with an LRU only if a project exceeds thousands of routes.\nconst docsCache = new Map<\n string,\n { mtimeMs: number; doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>> }\n>();\n\nasync function getDocsForSlug(\n slug: string,\n state: ServerState\n): Promise<{\n content: ReactNode;\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n tocs: TocItem[];\n filePath: string;\n resolvedContent: string;\n} | null> {\n if (!isSlugSafe(slug, DOCS_DIR)) return null;\n\n const paths = [\n resolve(DOCS_DIR, slug, \"index.mdx\"),\n resolve(DOCS_DIR, `${slug}.mdx`),\n resolve(DOCS_DIR, slug, \"index.md\"),\n resolve(DOCS_DIR, `${slug}.md`),\n ];\n\n const resolvedDocsDir = resolve(DOCS_DIR);\n let filePath: string | null = null;\n let raw: string | null = null;\n for (const p of paths) {\n const resolvedCandidate = resolve(p);\n if (\n resolvedCandidate !== resolvedDocsDir &&\n !resolvedCandidate.startsWith(resolvedDocsDir + \"/\")\n ) {\n continue;\n }\n try {\n raw = await readFile(resolvedCandidate, \"utf-8\");\n filePath = resolvedCandidate;\n break;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n if (!filePath || !raw) return null;\n\n const relPath = filePath.replace(PROJECT_ROOT + \"/\", \"\");\n\n const mtimeMs = (await stat(filePath)).mtimeMs;\n const cached = docsCache.get(relPath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.doc;\n }\n\n let content = raw;\n if (state.builder) {\n const transformed = await state.builder.runOnLoad(relPath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n const remarkPlugins = state.builder?.collectRemarkPlugins();\n const rehypePlugins = state.builder?.collectRehypePlugins();\n const result = await compileMdx(content, relPath, undefined, remarkPlugins, rehypePlugins);\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (state.builder) {\n frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {\n slug,\n filePath: relPath,\n content,\n });\n }\n\n const doc = {\n content: result.content,\n compiledSource: result.compiledSource,\n frontmatter,\n tocs: result.tocs,\n filePath: relPath,\n resolvedContent: content,\n };\n docsCache.set(relPath, { mtimeMs, doc });\n return doc;\n}\n\nasync function renderDocsServerPage(\n doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>>,\n slug: string[],\n pathname: string,\n state: ServerState\n): Promise<Response> {\n const title = frontmatterField(doc.frontmatter, \"title\") || slug.join(\"/\") || \"Docs\";\n const description = frontmatterField(doc.frontmatter, \"description\");\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname },\n React.createElement(DocsPage, {\n slug,\n title,\n description,\n date: frontmatterField(doc.frontmatter, \"date\") || undefined,\n // Same root-relative SSR as build — see build.ts.\n content: renderToString(doc.content),\n tocs: doc.tocs,\n filePath: doc.filePath,\n repoUrl: state.docuConfig.repo?.url,\n compiledSource: doc.compiledSource,\n })\n );\n\n const body = renderToString(page);\n\n // Match build.ts depth calculation: slug.split(\"/\").length, fallback to 1 for empty\n const depth = slug.length || 1;\n\n return createHtmlResponse(\n title,\n description,\n body,\n 200,\n state,\n \"docs\",\n {\n slug: slug.join(\"/\"),\n filePath: doc.filePath,\n frontmatter: doc.frontmatter,\n content: doc.resolvedContent,\n },\n depth\n );\n}\n\nasync function renderPage(\n Component: React.ComponentType<Record<string, unknown>>,\n title: string,\n description: string,\n status: number,\n state: ServerState,\n props: Record<string, unknown> = {},\n depth = 0\n): Promise<Response> {\n const body = renderToString(React.createElement(Component, props));\n return createHtmlResponse(\n title,\n description,\n body,\n status,\n state,\n \"notFound\",\n {\n slug: \"404\",\n filePath: resolve(PAGES_DIR, \"404.tsx\"),\n frontmatter: {},\n },\n depth\n );\n}\n\nexport async function handleDocsIndex(state: ServerState): Promise<Response> {\n const doc = await getDocsForSlug(\"\", state);\n if (!doc) return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, 1);\n return renderDocsServerPage(doc, [], \"/docs\", state);\n}\n\nexport async function handleDocsRoute(slug: string[], state: ServerState): Promise<Response> {\n const path = slug.join(\"/\");\n const doc = await getDocsForSlug(path, state);\n if (!doc)\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, slug.length || 1);\n return renderDocsServerPage(doc, slug, `/docs/${path}`, state);\n}\n\nexport async function handleIndex(state: ServerState): Promise<Response> {\n const page = React.createElement(IndexPage);\n const body = renderToString(page);\n return createHtmlResponse(\n state.docuConfig.meta?.title || \"DocuBook\",\n state.docuConfig.meta?.description || \"\",\n body,\n 200,\n state,\n \"home\",\n {\n slug: \"\",\n filePath: resolve(PAGES_DIR, \"index.tsx\"),\n frontmatter: { ...state.docuConfig.meta } as Record<string, unknown>,\n }\n );\n}\n\nexport async function handleNotFound(state: ServerState, depth = 0): Promise<Response> {\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, depth);\n}\n\nexport function serveStatic(pathname: string): Response | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (!isPathSafe(pathname, DIST_DIR)) return null;\n const assetPath = resolve(DIST_DIR, decoded.slice(1));\n try {\n const s = statSync(assetPath);\n if (s.isFile()) {\n return new Response(readFileSync(assetPath), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n\n if (decoded.startsWith(\"/docs/assets/\")) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(\"/docs/assets/\".length);\n const docsAsset = resolve(docsAssetsDir, requestedRelative);\n const docsAssetsDirWithSep = docsAssetsDir.endsWith(\"/\") ? docsAssetsDir : docsAssetsDir + \"/\";\n if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;\n try {\n const s = statSync(docsAsset);\n if (s.isFile()) {\n return new Response(readFileSync(docsAsset), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n return null;\n}\n\nexport function serverErrorResponse(error: unknown): Response {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n const st =\n process.env.NODE_ENV !== \"production\" && error instanceof Error ? error.stack : undefined;\n return new Response(errorHtml(msg, st), {\n status: 500,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": \"default-src 'none'; style-src 'unsafe-inline'\",\n },\n });\n}\n","/**\n * Runtime-neutral dev server — mirror of `server.ts` (Bun-only, protected)\n * driven by a `RuntimeAdapter` (./runtime) instead of `Bun.serve`,\n * with manual route matching instead of `Bun.FileSystemRouter`. The page set\n * is static (`/`, `/docs/[[...slug]]`, `/404`), so a router is unnecessary.\n */\n\nimport { watch, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { DOCS_DIR, loadDocuConfig } from \"./paths\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport {\n serveStatic,\n handleDocsIndex,\n handleDocsRoute,\n handleIndex,\n handleNotFound,\n serverErrorResponse,\n type ServerState,\n} from \"./server-routes\";\nimport { wrapPluginResponse } from \"./security\";\nimport { stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n\n const parsedPort = parseInt(process.env.PORT ?? \"3000\", 10);\n const PORT =\n Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3000;\n\n logger.buildStart();\n\n await initSentry();\n\n logger.bundleStart();\n let t = performance.now();\n const assetManifest = await buildClientBundle();\n logger.bundleDone(Math.round(performance.now() - t));\n\n const inlineThemeCss = computeInlineThemeCss();\n\n logger.indexStart();\n t = performance.now();\n const records = await generateSearchIndex();\n logger.indexDone(records, Math.round(performance.now() - t));\n\n logger.routes();\n\n // Plugin setup — all hooks active (onLoad, remark/rehype, frontmatter, head/body, html transform, handleRequest)\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n await builder.runOnStart();\n }\n\n const state: ServerState = {\n docuConfig,\n assetManifest,\n inlineThemeCss,\n builder,\n };\n\n const hmrClients = new Set<ReadableStreamDefaultController>();\n\n const require = createRequire(import.meta.url);\n /** Locate the installed @docubook/markdown source dir (workspace link), if any. */\n function resolveMdxContentSrc(): string | null {\n try {\n const pkgDir = dirname(require.resolve(\"@docubook/markdown/package.json\"));\n const src = join(pkgDir, \"src\");\n return existsSync(src) ? src : null;\n } catch {\n return null;\n }\n }\n\n let hmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const watcher = watch(DOCS_DIR, { recursive: true }, (_event, filename) => {\n if (!filename || (!filename.endsWith(\".mdx\") && !filename.endsWith(\".md\"))) return;\n if (hmrTimeout) clearTimeout(hmrTimeout);\n hmrTimeout = setTimeout(() => {\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n });\n\n // Rebuild the client bundle when component sources change (e.g. the\n // @docubook/markdown workspace package) so interactive islands pick up\n // edits, then reload connected clients. MDX edits are handled by the watcher\n // above — no rebuild needed, content recompiles per request.\n let srcHmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const mdxContentSrc = resolveMdxContentSrc();\n const srcWatcher = mdxContentSrc\n ? watch(mdxContentSrc, { recursive: true }, (_event, filename) => {\n if (!filename || !/\\.(ts|tsx|js|jsx|css)$/.test(filename)) return;\n if (srcHmrTimeout) clearTimeout(srcHmrTimeout);\n srcHmrTimeout = setTimeout(async () => {\n try {\n state.assetManifest = await buildClientBundle();\n logger.warn(\"[hmr] client bundle rebuilt (component change)\");\n } catch (e) {\n logger.warn(\n `[hmr] bundle rebuild failed: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n })\n : null;\n\n process.on(\"SIGINT\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n\n let handle: ServerHandle | null = null;\n\n const fetchHandler = async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n // Generated links carry `.html` (matching the static build output);\n // route them to the same handler as their extensionless form.\n const pathname = stripDocsHtmlSuffix(url.pathname);\n const startTime = performance.now();\n\n if (builder) {\n const pluginResponse = await builder.runHandleRequest(req, {\n port: handle?.port ?? PORT,\n hostname: handle?.hostname ?? \"localhost\",\n assetManifest: state.assetManifest,\n });\n if (pluginResponse) {\n const securedResponse = wrapPluginResponse(pluginResponse, true);\n logger.request(\n req.method,\n pathname,\n securedResponse.status,\n Math.round(performance.now() - startTime)\n );\n return securedResponse;\n }\n }\n\n try {\n if (pathname === \"/__hmr\") {\n const stream = new ReadableStream({\n start(controller) {\n hmrClients.add(controller);\n controller.enqueue(new TextEncoder().encode(\"data: connected\\n\\n\"));\n },\n cancel(controller) {\n hmrClients.delete(controller);\n },\n });\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n }\n\n if (pathname.startsWith(\"/assets/\") || /\\.\\w+$/.test(pathname)) {\n const staticRes = serveStatic(pathname);\n if (staticRes) return staticRes;\n }\n\n let response: Response;\n\n // Manual route matching — same routes Bun.FileSystemRouter derives\n // from `.docu/pages/` (\"/\", \"/docs/[[...slug]]\", \"/404\").\n if (pathname === \"/\") {\n response = await handleIndex(state);\n } else if (pathname === \"/docs\" || pathname === \"/docs/\") {\n response = await handleDocsIndex(state);\n } else if (pathname.startsWith(\"/docs/\")) {\n const slug = pathname.slice(\"/docs/\".length).split(\"/\").filter(Boolean);\n response =\n slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);\n } else {\n response = await handleNotFound(state);\n }\n\n logger.request(\n req.method,\n pathname,\n response.status,\n Math.round(performance.now() - startTime)\n );\n return response;\n } catch (err) {\n captureException(err, { method: req.method, pathname });\n logger.request(req.method, pathname, 500, Math.round(performance.now() - startTime));\n return serverErrorResponse(err);\n }\n };\n\n handle = await adapter.serve(fetchHandler, { port: PORT, idleTimeout: 255 });\n\n logger.ready(handle.port, true);\n return handle;\n}\n"],"mappings":";;;;;;;;;;;AAyBA,eAAe,mBACb,OACA,aACA,MACA,QACA,OACA,UACA,MACA,QAAQ,GACW;CACnB,MAAM,QAAQ,cAAc;CAC5B,MAAM,UAAU,MAAM,WAAW,MAAM,WAAA;CACvC,MAAM,SAAqB,MAAM,cAAc;CAC/C,MAAM,UAAuB;EAC3B,GAAG;EACH;EACA;EACA,QAAQ,MAAM;CAChB;CACA,IAAI,OAAO,UAAgB;EACzB;EACA;EACA;EACA;EACA,KAAK,OAAO;EACZ,IAAI,OAAO;EACX;EACA,cAAc,UAAU,KAAK;EAC7B,UAAU,MAAM;EAChB,WAAW,MAAM,SAAS,YAAY,OAAO;EAC7C,WAAW,MAAM,SAAS,YAAY,OAAO;EAC7C;EAIA,gBAAgB,WAAW;CAC7B,CAAC;CACD,IAAI,MAAM,SAAS,OAAO,MAAM,MAAM,QAAQ,sBAAsB,MAAM,OAAO;;CAEjF,OAAO,aAAa,MAAM,OAAO,QAAQ,IAAI;AAC/C;AAOA,IAAM,4BAAY,IAAI,IAGpB;AAEF,eAAe,eACb,MACA,OAQQ;CACR,IAAI,CAAC,WAAW,MAAM,QAAQ,GAAG,OAAO;CAExC,MAAM,QAAQ;EACZ,QAAQ,UAAU,MAAM,WAAW;EACnC,QAAQ,UAAU,GAAG,KAAK,KAAK;EAC/B,QAAQ,UAAU,MAAM,UAAU;EAClC,QAAQ,UAAU,GAAG,KAAK,IAAI;CAChC;CAEA,MAAM,kBAAkB,QAAQ,QAAQ;CACxC,IAAI,WAA0B;CAC9B,IAAI,MAAqB;CACzB,KAAK,MAAM,KAAK,OAAO;EACrB,MAAM,oBAAoB,QAAQ,CAAC;EACnC,IACE,sBAAsB,mBACtB,CAAC,kBAAkB,WAAW,kBAAkB,GAAG,GAEnD;EAEF,IAAI;GACF,MAAM,MAAM,SAAS,mBAAmB,OAAO;GAC/C,WAAW;GACX;EACF,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAAU,MAAM;EAC9D;CACF;CACA,IAAI,CAAC,YAAY,CAAC,KAAK,OAAO;CAE9B,MAAM,UAAU,SAAS,QAAQ,eAAe,KAAK,EAAE;CAEvD,MAAM,WAAW,MAAM,KAAK,QAAQ,EAAA,CAAG;CACvC,MAAM,SAAS,UAAU,IAAI,OAAO;CACpC,IAAI,UAAU,OAAO,YAAY,SAC/B,OAAO,OAAO;CAGhB,IAAI,UAAU;CACd,IAAI,MAAM,SAAS;EACjB,MAAM,cAAc,MAAM,MAAM,QAAQ,UAAU,SAAS,OAAO;EAClE,IAAI,aAAa,UACf,UAAU,YAAY;CAE1B;CAEA,MAAM,gBAAgB,MAAM,SAAS,qBAAqB;CAC1D,MAAM,gBAAgB,MAAM,SAAS,qBAAqB;CAC1D,MAAM,SAAS,MAAM,WAAW,SAAS,SAAS,KAAA,GAAW,eAAe,aAAa;CAEzF,IAAI,cAAc,OAAO;CACzB,IAAI,MAAM,SACR,cAAc,MAAM,MAAM,QAAQ,6BAA6B,aAAa;EAC1E;EACA,UAAU;EACV;CACF,CAAC;CAGH,MAAM,MAAM;EACV,SAAS,OAAO;EAChB,gBAAgB,OAAO;EACvB;EACA,MAAM,OAAO;EACb,UAAU;EACV,iBAAiB;CACnB;CACA,UAAU,IAAI,SAAS;EAAE;EAAS;CAAI,CAAC;CACvC,OAAO;AACT;AAEA,eAAe,qBACb,KACA,MACA,UACA,OACmB;CACnB,MAAM,QAAQ,iBAAiB,IAAI,aAAa,OAAO,KAAK,KAAK,KAAK,GAAG,KAAK;CAC9E,MAAM,cAAc,iBAAiB,IAAI,aAAa,aAAa;CAEnE,MAAM,OAAO,MAAM,cACjB,YACA;EAAE,SAAS,MAAM,WAAW,MAAM;EAAK;CAAS,GAChD,MAAM,cAAc,UAAU;EAC5B;EACA;EACA;EACA,MAAM,iBAAiB,IAAI,aAAa,MAAM,KAAK,KAAA;EAEnD,SAAS,eAAe,IAAI,OAAO;EACnC,MAAM,IAAI;EACV,UAAU,IAAI;EACd,SAAS,MAAM,WAAW,MAAM;EAChC,gBAAgB,IAAI;CACtB,CAAC,CACH;CAEA,MAAM,OAAO,eAAe,IAAI;CAGhC,MAAM,QAAQ,KAAK,UAAU;CAE7B,OAAO,mBACL,OACA,aACA,MACA,KACA,OACA,QACA;EACE,MAAM,KAAK,KAAK,GAAG;EACnB,UAAU,IAAI;EACd,aAAa,IAAI;EACjB,SAAS,IAAI;CACf,GACA,KACF;AACF;AAEA,eAAe,WACb,WACA,OACA,aACA,QACA,OACA,QAAiC,CAAC,GAClC,QAAQ,GACW;CAEnB,OAAO,mBACL,OACA,aAHW,eAAe,MAAM,cAAc,WAAW,KAAK,CAI9D,GACA,QACA,OACA,YACA;EACE,MAAM;EACN,UAAU,QAAQ,WAAW,SAAS;EACtC,aAAa,CAAC;CAChB,GACA,KACF;AACF;AAEA,eAAsB,gBAAgB,OAAuC;CAC3E,MAAM,MAAM,MAAM,eAAe,IAAI,KAAK;CAC1C,IAAI,CAAC,KAAK,OAAO,WAAW,cAAc,mBAAmB,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC;CAClF,OAAO,qBAAqB,KAAK,CAAC,GAAG,SAAS,KAAK;AACrD;AAEA,eAAsB,gBAAgB,MAAgB,OAAuC;CAC3F,MAAM,OAAO,KAAK,KAAK,GAAG;CAC1B,MAAM,MAAM,MAAM,eAAe,MAAM,KAAK;CAC5C,IAAI,CAAC,KACH,OAAO,WAAW,cAAc,mBAAmB,IAAI,KAAK,OAAO,CAAC,GAAG,KAAK,UAAU,CAAC;CACzF,OAAO,qBAAqB,KAAK,MAAM,SAAS,QAAQ,KAAK;AAC/D;AAEA,eAAsB,YAAY,OAAuC;CACvE,MAAM,OAAO,MAAM,cAAc,SAAS;CAC1C,MAAM,OAAO,eAAe,IAAI;CAChC,OAAO,mBACL,MAAM,WAAW,MAAM,SAAS,YAChC,MAAM,WAAW,MAAM,eAAe,IACtC,MACA,KACA,OACA,QACA;EACE,MAAM;EACN,UAAU,QAAQ,WAAW,WAAW;EACxC,aAAa,EAAE,GAAG,MAAM,WAAW,KAAK;CAC1C,CACF;AACF;AAEA,eAAsB,eAAe,OAAoB,QAAQ,GAAsB;CACrF,OAAO,WAAW,cAAc,mBAAmB,IAAI,KAAK,OAAO,CAAC,GAAG,KAAK;AAC9E;AAEA,SAAgB,YAAY,UAAmC;CAC7D,IAAI;CACJ,IAAI;EACF,UAAU,mBAAmB,QAAQ;CACvC,QAAQ;EACN,OAAO;CACT;CACA,IAAI,CAAC,WAAW,UAAU,QAAQ,GAAG,OAAO;CAC5C,MAAM,YAAY,QAAQ,UAAU,QAAQ,MAAM,CAAC,CAAC;CACpD,IAAI;EAEF,IADU,SAAS,SACf,CAAA,CAAE,OAAO,GACX,OAAO,IAAI,SAAS,aAAa,SAAS,GAAG,EAC3C,SAAS,EAAE,gBAAgB,eAAe,QAAQ,EAAE,EACtD,CAAC;CAEL,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAAU,MAAM;CAC9D;CAEA,IAAI,QAAQ,WAAW,eAAe,GAAG;EACvC,MAAM,gBAAgB,QAAQ,UAAU,QAAQ;EAChD,MAAM,oBAAoB,QAAQ,MAAM,EAAsB;EAC9D,MAAM,YAAY,QAAQ,eAAe,iBAAiB;EAC1D,MAAM,uBAAuB,cAAc,SAAS,GAAG,IAAI,gBAAgB,gBAAgB;EAC3F,IAAI,cAAc,iBAAiB,CAAC,UAAU,WAAW,oBAAoB,GAAG,OAAO;EACvF,IAAI;GAEF,IADU,SAAS,SACf,CAAA,CAAE,OAAO,GACX,OAAO,IAAI,SAAS,aAAa,SAAS,GAAG,EAC3C,SAAS,EAAE,gBAAgB,eAAe,QAAQ,EAAE,EACtD,CAAC;EAEL,SAAS,KAAK;GACZ,IAAK,IAA8B,SAAS,UAAU,MAAM;EAC9D;CACF;CACA,OAAO;AACT;AAEA,SAAgB,oBAAoB,OAA0B;CAC5D,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU;CACrD,MAAM,KAAA,QAAA,IAAA,aACqB,gBAAgB,iBAAiB,QAAQ,MAAM,QAAQ,KAAA;CAClF,OAAO,IAAI,SAAS,UAAU,KAAK,EAAE,GAAG;EACtC,QAAQ;EACR,SAAS;GACP,gBAAgB;GAChB,GAAG;GACH,2BAA2B;EAC7B;CACF,CAAC;AACH;;;;;;;;;ACnSA,eAAsB,UAAU,SAAgD;CAC9E,MAAM,aAAa,eAAe;CAElC,MAAM,aAAa,SAAS,QAAQ,IAAI,QAAQ,QAAQ,EAAE;CAC1D,MAAM,OACJ,OAAO,UAAU,UAAU,KAAK,aAAa,KAAK,cAAc,QAAQ,aAAa;CAEvF,OAAO,WAAW;CAElB,MAAM,WAAW;CAEjB,OAAO,YAAY;CACnB,IAAI,IAAI,YAAY,IAAI;CACxB,MAAM,gBAAgB,MAAM,kBAAkB;CAC9C,OAAO,WAAW,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,CAAC;CAEnD,MAAM,iBAAiB,sBAAsB;CAE7C,OAAO,WAAW;CAClB,IAAI,YAAY,IAAI;CACpB,MAAM,UAAU,MAAM,oBAAoB;CAC1C,OAAO,UAAU,SAAS,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,CAAC;CAE3D,OAAO,OAAO;CAGd,MAAM,gBAAgB,WAAW,WAAW,CAAC;CAC7C,MAAM,UAAU,cAAc,SAAS,IAAI,IAAI,mBAAmB,UAAU,IAAI;CAEhF,IAAI,SAAS;EACX,MAAM,UAAU,MAAM,YAAY,aAAa;EAE/C,KAAK,MAAM,UAAU,SACnB,MAAM,OAAO,MAAM,OAAO;EAE5B,MAAM,QAAQ,WAAW;CAC3B;CAEA,MAAM,QAAqB;EACzB;EACA;EACA;EACA;CACF;CAEA,MAAM,6BAAa,IAAI,IAAqC;CAE5D,MAAM,UAAU,cAAc,YAAY,GAAG;;CAE7C,SAAS,uBAAsC;EAC7C,IAAI;GACF,MAAM,SAAS,QAAQ,QAAQ,QAAQ,iCAAiC,CAAC;GACzE,MAAM,MAAM,KAAK,QAAQ,KAAK;GAC9B,OAAO,WAAW,GAAG,IAAI,MAAM;EACjC,QAAQ;GACN,OAAO;EACT;CACF;CAEA,IAAI,aAAmD;CACvD,MAAM,UAAU,MAAM,UAAU,EAAE,WAAW,KAAK,IAAI,QAAQ,aAAa;EACzE,IAAI,CAAC,YAAa,CAAC,SAAS,SAAS,MAAM,KAAK,CAAC,SAAS,SAAS,KAAK,GAAI;EAC5E,IAAI,YAAY,aAAa,UAAU;EACvC,aAAa,iBAAiB;GAC5B,KAAK,MAAM,UAAU,CAAC,GAAG,UAAU,GACjC,IAAI;IACF,OAAO,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,kBAAkB,CAAC;GAC7D,QAAQ;IACN,WAAW,OAAO,MAAM;GAC1B;EAEJ,GAAG,GAAG;CACR,CAAC;CAMD,IAAI,gBAAsD;CAC1D,MAAM,gBAAgB,qBAAqB;CAC3C,MAAM,aAAa,gBACf,MAAM,eAAe,EAAE,WAAW,KAAK,IAAI,QAAQ,aAAa;EAC9D,IAAI,CAAC,YAAY,CAAC,yBAAyB,KAAK,QAAQ,GAAG;EAC3D,IAAI,eAAe,aAAa,aAAa;EAC7C,gBAAgB,WAAW,YAAY;GACrC,IAAI;IACF,MAAM,gBAAgB,MAAM,kBAAkB;IAC9C,OAAO,KAAK,gDAAgD;GAC9D,SAAS,GAAG;IACV,OAAO,KACL,gCAAgC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC3E;GACF;GACA,KAAK,MAAM,UAAU,CAAC,GAAG,UAAU,GACjC,IAAI;IACF,OAAO,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,kBAAkB,CAAC;GAC7D,QAAQ;IACN,WAAW,OAAO,MAAM;GAC1B;EAEJ,GAAG,GAAG;CACR,CAAC,IACD;CAEJ,QAAQ,GAAG,gBAAgB;EACzB,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,QAAQ,KAAK,CAAC;CAChB,CAAC;CACD,QAAQ,GAAG,iBAAiB;EAC1B,QAAQ,MAAM;EACd,YAAY,MAAM;EAClB,QAAQ,KAAK,CAAC;CAChB,CAAC;CAED,IAAI,SAA8B;CAElC,MAAM,eAAe,OAAO,QAAoC;EAC9D,MAAM,MAAM,IAAI,IAAI,IAAI,GAAG;EAG3B,MAAM,WAAW,oBAAoB,IAAI,QAAQ;EACjD,MAAM,YAAY,YAAY,IAAI;EAElC,IAAI,SAAS;GACX,MAAM,iBAAiB,MAAM,QAAQ,iBAAiB,KAAK;IACzD,MAAM,QAAQ,QAAQ;IACtB,UAAU,QAAQ,YAAY;IAC9B,eAAe,MAAM;GACvB,CAAC;GACD,IAAI,gBAAgB;IAClB,MAAM,kBAAkB,mBAAmB,gBAAgB,IAAI;IAC/D,OAAO,QACL,IAAI,QACJ,UACA,gBAAgB,QAChB,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS,CAC1C;IACA,OAAO;GACT;EACF;EAEA,IAAI;GACF,IAAI,aAAa,UAAU;IACzB,MAAM,SAAS,IAAI,eAAe;KAChC,MAAM,YAAY;MAChB,WAAW,IAAI,UAAU;MACzB,WAAW,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,qBAAqB,CAAC;KACpE;KACA,OAAO,YAAY;MACjB,WAAW,OAAO,UAAU;KAC9B;IACF,CAAC;IACD,OAAO,IAAI,SAAS,QAAQ,EAC1B,SAAS;KACP,gBAAgB;KAChB,iBAAiB;KACjB,YAAY;IACd,EACF,CAAC;GACH;GAEA,IAAI,SAAS,WAAW,UAAU,KAAK,SAAS,KAAK,QAAQ,GAAG;IAC9D,MAAM,YAAY,YAAY,QAAQ;IACtC,IAAI,WAAW,OAAO;GACxB;GAEA,IAAI;GAIJ,IAAI,aAAa,KACf,WAAW,MAAM,YAAY,KAAK;QAC7B,IAAI,aAAa,WAAW,aAAa,UAC9C,WAAW,MAAM,gBAAgB,KAAK;QACjC,IAAI,SAAS,WAAW,QAAQ,GAAG;IACxC,MAAM,OAAO,SAAS,MAAM,CAAe,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;IACtE,WACE,KAAK,WAAW,IAAI,MAAM,gBAAgB,KAAK,IAAI,MAAM,gBAAgB,MAAM,KAAK;GACxF,OACE,WAAW,MAAM,eAAe,KAAK;GAGvC,OAAO,QACL,IAAI,QACJ,UACA,SAAS,QACT,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS,CAC1C;GACA,OAAO;EACT,SAAS,KAAK;GACZ,iBAAiB,KAAK;IAAE,QAAQ,IAAI;IAAQ;GAAS,CAAC;GACtD,OAAO,QAAQ,IAAI,QAAQ,UAAU,KAAK,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS,CAAC;GACnF,OAAO,oBAAoB,GAAG;EAChC;CACF;CAEA,SAAS,MAAM,QAAQ,MAAM,cAAc;EAAE,MAAM;EAAM,aAAa;CAAI,CAAC;CAE3E,OAAO,MAAM,OAAO,MAAM,IAAI;CAC9B,OAAO;AACT"}
|
package/.docu/lib/server.node.js
CHANGED
package/.docu/node/build.impl.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
CACHE_FILE,
|
|
30
30
|
DOCS_ASSETS_DIR,
|
|
31
31
|
PROJECT_ROOT,
|
|
32
|
+
PAGES_DIR,
|
|
32
33
|
loadDocuConfig,
|
|
33
34
|
} from "./paths";
|
|
34
35
|
import { htmlShell } from "./html.shared";
|
|
@@ -39,7 +40,7 @@ import { initSentry, captureException } from "./sentry";
|
|
|
39
40
|
import { loadPlugins } from "./plugin-loader";
|
|
40
41
|
import { BuildPluginBuilder } from "./plugin-builder";
|
|
41
42
|
import { scanMdxFiles, resolveDocsIndexSource, DEFAULT_FAVICON } from "./utils";
|
|
42
|
-
import type { BuildCache, BuildCacheMeta, CliArgs } from "./types";
|
|
43
|
+
import type { AssetManifest, BuildCache, BuildCacheMeta, CliArgs } from "./types";
|
|
43
44
|
import { isCacheEntry } from "./types";
|
|
44
45
|
import {
|
|
45
46
|
BUILD_CACHE_VERSION,
|
|
@@ -118,7 +119,11 @@ function shouldRebuild(path: string, mtime: number, cache: BuildCache): RebuildD
|
|
|
118
119
|
return "no";
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
let assetManifest = {
|
|
122
|
+
let assetManifest: AssetManifest = {
|
|
123
|
+
docs: { js: "client.js", css: "docs.css" },
|
|
124
|
+
home: { js: "home-client.js", css: "site.css" },
|
|
125
|
+
notFound: { css: "site.css" },
|
|
126
|
+
};
|
|
122
127
|
|
|
123
128
|
/**
|
|
124
129
|
* Reuse manifest.json on bundle cache hit; fall back to a full rebuild
|
|
@@ -127,15 +132,20 @@ let assetManifest = { js: "client.js", css: "client.css" };
|
|
|
127
132
|
async function resolveAssetManifest(
|
|
128
133
|
bundleHit: boolean,
|
|
129
134
|
mdxSources: Record<string, string>
|
|
130
|
-
): Promise<
|
|
135
|
+
): Promise<AssetManifest> {
|
|
131
136
|
if (!bundleHit) return buildClientBundle(mdxSources);
|
|
132
137
|
try {
|
|
133
|
-
const manifest = JSON.parse(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
const manifest = JSON.parse(
|
|
139
|
+
await readFile(join(ASSETS_DIR, "manifest.json"), "utf-8")
|
|
140
|
+
) as Partial<AssetManifest>;
|
|
141
|
+
if (
|
|
142
|
+
typeof manifest.docs?.js === "string" &&
|
|
143
|
+
typeof manifest.docs.css === "string" &&
|
|
144
|
+
typeof manifest.home?.js === "string" &&
|
|
145
|
+
typeof manifest.home.css === "string" &&
|
|
146
|
+
typeof manifest.notFound?.css === "string"
|
|
147
|
+
) {
|
|
148
|
+
return manifest as AssetManifest;
|
|
139
149
|
}
|
|
140
150
|
} catch {
|
|
141
151
|
// corrupt/missing manifest — rebuild below
|
|
@@ -223,7 +233,15 @@ async function renderDocsPage(
|
|
|
223
233
|
|
|
224
234
|
const body = renderToString(page);
|
|
225
235
|
|
|
226
|
-
const ctx: PageContext = {
|
|
236
|
+
const ctx: PageContext = {
|
|
237
|
+
pageType: "docs",
|
|
238
|
+
assets: assetManifest.docs,
|
|
239
|
+
slug,
|
|
240
|
+
filePath,
|
|
241
|
+
frontmatter,
|
|
242
|
+
content,
|
|
243
|
+
config: docuConfig,
|
|
244
|
+
};
|
|
227
245
|
const headExtra = builder?.collectHead(ctx);
|
|
228
246
|
const bodyExtra = builder?.collectBody(ctx);
|
|
229
247
|
|
|
@@ -240,8 +258,8 @@ async function renderDocsPage(
|
|
|
240
258
|
favicon,
|
|
241
259
|
seo,
|
|
242
260
|
csp,
|
|
243
|
-
css:
|
|
244
|
-
js:
|
|
261
|
+
css: ctx.assets.css,
|
|
262
|
+
js: ctx.assets.js,
|
|
245
263
|
nonce,
|
|
246
264
|
themeCss: inlineThemeCss,
|
|
247
265
|
depth,
|
|
@@ -366,6 +384,9 @@ export async function runBuild(): Promise<void> {
|
|
|
366
384
|
|
|
367
385
|
logger.bundleStart();
|
|
368
386
|
let t = performance.now();
|
|
387
|
+
// Skip the JS bundle when compiled MDX sources are unchanged: the bundle
|
|
388
|
+
// is shared by every page, so its hash doubles as the content fingerprint.
|
|
389
|
+
// CSS still builds via its own content-keyed cache inside the hydrator.
|
|
369
390
|
const bundleHash = hashMdxSources(mdxSources);
|
|
370
391
|
const lastBundle = cache["__bundle__"];
|
|
371
392
|
const bundleHit =
|
|
@@ -378,14 +399,10 @@ export async function runBuild(): Promise<void> {
|
|
|
378
399
|
inlineThemeCss = computeInlineThemeCss();
|
|
379
400
|
|
|
380
401
|
const lastManifest = cache["__assets__"];
|
|
381
|
-
const
|
|
382
|
-
|
|
402
|
+
const assetHash = JSON.stringify(assetManifest);
|
|
403
|
+
const assetsChanged = !isCacheEntry(lastManifest) || lastManifest.hash !== assetHash;
|
|
383
404
|
if (assetsChanged) {
|
|
384
|
-
cache["__assets__"] = {
|
|
385
|
-
hash: `${assetManifest.js}:${assetManifest.css}`,
|
|
386
|
-
mtime: 0,
|
|
387
|
-
builtAt: Date.now(),
|
|
388
|
-
};
|
|
405
|
+
cache["__assets__"] = { hash: assetHash, mtime: 0, builtAt: Date.now() };
|
|
389
406
|
}
|
|
390
407
|
if (!bundleHit) {
|
|
391
408
|
cache["__bundle__"] = { hash: bundleHash, mtime: 0, builtAt: Date.now() };
|
|
@@ -408,7 +425,7 @@ export async function runBuild(): Promise<void> {
|
|
|
408
425
|
for (const file of mdxFiles) {
|
|
409
426
|
const rebuildDecision = shouldRebuild(file.path, file.mtime, cache);
|
|
410
427
|
|
|
411
|
-
if (rebuildDecision === "no") {
|
|
428
|
+
if (rebuildDecision === "no" && !builder) {
|
|
412
429
|
const outputPath = join(DIST_DIR, "docs", `${file.path}.html`);
|
|
413
430
|
if (existsSync(outputPath) && !assetsChanged) {
|
|
414
431
|
skipped++;
|
|
@@ -426,7 +443,7 @@ export async function runBuild(): Promise<void> {
|
|
|
426
443
|
}
|
|
427
444
|
}
|
|
428
445
|
|
|
429
|
-
if (rebuildDecision === "hash_check") {
|
|
446
|
+
if (rebuildDecision === "hash_check" && !builder) {
|
|
430
447
|
const contentHash = hashContent(rawMdx);
|
|
431
448
|
const cached = cache[file.path];
|
|
432
449
|
if (isCacheEntry(cached) && cached.hash === contentHash) {
|
|
@@ -512,42 +529,62 @@ export async function runBuild(): Promise<void> {
|
|
|
512
529
|
""
|
|
513
530
|
);
|
|
514
531
|
const landingNonce = generateNonce();
|
|
515
|
-
const
|
|
532
|
+
const landingContext: PageContext = {
|
|
533
|
+
pageType: "home",
|
|
534
|
+
assets: assetManifest.home,
|
|
535
|
+
slug: "",
|
|
536
|
+
filePath: join(PAGES_DIR, "index.tsx"),
|
|
537
|
+
frontmatter: { ...docuConfig.meta } as unknown as Record<string, unknown>,
|
|
538
|
+
config: docuConfig,
|
|
539
|
+
};
|
|
540
|
+
let landingHtml = htmlShell({
|
|
516
541
|
title: docuConfig.meta?.title || "DocuBook",
|
|
517
542
|
description: docuConfig.meta?.description || "",
|
|
518
543
|
body: renderToString(landingPage),
|
|
519
544
|
favicon: landingFavicon,
|
|
520
545
|
seo: landingSeo,
|
|
521
546
|
csp: cspHeader(landingNonce),
|
|
522
|
-
css:
|
|
523
|
-
js:
|
|
547
|
+
css: landingContext.assets.css,
|
|
548
|
+
js: landingContext.assets.js,
|
|
524
549
|
nonce: landingNonce,
|
|
525
550
|
themeCss: inlineThemeCss,
|
|
551
|
+
headExtra: builder?.collectHead(landingContext),
|
|
552
|
+
bodyExtra: builder?.collectBody(landingContext),
|
|
526
553
|
});
|
|
554
|
+
if (builder) landingHtml = await builder.runTransformHtmlChain(landingHtml, landingContext);
|
|
527
555
|
await writeFile(join(DIST_DIR, "index.html"), landingHtml);
|
|
528
556
|
|
|
529
|
-
const notFoundPage = React.createElement(
|
|
530
|
-
DocsLayout,
|
|
531
|
-
{ repoUrl: docuConfig.repo?.url },
|
|
532
|
-
React.createElement(NotFoundPage)
|
|
533
|
-
);
|
|
557
|
+
const notFoundPage = React.createElement(NotFoundPage);
|
|
534
558
|
const notFoundFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
535
559
|
const notFoundNonce = generateNonce();
|
|
536
|
-
const
|
|
560
|
+
const notFoundContext: PageContext = {
|
|
561
|
+
pageType: "notFound",
|
|
562
|
+
assets: assetManifest.notFound,
|
|
563
|
+
slug: "404",
|
|
564
|
+
filePath: join(PAGES_DIR, "404.tsx"),
|
|
565
|
+
frontmatter: {},
|
|
566
|
+
config: docuConfig,
|
|
567
|
+
};
|
|
568
|
+
let notFoundHtml = htmlShell({
|
|
537
569
|
title: "404 - Not Found",
|
|
538
570
|
description: "",
|
|
539
571
|
body: renderToString(notFoundPage),
|
|
540
572
|
favicon: notFoundFavicon,
|
|
541
|
-
headExtra: [
|
|
573
|
+
headExtra: [
|
|
574
|
+
'<meta name="robots" content="noindex,follow">',
|
|
575
|
+
...(builder?.collectHead(notFoundContext) ?? []),
|
|
576
|
+
],
|
|
577
|
+
bodyExtra: builder?.collectBody(notFoundContext),
|
|
542
578
|
csp: cspHeader(notFoundNonce),
|
|
543
|
-
css:
|
|
544
|
-
js:
|
|
579
|
+
css: notFoundContext.assets.css,
|
|
580
|
+
js: notFoundContext.assets.js,
|
|
545
581
|
nonce: notFoundNonce,
|
|
546
582
|
themeCss: inlineThemeCss,
|
|
547
583
|
// Served as the static-host fallback at ANY requested path — relative
|
|
548
584
|
// depth can never be right there, so use root-absolute asset URLs.
|
|
549
585
|
absoluteAssets: true,
|
|
550
586
|
});
|
|
587
|
+
if (builder) notFoundHtml = await builder.runTransformHtmlChain(notFoundHtml, notFoundContext);
|
|
551
588
|
await writeFile(join(DIST_DIR, "404.html"), notFoundHtml);
|
|
552
589
|
|
|
553
590
|
logger.spinner.stop(
|
|
@@ -561,7 +598,7 @@ export async function runBuild(): Promise<void> {
|
|
|
561
598
|
filePath: join(DOCS_DIR, f.path),
|
|
562
599
|
outputPath: join(DIST_DIR, "docs", `${f.path}.html`),
|
|
563
600
|
}));
|
|
564
|
-
await builder.runOnEnd(pages);
|
|
601
|
+
await builder.runOnEnd(pages, { assetManifest, outDir: DIST_DIR });
|
|
565
602
|
}
|
|
566
603
|
|
|
567
604
|
logger.indexStart();
|