@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.
Files changed (58) hide show
  1. package/.docu/components/Theme.tsx +42 -34
  2. package/.docu/components/home/BackgroundBlobs.tsx +31 -0
  3. package/.docu/components/home/index.tsx +1 -0
  4. package/.docu/lib/build.deno.js +1 -1
  5. package/.docu/lib/build.impl--Pm9uO30.js +2 -0
  6. package/.docu/lib/{build.impl-BBlR3si3.js → build.impl-CsP6pPug.js} +60 -29
  7. package/.docu/lib/build.impl-CsP6pPug.js.map +1 -0
  8. package/.docu/lib/build.node.js +1 -1
  9. package/.docu/lib/clean.js +1 -1
  10. package/.docu/lib/deploy.deno.js +1 -1
  11. package/.docu/lib/deploy.node.js +1 -1
  12. package/.docu/lib/{deploy.shared-Dxv3lsOb.js → deploy.shared-CsEa3L1D.js} +45 -17
  13. package/.docu/lib/deploy.shared-CsEa3L1D.js.map +1 -0
  14. package/.docu/lib/{html.shared-DBMwv_Z6.js → html.shared-CsEFmy_Y.js} +181 -158
  15. package/.docu/lib/html.shared-CsEFmy_Y.js.map +1 -0
  16. package/.docu/lib/{logger-DYmvFF1d.js → logger-DLU3A-z2.js} +4 -3
  17. package/.docu/lib/logger-DLU3A-z2.js.map +1 -0
  18. package/.docu/lib/{paths-Bl2cdp9E.js → paths-Cbz-dj4s.js} +5 -9
  19. package/.docu/lib/{paths-Bl2cdp9E.js.map → paths-Cbz-dj4s.js.map} +1 -1
  20. package/.docu/lib/preview.deno.js +1 -1
  21. package/.docu/lib/{preview.impl-anVcZv2Z.js → preview.impl-CaZTgmnB.js} +3 -3
  22. package/.docu/lib/{preview.impl-anVcZv2Z.js.map → preview.impl-CaZTgmnB.js.map} +1 -1
  23. package/.docu/lib/preview.node.js +1 -1
  24. package/.docu/lib/server.deno.js +1 -1
  25. package/.docu/lib/{server.impl-Cvoa6Diq.js → server.impl-D-CgD1bn.js} +43 -52
  26. package/.docu/lib/server.impl-D-CgD1bn.js.map +1 -0
  27. package/.docu/lib/server.node.js +1 -1
  28. package/.docu/node/build.impl.ts +71 -34
  29. package/.docu/node/build.ts +68 -34
  30. package/.docu/node/cache-key.ts +8 -4
  31. package/.docu/node/client.ts +36 -27
  32. package/.docu/node/deploy.shared.ts +44 -18
  33. package/.docu/node/deploy.ts +5 -20
  34. package/.docu/node/home-client.ts +6 -0
  35. package/.docu/node/html.shared.ts +5 -4
  36. package/.docu/node/html.ts +4 -3
  37. package/.docu/node/hydrate.node.ts +87 -115
  38. package/.docu/node/hydrate.ts +74 -80
  39. package/.docu/node/mdx-manifest.d.ts +2 -2
  40. package/.docu/node/paths.ts +2 -9
  41. package/.docu/node/plugin-builder.ts +25 -13
  42. package/.docu/node/plugin.ts +33 -9
  43. package/.docu/node/server-routes.ts +59 -50
  44. package/.docu/node/server.impl.ts +4 -2
  45. package/.docu/node/server.ts +12 -16
  46. package/.docu/node/types.ts +11 -0
  47. package/.docu/pages/404.tsx +12 -9
  48. package/.docu/pages/index.tsx +8 -29
  49. package/.docu/styles/base.css +128 -0
  50. package/.docu/styles/globals.css +5 -137
  51. package/.docu/styles/site.css +7 -0
  52. package/package.json +6 -5
  53. package/.docu/lib/build.impl-BBlR3si3.js.map +0 -1
  54. package/.docu/lib/build.impl-DpoDUmei.js +0 -2
  55. package/.docu/lib/deploy.shared-Dxv3lsOb.js.map +0 -1
  56. package/.docu/lib/html.shared-DBMwv_Z6.js.map +0 -1
  57. package/.docu/lib/logger-DYmvFF1d.js.map +0 -1
  58. 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 (file.startsWith("client.") || file.startsWith("client-")) await unlink(join(ASSETS_DIR, file));
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.warn("[flame] Failed to clean chunks dir:", err.message);
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, PROJECT_ROOT as c, loadDocuConfig as d, resolveProjectFile as f, DOCS_ASSETS_DIR as i, STYLES_DIR as l, CACHE_FILE as n, FRAMEWORK_ROOT as o, DIST_DIR as r, LIB_DIR as s, ASSETS_DIR as t, cleanOldBundles as u };
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-Bl2cdp9E.js.map
72
+ //# sourceMappingURL=paths-Cbz-dj4s.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths-Bl2cdp9E.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 (file.startsWith(\"client.\") || file.startsWith(\"client-\")) {\n await unlink(join(ASSETS_DIR, file));\n }\n }\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 try {\n await rm(join(ASSETS_DIR, \"chunks\"), { recursive: true, force: true });\n } catch (err) {\n // chunks dir may not exist, or permission error — log to avoid silent failure\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n console.warn(\"[flame] Failed to clean chunks dir:\", (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;AAGnB,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,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,SAAS,GACzD,MAAM,OAAO,KAAK,YAAY,IAAI,CAAC;EAEvC;CACF,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAC1C,QAAQ,MAAM,gCAAiC,IAAc,OAAO;CAExE;CACA,IAAI;EACF,MAAM,GAAG,KAAK,YAAY,QAAQ,GAAG;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CACvE,SAAS,KAAK;EAEZ,IAAK,IAA8B,SAAS,UAC1C,QAAQ,KAAK,uCAAwC,IAAc,OAAO;CAE9E;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
+ {"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,5 +1,5 @@
1
1
  import { t as denoAdapter } from "./deno-DmEaKggj.js";
2
- import { t as runPreview } from "./preview.impl-anVcZv2Z.js";
2
+ import { t as runPreview } from "./preview.impl-CaZTgmnB.js";
3
3
  //#region .docu/node/preview.deno.ts
4
4
  await runPreview(denoAdapter);
5
5
  //#endregion
@@ -1,6 +1,6 @@
1
- import { r as DIST_DIR } from "./paths-Bl2cdp9E.js";
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-DYmvFF1d.js";
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-anVcZv2Z.js.map
77
+ //# sourceMappingURL=preview.impl-CaZTgmnB.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"preview.impl-anVcZv2Z.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"}
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"}
@@ -1,5 +1,5 @@
1
1
  import { t as nodeAdapter } from "./node-DPTySdwG.js";
2
- import { t as runPreview } from "./preview.impl-anVcZv2Z.js";
2
+ import { t as runPreview } from "./preview.impl-CaZTgmnB.js";
3
3
  //#region .docu/node/preview.node.ts
4
4
  await runPreview(nodeAdapter);
5
5
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { t as denoAdapter } from "./deno-DmEaKggj.js";
2
- import { t as runServer } from "./server.impl-Cvoa6Diq.js";
2
+ import { t as runServer } from "./server.impl-D-CgD1bn.js";
3
3
  //#region .docu/node/server.deno.ts
4
4
  await runServer(denoAdapter);
5
5
  //#endregion
@@ -1,7 +1,7 @@
1
- import { a as DOCS_DIR, c as PROJECT_ROOT, d as loadDocuConfig, r as DIST_DIR } from "./paths-Bl2cdp9E.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-DBMwv_Z6.js";
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-DYmvFF1d.js";
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 html = htmlShell({
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: state.assetManifest.css,
21
- js: state.assetManifest.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: 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
- if (state.builder) {
107
- const ctx = {
108
- slug: slug.join("/") || "/",
109
- filePath: doc.filePath,
110
- frontmatter: doc.frontmatter,
111
- content: doc.resolvedContent,
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
- const page = React.createElement(DocsLayout, {
140
- repoUrl: state.docuConfig.repo?.url,
141
- pathname: "/docs"
142
- }, React.createElement(Component, props));
143
- return createHtmlResponse(title, description, renderToString(page), status, state, depth);
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-Cvoa6Diq.js.map
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":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as nodeAdapter } from "./node-DPTySdwG.js";
2
- import { t as runServer } from "./server.impl-Cvoa6Diq.js";
2
+ import { t as runServer } from "./server.impl-D-CgD1bn.js";
3
3
  //#region .docu/node/server.node.ts
4
4
  await runServer(nodeAdapter);
5
5
  //#endregion
@@ -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 = { js: "client.js", css: "client.css" };
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<{ js: string; css: string }> {
135
+ ): Promise<AssetManifest> {
131
136
  if (!bundleHit) return buildClientBundle(mdxSources);
132
137
  try {
133
- const manifest = JSON.parse(await readFile(join(ASSETS_DIR, "manifest.json"), "utf-8")) as {
134
- js?: string;
135
- css?: string;
136
- };
137
- if (typeof manifest.js === "string" && typeof manifest.css === "string") {
138
- return { js: manifest.js, css: manifest.css };
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 = { slug, filePath, frontmatter, content, config: docuConfig };
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: assetManifest.css,
244
- js: assetManifest.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 assetsChanged =
382
- !isCacheEntry(lastManifest) || lastManifest.hash !== `${assetManifest.js}:${assetManifest.css}`;
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 landingHtml = htmlShell({
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: assetManifest.css,
523
- js: assetManifest.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 notFoundHtml = htmlShell({
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: ['<meta name="robots" content="noindex,follow">'],
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: assetManifest.css,
544
- js: assetManifest.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();