@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.
Files changed (47) hide show
  1. package/.docu/components/Pagination.tsx +12 -1
  2. package/.docu/components/Toc.tsx +1 -1
  3. package/.docu/lib/build.deno.js +1 -1
  4. package/.docu/lib/{build.impl-Bz_p421t.js → build.impl-BBlR3si3.js} +22 -21
  5. package/.docu/lib/build.impl-BBlR3si3.js.map +1 -0
  6. package/.docu/lib/build.impl-DpoDUmei.js +2 -0
  7. package/.docu/lib/build.node.js +1 -1
  8. package/.docu/lib/clean.js +1 -1
  9. package/.docu/lib/deploy.deno.js +1 -1
  10. package/.docu/lib/deploy.node.js +1 -1
  11. package/.docu/lib/{deploy.shared-fk8eM-r4.js → deploy.shared-Dxv3lsOb.js} +2 -2
  12. package/.docu/lib/{deploy.shared-fk8eM-r4.js.map → deploy.shared-Dxv3lsOb.js.map} +1 -1
  13. package/.docu/lib/{html.shared-FwgbE1WG.js → html.shared-DBMwv_Z6.js} +43 -31
  14. package/.docu/lib/html.shared-DBMwv_Z6.js.map +1 -0
  15. package/.docu/lib/{logger-CQyNTE6L.js → logger-DYmvFF1d.js} +2 -2
  16. package/.docu/lib/{logger-CQyNTE6L.js.map → logger-DYmvFF1d.js.map} +1 -1
  17. package/.docu/lib/preview.deno.js +1 -1
  18. package/.docu/lib/{preview.impl-CnsbLmDA.js → preview.impl-anVcZv2Z.js} +3 -3
  19. package/.docu/lib/{preview.impl-CnsbLmDA.js.map → preview.impl-anVcZv2Z.js.map} +1 -1
  20. package/.docu/lib/preview.node.js +1 -1
  21. package/.docu/lib/server.deno.js +1 -1
  22. package/.docu/lib/{server.impl-CXTzYqbF.js → server.impl-Cvoa6Diq.js} +6 -6
  23. package/.docu/lib/server.impl-Cvoa6Diq.js.map +1 -0
  24. package/.docu/lib/server.node.js +1 -1
  25. package/.docu/lib/{utils-DA17MyQG.js → utils-B_CoyKie.js} +28 -6
  26. package/.docu/lib/utils-B_CoyKie.js.map +1 -0
  27. package/.docu/node/build.impl.ts +42 -35
  28. package/.docu/node/build.ts +57 -41
  29. package/.docu/node/cache-key.ts +1 -1
  30. package/.docu/node/html.shared.ts +2 -1
  31. package/.docu/node/html.ts +2 -1
  32. package/.docu/node/mdx-manifest.d.ts +2 -3
  33. package/.docu/node/mdx.ts +12 -6
  34. package/.docu/node/plugin-builder.ts +10 -1
  35. package/.docu/node/route.ts +19 -21
  36. package/.docu/node/search-indexer.ts +10 -4
  37. package/.docu/node/search.ts +8 -2
  38. package/.docu/node/security.ts +18 -1
  39. package/.docu/node/server-routes.ts +3 -3
  40. package/.docu/node/types.ts +1 -1
  41. package/.docu/node/utils.ts +22 -2
  42. package/package.json +6 -6
  43. package/.docu/lib/build.impl-Bz_p421t.js.map +0 -1
  44. package/.docu/lib/build.impl-CtPrlYAE.js +0 -2
  45. package/.docu/lib/html.shared-FwgbE1WG.js.map +0 -1
  46. package/.docu/lib/server.impl-CXTzYqbF.js.map +0 -1
  47. package/.docu/lib/utils-DA17MyQG.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils-DA17MyQG.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\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 return 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}\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 { 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 index.mdx.\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 if (entry.name === \"index.mdx\" && !baseDir) 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/** 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":";;;;;;AAIA,IAAa,mBAA2C;CACtD,6BAA6B;CAC7B,mBAAmB;CACnB,0BAA0B;CAC1B,mBAAmB;CACnB,sBAAsB;AACxB;AAEA,SAAgB,gBAAwB;CACtC,OAAO,YAAY,EAAE,CAAC,CAAC,SAAS,QAAQ;AAC1C;AAEA,SAAgB,UAAU,OAAe,YAAY,OAAe;CAIlE,OAAO;EACL;EAJgB,YACd,4BAA4B,MAAM,mBAClC,4BAA4B,MAAM;EAIpC;EACA;EACA;EACA;EACA;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,WAAW,UAAkB,SAA0B;CAErE,MAAM,WAAW,QAAQ,SADT,mBAAmB,QACD,CAAA,CAAQ,MAAM,CAAC,CAAC;CAClD,MAAM,eAAe,QAAQ,SAAS,GAAG,IAAI,UAAU,UAAU;CACjE,IAAI,EAAE,aAAa,WAAW,SAAS,WAAW,YAAY,IAC5D,OAAO;CAGT,IAAI,aAAa,SAAS,OAAO;CACjC,IAAI;EACF,MAAM,OAAO,aAAa,QAAQ;EAClC,MAAM,WAAW,aAAa,OAAO;EACrC,MAAM,mBAAmB,SAAS,SAAS,GAAG,IAAI,WAAW,WAAW;EACxE,OAAO,KAAK,WAAW,gBAAgB;CACzC,SAAS,KAAK;EACZ,MAAM,UAAU;EAChB,IAAI,QAAQ,SAAS,UACnB,OAAO;EAET,QAAQ,MACN,6CAA6C,SAAS,KAAK,QAAQ,QAAQ,SAAS,QAAQ,KAAK,EACnG;EACA,OAAO;CACT;AACF;AAEA,SAAgB,WAAW,MAAc,SAA0B;CACjE,MAAM,WAAW,QAAQ,SAAS,IAAI;CACtC,MAAM,eAAe,QAAQ,SAAS,GAAG,IAAI,UAAU,UAAU;CACjE,IAAI,EAAE,aAAa,WAAW,SAAS,WAAW,YAAY,IAC5D,OAAO;CAGT,IAAI,aAAa,SAAS,OAAO;CACjC,IAAI;EACF,MAAM,OAAO,aAAa,QAAQ;EAClC,MAAM,cAAc,aAAa,OAAO;EACxC,MAAM,mBAAmB,YAAY,SAAS,GAAG,IAAI,cAAc,cAAc;EACjF,OAAO,KAAK,WAAW,gBAAgB;CACzC,SAAS,KAAK;EACZ,MAAM,UAAU;EAChB,IAAI,QAAQ,SAAS,UACnB,OAAO;EAET,QAAQ,MACN,yCAAyC,KAAK,KAAK,QAAQ,QAAQ,SAAS,QAAQ,KAAK,EAC3F;EACA,OAAO;CACT;AACF;;AAGA,SAAgB,sBAAsB,UAA0B;CAC9D,OAAO,QAAQ,QAAQ,CAAC,CAAC,QAAQ,OAAO,GAAG;AAC7C;AAEA,SAAgB,YAAY,MAAc,OAAuB;CAC/D,OAAO,KAAK,QAAQ,0CAA0C,UAAU;EACtE,IAAI,aAAa,KAAK,KAAK,GACzB,OAAO,MAAM,QAAQ,kBAAkB,UAAU,MAAM,EAAE;EAE3D,OAAO,MAAM,QAAQ,MAAM,WAAW,MAAM,GAAG;CACjD,CAAC;AACH;;;;;;;AAeA,SAAgB,mBACd,gBACA,YAAY,OACF;CACV,MAAM,iBAAiB,IAAI,QAAQ,eAAe,OAAO;CACzD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,gBAAgB,GACxD,IAAI,CAAC,eAAe,IAAI,GAAG,GACzB,eAAe,IAAI,KAAK,KAAK;CAGjC,IAAI,OAAO,eAAe;CAE1B,KADoB,eAAe,IAAI,cAAc,KAAK,GAAA,CAC1C,SAAS,WAAW,KAAK,CAAC,eAAe,IAAI,yBAAyB,GAAG;EACvF,MAAM,QAAQ,cAAc;EAC5B,eAAe,IAAI,2BAA2B,UAAU,OAAO,SAAS,CAAC;EACzE,IAAI,OAAO,SAAS,UAClB,OAAO,YAAY,MAAM,KAAK;CAElC;CACA,OAAO,IAAI,SAAS,MAAM;EACxB,QAAQ,eAAe;EACvB,YAAY,eAAe;EAC3B,SAAS;CACX,CAAC;AACH;AAEA,SAAgB,aACd,MACA,OACA,SAAS,KACT,YAAY,OACF;CACV,OAAO,IAAI,SAAS,MAAM;EACxB;EACA,SAAS;GACP,gBAAgB;GAChB,GAAG;GACH,2BAA2B,UAAU,OAAO,SAAS;EACvD;CACF,CAAC;AACH;;;;;;;;ACtIA,eAAsB,aAAa,KAAa,UAAU,IAA+B;CACvF,MAAM,QAA0B,CAAC;CACjC,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAE1D,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,MAAM,IAAI;EACrC,MAAM,eAAe,UAAU,GAAG,QAAQ,GAAG,MAAM,SAAS,MAAM;EAElE,IAAI,MAAM,YAAY,GAAG;GACvB,IAAI,MAAM,SAAS,YAAY,MAAM,KAAK,WAAW,GAAG,GAAG;GAC3D,MAAM,KAAK,GAAI,MAAM,aAAa,UAAU,YAAY,CAAE;EAC5D,OAAO,IAAI,MAAM,KAAK,SAAS,MAAM,KAAK,MAAM,KAAK,SAAS,KAAK,GAAG;GACpE,IAAI,MAAM,SAAS,eAAe,CAAC,SAAS;GAC5C,MAAM,QAAQ,MAAM,KAAK,QAAQ;GACjC,IAAI,OAAO,aAAa,QAAQ,eAAe,EAAE;GAEjD,IAAI,WAAW,KAAK,IAAI,GACtB,OAAO,KAAK,QAAQ,YAAY,EAAE;GAEpC,MAAM,KAAK;IAAE;IAAM,SAAS;IAAU,OAAO,MAAM;GAAQ,CAAC;EAC9D;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,cAAc,KAAsB;CAClD,OAAO,sBAAsB,KAAK,GAAG;AACvC;;AAGA,SAAgB,aAAa,MAAsB;CACjD,OAAO,GAAG,KAAK;AACjB;;;AAIA,SAAgB,oBAAoB,UAA0B;CAC5D,IAAI,aAAa,cAAc,OAAO;CACtC,IAAI,SAAS,WAAW,QAAQ,KAAK,SAAS,SAAS,OAAO,GAC5D,OAAO,SAAS,MAAM,GAAG,EAAe;CAC1C,OAAO;AACT;AA+EA,IAAM,aAAqC;CACzC,MAAM;CACN,KAAK;CACL,IAAI;CACJ,MAAM;CACN,KAAK;CACL,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,MAAM;CACN,OAAO;AACT;AAEA,SAAgB,eAAe,UAA0B;CAEvD,OAAO,WADK,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,YAAY,KAC1B,OAAO;AAClC"}