@docubook/flame 2.0.0-beta.3 → 2.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.docu/components/Pagination.tsx +12 -1
- package/.docu/components/Toc.tsx +1 -1
- package/.docu/lib/build.deno.js +1 -1
- package/.docu/lib/{build.impl-Bz_p421t.js → build.impl-BBlR3si3.js} +22 -21
- package/.docu/lib/build.impl-BBlR3si3.js.map +1 -0
- package/.docu/lib/build.impl-DpoDUmei.js +2 -0
- package/.docu/lib/build.node.js +1 -1
- package/.docu/lib/clean.js +1 -1
- package/.docu/lib/deploy.deno.js +1 -1
- package/.docu/lib/deploy.node.js +1 -1
- package/.docu/lib/{deploy.shared-fk8eM-r4.js → deploy.shared-Dxv3lsOb.js} +2 -2
- package/.docu/lib/{deploy.shared-fk8eM-r4.js.map → deploy.shared-Dxv3lsOb.js.map} +1 -1
- package/.docu/lib/{html.shared-FwgbE1WG.js → html.shared-DBMwv_Z6.js} +43 -31
- package/.docu/lib/html.shared-DBMwv_Z6.js.map +1 -0
- package/.docu/lib/{logger-CQyNTE6L.js → logger-DYmvFF1d.js} +2 -2
- package/.docu/lib/{logger-CQyNTE6L.js.map → logger-DYmvFF1d.js.map} +1 -1
- package/.docu/lib/preview.deno.js +1 -1
- package/.docu/lib/{preview.impl-CnsbLmDA.js → preview.impl-anVcZv2Z.js} +3 -3
- package/.docu/lib/{preview.impl-CnsbLmDA.js.map → preview.impl-anVcZv2Z.js.map} +1 -1
- package/.docu/lib/preview.node.js +1 -1
- package/.docu/lib/server.deno.js +1 -1
- package/.docu/lib/{server.impl-CXTzYqbF.js → server.impl-Cvoa6Diq.js} +6 -6
- package/.docu/lib/server.impl-Cvoa6Diq.js.map +1 -0
- package/.docu/lib/server.node.js +1 -1
- package/.docu/lib/{utils-DA17MyQG.js → utils-B_CoyKie.js} +28 -6
- package/.docu/lib/utils-B_CoyKie.js.map +1 -0
- package/.docu/node/build.impl.ts +42 -35
- package/.docu/node/build.ts +57 -41
- package/.docu/node/cache-key.ts +1 -1
- package/.docu/node/html.shared.ts +2 -1
- package/.docu/node/html.ts +2 -1
- package/.docu/node/mdx-manifest.d.ts +2 -3
- package/.docu/node/mdx.ts +12 -6
- package/.docu/node/plugin-builder.ts +10 -1
- package/.docu/node/route.ts +19 -21
- package/.docu/node/search-indexer.ts +10 -4
- package/.docu/node/search.ts +8 -2
- package/.docu/node/security.ts +18 -1
- package/.docu/node/server-routes.ts +3 -3
- package/.docu/node/types.ts +1 -1
- package/.docu/node/utils.ts +22 -2
- package/package.json +6 -6
- package/.docu/lib/build.impl-Bz_p421t.js.map +0 -1
- package/.docu/lib/build.impl-CtPrlYAE.js +0 -2
- package/.docu/lib/html.shared-FwgbE1WG.js.map +0 -1
- package/.docu/lib/server.impl-CXTzYqbF.js.map +0 -1
- package/.docu/lib/utils-DA17MyQG.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger-CQyNTE6L.js","names":[],"sources":["../../package.json","../node/logger.ts"],"sourcesContent":["{\n \"name\": \"@docubook/flame\",\n \"version\": \"2.0.0-beta.3\",\n \"description\": \"A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.\",\n \"type\": \"module\",\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-DYmvFF1d.js","names":[],"sources":["../../package.json","../node/logger.ts"],"sourcesContent":["{\n \"name\": \"@docubook/flame\",\n \"version\": \"2.0.0-beta.5\",\n \"description\": \"A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.\",\n \"type\": \"module\",\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,6 +1,6 @@
|
|
|
1
1
|
import { r as DIST_DIR } from "./paths-Bl2cdp9E.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as logger } from "./logger-
|
|
2
|
+
import { a as getContentType, d as cspHeader, h as injectNonce, p as generateNonce, u as SECURITY_HEADERS } from "./utils-B_CoyKie.js";
|
|
3
|
+
import { t as logger } from "./logger-DYmvFF1d.js";
|
|
4
4
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
@@ -74,4 +74,4 @@ async function runPreview(adapter) {
|
|
|
74
74
|
//#endregion
|
|
75
75
|
export { runPreview as t };
|
|
76
76
|
|
|
77
|
-
//# sourceMappingURL=preview.impl-
|
|
77
|
+
//# sourceMappingURL=preview.impl-anVcZv2Z.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.impl-
|
|
1
|
+
{"version":3,"file":"preview.impl-anVcZv2Z.js","names":[],"sources":["../node/preview.impl.ts"],"sourcesContent":["/**\n * Runtime-neutral preview server — mirror of `preview.ts` (Bun-only,\n * protected) driven by a `RuntimeAdapter` and `node:fs` file reads.\n */\n\nimport { existsSync, statSync, readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { logger } from \"./logger\";\nimport { DIST_DIR } from \"./paths\";\nimport { getContentType } from \"./utils\";\nimport { SECURITY_HEADERS, generateNonce, cspHeader, injectNonce } from \"./security\";\n\nfunction resolveFile(pathname: string): string | null {\n const path = pathname.slice(1);\n const isWithinDist = (p: string) => p === DIST_DIR || p.startsWith(DIST_DIR + \"/\");\n\n if (!path.includes(\".\")) {\n const withIndex = resolve(DIST_DIR, path, \"index.html\");\n if (isWithinDist(withIndex) && existsSync(withIndex)) return withIndex;\n const withHtml = resolve(DIST_DIR, path + \".html\");\n if (isWithinDist(withHtml) && existsSync(withHtml)) return withHtml;\n }\n\n const exact = resolve(DIST_DIR, path);\n if (!isWithinDist(exact)) return null;\n try {\n if (statSync(exact).isFile()) return exact;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n return null;\n}\n\nexport async function runPreview(adapter: RuntimeAdapter): Promise<ServerHandle | null> {\n const PORT = parseInt(process.env.PORT || \"4173\", 10);\n\n logger.buildStart();\n\n if (!existsSync(DIST_DIR)) {\n logger.spinner.start(\"Checking build output...\");\n logger.spinner.info(\"dist not found. Run \\x1b[1mflame build\\x1b[0m first.\");\n process.exit(0);\n }\n\n const notFoundPath = resolve(DIST_DIR, \"404.html\");\n\n const handle = await adapter.serve(\n async (req) => {\n const url = new URL(req.url);\n const pathname = decodeURIComponent(url.pathname);\n\n const filePath = resolveFile(pathname);\n\n if (filePath) {\n const contentType = getContentType(filePath);\n if (contentType === \"text/html\") {\n const nonce = generateNonce();\n const html = await readFile(filePath, \"utf-8\");\n const modified = injectNonce(html, nonce);\n return new Response(modified, {\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": cspHeader(nonce),\n },\n });\n }\n return new Response(readFileSync(filePath), {\n headers: { \"Content-Type\": contentType },\n });\n }\n\n if (existsSync(notFoundPath)) {\n const nonce = generateNonce();\n const html = await readFile(notFoundPath, \"utf-8\");\n const modified = injectNonce(html, nonce);\n return new Response(modified, {\n status: 404,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": cspHeader(nonce),\n },\n });\n }\n\n return new Response(\"404 - Not Found\", { status: 404 });\n },\n { port: PORT }\n );\n\n logger.ready(handle.port);\n return handle;\n}\n"],"mappings":";;;;;;;;;;;AAcA,SAAS,YAAY,UAAiC;CACpD,MAAM,OAAO,SAAS,MAAM,CAAC;CAC7B,MAAM,gBAAgB,MAAc,MAAM,YAAY,EAAE,WAAW,WAAW,GAAG;CAEjF,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG;EACvB,MAAM,YAAY,QAAQ,UAAU,MAAM,YAAY;EACtD,IAAI,aAAa,SAAS,KAAK,WAAW,SAAS,GAAG,OAAO;EAC7D,MAAM,WAAW,QAAQ,UAAU,OAAO,OAAO;EACjD,IAAI,aAAa,QAAQ,KAAK,WAAW,QAAQ,GAAG,OAAO;CAC7D;CAEA,MAAM,QAAQ,QAAQ,UAAU,IAAI;CACpC,IAAI,CAAC,aAAa,KAAK,GAAG,OAAO;CACjC,IAAI;EACF,IAAI,SAAS,KAAK,CAAC,CAAC,OAAO,GAAG,OAAO;CACvC,SAAS,KAAK;EACZ,IAAK,IAA8B,SAAS,UAAU,MAAM;CAC9D;CACA,OAAO;AACT;AAEA,eAAsB,WAAW,SAAuD;CACtF,MAAM,OAAO,SAAS,QAAQ,IAAI,QAAQ,QAAQ,EAAE;CAEpD,OAAO,WAAW;CAElB,IAAI,CAAC,WAAW,QAAQ,GAAG;EACzB,OAAO,QAAQ,MAAM,0BAA0B;EAC/C,OAAO,QAAQ,KAAK,sDAAsD;EAC1E,QAAQ,KAAK,CAAC;CAChB;CAEA,MAAM,eAAe,QAAQ,UAAU,UAAU;CAEjD,MAAM,SAAS,MAAM,QAAQ,MAC3B,OAAO,QAAQ;EACb,MAAM,MAAM,IAAI,IAAI,IAAI,GAAG;EAG3B,MAAM,WAAW,YAFA,mBAAmB,IAAI,QAEX,CAAQ;EAErC,IAAI,UAAU;GACZ,MAAM,cAAc,eAAe,QAAQ;GAC3C,IAAI,gBAAgB,aAAa;IAC/B,MAAM,QAAQ,cAAc;IAC5B,MAAM,OAAO,MAAM,SAAS,UAAU,OAAO;IAC7C,MAAM,WAAW,YAAY,MAAM,KAAK;IACxC,OAAO,IAAI,SAAS,UAAU,EAC5B,SAAS;KACP,gBAAgB;KAChB,GAAG;KACH,2BAA2B,UAAU,KAAK;IAC5C,EACF,CAAC;GACH;GACA,OAAO,IAAI,SAAS,aAAa,QAAQ,GAAG,EAC1C,SAAS,EAAE,gBAAgB,YAAY,EACzC,CAAC;EACH;EAEA,IAAI,WAAW,YAAY,GAAG;GAC5B,MAAM,QAAQ,cAAc;GAC5B,MAAM,OAAO,MAAM,SAAS,cAAc,OAAO;GACjD,MAAM,WAAW,YAAY,MAAM,KAAK;GACxC,OAAO,IAAI,SAAS,UAAU;IAC5B,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,GAAG;KACH,2BAA2B,UAAU,KAAK;IAC5C;GACF,CAAC;EACH;EAEA,OAAO,IAAI,SAAS,mBAAmB,EAAE,QAAQ,IAAI,CAAC;CACxD,GACA,EAAE,MAAM,KAAK,CACf;CAEA,OAAO,MAAM,OAAO,IAAI;CACxB,OAAO;AACT"}
|
package/.docu/lib/server.deno.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as DOCS_DIR, c as PROJECT_ROOT, d as loadDocuConfig, r as DIST_DIR } from "./paths-Bl2cdp9E.js";
|
|
2
|
-
import { D as loadPlugins, E as BuildPluginBuilder, a as IndexPage, b as buildClientBundle, c as captureException, f as compileMdx, i as DocsLayout, l as initSentry, m as frontmatterField, n as hmrScript, o as NotFoundPage, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as computeInlineThemeCss } from "./html.shared-
|
|
3
|
-
import {
|
|
4
|
-
import { t as logger } from "./logger-
|
|
2
|
+
import { D as loadPlugins, E as BuildPluginBuilder, a as IndexPage, b as buildClientBundle, c as captureException, f as compileMdx, i as DocsLayout, l as initSentry, m as frontmatterField, n as hmrScript, o as NotFoundPage, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as computeInlineThemeCss } from "./html.shared-DBMwv_Z6.js";
|
|
3
|
+
import { _ as isSlugSafe, a as getContentType, g as isPathSafe, l as stripDocsHtmlSuffix, m as htmlResponse, p as generateNonce, u as SECURITY_HEADERS, y as wrapPluginResponse } from "./utils-B_CoyKie.js";
|
|
4
|
+
import { t as logger } from "./logger-DYmvFF1d.js";
|
|
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
|
function createHtmlResponse(title, description, body, status, state, depth = 0) {
|
|
13
13
|
const nonce = generateNonce();
|
|
14
|
-
const favicon = state.docuConfig.meta?.favicon || "/favicon.ico";
|
|
14
|
+
const favicon = state.docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
15
15
|
const html = htmlShell({
|
|
16
16
|
title,
|
|
17
17
|
description,
|
|
@@ -114,7 +114,7 @@ async function renderDocsServerPage(doc, slug, pathname, state) {
|
|
|
114
114
|
const headExtra = state.builder.collectHead(ctx);
|
|
115
115
|
const bodyExtra = state.builder.collectBody(ctx);
|
|
116
116
|
const nonce = generateNonce();
|
|
117
|
-
const favicon = state.docuConfig.meta?.favicon || "/favicon.ico";
|
|
117
|
+
const favicon = state.docuConfig.meta?.favicon || "/docs/assets/images/favicon.ico";
|
|
118
118
|
let html = htmlShell({
|
|
119
119
|
title,
|
|
120
120
|
description,
|
|
@@ -355,4 +355,4 @@ async function runServer(adapter) {
|
|
|
355
355
|
//#endregion
|
|
356
356
|
export { runServer as t };
|
|
357
357
|
|
|
358
|
-
//# sourceMappingURL=server.impl-
|
|
358
|
+
//# sourceMappingURL=server.impl-Cvoa6Diq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.impl-Cvoa6Diq.js","names":[],"sources":["../node/server-routes.ts","../node/server.impl.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport React, { type ReactNode } from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport { compileMdx, frontmatterField } from \"./mdx\";\nimport { DEFAULT_FAVICON, getContentType } from \"./utils\";\nimport { DOCS_DIR, DIST_DIR, PROJECT_ROOT } from \"./paths\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport type { PageContext } from \"./plugin\";\nimport type { 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: { css: string; js: string };\n inlineThemeCss?: string;\n builder: BuildPluginBuilder | null;\n}\n\nfunction createHtmlResponse(\n title: string,\n description: string,\n body: string,\n status: number,\n state: ServerState,\n depth = 0\n): Response {\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: state.assetManifest.css,\n js: state.assetManifest.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n depth,\n // 404 pages can be requested at arbitrary depths (e.g. a noLink section\n // path typed in the address bar) — relative asset paths would resolve\n // against the wrong directory and break CSS/JS.\n absoluteAssets: status === 404,\n });\n /** 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: slug || \"/\",\n filePath: relPath,\n content,\n });\n }\n\n const doc = {\n content: result.content,\n compiledSource: result.compiledSource,\n frontmatter,\n tocs: result.tocs,\n filePath: relPath,\n resolvedContent: content,\n };\n docsCache.set(relPath, { mtimeMs, doc });\n return doc;\n}\n\nasync function renderDocsServerPage(\n doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>>,\n slug: string[],\n pathname: string,\n state: ServerState\n): Promise<Response> {\n const title = frontmatterField(doc.frontmatter, \"title\") || slug.join(\"/\") || \"Docs\";\n const description = frontmatterField(doc.frontmatter, \"description\");\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname },\n React.createElement(DocsPage, {\n slug,\n title,\n description,\n date: frontmatterField(doc.frontmatter, \"date\") || undefined,\n // Same root-relative SSR as build — see build.ts.\n content: renderToString(doc.content),\n tocs: doc.tocs,\n filePath: doc.filePath,\n repoUrl: state.docuConfig.repo?.url,\n compiledSource: doc.compiledSource,\n })\n );\n\n const body = renderToString(page);\n\n // Match build.ts depth calculation: slug.split(\"/\").length, fallback to 1 for empty\n const depth = slug.length || 1;\n\n if (state.builder) {\n const ctx: PageContext = {\n slug: slug.join(\"/\") || \"/\",\n filePath: doc.filePath,\n frontmatter: doc.frontmatter,\n content: doc.resolvedContent,\n config: state.docuConfig,\n };\n const headExtra = state.builder.collectHead(ctx);\n const bodyExtra = state.builder.collectBody(ctx);\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;\n let html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: state.assetManifest.css,\n js: state.assetManifest.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n headExtra,\n bodyExtra,\n depth,\n });\n html = await state.builder.runTransformHtmlChain(html, ctx);\n /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */\n return htmlResponse(html, nonce, 200, true);\n }\n\n return createHtmlResponse(title, description, body, 200, state, depth);\n}\n\nfunction 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): Response {\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname: \"/docs\" },\n React.createElement(Component, props)\n );\n const body = renderToString(page);\n return createHtmlResponse(title, description, body, status, state, depth);\n}\n\nexport async function handleDocsIndex(state: ServerState): Promise<Response> {\n const doc = await getDocsForSlug(\"\", state);\n if (!doc) return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, 1);\n return renderDocsServerPage(doc, [], \"/docs\", state);\n}\n\nexport async function handleDocsRoute(slug: string[], state: ServerState): Promise<Response> {\n const path = slug.join(\"/\");\n const doc = await getDocsForSlug(path, state);\n if (!doc)\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, slug.length || 1);\n return renderDocsServerPage(doc, slug, `/docs/${path}`, state);\n}\n\nexport function handleIndex(state: ServerState): 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 );\n}\n\nexport function handleNotFound(state: ServerState, depth = 0): Response {\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, depth);\n}\n\nexport function serveStatic(pathname: string): Response | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (!isPathSafe(pathname, DIST_DIR)) return null;\n const assetPath = resolve(DIST_DIR, decoded.slice(1));\n try {\n const s = statSync(assetPath);\n if (s.isFile()) {\n return new Response(readFileSync(assetPath), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n\n if (decoded.startsWith(\"/docs/assets/\")) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(\"/docs/assets/\".length);\n const docsAsset = resolve(docsAssetsDir, requestedRelative);\n const docsAssetsDirWithSep = docsAssetsDir.endsWith(\"/\") ? docsAssetsDir : docsAssetsDir + \"/\";\n if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;\n try {\n const s = statSync(docsAsset);\n if (s.isFile()) {\n return new Response(readFileSync(docsAsset), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n return null;\n}\n\nexport function serverErrorResponse(error: unknown): Response {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n const st =\n process.env.NODE_ENV !== \"production\" && error instanceof Error ? error.stack : undefined;\n return new Response(errorHtml(msg, st), {\n status: 500,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": \"default-src 'none'; style-src 'unsafe-inline'\",\n },\n });\n}\n","/**\n * Runtime-neutral dev server — mirror of `server.ts` (Bun-only, protected)\n * driven by a `RuntimeAdapter` (./runtime) instead of `Bun.serve`,\n * with manual route matching instead of `Bun.FileSystemRouter`. The page set\n * is static (`/`, `/docs/[[...slug]]`, `/404`), so a router is unnecessary.\n */\n\nimport { watch, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { DOCS_DIR, loadDocuConfig } from \"./paths\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport {\n serveStatic,\n handleDocsIndex,\n handleDocsRoute,\n handleIndex,\n handleNotFound,\n serverErrorResponse,\n type ServerState,\n} from \"./server-routes\";\nimport { wrapPluginResponse } from \"./security\";\nimport { stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n\n const parsedPort = parseInt(process.env.PORT ?? \"3000\", 10);\n const PORT =\n Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3000;\n\n logger.buildStart();\n\n await initSentry();\n\n logger.bundleStart();\n let t = performance.now();\n const assetManifest = await buildClientBundle();\n logger.bundleDone(Math.round(performance.now() - t));\n\n const inlineThemeCss = computeInlineThemeCss();\n\n logger.indexStart();\n t = performance.now();\n const records = await generateSearchIndex();\n logger.indexDone(records, Math.round(performance.now() - t));\n\n logger.routes();\n\n // Plugin setup — all hooks active (onLoad, remark/rehype, frontmatter, head/body, html transform, handleRequest)\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n }\n\n const state: ServerState = {\n docuConfig,\n assetManifest,\n inlineThemeCss,\n builder,\n };\n\n const hmrClients = new Set<ReadableStreamDefaultController>();\n\n const require = createRequire(import.meta.url);\n /** Locate the installed @docubook/markdown source dir (workspace link), if any. */\n function resolveMdxContentSrc(): string | null {\n try {\n const pkgDir = dirname(require.resolve(\"@docubook/markdown/package.json\"));\n const src = join(pkgDir, \"src\");\n return existsSync(src) ? src : null;\n } catch {\n return null;\n }\n }\n\n let hmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const watcher = watch(DOCS_DIR, { recursive: true }, (_event, filename) => {\n if (!filename || (!filename.endsWith(\".mdx\") && !filename.endsWith(\".md\"))) return;\n if (hmrTimeout) clearTimeout(hmrTimeout);\n hmrTimeout = setTimeout(() => {\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n });\n\n // Rebuild the client bundle when component sources change (e.g. the\n // @docubook/markdown workspace package) so interactive islands pick up\n // edits, then reload connected clients. MDX edits are handled by the watcher\n // above — no rebuild needed, content recompiles per request.\n let srcHmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const mdxContentSrc = resolveMdxContentSrc();\n const srcWatcher = mdxContentSrc\n ? watch(mdxContentSrc, { recursive: true }, (_event, filename) => {\n if (!filename || !/\\.(ts|tsx|js|jsx|css)$/.test(filename)) return;\n if (srcHmrTimeout) clearTimeout(srcHmrTimeout);\n srcHmrTimeout = setTimeout(async () => {\n try {\n state.assetManifest = await buildClientBundle();\n logger.warn(\"[hmr] client bundle rebuilt (component change)\");\n } catch (e) {\n logger.warn(\n `[hmr] bundle rebuild failed: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n })\n : null;\n\n process.on(\"SIGINT\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n\n let handle: ServerHandle | null = null;\n\n const fetchHandler = async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n // Generated links carry `.html` (matching the static build output);\n // route them to the same handler as their extensionless form.\n const pathname = stripDocsHtmlSuffix(url.pathname);\n const startTime = performance.now();\n\n if (builder) {\n const pluginResponse = await builder.runHandleRequest(req, {\n port: handle?.port ?? PORT,\n hostname: handle?.hostname ?? \"localhost\",\n });\n if (pluginResponse) {\n const securedResponse = wrapPluginResponse(pluginResponse, true);\n logger.request(\n req.method,\n pathname,\n securedResponse.status,\n Math.round(performance.now() - startTime)\n );\n return securedResponse;\n }\n }\n\n try {\n if (pathname === \"/__hmr\") {\n const stream = new ReadableStream({\n start(controller) {\n hmrClients.add(controller);\n controller.enqueue(new TextEncoder().encode(\"data: connected\\n\\n\"));\n },\n cancel(controller) {\n hmrClients.delete(controller);\n },\n });\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n }\n\n if (pathname.startsWith(\"/assets/\") || /\\.\\w+$/.test(pathname)) {\n const staticRes = serveStatic(pathname);\n if (staticRes) return staticRes;\n }\n\n let response: Response;\n\n // Manual route matching — same routes Bun.FileSystemRouter derives\n // from `.docu/pages/` (\"/\", \"/docs/[[...slug]]\", \"/404\").\n if (pathname === \"/\") {\n response = handleIndex(state);\n } else if (pathname === \"/docs\" || pathname === \"/docs/\") {\n response = await handleDocsIndex(state);\n } else if (pathname.startsWith(\"/docs/\")) {\n const slug = pathname.slice(\"/docs/\".length).split(\"/\").filter(Boolean);\n response =\n slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);\n } else {\n response = handleNotFound(state);\n }\n\n logger.request(\n req.method,\n pathname,\n response.status,\n Math.round(performance.now() - startTime)\n );\n return response;\n } catch (err) {\n captureException(err, { method: req.method, pathname });\n logger.request(req.method, pathname, 500, Math.round(performance.now() - startTime));\n return serverErrorResponse(err);\n }\n };\n\n handle = await adapter.serve(fetchHandler, { port: PORT, idleTimeout: 255 });\n\n logger.ready(handle.port, true);\n return handle;\n}\n"],"mappings":"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"}
|
package/.docu/lib/server.node.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { realpathSync } from "node:fs";
|
|
1
|
+
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";
|
|
@@ -26,6 +26,14 @@ function cspHeader(nonce, allowEval = false) {
|
|
|
26
26
|
"frame-ancestors 'none'"
|
|
27
27
|
].join("; ");
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Strip `frame-ancestors` for `<meta http-equiv="Content-Security-Policy">`.
|
|
31
|
+
* Browsers ignore `frame-ancestors` in meta CSP (header-only directive)
|
|
32
|
+
* and log a console warning. HTTP header from `cspHeader()` keeps it.
|
|
33
|
+
*/
|
|
34
|
+
function cspMeta(csp) {
|
|
35
|
+
return csp.replace(/;\s*frame-ancestors 'none'/, "");
|
|
36
|
+
}
|
|
29
37
|
function isPathSafe(pathname, baseDir) {
|
|
30
38
|
const resolved = resolve(baseDir, decodeURIComponent(pathname).slice(1));
|
|
31
39
|
const baseDirSlash = baseDir.endsWith("/") ? baseDir : baseDir + "/";
|
|
@@ -68,7 +76,7 @@ function injectNonce(html, nonce) {
|
|
|
68
76
|
return html.replace(/<script\b(?![^>]*\bsrc\s*=)([^>]*)>/gi, (match) => {
|
|
69
77
|
if (/nonce\s*=/i.test(match)) return match.replace(/nonce="[^"]*"/i, `nonce="${nonce}"`);
|
|
70
78
|
return match.replace(/>$/, ` nonce="${nonce}">`);
|
|
71
|
-
});
|
|
79
|
+
}).replace(/<meta\b[^>]*Content-Security-Policy[^>]*>/gi, (tag) => tag.replace(/'nonce-[^']*'|&#(?:x27;|39;)nonce-[^&]*(?:&#(?:x27;|39;))/i, (match) => match.startsWith("&#") ? `'nonce-${nonce}'` : `'nonce-${nonce}'`));
|
|
72
80
|
}
|
|
73
81
|
/**
|
|
74
82
|
* Wrap a plugin response with security headers.
|
|
@@ -105,7 +113,8 @@ function htmlResponse(html, nonce, status = 200, allowEval = false) {
|
|
|
105
113
|
//#region .docu/node/utils.ts
|
|
106
114
|
/**
|
|
107
115
|
* Scan a directory recursively for MDX/MD files.
|
|
108
|
-
* Skips "assets" directories, hidden directories (dot-prefixed), and root-level
|
|
116
|
+
* Skips "assets" directories, hidden directories (dot-prefixed), and root-level
|
|
117
|
+
* index.mdx/index.md (the docs root renders separately in build).
|
|
109
118
|
* Shared between build.ts and search-indexer.ts.
|
|
110
119
|
*/
|
|
111
120
|
async function scanMdxFiles(dir, baseDir = "") {
|
|
@@ -118,7 +127,7 @@ async function scanMdxFiles(dir, baseDir = "") {
|
|
|
118
127
|
if (entry.name === "assets" || entry.name.startsWith(".")) continue;
|
|
119
128
|
files.push(...await scanMdxFiles(fullPath, relativePath));
|
|
120
129
|
} else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
|
|
121
|
-
if (entry.name === "index.mdx"
|
|
130
|
+
if (!baseDir && (entry.name === "index.mdx" || entry.name === "index.md")) continue;
|
|
122
131
|
const stats = await stat(fullPath);
|
|
123
132
|
let path = relativePath.replace(/\.(mdx|md)$/, "");
|
|
124
133
|
if (/\/index$/.test(path)) path = path.replace(/\/index$/, "");
|
|
@@ -134,6 +143,19 @@ async function scanMdxFiles(dir, baseDir = "") {
|
|
|
134
143
|
function isExternalUrl(url) {
|
|
135
144
|
return /^(https?:\/\/|\/\/)/.test(url);
|
|
136
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Resolve the docs root index source — docs/index.mdx preferred, docs/index.md
|
|
148
|
+
* as fallback (mirrors the dev server's getDocsForSlug extension handling).
|
|
149
|
+
*/
|
|
150
|
+
function resolveDocsIndexSource(docsDir) {
|
|
151
|
+
for (const ext of [".mdx", ".md"]) {
|
|
152
|
+
const candidate = join(docsDir, `index${ext}`);
|
|
153
|
+
if (existsSync(candidate)) return candidate;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
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";
|
|
137
159
|
/** Suffix an internal docs link with `.html` to match the flat static build output. */
|
|
138
160
|
function docsHtmlHref(path) {
|
|
139
161
|
return `${path}.html`;
|
|
@@ -162,6 +184,6 @@ function getContentType(pathname) {
|
|
|
162
184
|
return MIME_TYPES[pathname.split(".").pop()?.toLowerCase() || ""] || "application/octet-stream";
|
|
163
185
|
}
|
|
164
186
|
//#endregion
|
|
165
|
-
export {
|
|
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 };
|
|
166
188
|
|
|
167
|
-
//# sourceMappingURL=utils-
|
|
189
|
+
//# sourceMappingURL=utils-B_CoyKie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-B_CoyKie.js","names":[],"sources":["../node/security.ts","../node/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(\"&#\") ? `'nonce-${nonce}'` : `'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","export { cn, parseDate, formatDate, formatDate2 } from \"@docubook/core\";\n\nimport { existsSync } from \"node:fs\";\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\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\nexport function isExternalUrl(url: string): boolean {\n return /^(https?:\\/\\/|\\/\\/)/.test(url);\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/** Default favicon — resolves in both build output (`docs/assets/` is copied\n * to `dist/docs/assets/`) and dev (served from `docs/assets/` via fallback). */\nexport const DEFAULT_FAVICON = \"/docs/assets/images/favicon.ico\";\n\n/** Suffix an internal docs link with `.html` to match the flat static build output. */\nexport function docsHtmlHref(path: string): string {\n return `${path}.html`;\n}\n\n/** Map a `/docs{.html,/*.html}` request back to its extensionless route (dev server).\n * Handles both `/docs/*.html` (pages) and `/docs.html` (edge case). */\nexport function stripDocsHtmlSuffix(pathname: string): string {\n if (pathname === \"/docs.html\") return \"/docs\";\n if (pathname.startsWith(\"/docs/\") && pathname.endsWith(\".html\"))\n return pathname.slice(0, -\".html\".length);\n return pathname;\n}\n\nexport function getPath(url: string): string {\n try {\n return new URL(url).pathname;\n } catch (err) {\n console.error(\"Failed to parse URL\", url, err);\n return url;\n }\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(`[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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"}
|
package/.docu/node/build.impl.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { logger } from "./logger";
|
|
|
38
38
|
import { initSentry, captureException } from "./sentry";
|
|
39
39
|
import { loadPlugins } from "./plugin-loader";
|
|
40
40
|
import { BuildPluginBuilder } from "./plugin-builder";
|
|
41
|
-
import { scanMdxFiles } from "./utils";
|
|
41
|
+
import { scanMdxFiles, resolveDocsIndexSource, DEFAULT_FAVICON } from "./utils";
|
|
42
42
|
import type { BuildCache, BuildCacheMeta, CliArgs } from "./types";
|
|
43
43
|
import { isCacheEntry } from "./types";
|
|
44
44
|
import {
|
|
@@ -110,9 +110,11 @@ type RebuildDecision = "yes" | "hash_check" | "no";
|
|
|
110
110
|
function shouldRebuild(path: string, mtime: number, cache: BuildCache): RebuildDecision {
|
|
111
111
|
const cached = cache[path];
|
|
112
112
|
if (!isCacheEntry(cached)) return "yes";
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
// Any mtime drift vs the recorded one — a newer edit, or mtime moving
|
|
114
|
+
// backwards (rsync -a, cp -p, snapshot restore) — falls through to the
|
|
115
|
+
// hash check: hashing an unchanged file is cheap, serving a stale page
|
|
116
|
+
// is not. Only an untouched mtime skips without reading the file.
|
|
117
|
+
if (mtime !== cached.mtime) return "hash_check";
|
|
116
118
|
return "no";
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -226,7 +228,7 @@ async function renderDocsPage(
|
|
|
226
228
|
const bodyExtra = builder?.collectBody(ctx);
|
|
227
229
|
|
|
228
230
|
const depth = slug ? slug.split("/").length : 1;
|
|
229
|
-
const favicon = docuConfig.meta?.favicon ||
|
|
231
|
+
const favicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
230
232
|
const seo = buildSeoMeta(docuConfig, frontmatter, slug || "");
|
|
231
233
|
// MDX content hydrates from the bundled ESM module (mdx-hydrate), not
|
|
232
234
|
// new Function — no 'unsafe-eval' needed in the CSP.
|
|
@@ -339,15 +341,16 @@ export async function runBuild(): Promise<void> {
|
|
|
339
341
|
});
|
|
340
342
|
await Promise.all(prePassTasks);
|
|
341
343
|
|
|
342
|
-
// The docs root (index.mdx) renders with slug "" — mirror that
|
|
343
|
-
// index page hydrates too. Its render has its own try/catch; skip
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
// The docs root (index.mdx, or index.md) renders with slug "" — mirror that
|
|
345
|
+
// key so the index page hydrates too. Its render has its own try/catch; skip
|
|
346
|
+
// on error.
|
|
347
|
+
const indexSource = resolveDocsIndexSource(DOCS_DIR);
|
|
348
|
+
if (indexSource) {
|
|
346
349
|
try {
|
|
347
|
-
const indexRaw = await readFile(
|
|
350
|
+
const indexRaw = await readFile(indexSource, "utf-8");
|
|
348
351
|
let indexContent = indexRaw;
|
|
349
352
|
if (builder) {
|
|
350
|
-
const relPath =
|
|
353
|
+
const relPath = indexSource.replace(PROJECT_ROOT + "/", "");
|
|
351
354
|
const transformed = await builder.runOnLoad(relPath, indexContent);
|
|
352
355
|
if (transformed?.contents) indexContent = transformed.contents;
|
|
353
356
|
}
|
|
@@ -393,9 +396,8 @@ export async function runBuild(): Promise<void> {
|
|
|
393
396
|
|
|
394
397
|
const allRelPaths = mdxFiles.map((f) => f.absPath.replace(PROJECT_ROOT + "/", ""));
|
|
395
398
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
allRelPaths.push(indexMdxFull.replace(PROJECT_ROOT + "/", ""));
|
|
399
|
+
if (indexSource) {
|
|
400
|
+
allRelPaths.push(indexSource.replace(PROJECT_ROOT + "/", ""));
|
|
399
401
|
}
|
|
400
402
|
const gitDates = await getGitLastModifiedBatch(allRelPaths);
|
|
401
403
|
|
|
@@ -476,29 +478,34 @@ export async function runBuild(): Promise<void> {
|
|
|
476
478
|
await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));
|
|
477
479
|
}
|
|
478
480
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
481
|
+
if (indexSource) {
|
|
482
|
+
try {
|
|
483
|
+
const indexRaw = await readFile(indexSource, "utf-8");
|
|
484
|
+
const indexRelPath = indexSource.replace(PROJECT_ROOT + "/", "");
|
|
485
|
+
const indexHtml = await renderDocsPage(
|
|
486
|
+
docuConfig,
|
|
487
|
+
"",
|
|
488
|
+
indexRaw,
|
|
489
|
+
indexRelPath,
|
|
490
|
+
gitDates,
|
|
491
|
+
builder,
|
|
492
|
+
generateNonce()
|
|
493
|
+
);
|
|
494
|
+
await mkdir(join(DIST_DIR, "docs"), { recursive: true });
|
|
495
|
+
await writeFile(join(DIST_DIR, "docs", "index.html"), indexHtml);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
498
|
+
errors.push(`index: ${msg}`);
|
|
499
|
+
console.error(`\n❌ Failed to build index: ${msg}\n`);
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
const msg = "docs root index: docs/index.mdx (or docs/index.md) not found";
|
|
503
|
+
errors.push(`index: ${msg}`);
|
|
504
|
+
console.error(`\n❌ Failed to build index: ${msg}\n`);
|
|
498
505
|
}
|
|
499
506
|
|
|
500
507
|
const landingPage = React.createElement(IndexPage);
|
|
501
|
-
const landingFavicon = docuConfig.meta?.favicon ||
|
|
508
|
+
const landingFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
502
509
|
const landingSeo = buildSeoMeta(
|
|
503
510
|
docuConfig,
|
|
504
511
|
docuConfig.meta as unknown as Record<string, unknown>,
|
|
@@ -524,7 +531,7 @@ export async function runBuild(): Promise<void> {
|
|
|
524
531
|
{ repoUrl: docuConfig.repo?.url },
|
|
525
532
|
React.createElement(NotFoundPage)
|
|
526
533
|
);
|
|
527
|
-
const notFoundFavicon = docuConfig.meta?.favicon ||
|
|
534
|
+
const notFoundFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;
|
|
528
535
|
const notFoundNonce = generateNonce();
|
|
529
536
|
const notFoundHtml = htmlShell({
|
|
530
537
|
title: "404 - Not Found",
|