@docubook/flame 2.0.3 → 2.1.0

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 (72) hide show
  1. package/.docu/components/Context.tsx +6 -5
  2. package/.docu/components/DocsLayout.tsx +7 -4
  3. package/.docu/components/Menu.tsx +10 -9
  4. package/.docu/components/Navbar.tsx +24 -22
  5. package/.docu/components/Pagination.tsx +3 -2
  6. package/.docu/components/Search.tsx +6 -1
  7. package/.docu/components/Sidebar.tsx +22 -21
  8. package/.docu/components/Sublink.tsx +3 -3
  9. package/.docu/lib/build.deno.js +1 -1
  10. package/.docu/lib/{build.impl-BSt6Vikt.js → build.impl-DK485Nx1.js} +60 -31
  11. package/.docu/lib/build.impl-DK485Nx1.js.map +1 -0
  12. package/.docu/lib/build.impl-Dmybgcj2.js +2 -0
  13. package/.docu/lib/build.node.js +1 -1
  14. package/.docu/lib/clean.js +1 -1
  15. package/.docu/lib/deploy.deno.js +1 -1
  16. package/.docu/lib/deploy.node.js +1 -1
  17. package/.docu/lib/{deploy.shared-D6S_FE7d.js → deploy.shared-BW2KXFXs.js} +27 -8
  18. package/.docu/lib/deploy.shared-BW2KXFXs.js.map +1 -0
  19. package/.docu/lib/{html.shared-CsEFmy_Y.js → html.shared-RPCM1y5Q.js} +264 -73
  20. package/.docu/lib/html.shared-RPCM1y5Q.js.map +1 -0
  21. package/.docu/lib/{logger-DFKQUy8M.js → logger-DYFD47f8.js} +3 -3
  22. package/.docu/lib/{logger-DFKQUy8M.js.map → logger-DYFD47f8.js.map} +1 -1
  23. package/.docu/lib/paths-CHXABfXa.js +332 -0
  24. package/.docu/lib/paths-CHXABfXa.js.map +1 -0
  25. package/.docu/lib/preview.deno.js +1 -1
  26. package/.docu/lib/{preview.impl-BfESMUM7.js → preview.impl-DMXVsGs0.js} +4 -4
  27. package/.docu/lib/preview.impl-DMXVsGs0.js.map +1 -0
  28. package/.docu/lib/preview.node.js +1 -1
  29. package/.docu/lib/{utils-B_CoyKie.js → server-utils-BYWB7A6T.js} +3 -21
  30. package/.docu/lib/server-utils-BYWB7A6T.js.map +1 -0
  31. package/.docu/lib/server.deno.js +1 -1
  32. package/.docu/lib/{server.impl-CZ4eMe5L.js → server.impl-DcyZhPA_.js} +24 -19
  33. package/.docu/lib/server.impl-DcyZhPA_.js.map +1 -0
  34. package/.docu/lib/server.node.js +1 -1
  35. package/.docu/node/base-path.ts +306 -0
  36. package/.docu/node/build.impl.ts +50 -17
  37. package/.docu/node/build.ts +50 -17
  38. package/.docu/node/cache-key.ts +92 -4
  39. package/.docu/node/client-routes.ts +16 -1
  40. package/.docu/node/deploy.shared.ts +51 -15
  41. package/.docu/node/deploy.ts +7 -3
  42. package/.docu/node/html.shared.ts +33 -3
  43. package/.docu/node/html.ts +18 -3
  44. package/.docu/node/hydrate.node.ts +6 -0
  45. package/.docu/node/hydrate.ts +16 -1
  46. package/.docu/node/mdx.ts +102 -23
  47. package/.docu/node/paths.ts +115 -0
  48. package/.docu/node/preview.impl.ts +1 -1
  49. package/.docu/node/preview.ts +1 -1
  50. package/.docu/node/search-indexer.ts +20 -3
  51. package/.docu/node/seo.ts +41 -7
  52. package/.docu/node/server-routes.ts +34 -11
  53. package/.docu/node/server-utils.ts +152 -0
  54. package/.docu/node/server.impl.ts +22 -12
  55. package/.docu/node/server.ts +23 -27
  56. package/.docu/node/types.ts +13 -0
  57. package/.docu/node/utils.ts +56 -149
  58. package/.docu/pages/404.tsx +2 -1
  59. package/.docu/pages/index.tsx +6 -13
  60. package/bin/cli.js +5 -0
  61. package/docu.schema.json +10 -1
  62. package/package.json +5 -5
  63. package/template/docu.json +1 -0
  64. package/.docu/lib/build.impl-BSt6Vikt.js.map +0 -1
  65. package/.docu/lib/build.impl-xNDEh8DJ.js +0 -2
  66. package/.docu/lib/deploy.shared-D6S_FE7d.js.map +0 -1
  67. package/.docu/lib/html.shared-CsEFmy_Y.js.map +0 -1
  68. package/.docu/lib/paths-Cbz-dj4s.js +0 -72
  69. package/.docu/lib/paths-Cbz-dj4s.js.map +0 -1
  70. package/.docu/lib/preview.impl-BfESMUM7.js.map +0 -1
  71. package/.docu/lib/server.impl-CZ4eMe5L.js.map +0 -1
  72. package/.docu/lib/utils-B_CoyKie.js.map +0 -1
@@ -1,7 +1,7 @@
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
- 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-DFKQUy8M.js";
1
+ import { E as resolveBasePath, O as stripDocsHtmlSuffix, S as docsDepth, _ as loadDocuConfig, a as DOCS_DIR, f as assertValidBasePath, h as defaultFavicon, l as PAGES_DIR, r as DIST_DIR, u as PROJECT_ROOT, w as matchDocsSlug, y as servedBasePath } from "./paths-CHXABfXa.js";
2
+ import { D as BuildPluginBuilder, O as loadPlugins, 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-RPCM1y5Q.js";
3
+ import { c as htmlResponse, d as isSlugSafe, i as SECURITY_HEADERS, p as wrapPluginResponse, s as generateNonce, t as getContentType, u as isPathSafe } from "./server-utils-BYWB7A6T.js";
4
+ import { t as logger } from "./logger-DYFD47f8.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";
@@ -11,7 +11,7 @@ import { renderToString } from "react-dom/server";
11
11
  //#region .docu/node/server-routes.ts
12
12
  async function createHtmlResponse(title, description, body, status, state, pageType, page, depth = 0) {
13
13
  const nonce = generateNonce();
14
- const favicon = state.docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
14
+ const favicon = state.docuConfig.meta?.favicon || defaultFavicon();
15
15
  const assets = state.assetManifest[pageType];
16
16
  const context = {
17
17
  ...page,
@@ -32,6 +32,8 @@ async function createHtmlResponse(title, description, body, status, state, pageT
32
32
  headExtra: state.builder?.collectHead(context),
33
33
  bodyExtra: state.builder?.collectBody(context),
34
34
  depth,
35
+ basePath: servedBasePath(),
36
+ deployPath: "",
35
37
  absoluteAssets: status === 404
36
38
  });
37
39
  if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);
@@ -112,7 +114,7 @@ async function renderDocsServerPage(doc, slug, pathname, state) {
112
114
  compiledSource: doc.compiledSource
113
115
  }));
114
116
  const body = renderToString(page);
115
- const depth = slug.length || 1;
117
+ const depth = docsDepth(slug.join("/"), servedBasePath());
116
118
  return createHtmlResponse(title, description, body, 200, state, "docs", {
117
119
  slug: slug.join("/"),
118
120
  filePath: doc.filePath,
@@ -129,14 +131,14 @@ async function renderPage(Component, title, description, status, state, props =
129
131
  }
130
132
  async function handleDocsIndex(state) {
131
133
  const doc = await getDocsForSlug("", state);
132
- if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, 1);
133
- return renderDocsServerPage(doc, [], "/docs", state);
134
+ if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, docsDepth("", servedBasePath()));
135
+ return renderDocsServerPage(doc, [], servedBasePath() || "/", state);
134
136
  }
135
137
  async function handleDocsRoute(slug, state) {
136
138
  const path = slug.join("/");
137
139
  const doc = await getDocsForSlug(path, state);
138
- if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, slug.length || 1);
139
- return renderDocsServerPage(doc, slug, `/docs/${path}`, state);
140
+ if (!doc) return renderPage(NotFoundPage, "404 - Not Found", "", 404, state, {}, docsDepth(path, servedBasePath()));
141
+ return renderDocsServerPage(doc, slug, `${servedBasePath()}/${path}`, state);
140
142
  }
141
143
  async function handleIndex(state) {
142
144
  const page = React.createElement(IndexPage);
@@ -164,9 +166,10 @@ function serveStatic(pathname) {
164
166
  } catch (err) {
165
167
  if (err.code !== "ENOENT") throw err;
166
168
  }
167
- if (decoded.startsWith("/docs/assets/")) {
169
+ const docsAssetsPrefix = `${servedBasePath()}/assets/`;
170
+ if (decoded.startsWith(docsAssetsPrefix)) {
168
171
  const docsAssetsDir = resolve(DOCS_DIR, "assets");
169
- const requestedRelative = decoded.slice(13);
172
+ const requestedRelative = decoded.slice(docsAssetsPrefix.length);
170
173
  const docsAsset = resolve(docsAssetsDir, requestedRelative);
171
174
  const docsAssetsDirWithSep = docsAssetsDir.endsWith("/") ? docsAssetsDir : docsAssetsDir + "/";
172
175
  if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;
@@ -200,6 +203,8 @@ function serverErrorResponse(error) {
200
203
  */
201
204
  async function runServer(adapter) {
202
205
  const docuConfig = loadDocuConfig();
206
+ const resolvedBasePath = resolveBasePath(docuConfig);
207
+ for (const warning of assertValidBasePath()) logger.warn(warning.message);
203
208
  const parsedPort = parseInt(process.env.PORT ?? "3000", 10);
204
209
  const PORT = Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3e3;
205
210
  logger.buildStart();
@@ -283,7 +288,7 @@ async function runServer(adapter) {
283
288
  let handle = null;
284
289
  const fetchHandler = async (req) => {
285
290
  const url = new URL(req.url);
286
- const pathname = stripDocsHtmlSuffix(url.pathname);
291
+ const pathname = stripDocsHtmlSuffix(url.pathname, resolvedBasePath);
287
292
  const startTime = performance.now();
288
293
  if (builder) {
289
294
  const pluginResponse = await builder.runHandleRequest(req, {
@@ -320,11 +325,11 @@ async function runServer(adapter) {
320
325
  }
321
326
  let response;
322
327
  if (pathname === "/") response = await handleIndex(state);
323
- else if (pathname === "/docs" || pathname === "/docs/") response = await handleDocsIndex(state);
324
- else if (pathname.startsWith("/docs/")) {
325
- const slug = pathname.slice(6).split("/").filter(Boolean);
326
- response = slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);
327
- } else response = await handleNotFound(state);
328
+ else {
329
+ const docsSlug = matchDocsSlug(pathname, resolvedBasePath);
330
+ if (docsSlug) response = docsSlug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(docsSlug, state);
331
+ else response = await handleNotFound(state);
332
+ }
328
333
  logger.request(req.method, pathname, response.status, Math.round(performance.now() - startTime));
329
334
  return response;
330
335
  } catch (err) {
@@ -346,4 +351,4 @@ async function runServer(adapter) {
346
351
  //#endregion
347
352
  export { runServer as t };
348
353
 
349
- //# sourceMappingURL=server.impl-CZ4eMe5L.js.map
354
+ //# sourceMappingURL=server.impl-DcyZhPA_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.impl-DcyZhPA_.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 { getContentType } from \"./server-utils\";\nimport { defaultFavicon } from \"./paths\";\nimport { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT, servedBasePath, docsDepth } 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 || defaultFavicon();\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 basePath: servedBasePath(),\n // Dev serves the project from the origin root, so the host's deployment\n // path never applies here — only the static build writes it into HTML.\n deployPath: \"\",\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 // Same relative climb as the static build — see docsDepth.\n const depth = docsDepth(slug.join(\"/\"), servedBasePath());\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)\n return renderPage(\n NotFoundPage,\n \"404 - Not Found\",\n \"\",\n 404,\n state,\n {},\n docsDepth(\"\", servedBasePath())\n );\n return renderDocsServerPage(doc, [], servedBasePath() || \"/\", 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(\n NotFoundPage,\n \"404 - Not Found\",\n \"\",\n 404,\n state,\n {},\n docsDepth(path, servedBasePath())\n );\n return renderDocsServerPage(doc, slug, `${servedBasePath()}/${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 const docsAssetsPrefix = `${servedBasePath()}/assets/`;\n if (decoded.startsWith(docsAssetsPrefix)) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(docsAssetsPrefix.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, resolveBasePath, assertValidBasePath } 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 { matchDocsSlug, stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n const resolvedBasePath = resolveBasePath(docuConfig);\n\n // Report normalized prefixes (and stop on a value that cannot be honored)\n // before the server announces a site that would be built somewhere else.\n for (const warning of assertValidBasePath()) logger.warn(warning.message);\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, resolvedBasePath);\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 // The landing page owns `/` in every deployment. At the deployment root\n // (`meta.basePath: \"\"`) docs would otherwise claim it, but the build\n // skips the docs index there — so dev must not serve it either.\n // Static assets were already handled above, so a root deployment may\n // still match every other path as a docs route.\n if (pathname === \"/\") {\n response = await handleIndex(state);\n } else {\n const docsSlug = matchDocsSlug(pathname, resolvedBasePath);\n if (docsSlug) {\n response =\n docsSlug.length === 0\n ? await handleDocsIndex(state)\n : await handleDocsRoute(docsSlug, state);\n } else {\n response = await handleNotFound(state);\n }\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-CZ4eMe5L.js";
2
+ import { t as runServer } from "./server.impl-DcyZhPA_.js";
3
3
  //#region .docu/node/server.node.ts
4
4
  await runServer(nodeAdapter);
5
5
  //#endregion
@@ -0,0 +1,306 @@
1
+ import type { DocuConfig } from "./types";
2
+
3
+ /**
4
+ * Pure base-path helpers shared by server modules and client-side components.
5
+ *
6
+ * This module MUST stay free of Node built-ins (`node:path`, `node:fs`) and of
7
+ * modules that evaluate them at import time (like `./paths`): it is bundled
8
+ * into the client, where `import.meta.dirname` and `process.cwd()` do not
9
+ * exist. All functions here are string operations over a resolved prefix.
10
+ */
11
+
12
+ /** Default URL prefix the docs site is served under. Kept as `/docs` so
13
+ * existing deployments keep byte-identical output. */
14
+ export const DEFAULT_BASE_PATH = "/docs";
15
+
16
+ /**
17
+ * Normalize a configured base path into `/prefix` form with no trailing slash.
18
+ * `""` and `"/"` mean "served at the domain root"; a missing value is not
19
+ * handled here — `resolveBasePath` owns the default. Absolute URLs
20
+ * (`https://host/x`) contribute only their pathname, so a `meta.baseURL` can be
21
+ * passed straight in.
22
+ */
23
+ export function normalizeBasePath(value: string | undefined | null): string {
24
+ if (typeof value !== "string") return DEFAULT_BASE_PATH;
25
+ let path = value.trim();
26
+ if (path.length === 0 || path === "/") return "";
27
+ try {
28
+ if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(path)) path = new URL(path).pathname;
29
+ } catch {
30
+ // malformed URL — fall through and treat the raw value as a path
31
+ }
32
+ path = `/${path.replace(/^\/+|\/+$/g, "")}`;
33
+ return path === "/" ? "" : path;
34
+ }
35
+
36
+ /**
37
+ * Resolve the docs URL prefix from config.
38
+ *
39
+ * An explicit `meta.basePath` wins, including `""` for a root deployment.
40
+ * Otherwise `/docs` (the historical behaviour).
41
+ *
42
+ * Deliberately NOT derived from `meta.baseURL`'s pathname: `baseURL` is the
43
+ * origin + deployment root and `basePath` is the prefix beneath it. Deriving
44
+ * one from the other's pathname double-counts that path — the SEO/canonical
45
+ * builder appends the prefix to `baseURL`, so `baseURL: ".../repo"` deriving
46
+ * `basePath: "/repo"` produced `.../repo/repo/...`. They must be configured
47
+ * independently; the schema documents both.
48
+ */
49
+ export function resolveBasePath(config?: DocuConfig | null): string {
50
+ const meta = config?.meta;
51
+ if (typeof meta?.basePath !== "string") return DEFAULT_BASE_PATH;
52
+ // Root forms (`""`, `"/"`, `"///"`) stay root. Everything else is
53
+ // canonicalized so routing, output directories and every generated URL agree
54
+ // on one spelling — `"/Docs"` and `"/docs me"` are honored as `"/docs"`
55
+ // and `"/docs-me"` (reported by `validateBasePath`).
56
+ if (normalizeBasePath(meta.basePath) === "") return "";
57
+ return canonicalBasePath(meta.basePath) || DEFAULT_BASE_PATH;
58
+ }
59
+
60
+ /**
61
+ * Deployment-root path contributed by the host, taken from `meta.baseURL`:
62
+ * `""` when the origin serves the dist at `/`, `/repo` for a GitHub Pages
63
+ * project site (which serves the artifact under `<origin>/repo/`).
64
+ *
65
+ * Distinct from the base path — `basePath` is the prefix *inside* the dist,
66
+ * this is the prefix *above* it. Only root-absolute references need it (the 404
67
+ * fallback and the search index); relative ones line up already because pages
68
+ * and assets share the deployment root.
69
+ */
70
+ export function resolveDeployPath(config?: DocuConfig | null): string {
71
+ const raw = config?.meta?.baseURL;
72
+ if (typeof raw !== "string") return "";
73
+ try {
74
+ return normalizeBasePath(new URL(raw).pathname);
75
+ } catch {
76
+ // Not an absolute URL. A root-relative value is still a usable path; a bare
77
+ // hostname without a scheme is a config mistake, not a prefix — ignore it.
78
+ return raw.startsWith("/") ? normalizeBasePath(raw) : "";
79
+ }
80
+ }
81
+
82
+ export interface BasePathIssue {
83
+ /** `error` blocks the build; `warning` only reports. */
84
+ level: "error" | "warning";
85
+ message: string;
86
+ }
87
+
88
+ /**
89
+ * Canonical prefix form: lowercase segments with whitespace collapsed to dashes
90
+ * and characters a static host cannot serve dropped (`"/Docs"` → `"/docs"`,
91
+ * `"/docs me"` → `"/docs-me"`). Structural normalization runs first, so an
92
+ * absolute value (`https://host/x`) works too. Returns `""` when nothing
93
+ * servable is left, which callers treat as a config mistake rather than a
94
+ * silent root deployment.
95
+ */
96
+ export function canonicalBasePath(value: string): string {
97
+ const structural = normalizeBasePath(value);
98
+ if (structural === "") return "";
99
+ // Whitespace and unservable characters fold away, then dot segments are
100
+ // dropped: a prefix is a URL path *and* an output directory, so `.`/`..`
101
+ // would either escape the dist or resolve differently than the URL does.
102
+ const cleaned = structural
103
+ .replace(/\s+/g, "-")
104
+ .replace(/[^A-Za-z0-9\-._~/]+/g, "")
105
+ .split("/")
106
+ .filter((segment) => segment.length > 0 && segment !== "." && segment !== "..")
107
+ .join("/")
108
+ .replace(/-{2,}/g, "-")
109
+ .toLowerCase();
110
+ return cleaned.length > 0 ? `/${cleaned}` : "";
111
+ }
112
+
113
+ /**
114
+ * Validate an authored `meta.basePath`.
115
+ *
116
+ * `resolveBasePath` normalizes what a static host cannot serve, so this reports
117
+ * what it had to change instead of blocking the build: only a non-string value
118
+ * (which cannot be normalized) is an error. Warnings name the reasons and the
119
+ * canonical value, so the author can fix `docu.json` and keep the editor schema
120
+ * — which only accepts the canonical form — quiet.
121
+ */
122
+ export function validateBasePath(value: unknown): BasePathIssue[] {
123
+ if (value === undefined) return [];
124
+ if (typeof value !== "string") {
125
+ return [
126
+ {
127
+ level: "error",
128
+ message: `must be a string (received ${
129
+ value === null ? "null" : typeof value
130
+ }) — remove it or set "${DEFAULT_BASE_PATH}"`,
131
+ },
132
+ ];
133
+ }
134
+
135
+ // `""`, `"/"`, `"///"` and whitespace-only values all mean the deployment
136
+ // root, which is always servable.
137
+ const structural = normalizeBasePath(value);
138
+ if (structural === "") return [];
139
+
140
+ const canonical = canonicalBasePath(value);
141
+ if (canonical === "") {
142
+ return [
143
+ {
144
+ level: "warning",
145
+ message: `"${value}" has no characters a static host can serve — falling back to "${DEFAULT_BASE_PATH}"`,
146
+ },
147
+ ];
148
+ }
149
+ if (canonical === structural) return [];
150
+
151
+ const segments = structural.replace(/^\/+/, "");
152
+ const reasons: string[] = [];
153
+ if (/\s/.test(segments)) reasons.push("whitespace");
154
+ // Whitespace is already reported above — list only the other unservable
155
+ // characters here, or the message prints a blank `( )`.
156
+ const invalid = [
157
+ ...new Set(segments.replace(/[\s]/g, "").replace(/[A-Za-z0-9\-._~/]/g, "")),
158
+ ].join(" ");
159
+ if (invalid.length > 0) reasons.push(`characters that need percent-encoding (${invalid})`);
160
+ if (segments !== segments.toLowerCase()) reasons.push("non-lowercase letters");
161
+ if (/\.\.|(^|\/)\.(\/|$)|--|\/\//.test(segments))
162
+ reasons.push("dot segments or doubled separators");
163
+
164
+ return [
165
+ {
166
+ level: "warning",
167
+ message: `"${value}" ${
168
+ reasons.length > 0 ? `contains ${reasons.join(", ")}` : "is not canonical"
169
+ } — normalized to "${canonical}"; update docu.json to match`,
170
+ },
171
+ ];
172
+ }
173
+
174
+ /** Join a serve-prefix with a slash-relative path, keeping exactly one slash. */
175
+ export function withBasePath(path: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {
176
+ const suffix = path.length === 0 || path === "/" ? "" : `/${path.replace(/^\/+|\/+$/g, "")}`;
177
+ return `${resolvedBasePath}${suffix}`;
178
+ }
179
+
180
+ /** Prefix a root-relative asset path (e.g. `/assets/x.js`) with the base path. */
181
+ export function assetHref(assetPath: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {
182
+ return withBasePath(assetPath, resolvedBasePath);
183
+ }
184
+
185
+ /**
186
+ * Apply the base path to a docs route unless it already carries it.
187
+ * Idempotent, which matters because routes are prefixed at more than one
188
+ * layer (nav components, SEO, search indexer) and a route may already be
189
+ * absolute when it reaches us.
190
+ */
191
+ export function applyBasePath(path: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {
192
+ if (resolvedBasePath.length === 0) return path;
193
+ if (path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`)) return path;
194
+ return withBasePath(path, resolvedBasePath);
195
+ }
196
+
197
+ /**
198
+ * Is `pathname` inside the docs site?
199
+ *
200
+ * True under the configured prefix (when there is one) and for paths still
201
+ * authored with the default `/docs` prefix — those are what callers re-base.
202
+ * Anything else is a sibling route on the same origin (an app at `/app`, a
203
+ * status page) and has to be left exactly as the author wrote it.
204
+ */
205
+ export function isDocsPath(
206
+ pathname: string,
207
+ resolvedBasePath: string = DEFAULT_BASE_PATH
208
+ ): boolean {
209
+ if (
210
+ resolvedBasePath.length > 0 &&
211
+ (pathname === resolvedBasePath || pathname.startsWith(`${resolvedBasePath}/`))
212
+ ) {
213
+ return true;
214
+ }
215
+ return pathname === DEFAULT_BASE_PATH || pathname.startsWith(`${DEFAULT_BASE_PATH}/`);
216
+ }
217
+
218
+ /**
219
+ * Point an author-written root-relative content path at the configured prefix.
220
+ *
221
+ * Authors reference content assets as they appear on disk
222
+ * (`/docs/assets/images/favicon.ico`, `/docs/assets/images/og.png`). When the
223
+ * site is served under a different prefix that leading segment must be
224
+ * rewritten, otherwise the asset 404s at the old path. Paths that already carry
225
+ * the configured prefix, or sit outside the default one, are left untouched.
226
+ */
227
+ export function rebaseContentPath(
228
+ path: string,
229
+ resolvedBasePath: string = DEFAULT_BASE_PATH
230
+ ): string {
231
+ // A path already carrying the configured prefix is final. At the deployment
232
+ // root ("") no path can carry it, so that guard must not swallow the
233
+ // default-prefix rewrite below.
234
+ if (
235
+ resolvedBasePath.length > 0 &&
236
+ (path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`))
237
+ ) {
238
+ return path;
239
+ }
240
+ // The authored default prefix collapses to the deployment root ("") or is
241
+ // rewritten onto the configured prefix; content assets follow the copy in
242
+ // `<dist>/<prefix>/assets/`, which is the root at a root deployment.
243
+ if (path === DEFAULT_BASE_PATH) return resolvedBasePath || "/";
244
+ if (path.startsWith(`${DEFAULT_BASE_PATH}/`)) {
245
+ return `${resolvedBasePath}${path.slice(DEFAULT_BASE_PATH.length)}`;
246
+ }
247
+ return path;
248
+ }
249
+
250
+ /** Map an `/{basePath}` request back to its extensionless route (dev server).
251
+ * Handles both `/{basePath}/*.html` (pages) and `/{basePath}.html` (the
252
+ * directory-index edge case), with `basePath` taken from `meta.basePath`. */
253
+ export function stripDocsHtmlSuffix(
254
+ pathname: string,
255
+ resolvedBasePath: string = DEFAULT_BASE_PATH
256
+ ): string {
257
+ if (resolvedBasePath.length === 0) {
258
+ // Root deployment: docs live at `/…`, so there is no prefix to match. Only
259
+ // the directory-index edge case can be mapped back safely.
260
+ return pathname === "/index.html" ? "/" : pathname;
261
+ }
262
+ if (pathname === `${resolvedBasePath}.html`) return resolvedBasePath;
263
+ if (pathname.startsWith(`${resolvedBasePath}/`) && pathname.endsWith(".html"))
264
+ return pathname.slice(0, -".html".length);
265
+ return pathname;
266
+ }
267
+
268
+ /**
269
+ * Strip the configured docs prefix from a request path, returning the slug
270
+ * segments. `null` means the request is not a docs route.
271
+ *
272
+ * Root deployments match every request (docs own `/`), so the caller must
273
+ * dispatch `/assets/…` and other static paths to `serveStatic` first — which
274
+ * both dev servers already do.
275
+ */
276
+ export function matchDocsSlug(
277
+ pathname: string,
278
+ resolvedBasePath: string = DEFAULT_BASE_PATH
279
+ ): string[] | null {
280
+ if (resolvedBasePath.length === 0) {
281
+ if (pathname !== "/" && pathname.endsWith("/")) return null;
282
+ return pathname === "/" ? [] : pathname.slice(1).split("/").filter(Boolean);
283
+ }
284
+ if (pathname === resolvedBasePath || pathname === `${resolvedBasePath}/`) return [];
285
+ if (pathname.startsWith(`${resolvedBasePath}/`))
286
+ return pathname
287
+ .slice(resolvedBasePath.length + 1)
288
+ .split("/")
289
+ .filter(Boolean);
290
+ return null;
291
+ }
292
+
293
+ /**
294
+ * Relative climb from a built page to the dist root — the `../` count the HTML
295
+ * shell needs for bundle assets.
296
+ *
297
+ * A page lands at `<dist>/<basePath>/<slug>.html`, so it reaches the dist root
298
+ * through the slug's own directories plus every prefix segment. The host's
299
+ * deployment path is deliberately not counted: pages and assets share it (both
300
+ * resolve under `<deployPath>/`), so it cancels out of the relative math.
301
+ */
302
+ export function docsDepth(slug: string, resolvedBasePath: string = DEFAULT_BASE_PATH): number {
303
+ const pageDirs = slug ? slug.split("/").filter(Boolean).length - 1 : 0;
304
+ const prefixSegments = resolvedBasePath.split("/").filter(Boolean).length;
305
+ return Math.max(0, pageDirs) + prefixSegments;
306
+ }