@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 +1 @@
1
- {"version":3,"file":"logger-DFKQUy8M.js","names":[],"sources":["../../package.json","../node/logger.ts"],"sourcesContent":["{\n \"name\": \"@docubook/flame\",\n \"version\": \"2.0.3\",\n \"description\": \"A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.\",\n \"type\": \"module\",\n \"types\": \"./.docu/node/plugin.ts\",\n \"bin\": {\n \"flame\": \"./bin/cli.js\"\n },\n \"files\": [\n \"bin\",\n \"template\",\n \"docu.schema.json\",\n \".docu\",\n \"!.docu/__tests__\",\n \"!.docu/dist\",\n \"!.docu/build-cache.json\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"publishConfig\": {\n \"registry\": \"https://registry.npmjs.org/\",\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=20.11\",\n \"bun\": \">=1.4.0\"\n },\n \"scripts\": {\n \"dev\": \"bun .docu/node/server.ts\",\n \"build\": \"NODE_ENV=production bun .docu/node/build.ts\",\n \"clean\": \"bun .docu/node/clean.ts\",\n \"preview\": \"bun .docu/node/preview.ts\",\n \"deploy\": \"bun .docu/node/deploy.ts\",\n \"compile:lib\": \"node bin/compile-lib.mjs\",\n \"test\": \"vitest run\",\n \"lint\": \"oxlint .\",\n \"prepublishOnly\": \"bun .docu/node/clean.ts && oxlint . && vitest run && node bin/compile-lib.mjs\"\n },\n \"keywords\": [\n \"docubook\",\n \"documentation\",\n \"static-site\",\n \"daisyui\",\n \"bun\",\n \"docs framework\",\n \"react\",\n \"react-dom\"\n ],\n \"homepage\": \"https://github.com/DocuBook/docubook/tree/main/packages/flame\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/DocuBook/docubook.git\",\n \"directory\": \"packages/flame\"\n },\n \"author\": \"wildan.nrs\",\n \"author-url\": \"https://wildan.dev\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@docubook/core\": \"workspace:^\",\n \"@docubook/markdown\": \"workspace:^\",\n \"@docubook/themes-colors\": \"workspace:^\",\n \"@docubook/ui-react\": \"workspace:^\",\n \"@mdx-js/react\": \"^3.0.1\",\n \"@tailwindcss/cli\": \"4.3.0\",\n \"@tailwindcss/typography\": \"0.5.16\",\n \"bun-plugin-tailwind\": \"^0.1.2\",\n \"daisyui\": \"^5.5.19\",\n \"lucide-react\": \"^1.14.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"unified\": \"^11.0.0\",\n \"vite\": \"^8.2.1\",\n \"zod\": \"^4.4.3\"\n },\n \"peerDependencies\": {\n \"@sentry/bun\": \"^10.0.0\"\n },\n \"peerDependenciesMeta\": {\n \"@sentry/bun\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"bun-types\": \"^1.4.0\",\n \"tailwindcss\": \"^4.3.0\",\n \"typescript\": \"^7.0.2\"\n }\n}\n","/**\n * Interactive CLI logger with spinner and tree-style route display.\n * Inspired by Next.js/Turbopack build output.\n *\n * Supports structured logging via environment variables:\n * - LOG_LEVEL: debug | info | warn | error (default: info)\n * - LOG_FORMAT: json | pretty (default: pretty)\n */\n\nimport { loadDocuConfig } from \"./paths\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nconst docuConfig = loadDocuConfig();\nconst isCI = !!(process.env.CI || process.env.NO_COLOR || !process.stdout.isTTY);\nconst LOG_FORMAT = process.env.LOG_FORMAT || \"pretty\";\nconst isJSON = LOG_FORMAT === \"json\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\nconst LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\nconst currentLevel = LEVELS[(process.env.LOG_LEVEL as LogLevel) || \"info\"] ?? LEVELS.info;\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVELS[level] >= currentLevel;\n}\n\nfunction jsonLog(level: LogLevel, msg: string, meta?: Record<string, unknown>) {\n if (!shouldLog(level)) return;\n const entry = { ts: new Date().toISOString(), ...meta, level, msg };\n const out = level === \"error\" ? console.error : level === \"warn\" ? console.warn : console.log;\n out(JSON.stringify(entry));\n}\n\n/** Returns true if the caller should skip (already handled or filtered). */\nfunction guard(level: LogLevel, msg: string, meta?: Record<string, unknown>): boolean {\n if (isJSON) {\n jsonLog(level, msg, meta);\n return true;\n }\n return !shouldLog(level);\n}\n\nconst c =\n isCI || isJSON\n ? {\n reset: \"\",\n bold: \"\",\n dim: \"\",\n green: \"\",\n cyan: \"\",\n magenta: \"\",\n yellow: \"\",\n red: \"\",\n white: \"\",\n gray: \"\",\n }\n : {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n green: \"\\x1b[32m\",\n cyan: \"\\x1b[36m\",\n magenta: \"\\x1b[35m\",\n yellow: \"\\x1b[33m\",\n red: \"\\x1b[31m\",\n white: \"\\x1b[37m\",\n gray: \"\\x1b[90m\",\n };\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\nclass Spinner {\n private frame = 0;\n private message = \"\";\n private timer: ReturnType<typeof setInterval> | null = null;\n\n info(finalMsg: string) {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n if (isCI) {\n console.log(`ℹ ${finalMsg}`);\n } else {\n process.stdout.write(`\\r\\x1b[K${c.cyan}ℹ${c.reset} ${finalMsg}\\n`);\n process.stdout.write(\"\\x1b[?25h\");\n }\n }\n\n start(msg: string) {\n this.message = msg;\n if (isCI) {\n console.log(`… ${msg}`);\n return;\n }\n this.frame = 0;\n process.stdout.write(\"\\x1b[?25l\");\n this.render();\n this.timer = setInterval(() => this.render(), 80);\n }\n\n stop(finalMsg: string) {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n if (isCI) {\n console.log(`✓ ${finalMsg}`);\n return;\n }\n process.stdout.write(`\\r\\x1b[K${c.green}✓${c.reset} ${finalMsg}\\n`);\n process.stdout.write(\"\\x1b[?25h\");\n }\n\n private render() {\n const f = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];\n process.stdout.write(`\\r${c.cyan}${f}${c.reset} ${this.message}`);\n this.frame++;\n }\n}\n\ninterface RouteItem {\n title: string;\n href: string;\n noLink?: boolean;\n items?: RouteItem[];\n}\n\nconst MAX_ROUTE_LINES = 5;\nlet routeLineCount = 0;\nlet routeTruncated = false;\n\nfunction printRouteTree(routes: RouteItem[], prefix = \"\", isRoot = true) {\n for (let i = 0; i < routes.length; i++) {\n if (routeTruncated) return;\n if (routeLineCount >= MAX_ROUTE_LINES) {\n routeTruncated = true;\n process.stdout.write(`${prefix}${c.dim}...${c.reset}\\n`);\n return;\n }\n\n const route = routes[i];\n const last = i === routes.length - 1;\n const connector = isRoot ? \"\" : last ? \"└── \" : \"├── \";\n const childPrefix = isRoot ? \"\" : last ? \" \" : \"│ \";\n\n const path = route.noLink\n ? `${c.dim}${route.href}${c.reset}`\n : `${c.white}${route.href}${c.reset}`;\n\n const label = route.noLink ? `${c.gray}(group)${c.reset}` : \"\";\n\n process.stdout.write(`${prefix}${connector}${path} ${label}\\n`);\n routeLineCount++;\n\n if (route.items?.length) {\n printRouteTree(route.items, prefix + childPrefix, false);\n }\n }\n}\n\nexport const logger = {\n spinner: new Spinner(),\n\n buildStart() {\n if (guard(\"info\", \"build_start\", { package: \"@docubook/flame\", version: pkg.version })) return;\n console.log(\n `\\n${c.bold}${c.cyan} 🔥 DocuBook Flame${c.reset} ${c.dim}v${pkg.version}${c.reset}\\n`\n );\n },\n\n bundleStart() {\n if (guard(\"info\", \"bundle_start\")) return;\n this.spinner.start(\"Building client bundle...\");\n },\n\n bundleDone(ms: number) {\n if (guard(\"info\", \"bundle_done\", { duration_ms: ms })) return;\n this.spinner.stop(`Client bundle compiled ${c.dim}(${ms}ms)${c.reset}`);\n },\n\n indexStart() {\n if (guard(\"info\", \"index_start\")) return;\n this.spinner.start(\"Generating search index...\");\n },\n\n indexDone(records: number, ms: number, skipped = false) {\n if (guard(\"info\", \"index_done\", { records, duration_ms: ms, skipped })) return;\n this.spinner.stop(\n skipped\n ? `Search index cached ${c.dim}(${ms}ms)${c.reset}`\n : `Search index generated ${c.dim}(${records} records, ${ms}ms)${c.reset}`\n );\n },\n\n routes() {\n if (guard(\"debug\", \"routes\")) return;\n const routes = docuConfig.routes as RouteItem[] | undefined;\n if (!routes?.length) return;\n\n console.log(`\\n${c.bold} Routes${c.reset} ${c.dim}(${countRoutes(routes)} pages)${c.reset}\\n`);\n routeLineCount = 0;\n routeTruncated = false;\n printRouteTree(routes, \" \");\n },\n\n ready(port: number, hmr = false) {\n if (guard(\"info\", \"server_ready\", { port, hmr })) return;\n console.log(\n `\\n${c.green}${c.bold} ✓ Ready${c.reset} in ${c.bold}http://localhost:${port}/docs/${c.reset}`\n );\n if (hmr) console.log(`${c.dim} HMR enabled — watching docs/ for changes${c.reset}`);\n console.log(\"\");\n },\n\n request(method: string, pathname: string, status: number, duration_ms?: number) {\n if (guard(\"info\", \"http_request\", { method, path: pathname, status, duration_ms })) return;\n const color = status >= 500 ? c.red : status >= 400 ? c.gray : c.green;\n const ts = new Date().toLocaleTimeString();\n const dur = duration_ms != null ? ` ${c.dim}${duration_ms}ms${c.reset}` : \"\";\n console.log(\n `${c.dim} ${ts}${c.reset} ${method} ${pathname} ${color}${status}${c.reset}${dur}`\n );\n },\n\n debug(msg: string, meta?: Record<string, unknown>) {\n if (guard(\"debug\", msg, meta)) return;\n console.log(`${c.gray} [debug] ${msg}${c.reset}`);\n },\n\n warn(msg: string, meta?: Record<string, unknown>) {\n if (guard(\"warn\", msg, meta)) return;\n console.warn(`${c.yellow} ⚠ ${msg}${c.reset}`);\n },\n\n error(msg: string, meta?: Record<string, unknown>) {\n if (guard(\"error\", msg, meta)) return;\n console.error(`${c.red} ✖ ${msg}${c.reset}`);\n },\n};\n\nfunction countRoutes(routes: RouteItem[]): number {\n let count = 0;\n for (const r of routes) {\n if (!r.noLink) count++;\n if (r.items) count += countRoutes(r.items);\n }\n return count;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"logger-DYFD47f8.js","names":[],"sources":["../../package.json","../node/logger.ts"],"sourcesContent":["{\n \"name\": \"@docubook/flame\",\n \"version\": \"2.1.0\",\n \"description\": \"A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.\",\n \"type\": \"module\",\n \"types\": \"./.docu/node/plugin.ts\",\n \"bin\": {\n \"flame\": \"./bin/cli.js\"\n },\n \"files\": [\n \"bin\",\n \"template\",\n \"docu.schema.json\",\n \".docu\",\n \"!.docu/__tests__\",\n \"!.docu/dist\",\n \"!.docu/build-cache.json\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"publishConfig\": {\n \"registry\": \"https://registry.npmjs.org/\",\n \"access\": \"public\"\n },\n \"engines\": {\n \"node\": \">=20.11\",\n \"bun\": \">=1.4.0\"\n },\n \"scripts\": {\n \"dev\": \"bun .docu/node/server.ts\",\n \"build\": \"NODE_ENV=production bun .docu/node/build.ts\",\n \"clean\": \"bun .docu/node/clean.ts\",\n \"preview\": \"bun .docu/node/preview.ts\",\n \"deploy\": \"bun .docu/node/deploy.ts\",\n \"compile:lib\": \"node bin/compile-lib.mjs\",\n \"test\": \"vitest run\",\n \"lint\": \"oxlint .\",\n \"prepublishOnly\": \"bun .docu/node/clean.ts && oxlint . && vitest run && node bin/compile-lib.mjs\"\n },\n \"keywords\": [\n \"docubook\",\n \"documentation\",\n \"static-site\",\n \"daisyui\",\n \"bun\",\n \"docs framework\",\n \"react\",\n \"react-dom\"\n ],\n \"homepage\": \"https://github.com/DocuBook/docubook/tree/main/packages/flame\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/DocuBook/docubook.git\",\n \"directory\": \"packages/flame\"\n },\n \"author\": \"wildan.nrs\",\n \"author-url\": \"https://wildan.dev\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@docubook/core\": \"workspace:^\",\n \"@docubook/markdown\": \"workspace:^\",\n \"@docubook/themes-colors\": \"workspace:^\",\n \"@docubook/ui-react\": \"workspace:^\",\n \"@mdx-js/react\": \"^3.0.1\",\n \"@tailwindcss/cli\": \"4.3.0\",\n \"@tailwindcss/typography\": \"0.5.16\",\n \"bun-plugin-tailwind\": \"^0.1.2\",\n \"daisyui\": \"^5.5.19\",\n \"lucide-react\": \"^1.14.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"unified\": \"^11.0.0\",\n \"vite\": \"^8.2.1\",\n \"zod\": \"^4.4.3\"\n },\n \"peerDependencies\": {\n \"@sentry/bun\": \"^10.0.0\"\n },\n \"peerDependenciesMeta\": {\n \"@sentry/bun\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/node\": \"^22.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"bun-types\": \"^1.4.0\",\n \"tailwindcss\": \"^4.3.0\",\n \"typescript\": \"^7.0.2\"\n }\n}\n","/**\n * Interactive CLI logger with spinner and tree-style route display.\n * Inspired by Next.js/Turbopack build output.\n *\n * Supports structured logging via environment variables:\n * - LOG_LEVEL: debug | info | warn | error (default: info)\n * - LOG_FORMAT: json | pretty (default: pretty)\n */\n\nimport { loadDocuConfig } from \"./paths\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nconst docuConfig = loadDocuConfig();\nconst isCI = !!(process.env.CI || process.env.NO_COLOR || !process.stdout.isTTY);\nconst LOG_FORMAT = process.env.LOG_FORMAT || \"pretty\";\nconst isJSON = LOG_FORMAT === \"json\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\nconst LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\nconst currentLevel = LEVELS[(process.env.LOG_LEVEL as LogLevel) || \"info\"] ?? LEVELS.info;\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVELS[level] >= currentLevel;\n}\n\nfunction jsonLog(level: LogLevel, msg: string, meta?: Record<string, unknown>) {\n if (!shouldLog(level)) return;\n const entry = { ts: new Date().toISOString(), ...meta, level, msg };\n const out = level === \"error\" ? console.error : level === \"warn\" ? console.warn : console.log;\n out(JSON.stringify(entry));\n}\n\n/** Returns true if the caller should skip (already handled or filtered). */\nfunction guard(level: LogLevel, msg: string, meta?: Record<string, unknown>): boolean {\n if (isJSON) {\n jsonLog(level, msg, meta);\n return true;\n }\n return !shouldLog(level);\n}\n\nconst c =\n isCI || isJSON\n ? {\n reset: \"\",\n bold: \"\",\n dim: \"\",\n green: \"\",\n cyan: \"\",\n magenta: \"\",\n yellow: \"\",\n red: \"\",\n white: \"\",\n gray: \"\",\n }\n : {\n reset: \"\\x1b[0m\",\n bold: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n green: \"\\x1b[32m\",\n cyan: \"\\x1b[36m\",\n magenta: \"\\x1b[35m\",\n yellow: \"\\x1b[33m\",\n red: \"\\x1b[31m\",\n white: \"\\x1b[37m\",\n gray: \"\\x1b[90m\",\n };\n\nconst SPINNER_FRAMES = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n\nclass Spinner {\n private frame = 0;\n private message = \"\";\n private timer: ReturnType<typeof setInterval> | null = null;\n\n info(finalMsg: string) {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n if (isCI) {\n console.log(`ℹ ${finalMsg}`);\n } else {\n process.stdout.write(`\\r\\x1b[K${c.cyan}ℹ${c.reset} ${finalMsg}\\n`);\n process.stdout.write(\"\\x1b[?25h\");\n }\n }\n\n start(msg: string) {\n this.message = msg;\n if (isCI) {\n console.log(`… ${msg}`);\n return;\n }\n this.frame = 0;\n process.stdout.write(\"\\x1b[?25l\");\n this.render();\n this.timer = setInterval(() => this.render(), 80);\n }\n\n stop(finalMsg: string) {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n if (isCI) {\n console.log(`✓ ${finalMsg}`);\n return;\n }\n process.stdout.write(`\\r\\x1b[K${c.green}✓${c.reset} ${finalMsg}\\n`);\n process.stdout.write(\"\\x1b[?25h\");\n }\n\n private render() {\n const f = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];\n process.stdout.write(`\\r${c.cyan}${f}${c.reset} ${this.message}`);\n this.frame++;\n }\n}\n\ninterface RouteItem {\n title: string;\n href: string;\n noLink?: boolean;\n items?: RouteItem[];\n}\n\nconst MAX_ROUTE_LINES = 5;\nlet routeLineCount = 0;\nlet routeTruncated = false;\n\nfunction printRouteTree(routes: RouteItem[], prefix = \"\", isRoot = true) {\n for (let i = 0; i < routes.length; i++) {\n if (routeTruncated) return;\n if (routeLineCount >= MAX_ROUTE_LINES) {\n routeTruncated = true;\n process.stdout.write(`${prefix}${c.dim}...${c.reset}\\n`);\n return;\n }\n\n const route = routes[i];\n const last = i === routes.length - 1;\n const connector = isRoot ? \"\" : last ? \"└── \" : \"├── \";\n const childPrefix = isRoot ? \"\" : last ? \" \" : \"│ \";\n\n const path = route.noLink\n ? `${c.dim}${route.href}${c.reset}`\n : `${c.white}${route.href}${c.reset}`;\n\n const label = route.noLink ? `${c.gray}(group)${c.reset}` : \"\";\n\n process.stdout.write(`${prefix}${connector}${path} ${label}\\n`);\n routeLineCount++;\n\n if (route.items?.length) {\n printRouteTree(route.items, prefix + childPrefix, false);\n }\n }\n}\n\nexport const logger = {\n spinner: new Spinner(),\n\n buildStart() {\n if (guard(\"info\", \"build_start\", { package: \"@docubook/flame\", version: pkg.version })) return;\n console.log(\n `\\n${c.bold}${c.cyan} 🔥 DocuBook Flame${c.reset} ${c.dim}v${pkg.version}${c.reset}\\n`\n );\n },\n\n bundleStart() {\n if (guard(\"info\", \"bundle_start\")) return;\n this.spinner.start(\"Building client bundle...\");\n },\n\n bundleDone(ms: number) {\n if (guard(\"info\", \"bundle_done\", { duration_ms: ms })) return;\n this.spinner.stop(`Client bundle compiled ${c.dim}(${ms}ms)${c.reset}`);\n },\n\n indexStart() {\n if (guard(\"info\", \"index_start\")) return;\n this.spinner.start(\"Generating search index...\");\n },\n\n indexDone(records: number, ms: number, skipped = false) {\n if (guard(\"info\", \"index_done\", { records, duration_ms: ms, skipped })) return;\n this.spinner.stop(\n skipped\n ? `Search index cached ${c.dim}(${ms}ms)${c.reset}`\n : `Search index generated ${c.dim}(${records} records, ${ms}ms)${c.reset}`\n );\n },\n\n routes() {\n if (guard(\"debug\", \"routes\")) return;\n const routes = docuConfig.routes as RouteItem[] | undefined;\n if (!routes?.length) return;\n\n console.log(`\\n${c.bold} Routes${c.reset} ${c.dim}(${countRoutes(routes)} pages)${c.reset}\\n`);\n routeLineCount = 0;\n routeTruncated = false;\n printRouteTree(routes, \" \");\n },\n\n ready(port: number, hmr = false) {\n if (guard(\"info\", \"server_ready\", { port, hmr })) return;\n console.log(\n `\\n${c.green}${c.bold} ✓ Ready${c.reset} in ${c.bold}http://localhost:${port}/docs/${c.reset}`\n );\n if (hmr) console.log(`${c.dim} HMR enabled — watching docs/ for changes${c.reset}`);\n console.log(\"\");\n },\n\n request(method: string, pathname: string, status: number, duration_ms?: number) {\n if (guard(\"info\", \"http_request\", { method, path: pathname, status, duration_ms })) return;\n const color = status >= 500 ? c.red : status >= 400 ? c.gray : c.green;\n const ts = new Date().toLocaleTimeString();\n const dur = duration_ms != null ? ` ${c.dim}${duration_ms}ms${c.reset}` : \"\";\n console.log(\n `${c.dim} ${ts}${c.reset} ${method} ${pathname} ${color}${status}${c.reset}${dur}`\n );\n },\n\n debug(msg: string, meta?: Record<string, unknown>) {\n if (guard(\"debug\", msg, meta)) return;\n console.log(`${c.gray} [debug] ${msg}${c.reset}`);\n },\n\n warn(msg: string, meta?: Record<string, unknown>) {\n if (guard(\"warn\", msg, meta)) return;\n console.warn(`${c.yellow} ⚠ ${msg}${c.reset}`);\n },\n\n error(msg: string, meta?: Record<string, unknown>) {\n if (guard(\"error\", msg, meta)) return;\n console.error(`${c.red} ✖ ${msg}${c.reset}`);\n },\n};\n\nfunction countRoutes(routes: RouteItem[]): number {\n let count = 0;\n for (const r of routes) {\n if (!r.noLink) count++;\n if (r.items) count += countRoutes(r.items);\n }\n return count;\n}\n"],"mappings":"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"}
@@ -0,0 +1,332 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { readdir, rm, unlink } from "node:fs/promises";
4
+ //#region .docu/node/base-path.ts
5
+ /**
6
+ * Pure base-path helpers shared by server modules and client-side components.
7
+ *
8
+ * This module MUST stay free of Node built-ins (`node:path`, `node:fs`) and of
9
+ * modules that evaluate them at import time (like `./paths`): it is bundled
10
+ * into the client, where `import.meta.dirname` and `process.cwd()` do not
11
+ * exist. All functions here are string operations over a resolved prefix.
12
+ */
13
+ /** Default URL prefix the docs site is served under. Kept as `/docs` so
14
+ * existing deployments keep byte-identical output. */
15
+ var DEFAULT_BASE_PATH = "/docs";
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
+ function normalizeBasePath(value) {
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
+ path = `/${path.replace(/^\/+|\/+$/g, "")}`;
31
+ return path === "/" ? "" : path;
32
+ }
33
+ /**
34
+ * Resolve the docs URL prefix from config.
35
+ *
36
+ * An explicit `meta.basePath` wins, including `""` for a root deployment.
37
+ * Otherwise `/docs` (the historical behaviour).
38
+ *
39
+ * Deliberately NOT derived from `meta.baseURL`'s pathname: `baseURL` is the
40
+ * origin + deployment root and `basePath` is the prefix beneath it. Deriving
41
+ * one from the other's pathname double-counts that path — the SEO/canonical
42
+ * builder appends the prefix to `baseURL`, so `baseURL: ".../repo"` deriving
43
+ * `basePath: "/repo"` produced `.../repo/repo/...`. They must be configured
44
+ * independently; the schema documents both.
45
+ */
46
+ function resolveBasePath(config) {
47
+ const meta = config?.meta;
48
+ if (typeof meta?.basePath !== "string") return DEFAULT_BASE_PATH;
49
+ if (normalizeBasePath(meta.basePath) === "") return "";
50
+ return canonicalBasePath(meta.basePath) || "/docs";
51
+ }
52
+ /**
53
+ * Deployment-root path contributed by the host, taken from `meta.baseURL`:
54
+ * `""` when the origin serves the dist at `/`, `/repo` for a GitHub Pages
55
+ * project site (which serves the artifact under `<origin>/repo/`).
56
+ *
57
+ * Distinct from the base path — `basePath` is the prefix *inside* the dist,
58
+ * this is the prefix *above* it. Only root-absolute references need it (the 404
59
+ * fallback and the search index); relative ones line up already because pages
60
+ * and assets share the deployment root.
61
+ */
62
+ function resolveDeployPath(config) {
63
+ const raw = config?.meta?.baseURL;
64
+ if (typeof raw !== "string") return "";
65
+ try {
66
+ return normalizeBasePath(new URL(raw).pathname);
67
+ } catch {
68
+ return raw.startsWith("/") ? normalizeBasePath(raw) : "";
69
+ }
70
+ }
71
+ /**
72
+ * Canonical prefix form: lowercase segments with whitespace collapsed to dashes
73
+ * and characters a static host cannot serve dropped (`"/Docs"` → `"/docs"`,
74
+ * `"/docs me"` → `"/docs-me"`). Structural normalization runs first, so an
75
+ * absolute value (`https://host/x`) works too. Returns `""` when nothing
76
+ * servable is left, which callers treat as a config mistake rather than a
77
+ * silent root deployment.
78
+ */
79
+ function canonicalBasePath(value) {
80
+ const structural = normalizeBasePath(value);
81
+ if (structural === "") return "";
82
+ const cleaned = structural.replace(/\s+/g, "-").replace(/[^A-Za-z0-9\-._~/]+/g, "").split("/").filter((segment) => segment.length > 0 && segment !== "." && segment !== "..").join("/").replace(/-{2,}/g, "-").toLowerCase();
83
+ return cleaned.length > 0 ? `/${cleaned}` : "";
84
+ }
85
+ /**
86
+ * Validate an authored `meta.basePath`.
87
+ *
88
+ * `resolveBasePath` normalizes what a static host cannot serve, so this reports
89
+ * what it had to change instead of blocking the build: only a non-string value
90
+ * (which cannot be normalized) is an error. Warnings name the reasons and the
91
+ * canonical value, so the author can fix `docu.json` and keep the editor schema
92
+ * — which only accepts the canonical form — quiet.
93
+ */
94
+ function validateBasePath(value) {
95
+ if (value === void 0) return [];
96
+ if (typeof value !== "string") return [{
97
+ level: "error",
98
+ message: `must be a string (received ${value === null ? "null" : typeof value}) — remove it or set "${DEFAULT_BASE_PATH}"`
99
+ }];
100
+ const structural = normalizeBasePath(value);
101
+ if (structural === "") return [];
102
+ const canonical = canonicalBasePath(value);
103
+ if (canonical === "") return [{
104
+ level: "warning",
105
+ message: `"${value}" has no characters a static host can serve — falling back to "${DEFAULT_BASE_PATH}"`
106
+ }];
107
+ if (canonical === structural) return [];
108
+ const segments = structural.replace(/^\/+/, "");
109
+ const reasons = [];
110
+ if (/\s/.test(segments)) reasons.push("whitespace");
111
+ const invalid = [...new Set(segments.replace(/[\s]/g, "").replace(/[A-Za-z0-9\-._~/]/g, ""))].join(" ");
112
+ if (invalid.length > 0) reasons.push(`characters that need percent-encoding (${invalid})`);
113
+ if (segments !== segments.toLowerCase()) reasons.push("non-lowercase letters");
114
+ if (/\.\.|(^|\/)\.(\/|$)|--|\/\//.test(segments)) reasons.push("dot segments or doubled separators");
115
+ return [{
116
+ level: "warning",
117
+ message: `"${value}" ${reasons.length > 0 ? `contains ${reasons.join(", ")}` : "is not canonical"} — normalized to "${canonical}"; update docu.json to match`
118
+ }];
119
+ }
120
+ /**
121
+ * Is `pathname` inside the docs site?
122
+ *
123
+ * True under the configured prefix (when there is one) and for paths still
124
+ * authored with the default `/docs` prefix — those are what callers re-base.
125
+ * Anything else is a sibling route on the same origin (an app at `/app`, a
126
+ * status page) and has to be left exactly as the author wrote it.
127
+ */
128
+ function isDocsPath(pathname, resolvedBasePath = DEFAULT_BASE_PATH) {
129
+ if (resolvedBasePath.length > 0 && (pathname === resolvedBasePath || pathname.startsWith(`${resolvedBasePath}/`))) return true;
130
+ return pathname === "/docs" || pathname.startsWith(`/docs/`);
131
+ }
132
+ /**
133
+ * Point an author-written root-relative content path at the configured prefix.
134
+ *
135
+ * Authors reference content assets as they appear on disk
136
+ * (`/docs/assets/images/favicon.ico`, `/docs/assets/images/og.png`). When the
137
+ * site is served under a different prefix that leading segment must be
138
+ * rewritten, otherwise the asset 404s at the old path. Paths that already carry
139
+ * the configured prefix, or sit outside the default one, are left untouched.
140
+ */
141
+ function rebaseContentPath(path, resolvedBasePath = DEFAULT_BASE_PATH) {
142
+ if (resolvedBasePath.length > 0 && (path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`))) return path;
143
+ if (path === "/docs") return resolvedBasePath || "/";
144
+ if (path.startsWith(`/docs/`)) return `${resolvedBasePath}${path.slice(5)}`;
145
+ return path;
146
+ }
147
+ /** Map an `/{basePath}` request back to its extensionless route (dev server).
148
+ * Handles both `/{basePath}/*.html` (pages) and `/{basePath}.html` (the
149
+ * directory-index edge case), with `basePath` taken from `meta.basePath`. */
150
+ function stripDocsHtmlSuffix(pathname, resolvedBasePath = DEFAULT_BASE_PATH) {
151
+ if (resolvedBasePath.length === 0) return pathname === "/index.html" ? "/" : pathname;
152
+ if (pathname === `${resolvedBasePath}.html`) return resolvedBasePath;
153
+ if (pathname.startsWith(`${resolvedBasePath}/`) && pathname.endsWith(".html")) return pathname.slice(0, -5);
154
+ return pathname;
155
+ }
156
+ /**
157
+ * Strip the configured docs prefix from a request path, returning the slug
158
+ * segments. `null` means the request is not a docs route.
159
+ *
160
+ * Root deployments match every request (docs own `/`), so the caller must
161
+ * dispatch `/assets/…` and other static paths to `serveStatic` first — which
162
+ * both dev servers already do.
163
+ */
164
+ function matchDocsSlug(pathname, resolvedBasePath = DEFAULT_BASE_PATH) {
165
+ if (resolvedBasePath.length === 0) {
166
+ if (pathname !== "/" && pathname.endsWith("/")) return null;
167
+ return pathname === "/" ? [] : pathname.slice(1).split("/").filter(Boolean);
168
+ }
169
+ if (pathname === resolvedBasePath || pathname === `${resolvedBasePath}/`) return [];
170
+ if (pathname.startsWith(`${resolvedBasePath}/`)) return pathname.slice(resolvedBasePath.length + 1).split("/").filter(Boolean);
171
+ return null;
172
+ }
173
+ /**
174
+ * Relative climb from a built page to the dist root — the `../` count the HTML
175
+ * shell needs for bundle assets.
176
+ *
177
+ * A page lands at `<dist>/<basePath>/<slug>.html`, so it reaches the dist root
178
+ * through the slug's own directories plus every prefix segment. The host's
179
+ * deployment path is deliberately not counted: pages and assets share it (both
180
+ * resolve under `<deployPath>/`), so it cancels out of the relative math.
181
+ */
182
+ function docsDepth(slug, resolvedBasePath = DEFAULT_BASE_PATH) {
183
+ const pageDirs = slug ? slug.split("/").filter(Boolean).length - 1 : 0;
184
+ const prefixSegments = resolvedBasePath.split("/").filter(Boolean).length;
185
+ return Math.max(0, pageDirs) + prefixSegments;
186
+ }
187
+ //#endregion
188
+ //#region .docu/node/paths.ts
189
+ /**
190
+ * FRAMEWORK_ROOT: Where the package code lives (.docu/components, .docu/pages, .docu/styles, .docu/node)
191
+ * PROJECT_ROOT: Where the user's project lives (docs/, docu.json)
192
+ */
193
+ var FRAMEWORK_ROOT = resolve(import.meta.dirname, "../..");
194
+ /**
195
+ * Find the project root by walking up from cwd until docu.json is found.
196
+ * Falls back to cwd when not found (tests, ad-hoc scripts). Bun 1.4
197
+ * `run --parallel --filter` may change cwd per workspace task, so a bare
198
+ * process.cwd() can point at the wrong package.
199
+ */
200
+ function findProjectRoot() {
201
+ let dir = process.cwd();
202
+ for (let i = 0; i < 6; i++) {
203
+ try {
204
+ if (existsSync(join(dir, "docu.json"))) return dir;
205
+ } catch {
206
+ break;
207
+ }
208
+ const parent = resolve(dir, "..");
209
+ if (parent === dir) break;
210
+ dir = parent;
211
+ }
212
+ return process.cwd();
213
+ }
214
+ var PROJECT_ROOT = findProjectRoot();
215
+ var PAGES_DIR = join(FRAMEWORK_ROOT, ".docu/pages");
216
+ var STYLES_DIR = join(FRAMEWORK_ROOT, ".docu/styles");
217
+ var nodeDir = join(FRAMEWORK_ROOT, ".docu/node");
218
+ var libDir = join(FRAMEWORK_ROOT, ".docu/lib");
219
+ var LIB_DIR = existsSync(nodeDir) ? nodeDir : libDir;
220
+ var DIST_DIR = join(PROJECT_ROOT, ".docu/dist");
221
+ var ASSETS_DIR = join(DIST_DIR, "assets");
222
+ var CACHE_FILE = join(PROJECT_ROOT, ".docu/build-cache.json");
223
+ var DOCS_DIR = join(PROJECT_ROOT, "docs");
224
+ var DOCS_ASSETS_DIR = join(PROJECT_ROOT, "docs/assets");
225
+ var DOCU_CONFIG_PATH = join(PROJECT_ROOT, "docu.json");
226
+ /** Resolve a project-relative file against the discovered PROJECT_ROOT. */
227
+ function resolveProjectFile(...segments) {
228
+ return join(PROJECT_ROOT, ...segments);
229
+ }
230
+ var _config = null;
231
+ /** Clean stale client bundles from a previous build. */
232
+ async function cleanOldBundles(preserve) {
233
+ try {
234
+ const files = await readdir(ASSETS_DIR);
235
+ for (const file of files) {
236
+ if (preserve?.has(file)) continue;
237
+ if (/^(client|home-client|docs|site)[.-]/.test(file)) await unlink(join(ASSETS_DIR, file));
238
+ }
239
+ await rm(join(ASSETS_DIR, "chunks"), {
240
+ recursive: true,
241
+ force: true
242
+ });
243
+ } catch (err) {
244
+ if (err.code !== "ENOENT") console.error("Failed to clean old bundles:", err.message);
245
+ }
246
+ }
247
+ function loadDocuConfig() {
248
+ if (_config) return _config;
249
+ if (!existsSync(DOCU_CONFIG_PATH)) throw new Error(`docu.json not found at ${DOCU_CONFIG_PATH}`);
250
+ _config = JSON.parse(readFileSync(DOCU_CONFIG_PATH, "utf-8"));
251
+ return _config;
252
+ }
253
+ /** Default favicon — resolves against the runtime `meta.basePath`. Lives here
254
+ * (not in `./base-path`) because it needs the config-loaded `basePath()`. */
255
+ function defaultFavicon() {
256
+ return `${basePath()}/assets/images/favicon.ico`;
257
+ }
258
+ /**
259
+ * Cached base path for the current project. Callers that already hold a config
260
+ * may pass it in; otherwise the config singleton is loaded on first use.
261
+ *
262
+ * Safe to read eagerly at module scope even though `loadDocuConfig()` is only
263
+ * invoked inside a build: `PROJECT_ROOT` is resolved at import time, and the
264
+ * fallback (missing or malformed docu.json) is the `/docs` default, so the
265
+ * value never depends on call order.
266
+ */
267
+ function basePath() {
268
+ let config = null;
269
+ try {
270
+ config = loadDocuConfig();
271
+ } catch {}
272
+ return resolveBasePath(config);
273
+ }
274
+ /** Build-output directory for docs pages, e.g. `.docu/dist/docs` or `dist/` at the root. */
275
+ function docsOutDir(distDir, resolvedBasePath = basePath()) {
276
+ return resolvedBasePath.length === 0 ? distDir : join(distDir, resolvedBasePath.slice(1));
277
+ }
278
+ /** Serve-prefix counterpart of {@link docsOutDir}: `""` at the root, else
279
+ * `/prefix` with no trailing slash. */
280
+ function servedBasePath(resolvedBasePath = basePath()) {
281
+ return resolvedBasePath;
282
+ }
283
+ /**
284
+ * Deployment-root path contributed by the host — `""` when the origin serves
285
+ * the dist at `/`, `/repo` for a GitHub Pages project site. Root-absolute
286
+ * references (404 fallback assets, the search index) are built from it.
287
+ */
288
+ function deployPath() {
289
+ let config = null;
290
+ try {
291
+ config = loadDocuConfig();
292
+ } catch {}
293
+ return resolveDeployPath(config);
294
+ }
295
+ /**
296
+ * {@link deployPath} for the current mode: a host path only exists in built
297
+ * output. Dev serves the project from the origin root, so writing it into dev
298
+ * HTML (or a dev client bundle) would point at directories the dev server does
299
+ * not own.
300
+ */
301
+ function servedDeployPath() {
302
+ return process.env.NODE_ENV === "production" ? deployPath() : "";
303
+ }
304
+ /**
305
+ * Fail fast on a `meta.basePath` that cannot be honored (a non-string value) and
306
+ * hand back the normalization warnings for the caller to log — case, whitespace
307
+ * and unservable characters are normalized by `resolveBasePath`, not rejected.
308
+ * Called from the build and dev-server entries so the message lands before any
309
+ * output is written.
310
+ */
311
+ function assertValidBasePath() {
312
+ let config = null;
313
+ try {
314
+ config = loadDocuConfig();
315
+ } catch {
316
+ return [];
317
+ }
318
+ const issues = validateBasePath(config?.meta?.basePath);
319
+ const errors = issues.filter((issue) => issue.level === "error");
320
+ if (errors.length > 0) throw new Error([`Invalid "meta.basePath" in ${DOCU_CONFIG_PATH}:`, ...errors.map((issue) => ` • ${issue.message}`)].join("\n"));
321
+ return issues.filter((issue) => issue.level === "warning");
322
+ }
323
+ /**
324
+ * Build-output directory for docs pages — derived from `meta.basePath`.
325
+ * Declared after the resolver above: `const` bindings initialize in order, and
326
+ * `DOCS_OUT_DIR` reads `DEFAULT_BASE_PATH` through the chain.
327
+ */
328
+ var DOCS_OUT_DIR = docsOutDir(DIST_DIR);
329
+ //#endregion
330
+ export { isDocsPath as C, resolveDeployPath as D, resolveBasePath as E, stripDocsHtmlSuffix as O, docsDepth as S, rebaseContentPath as T, loadDocuConfig as _, DOCS_DIR as a, servedDeployPath as b, LIB_DIR as c, STYLES_DIR as d, assertValidBasePath as f, deployPath as g, defaultFavicon as h, DOCS_ASSETS_DIR as i, PAGES_DIR as l, cleanOldBundles as m, CACHE_FILE as n, DOCS_OUT_DIR as o, basePath as p, DIST_DIR as r, FRAMEWORK_ROOT as s, ASSETS_DIR as t, PROJECT_ROOT as u, resolveProjectFile as v, matchDocsSlug as w, DEFAULT_BASE_PATH as x, servedBasePath as y };
331
+
332
+ //# sourceMappingURL=paths-CHXABfXa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths-CHXABfXa.js","names":[],"sources":["../node/base-path.ts","../node/paths.ts"],"sourcesContent":["import type { DocuConfig } from \"./types\";\n\n/**\n * Pure base-path helpers shared by server modules and client-side components.\n *\n * This module MUST stay free of Node built-ins (`node:path`, `node:fs`) and of\n * modules that evaluate them at import time (like `./paths`): it is bundled\n * into the client, where `import.meta.dirname` and `process.cwd()` do not\n * exist. All functions here are string operations over a resolved prefix.\n */\n\n/** Default URL prefix the docs site is served under. Kept as `/docs` so\n * existing deployments keep byte-identical output. */\nexport const DEFAULT_BASE_PATH = \"/docs\";\n\n/**\n * Normalize a configured base path into `/prefix` form with no trailing slash.\n * `\"\"` and `\"/\"` mean \"served at the domain root\"; a missing value is not\n * handled here — `resolveBasePath` owns the default. Absolute URLs\n * (`https://host/x`) contribute only their pathname, so a `meta.baseURL` can be\n * passed straight in.\n */\nexport function normalizeBasePath(value: string | undefined | null): string {\n if (typeof value !== \"string\") return DEFAULT_BASE_PATH;\n let path = value.trim();\n if (path.length === 0 || path === \"/\") return \"\";\n try {\n if (/^[a-zA-Z][a-zA-Z\\d+.-]*:\\/\\//.test(path)) path = new URL(path).pathname;\n } catch {\n // malformed URL — fall through and treat the raw value as a path\n }\n path = `/${path.replace(/^\\/+|\\/+$/g, \"\")}`;\n return path === \"/\" ? \"\" : path;\n}\n\n/**\n * Resolve the docs URL prefix from config.\n *\n * An explicit `meta.basePath` wins, including `\"\"` for a root deployment.\n * Otherwise `/docs` (the historical behaviour).\n *\n * Deliberately NOT derived from `meta.baseURL`'s pathname: `baseURL` is the\n * origin + deployment root and `basePath` is the prefix beneath it. Deriving\n * one from the other's pathname double-counts that path — the SEO/canonical\n * builder appends the prefix to `baseURL`, so `baseURL: \".../repo\"` deriving\n * `basePath: \"/repo\"` produced `.../repo/repo/...`. They must be configured\n * independently; the schema documents both.\n */\nexport function resolveBasePath(config?: DocuConfig | null): string {\n const meta = config?.meta;\n if (typeof meta?.basePath !== \"string\") return DEFAULT_BASE_PATH;\n // Root forms (`\"\"`, `\"/\"`, `\"///\"`) stay root. Everything else is\n // canonicalized so routing, output directories and every generated URL agree\n // on one spelling — `\"/Docs\"` and `\"/docs me\"` are honored as `\"/docs\"`\n // and `\"/docs-me\"` (reported by `validateBasePath`).\n if (normalizeBasePath(meta.basePath) === \"\") return \"\";\n return canonicalBasePath(meta.basePath) || DEFAULT_BASE_PATH;\n}\n\n/**\n * Deployment-root path contributed by the host, taken from `meta.baseURL`:\n * `\"\"` when the origin serves the dist at `/`, `/repo` for a GitHub Pages\n * project site (which serves the artifact under `<origin>/repo/`).\n *\n * Distinct from the base path — `basePath` is the prefix *inside* the dist,\n * this is the prefix *above* it. Only root-absolute references need it (the 404\n * fallback and the search index); relative ones line up already because pages\n * and assets share the deployment root.\n */\nexport function resolveDeployPath(config?: DocuConfig | null): string {\n const raw = config?.meta?.baseURL;\n if (typeof raw !== \"string\") return \"\";\n try {\n return normalizeBasePath(new URL(raw).pathname);\n } catch {\n // Not an absolute URL. A root-relative value is still a usable path; a bare\n // hostname without a scheme is a config mistake, not a prefix — ignore it.\n return raw.startsWith(\"/\") ? normalizeBasePath(raw) : \"\";\n }\n}\n\nexport interface BasePathIssue {\n /** `error` blocks the build; `warning` only reports. */\n level: \"error\" | \"warning\";\n message: string;\n}\n\n/**\n * Canonical prefix form: lowercase segments with whitespace collapsed to dashes\n * and characters a static host cannot serve dropped (`\"/Docs\"` → `\"/docs\"`,\n * `\"/docs me\"` → `\"/docs-me\"`). Structural normalization runs first, so an\n * absolute value (`https://host/x`) works too. Returns `\"\"` when nothing\n * servable is left, which callers treat as a config mistake rather than a\n * silent root deployment.\n */\nexport function canonicalBasePath(value: string): string {\n const structural = normalizeBasePath(value);\n if (structural === \"\") return \"\";\n // Whitespace and unservable characters fold away, then dot segments are\n // dropped: a prefix is a URL path *and* an output directory, so `.`/`..`\n // would either escape the dist or resolve differently than the URL does.\n const cleaned = structural\n .replace(/\\s+/g, \"-\")\n .replace(/[^A-Za-z0-9\\-._~/]+/g, \"\")\n .split(\"/\")\n .filter((segment) => segment.length > 0 && segment !== \".\" && segment !== \"..\")\n .join(\"/\")\n .replace(/-{2,}/g, \"-\")\n .toLowerCase();\n return cleaned.length > 0 ? `/${cleaned}` : \"\";\n}\n\n/**\n * Validate an authored `meta.basePath`.\n *\n * `resolveBasePath` normalizes what a static host cannot serve, so this reports\n * what it had to change instead of blocking the build: only a non-string value\n * (which cannot be normalized) is an error. Warnings name the reasons and the\n * canonical value, so the author can fix `docu.json` and keep the editor schema\n * — which only accepts the canonical form — quiet.\n */\nexport function validateBasePath(value: unknown): BasePathIssue[] {\n if (value === undefined) return [];\n if (typeof value !== \"string\") {\n return [\n {\n level: \"error\",\n message: `must be a string (received ${\n value === null ? \"null\" : typeof value\n }) — remove it or set \"${DEFAULT_BASE_PATH}\"`,\n },\n ];\n }\n\n // `\"\"`, `\"/\"`, `\"///\"` and whitespace-only values all mean the deployment\n // root, which is always servable.\n const structural = normalizeBasePath(value);\n if (structural === \"\") return [];\n\n const canonical = canonicalBasePath(value);\n if (canonical === \"\") {\n return [\n {\n level: \"warning\",\n message: `\"${value}\" has no characters a static host can serve — falling back to \"${DEFAULT_BASE_PATH}\"`,\n },\n ];\n }\n if (canonical === structural) return [];\n\n const segments = structural.replace(/^\\/+/, \"\");\n const reasons: string[] = [];\n if (/\\s/.test(segments)) reasons.push(\"whitespace\");\n // Whitespace is already reported above — list only the other unservable\n // characters here, or the message prints a blank `( )`.\n const invalid = [\n ...new Set(segments.replace(/[\\s]/g, \"\").replace(/[A-Za-z0-9\\-._~/]/g, \"\")),\n ].join(\" \");\n if (invalid.length > 0) reasons.push(`characters that need percent-encoding (${invalid})`);\n if (segments !== segments.toLowerCase()) reasons.push(\"non-lowercase letters\");\n if (/\\.\\.|(^|\\/)\\.(\\/|$)|--|\\/\\//.test(segments))\n reasons.push(\"dot segments or doubled separators\");\n\n return [\n {\n level: \"warning\",\n message: `\"${value}\" ${\n reasons.length > 0 ? `contains ${reasons.join(\", \")}` : \"is not canonical\"\n } — normalized to \"${canonical}\"; update docu.json to match`,\n },\n ];\n}\n\n/** Join a serve-prefix with a slash-relative path, keeping exactly one slash. */\nexport function withBasePath(path: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {\n const suffix = path.length === 0 || path === \"/\" ? \"\" : `/${path.replace(/^\\/+|\\/+$/g, \"\")}`;\n return `${resolvedBasePath}${suffix}`;\n}\n\n/** Prefix a root-relative asset path (e.g. `/assets/x.js`) with the base path. */\nexport function assetHref(assetPath: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {\n return withBasePath(assetPath, resolvedBasePath);\n}\n\n/**\n * Apply the base path to a docs route unless it already carries it.\n * Idempotent, which matters because routes are prefixed at more than one\n * layer (nav components, SEO, search indexer) and a route may already be\n * absolute when it reaches us.\n */\nexport function applyBasePath(path: string, resolvedBasePath: string = DEFAULT_BASE_PATH): string {\n if (resolvedBasePath.length === 0) return path;\n if (path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`)) return path;\n return withBasePath(path, resolvedBasePath);\n}\n\n/**\n * Is `pathname` inside the docs site?\n *\n * True under the configured prefix (when there is one) and for paths still\n * authored with the default `/docs` prefix — those are what callers re-base.\n * Anything else is a sibling route on the same origin (an app at `/app`, a\n * status page) and has to be left exactly as the author wrote it.\n */\nexport function isDocsPath(\n pathname: string,\n resolvedBasePath: string = DEFAULT_BASE_PATH\n): boolean {\n if (\n resolvedBasePath.length > 0 &&\n (pathname === resolvedBasePath || pathname.startsWith(`${resolvedBasePath}/`))\n ) {\n return true;\n }\n return pathname === DEFAULT_BASE_PATH || pathname.startsWith(`${DEFAULT_BASE_PATH}/`);\n}\n\n/**\n * Point an author-written root-relative content path at the configured prefix.\n *\n * Authors reference content assets as they appear on disk\n * (`/docs/assets/images/favicon.ico`, `/docs/assets/images/og.png`). When the\n * site is served under a different prefix that leading segment must be\n * rewritten, otherwise the asset 404s at the old path. Paths that already carry\n * the configured prefix, or sit outside the default one, are left untouched.\n */\nexport function rebaseContentPath(\n path: string,\n resolvedBasePath: string = DEFAULT_BASE_PATH\n): string {\n // A path already carrying the configured prefix is final. At the deployment\n // root (\"\") no path can carry it, so that guard must not swallow the\n // default-prefix rewrite below.\n if (\n resolvedBasePath.length > 0 &&\n (path === resolvedBasePath || path.startsWith(`${resolvedBasePath}/`))\n ) {\n return path;\n }\n // The authored default prefix collapses to the deployment root (\"\") or is\n // rewritten onto the configured prefix; content assets follow the copy in\n // `<dist>/<prefix>/assets/`, which is the root at a root deployment.\n if (path === DEFAULT_BASE_PATH) return resolvedBasePath || \"/\";\n if (path.startsWith(`${DEFAULT_BASE_PATH}/`)) {\n return `${resolvedBasePath}${path.slice(DEFAULT_BASE_PATH.length)}`;\n }\n return path;\n}\n\n/** Map an `/{basePath}` request back to its extensionless route (dev server).\n * Handles both `/{basePath}/*.html` (pages) and `/{basePath}.html` (the\n * directory-index edge case), with `basePath` taken from `meta.basePath`. */\nexport function stripDocsHtmlSuffix(\n pathname: string,\n resolvedBasePath: string = DEFAULT_BASE_PATH\n): string {\n if (resolvedBasePath.length === 0) {\n // Root deployment: docs live at `/…`, so there is no prefix to match. Only\n // the directory-index edge case can be mapped back safely.\n return pathname === \"/index.html\" ? \"/\" : pathname;\n }\n if (pathname === `${resolvedBasePath}.html`) return resolvedBasePath;\n if (pathname.startsWith(`${resolvedBasePath}/`) && pathname.endsWith(\".html\"))\n return pathname.slice(0, -\".html\".length);\n return pathname;\n}\n\n/**\n * Strip the configured docs prefix from a request path, returning the slug\n * segments. `null` means the request is not a docs route.\n *\n * Root deployments match every request (docs own `/`), so the caller must\n * dispatch `/assets/…` and other static paths to `serveStatic` first — which\n * both dev servers already do.\n */\nexport function matchDocsSlug(\n pathname: string,\n resolvedBasePath: string = DEFAULT_BASE_PATH\n): string[] | null {\n if (resolvedBasePath.length === 0) {\n if (pathname !== \"/\" && pathname.endsWith(\"/\")) return null;\n return pathname === \"/\" ? [] : pathname.slice(1).split(\"/\").filter(Boolean);\n }\n if (pathname === resolvedBasePath || pathname === `${resolvedBasePath}/`) return [];\n if (pathname.startsWith(`${resolvedBasePath}/`))\n return pathname\n .slice(resolvedBasePath.length + 1)\n .split(\"/\")\n .filter(Boolean);\n return null;\n}\n\n/**\n * Relative climb from a built page to the dist root — the `../` count the HTML\n * shell needs for bundle assets.\n *\n * A page lands at `<dist>/<basePath>/<slug>.html`, so it reaches the dist root\n * through the slug's own directories plus every prefix segment. The host's\n * deployment path is deliberately not counted: pages and assets share it (both\n * resolve under `<deployPath>/`), so it cancels out of the relative math.\n */\nexport function docsDepth(slug: string, resolvedBasePath: string = DEFAULT_BASE_PATH): number {\n const pageDirs = slug ? slug.split(\"/\").filter(Boolean).length - 1 : 0;\n const prefixSegments = resolvedBasePath.split(\"/\").filter(Boolean).length;\n return Math.max(0, pageDirs) + prefixSegments;\n}\n","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\";\nimport { resolveBasePath, resolveDeployPath, validateBasePath } from \"./base-path\";\nimport type { BasePathIssue } from \"./base-path\";\n\nexport {\n DEFAULT_BASE_PATH,\n normalizeBasePath,\n canonicalBasePath,\n resolveBasePath,\n resolveDeployPath,\n docsDepth,\n validateBasePath,\n} from \"./base-path\";\nexport type { BasePathIssue } from \"./base-path\";\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\n/** Default favicon — resolves against the runtime `meta.basePath`. Lives here\n * (not in `./base-path`) because it needs the config-loaded `basePath()`. */\nexport function defaultFavicon(): string {\n return `${basePath()}/assets/images/favicon.ico`;\n}\n\n/**\n * @deprecated Read the value at call time via {@link defaultFavicon} — this\n * constant is frozen at import and cannot follow `meta.basePath`.\n */\nexport const DEFAULT_FAVICON = \"/docs/assets/images/favicon.ico\";\n\n/**\n * Cached base path for the current project. Callers that already hold a config\n * may pass it in; otherwise the config singleton is loaded on first use.\n *\n * Safe to read eagerly at module scope even though `loadDocuConfig()` is only\n * invoked inside a build: `PROJECT_ROOT` is resolved at import time, and the\n * fallback (missing or malformed docu.json) is the `/docs` default, so the\n * value never depends on call order.\n */\nexport function basePath(): string {\n let config: DocuConfig | null = null;\n try {\n config = loadDocuConfig();\n } catch {\n // config absent/unreadable (tests, ad-hoc scripts) — use the default\n }\n return resolveBasePath(config);\n}\n\n/** Build-output directory for docs pages, e.g. `.docu/dist/docs` or `dist/` at the root. */\nexport function docsOutDir(distDir: string, resolvedBasePath: string = basePath()): string {\n return resolvedBasePath.length === 0 ? distDir : join(distDir, resolvedBasePath.slice(1));\n}\n\n/** Serve-prefix counterpart of {@link docsOutDir}: `\"\"` at the root, else\n * `/prefix` with no trailing slash. */\nexport function servedBasePath(resolvedBasePath: string = basePath()): string {\n return resolvedBasePath;\n}\n\n/**\n * Deployment-root path contributed by the host — `\"\"` when the origin serves\n * the dist at `/`, `/repo` for a GitHub Pages project site. Root-absolute\n * references (404 fallback assets, the search index) are built from it.\n */\nexport function deployPath(): string {\n let config: DocuConfig | null = null;\n try {\n config = loadDocuConfig();\n } catch {\n // config absent/unreadable (tests, ad-hoc scripts) — assume an origin root\n }\n return resolveDeployPath(config);\n}\n\n/**\n * {@link deployPath} for the current mode: a host path only exists in built\n * output. Dev serves the project from the origin root, so writing it into dev\n * HTML (or a dev client bundle) would point at directories the dev server does\n * not own.\n */\nexport function servedDeployPath(): string {\n return process.env.NODE_ENV === \"production\" ? deployPath() : \"\";\n}\n\n/**\n * Fail fast on a `meta.basePath` that cannot be honored (a non-string value) and\n * hand back the normalization warnings for the caller to log — case, whitespace\n * and unservable characters are normalized by `resolveBasePath`, not rejected.\n * Called from the build and dev-server entries so the message lands before any\n * output is written.\n */\nexport function assertValidBasePath(): BasePathIssue[] {\n let config: DocuConfig | null = null;\n try {\n config = loadDocuConfig();\n } catch {\n return [];\n }\n\n const issues = validateBasePath(config?.meta?.basePath);\n const errors = issues.filter((issue) => issue.level === \"error\");\n if (errors.length > 0) {\n throw new Error(\n [\n `Invalid \"meta.basePath\" in ${DOCU_CONFIG_PATH}:`,\n ...errors.map((issue) => ` • ${issue.message}`),\n ].join(\"\\n\")\n );\n }\n return issues.filter((issue) => issue.level === \"warning\");\n}\n\n/**\n * Build-output directory for docs pages — derived from `meta.basePath`.\n * Declared after the resolver above: `const` bindings initialize in order, and\n * `DOCS_OUT_DIR` reads `DEFAULT_BASE_PATH` through the chain.\n */\nexport const DOCS_OUT_DIR = docsOutDir(DIST_DIR);\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as denoAdapter } from "./deno-DmEaKggj.js";
2
- import { t as runPreview } from "./preview.impl-BfESMUM7.js";
2
+ import { t as runPreview } from "./preview.impl-DMXVsGs0.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-Cbz-dj4s.js";
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-DFKQUy8M.js";
1
+ import { r as DIST_DIR } from "./paths-CHXABfXa.js";
2
+ import { a as cspHeader, i as SECURITY_HEADERS, l as injectNonce, s as generateNonce, t as getContentType } from "./server-utils-BYWB7A6T.js";
3
+ import { t as logger } from "./logger-DYFD47f8.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-BfESMUM7.js.map
77
+ //# sourceMappingURL=preview.impl-DMXVsGs0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.impl-DMXVsGs0.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 \"./server-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-BfESMUM7.js";
2
+ import { t as runPreview } from "./preview.impl-DMXVsGs0.js";
3
3
  //#region .docu/node/preview.node.ts
4
4
  await runPreview(nodeAdapter);
5
5
  //#endregion
@@ -2,7 +2,6 @@ import { existsSync, realpathSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { readdir, stat } from "node:fs/promises";
4
4
  import { randomBytes } from "node:crypto";
5
- import { cn, formatDate2 } from "@docubook/core";
6
5
  //#region .docu/node/security.ts
7
6
  var SECURITY_HEADERS = {
8
7
  "Strict-Transport-Security": "max-age=63072000; includeSubDomains; preload",
@@ -110,7 +109,7 @@ function htmlResponse(html, nonce, status = 200, allowEval = false) {
110
109
  });
111
110
  }
112
111
  //#endregion
113
- //#region .docu/node/utils.ts
112
+ //#region .docu/node/server-utils.ts
114
113
  /**
115
114
  * Scan a directory recursively for MDX/MD files.
116
115
  * Skips "assets" directories, hidden directories (dot-prefixed), and root-level
@@ -140,9 +139,6 @@ async function scanMdxFiles(dir, baseDir = "") {
140
139
  }
141
140
  return files;
142
141
  }
143
- function isExternalUrl(url) {
144
- return /^(https?:\/\/|\/\/)/.test(url);
145
- }
146
142
  /**
147
143
  * Resolve the docs root index source — docs/index.mdx preferred, docs/index.md
148
144
  * as fallback (mirrors the dev server's getDocsForSlug extension handling).
@@ -153,20 +149,6 @@ function resolveDocsIndexSource(docsDir) {
153
149
  if (existsSync(candidate)) return candidate;
154
150
  }
155
151
  }
156
- /** Default favicon — resolves in both build output (`docs/assets/` is copied
157
- * to `dist/docs/assets/`) and dev (served from `docs/assets/` via fallback). */
158
- var DEFAULT_FAVICON = "/docs/assets/images/favicon.ico";
159
- /** Suffix an internal docs link with `.html` to match the flat static build output. */
160
- function docsHtmlHref(path) {
161
- return `${path}.html`;
162
- }
163
- /** Map a `/docs{.html,/*.html}` request back to its extensionless route (dev server).
164
- * Handles both `/docs/*.html` (pages) and `/docs.html` (edge case). */
165
- function stripDocsHtmlSuffix(pathname) {
166
- if (pathname === "/docs.html") return "/docs";
167
- if (pathname.startsWith("/docs/") && pathname.endsWith(".html")) return pathname.slice(0, -5);
168
- return pathname;
169
- }
170
152
  var MIME_TYPES = {
171
153
  html: "text/html",
172
154
  css: "text/css",
@@ -184,6 +166,6 @@ function getContentType(pathname) {
184
166
  return MIME_TYPES[pathname.split(".").pop()?.toLowerCase() || ""] || "application/octet-stream";
185
167
  }
186
168
  //#endregion
187
- export { isSlugSafe as _, getContentType as a, scanMdxFiles as c, cspHeader as d, cspMeta as f, isPathSafe as g, injectNonce as h, formatDate2 as i, stripDocsHtmlSuffix as l, htmlResponse as m, cn as n, isExternalUrl as o, generateNonce as p, docsHtmlHref as r, resolveDocsIndexSource as s, DEFAULT_FAVICON as t, SECURITY_HEADERS as u, normalizeImporterPath as v, wrapPluginResponse as y };
169
+ export { cspHeader as a, htmlResponse as c, isSlugSafe as d, normalizeImporterPath as f, SECURITY_HEADERS as i, injectNonce as l, resolveDocsIndexSource as n, cspMeta as o, wrapPluginResponse as p, scanMdxFiles as r, generateNonce as s, getContentType as t, isPathSafe as u };
188
170
 
189
- //# sourceMappingURL=utils-B_CoyKie.js.map
171
+ //# sourceMappingURL=server-utils-BYWB7A6T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-utils-BYWB7A6T.js","names":[],"sources":["../node/security.ts","../node/server-utils.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { resolve } from \"node:path\";\nimport { realpathSync } from \"node:fs\";\n\nexport const SECURITY_HEADERS: Record<string, string> = {\n \"Strict-Transport-Security\": \"max-age=63072000; includeSubDomains; preload\",\n \"X-Frame-Options\": \"DENY\",\n \"X-Content-Type-Options\": \"nosniff\",\n \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n \"Permissions-Policy\": \"camera=(), microphone=(), geolocation=()\",\n};\n\nexport function generateNonce(): string {\n return randomBytes(16).toString(\"base64\");\n}\n\nexport function cspHeader(nonce: string, allowEval = false): string {\n const scriptSrc = allowEval\n ? `script-src 'self' 'nonce-${nonce}' 'unsafe-eval'`\n : `script-src 'self' 'nonce-${nonce}'`;\n return [\n \"default-src 'self'\",\n scriptSrc,\n \"style-src 'self' 'unsafe-inline'\",\n \"img-src 'self' https: data:\",\n \"font-src 'self' data:\",\n \"connect-src 'self' https:\",\n \"frame-src https://www.youtube-nocookie.com\",\n \"frame-ancestors 'none'\",\n ].join(\"; \");\n}\n\n/**\n * Strip `frame-ancestors` for `<meta http-equiv=\"Content-Security-Policy\">`.\n * Browsers ignore `frame-ancestors` in meta CSP (header-only directive)\n * and log a console warning. HTTP header from `cspHeader()` keeps it.\n */\nexport function cspMeta(csp: string): string {\n return csp.replace(/;\\s*frame-ancestors 'none'/, \"\");\n}\n\nexport function isPathSafe(pathname: string, baseDir: string): boolean {\n const decoded = decodeURIComponent(pathname);\n const resolved = resolve(baseDir, decoded.slice(1));\n const baseDirSlash = baseDir.endsWith(\"/\") ? baseDir : baseDir + \"/\";\n if (!(resolved === baseDir || resolved.startsWith(baseDirSlash))) {\n return false;\n }\n\n if (resolved === baseDir) return true;\n try {\n const real = realpathSync(resolved);\n const realBase = realpathSync(baseDir);\n const realBaseDirSlash = realBase.endsWith(\"/\") ? realBase : realBase + \"/\";\n return real.startsWith(realBaseDirSlash);\n } catch (err) {\n const nodeErr = err as NodeJS.ErrnoException;\n if (nodeErr.code === \"ENOENT\") {\n return true;\n }\n console.error(\n `[security] isPathSafe error for pathname=\"${pathname}\": ${nodeErr.message} (code=${nodeErr.code})`\n );\n return false;\n }\n}\n\nexport function isSlugSafe(slug: string, docsDir: string): boolean {\n const resolved = resolve(docsDir, slug);\n const docsDirSlash = docsDir.endsWith(\"/\") ? docsDir : docsDir + \"/\";\n if (!(resolved === docsDir || resolved.startsWith(docsDirSlash))) {\n return false;\n }\n\n if (resolved === docsDir) return true;\n try {\n const real = realpathSync(resolved);\n const realDocsDir = realpathSync(docsDir);\n const realDocsDirSlash = realDocsDir.endsWith(\"/\") ? realDocsDir : realDocsDir + \"/\";\n return real.startsWith(realDocsDirSlash);\n } catch (err) {\n const nodeErr = err as NodeJS.ErrnoException;\n if (nodeErr.code === \"ENOENT\") {\n return true;\n }\n console.error(\n `[security] isSlugSafe error for slug=\"${slug}\": ${nodeErr.message} (code=${nodeErr.code})`\n );\n return false;\n }\n}\n\n/** Normalize an esbuild importer path to a canonical forward-slash absolute form. */\nexport function normalizeImporterPath(importer: string): string {\n return resolve(importer).replace(/\\\\/g, \"/\");\n}\n\nexport function injectNonce(html: string, nonce: string): string {\n const scripts = html.replace(/<script\\b(?![^>]*\\bsrc\\s*=)([^>]*)>/gi, (match) => {\n if (/nonce\\s*=/i.test(match)) {\n return match.replace(/nonce=\"[^\"]*\"/i, `nonce=\"${nonce}\"`);\n }\n return match.replace(/>$/, ` nonce=\"${nonce}\">`);\n });\n // Keep the <meta> CSP nonce in sync: browsers intersect the meta policy\n // with the response-header policy, so a stale build-time nonce would block\n // the very scripts re-tagged above.\n return scripts.replace(/<meta\\b[^>]*Content-Security-Policy[^>]*>/gi, (tag) =>\n tag.replace(/'nonce-[^']*'|&#(?:x27;|39;)nonce-[^&]*(?:&#(?:x27;|39;))/i, (match) =>\n match.startsWith(\"&#\") ? `&#x27;nonce-${nonce}&#x27;` : `'nonce-${nonce}'`\n )\n );\n}\n\nexport interface PluginResponseLike {\n status: number;\n statusText?: string;\n headers: Headers;\n body: BodyInit | null;\n}\n\n/**\n * Wrap a plugin response with security headers.\n * - Fills in SECURITY_HEADERS defaults where plugin hasn't set a value\n * - Adds Content-Security-Policy for HTML responses (with optional unsafe-eval)\n * - Preserves plugin body, status, statusText unchanged\n */\nexport function wrapPluginResponse(\n pluginResponse: PluginResponseLike,\n allowEval = false\n): Response {\n const securedHeaders = new Headers(pluginResponse.headers);\n for (const [key, value] of Object.entries(SECURITY_HEADERS)) {\n if (!securedHeaders.has(key)) {\n securedHeaders.set(key, value);\n }\n }\n let body = pluginResponse.body;\n const contentType = securedHeaders.get(\"Content-Type\") || \"\";\n if (contentType.includes(\"text/html\") && !securedHeaders.has(\"Content-Security-Policy\")) {\n const nonce = generateNonce();\n securedHeaders.set(\"Content-Security-Policy\", cspHeader(nonce, allowEval));\n if (typeof body === \"string\") {\n body = injectNonce(body, nonce);\n }\n }\n return new Response(body, {\n status: pluginResponse.status,\n statusText: pluginResponse.statusText,\n headers: securedHeaders,\n });\n}\n\nexport function htmlResponse(\n html: string,\n nonce: string,\n status = 200,\n allowEval = false\n): Response {\n return new Response(html, {\n status,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": cspHeader(nonce, allowEval),\n },\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/**\n * Server-only helpers that touch the filesystem, git, or `node:path`.\n *\n * Kept OUT of `./utils` (which is bundled into the browser): Bun's bundler\n * injects a full `node:path` polyfill into browser builds for any module that\n * imports it, even when the calls are never reached at runtime. Everything in\n * this file is imported exclusively by server-side modules (build, dev server,\n * search indexer) and their tests.\n */\n\nexport interface ScannedMdxFile {\n path: string;\n absPath: string;\n mtime: number;\n}\n\n/**\n * Scan a directory recursively for MDX/MD files.\n * Skips \"assets\" directories, hidden directories (dot-prefixed), and root-level\n * index.mdx/index.md (the docs root renders separately in build).\n * Shared between build.ts and search-indexer.ts.\n */\nexport async function scanMdxFiles(dir: string, baseDir = \"\"): Promise<ScannedMdxFile[]> {\n const files: ScannedMdxFile[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n const relativePath = baseDir ? `${baseDir}/${entry.name}` : entry.name;\n\n if (entry.isDirectory()) {\n if (entry.name === \"assets\" || entry.name.startsWith(\".\")) continue;\n files.push(...(await scanMdxFiles(fullPath, relativePath)));\n } else if (entry.name.endsWith(\".mdx\") || entry.name.endsWith(\".md\")) {\n // Root index renders separately (either extension) — scanning it would\n // produce a duplicate \"index\" page colliding with dist/docs/index.html.\n if (!baseDir && (entry.name === \"index.mdx\" || entry.name === \"index.md\")) continue;\n const stats = await stat(fullPath);\n let path = relativePath.replace(/\\.(mdx|md)$/, \"\");\n\n if (/\\/index$/.test(path)) {\n path = path.replace(/\\/index$/, \"\");\n }\n files.push({ path, absPath: fullPath, mtime: stats.mtimeMs });\n }\n }\n\n return files;\n}\n\n/**\n * Resolve the docs root index source — docs/index.mdx preferred, docs/index.md\n * as fallback (mirrors the dev server's getDocsForSlug extension handling).\n */\nexport function resolveDocsIndexSource(docsDir: string): string | undefined {\n for (const ext of [\".mdx\", \".md\"]) {\n const candidate = join(docsDir, `index${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n return undefined;\n}\n\n/** Get git last modified date for a file */\nexport async function getGitLastModified(filePath: string): Promise<string | null> {\n try {\n const cleanPath = filePath.replace(/^\\//, \"\");\n if (\n !cleanPath ||\n !/^[a-zA-Z0-9\\-_/.\\s]+$/.test(cleanPath) ||\n /(^|\\/)\\.\\.($|\\/)/.test(cleanPath)\n )\n return null;\n const proc = Bun.spawn([\"git\", \"log\", \"-1\", \"--format=%cI\", \"--\", cleanPath], {\n stderr: \"ignore\",\n });\n const text = await new Response(proc.stdout).text();\n const date = text.trim();\n return date || null;\n } catch (err) {\n console.error(\"Failed to get git last modified for\", filePath, err);\n return null;\n }\n}\n\n/** Batch git last modified dates for multiple files in a single spawn */\nexport async function getGitLastModifiedBatch(filePaths: string[]): Promise<Map<string, string>> {\n const result = new Map<string, string>();\n if (filePaths.length === 0) return result;\n\n // Filter and validate paths — same guard as getGitLastModified\n const safePaths: string[] = [];\n for (const fp of filePaths) {\n const cleanPath = fp.replace(/^\\//, \"\");\n if (\n !cleanPath ||\n !/^[a-zA-Z0-9\\-_/.\\s]+$/.test(cleanPath) ||\n /(^|\\/)\\.\\.($|\\/)/.test(cleanPath)\n ) {\n console.warn(`[server-utils] getGitLastModifiedBatch: skipping invalid path \"${fp}\"`);\n continue;\n }\n safePaths.push(cleanPath);\n }\n\n if (safePaths.length === 0) return result;\n\n try {\n const proc = Bun.spawn(\n [\"git\", \"log\", \"--format=%cI\", \"--name-only\", \"--diff-filter=ACMR\", ...safePaths],\n { stderr: \"ignore\" }\n );\n const text = await new Response(proc.stdout).text();\n let currentDate = \"\";\n\n for (const line of text.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n if (/^\\d{4}-\\d{2}-\\d{2}T/.test(trimmed)) {\n currentDate = trimmed;\n } else if (currentDate && !result.has(trimmed)) {\n result.set(trimmed, currentDate);\n }\n }\n } catch (err) {\n console.error(\"Failed to get git last modified batch for\", filePaths, err);\n }\n\n return result;\n}\n\nconst MIME_TYPES: Record<string, string> = {\n html: \"text/html\",\n css: \"text/css\",\n js: \"application/javascript\",\n json: \"application/json\",\n png: \"image/png\",\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n svg: \"image/svg+xml\",\n ico: \"image/x-icon\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n};\n\nexport function getContentType(pathname: string): string {\n const ext = pathname.split(\".\").pop()?.toLowerCase();\n return MIME_TYPES[ext || \"\"] || \"application/octet-stream\";\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { t as denoAdapter } from "./deno-DmEaKggj.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.deno.ts
4
4
  await runServer(denoAdapter);
5
5
  //#endregion