@docubook/flame 2.0.0 → 2.0.2
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/lib/build.deno.js +1 -1
- package/.docu/lib/build.impl-C97QWe1I.js +2 -0
- package/.docu/lib/{build.impl-CAQr352X.js → build.impl-CA7XPLz0.js} +2 -2
- package/.docu/lib/{build.impl-CAQr352X.js.map → build.impl-CA7XPLz0.js.map} +1 -1
- 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-CvgObu6_.js → deploy.shared-DjD3qs7t.js} +101 -26
- package/.docu/lib/deploy.shared-DjD3qs7t.js.map +1 -0
- package/.docu/lib/{logger-DwxYdOL7.js → logger-_e2TNmEq.js} +2 -2
- package/.docu/lib/{logger-DwxYdOL7.js.map → logger-_e2TNmEq.js.map} +1 -1
- package/.docu/lib/preview.deno.js +1 -1
- package/.docu/lib/{preview.impl-BwrVLNIQ.js → preview.impl-DiTu2Coo.js} +2 -2
- package/.docu/lib/{preview.impl-BwrVLNIQ.js.map → preview.impl-DiTu2Coo.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-Cs6DEy4V.js → server.impl-DPBnKdIh.js} +2 -2
- package/.docu/lib/{server.impl-Cs6DEy4V.js.map → server.impl-DPBnKdIh.js.map} +1 -1
- package/.docu/lib/server.node.js +1 -1
- package/.docu/node/deploy.shared.ts +141 -52
- package/.docu/node/deploy.ts +20 -7
- package/package.json +5 -5
- package/.docu/lib/build.impl-NLhjTqTa.js +0 -2
- package/.docu/lib/deploy.shared-CvgObu6_.js.map +0 -1
package/.docu/lib/build.deno.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as DOCS_DIR, c as PAGES_DIR, f as loadDocuConfig, i as DOCS_ASSETS_DIR, l as PROJECT_ROOT, n as CACHE_FILE, r as DIST_DIR, t as ASSETS_DIR } from "./paths-Cbz-dj4s.js";
|
|
2
2
|
import { C as hashMdxSources, D as loadPlugins, E as BuildPluginBuilder, S as atomicWriteFile, T as runtimeStamp, _ as getPageStripped, a as IndexPage, b as buildClientBundle, c as captureException, d as clearDerivedPageCaches, f as compileMdx, g as getPageFrontmatter, h as getPageContent, i as DocsLayout, l as initSentry, m as frontmatterField, o as NotFoundPage, p as compileMdxModule, r as htmlShell, s as DocsPage, u as generateSearchIndex, v as registerPageContent, w as hookMemoryPressure, x as computeInlineThemeCss, y as getGitLastModifiedBatch } from "./html.shared-CsEFmy_Y.js";
|
|
3
3
|
import { c as scanMdxFiles, d as cspHeader, p as generateNonce, s as resolveDocsIndexSource } from "./utils-B_CoyKie.js";
|
|
4
|
-
import { t as logger } from "./logger-
|
|
4
|
+
import { t as logger } from "./logger-_e2TNmEq.js";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { copyFile, mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
@@ -453,4 +453,4 @@ async function runBuildCli() {
|
|
|
453
453
|
//#endregion
|
|
454
454
|
export { runBuildCli as n, runBuild as t };
|
|
455
455
|
|
|
456
|
-
//# sourceMappingURL=build.impl-
|
|
456
|
+
//# sourceMappingURL=build.impl-CA7XPLz0.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.impl-CAQr352X.js","names":[],"sources":["../node/types.ts","../node/seo.ts","../node/build.impl.ts"],"sourcesContent":["import type { ThemeConfig } from \"@docubook/themes-colors\";\nimport type { PluginEntry } from \"./plugin\";\n\nexport interface DocuRouteContext {\n title?: string;\n icon?: string;\n description?: string;\n}\n\nexport interface DocuRoute {\n title: string;\n href: string;\n noLink?: boolean;\n items?: DocuRoute[];\n context?: DocuRouteContext;\n}\n\nexport interface DocuMeta {\n title: string;\n description: string;\n baseURL: string;\n favicon?: string;\n /** Default OG image path (e.g. /docs/assets/images/og.png). Used when page frontmatter has no image. */\n ogImage?: string;\n}\n\nexport interface SocialLink {\n name: string;\n url: string;\n}\n\nexport interface DocuNavbar {\n logoText: string;\n logo?: { src?: string; alt?: string };\n menu: { title: string; href: string }[];\n}\n\nexport interface DocuFooter {\n social: SocialLink[];\n}\n\nexport interface DocuSidebar {\n /** How context sections are displayed.\n * \"dropdown\" — context switcher dropdown (default)\n * \"separator\" — inline group headers in sidebar */\n context?: \"dropdown\" | \"separator\";\n}\n\nexport interface RepoConfig {\n url: string;\n path: string;\n edit: boolean;\n}\n\nexport interface HeroAction {\n text: string;\n link: string;\n theme?: \"primary\" | \"secondary\" | \"ghost\";\n icon?: string;\n}\n\nexport interface Hero {\n tagline?: string;\n headline: string;\n description?: string;\n actions?: HeroAction[];\n}\n\nexport interface HomeFeature {\n icon?: string;\n title: string;\n description: string;\n link?: string;\n}\n\nexport interface HomeConfig {\n hero?: Hero;\n features?: HomeFeature[];\n}\n\nexport interface DocuConfig {\n meta: DocuMeta;\n home?: HomeConfig;\n navbar: DocuNavbar;\n footer: DocuFooter;\n repo: RepoConfig;\n sidebar?: DocuSidebar;\n routes: DocuRoute[];\n themes?: {\n colors: ThemeConfig;\n };\n /** List of DocuBook plugins to load. Empty by default — no-op when absent. */\n plugins?: PluginEntry[];\n}\n\nexport interface AssetEntry {\n css: string;\n js?: string;\n}\n\nexport interface AssetManifest {\n docs: AssetEntry;\n home: AssetEntry;\n notFound: AssetEntry;\n}\n\nexport interface BuildCacheEntry {\n hash: string;\n mtime: number;\n builtAt: number;\n}\n\n/** Version stamp stored as `__meta__` — extends entry so the index signature stays valid. */\nexport interface BuildCacheMeta extends BuildCacheEntry {\n version: number;\n runtime: string;\n}\n\nexport interface BuildCache {\n [path: string]: BuildCacheEntry | undefined;\n __meta__?: BuildCacheMeta;\n __assets__?: BuildCacheEntry;\n __bundle__?: BuildCacheEntry;\n}\n\n/** Narrow a cache slot to a real page/asset entry (guards `__meta__` shape). */\nexport function isCacheEntry(value: unknown): value is BuildCacheEntry {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.hash === \"string\" && typeof v.mtime === \"number\" && typeof v.builtAt === \"number\";\n}\n\nexport interface CliArgs {\n force?: boolean;\n clean?: boolean;\n ssr?: boolean;\n}\n\nexport interface ParsedDoc {\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n raw: string;\n scope?: Record<string, unknown>;\n}\n\nexport type { TocItem } from \"@docubook/core\";\n","import type { DocuConfig } from \"./types\";\nimport { frontmatterField } from \"./mdx\";\n\nexport interface SeoMeta {\n /** Absolute canonical URL */\n url: string;\n /** Site name for og:site_name */\n siteName: string;\n /** Absolute OG image URL (from frontmatter.image, if set) */\n image?: string;\n}\n\n/**\n * Build SEO metadata from config and per-page frontmatter.\n * All fields are derived from existing data — no extra config required.\n */\nexport function buildSeoMeta(\n config: DocuConfig,\n frontmatter: Record<string, unknown>,\n slug: string\n): SeoMeta {\n const baseURL = config.meta?.baseURL?.replace(/\\/+$/, \"\") || \"\";\n const url = slug ? `${baseURL}/docs/${slug}` : `${baseURL}/`;\n\n const result: SeoMeta = {\n url,\n siteName: config.meta?.title || \"\",\n };\n\n // Per-page image from frontmatter, fallback to global default from config\n const image = frontmatterField(frontmatter, \"image\") || config.meta?.ogImage;\n if (image) {\n // Resolve using URL constructor — handles absolute, root-relative, and relative paths\n try {\n result.image = new URL(image, image.startsWith(\"/\") ? baseURL : `${baseURL}/docs/`).href;\n } catch {\n result.image = image;\n }\n }\n\n return result;\n}\n","/**\n * Runtime-neutral static build — mirror of `build.ts` (Bun-only via its\n * transitive imports, protected) with the Bun-coupled modules swapped for\n * their neutral counterparts: `html.shared` (pure escaping) and\n * `hydrate.node` (esbuild client bundling). Everything else is identical.\n * The Node/Deno build entries call `runBuildCli()`.\n */\n\nimport { readFile, writeFile, mkdir, readdir, copyFile, rename, unlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { join, dirname } from \"node:path\";\nimport React from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport {\n compileMdx,\n compileMdxModule,\n frontmatterField,\n getGitLastModifiedBatch,\n getPageContent,\n getPageFrontmatter,\n getPageStripped,\n registerPageContent,\n} from \"./mdx\";\nimport {\n DOCS_DIR,\n DIST_DIR,\n ASSETS_DIR,\n CACHE_FILE,\n DOCS_ASSETS_DIR,\n PROJECT_ROOT,\n PAGES_DIR,\n loadDocuConfig,\n} from \"./paths\";\nimport { htmlShell } from \"./html.shared\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { scanMdxFiles, resolveDocsIndexSource, DEFAULT_FAVICON } from \"./utils\";\nimport type { AssetManifest, BuildCache, BuildCacheMeta, CliArgs } from \"./types\";\nimport { isCacheEntry } from \"./types\";\nimport {\n BUILD_CACHE_VERSION,\n atomicWriteFile,\n hashMdxSources,\n hookMemoryPressure,\n runtimeStamp,\n} from \"./cache-key\";\nimport { clearDerivedPageCaches } from \"./mdx\";\nimport { generateNonce, cspHeader } from \"./security\";\nimport type { PageMeta, PageContext } from \"./plugin\";\nimport { buildSeoMeta } from \"./seo\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport IndexPage from \"../pages/index\";\nimport NotFoundPage from \"../pages/404\";\nimport { DocsLayout } from \"../components/DocsLayout\";\n\nfunction parseArgs(): CliArgs {\n const args = process.argv.slice(2);\n return {\n force: args.includes(\"--force\") || args.includes(\"-f\"),\n clean: args.includes(\"--clean\") || args.includes(\"-c\"),\n };\n}\n\nfunction hashContent(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nasync function readCache(): Promise<BuildCache> {\n try {\n if (existsSync(CACHE_FILE)) {\n const data = await readFile(CACHE_FILE, \"utf-8\");\n const parsed = JSON.parse(data) as BuildCache;\n const meta = parsed.__meta__ as BuildCacheMeta | undefined;\n if (!meta || meta.version !== BUILD_CACHE_VERSION || meta.runtime !== runtimeStamp()) {\n return {};\n }\n return parsed;\n }\n } catch (err) {\n console.error(\"Failed to load build cache:\", (err as Error).message);\n }\n return {};\n}\n\nfunction stampCache(cache: BuildCache): void {\n cache.__meta__ = {\n hash: `${BUILD_CACHE_VERSION}:${runtimeStamp()}`,\n mtime: 0,\n builtAt: Date.now(),\n version: BUILD_CACHE_VERSION,\n runtime: runtimeStamp(),\n };\n}\n\nasync function writeCache(cache: BuildCache): Promise<void> {\n stampCache(cache);\n await atomicWriteFile(writeFile, rename, unlink, CACHE_FILE, JSON.stringify(cache, null, 2));\n}\n\nfunction parseConcurrency(): number {\n return Math.max(1, parseInt(process.env.BUILD_CONCURRENCY || \"4\", 10) || 4);\n}\n\ntype RebuildDecision = \"yes\" | \"hash_check\" | \"no\";\n\nfunction shouldRebuild(path: string, mtime: number, cache: BuildCache): RebuildDecision {\n const cached = cache[path];\n if (!isCacheEntry(cached)) return \"yes\";\n // Any mtime drift vs the recorded one — a newer edit, or mtime moving\n // backwards (rsync -a, cp -p, snapshot restore) — falls through to the\n // hash check: hashing an unchanged file is cheap, serving a stale page\n // is not. Only an untouched mtime skips without reading the file.\n if (mtime !== cached.mtime) return \"hash_check\";\n return \"no\";\n}\n\nlet assetManifest: AssetManifest = {\n docs: { js: \"client.js\", css: \"docs.css\" },\n home: { js: \"home-client.js\", css: \"site.css\" },\n notFound: { css: \"site.css\" },\n};\n\n/**\n * Reuse manifest.json on bundle cache hit; fall back to a full rebuild\n * when the manifest is missing or malformed.\n */\nasync function resolveAssetManifest(\n bundleHit: boolean,\n mdxSources: Record<string, string>\n): Promise<AssetManifest> {\n if (!bundleHit) return buildClientBundle(mdxSources);\n try {\n const manifest = JSON.parse(\n await readFile(join(ASSETS_DIR, \"manifest.json\"), \"utf-8\")\n ) as Partial<AssetManifest>;\n if (\n typeof manifest.docs?.js === \"string\" &&\n typeof manifest.docs.css === \"string\" &&\n typeof manifest.home?.js === \"string\" &&\n typeof manifest.home.css === \"string\" &&\n typeof manifest.notFound?.css === \"string\"\n ) {\n return manifest as AssetManifest;\n }\n } catch {\n // corrupt/missing manifest — rebuild below\n }\n return buildClientBundle(mdxSources);\n}\n\nlet inlineThemeCss: string | undefined;\n\nasync function renderDocsPage(\n docuConfig: ReturnType<typeof loadDocuConfig>,\n slug: string,\n rawMdx: string,\n filePath: string,\n gitDates?: Map<string, string>,\n builder?: BuildPluginBuilder | null,\n nonce?: string\n): Promise<string> {\n let content = rawMdx;\n if (builder) {\n const transformed = await builder.runOnLoad(filePath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n let result;\n try {\n const remarkPlugins = builder?.collectRemarkPlugins();\n const rehypePlugins = builder?.collectRehypePlugins();\n // Parse-once: reuse the prePass frontmatter + stripped content so the\n // SSR phase does not re-extract them. Only when the prePass actually\n // registered them — otherwise compileMdx does its own extraction.\n const preFm = getPageFrontmatter(`/${slug}`);\n const preStripped = getPageStripped(`/${slug}`);\n const pre =\n preFm !== undefined && preStripped !== undefined\n ? { frontmatter: preFm, strippedContent: preStripped }\n : undefined;\n result = await compileMdx(\n content,\n filePath,\n gitDates,\n remarkPlugins,\n rehypePlugins,\n undefined,\n pre\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown MDX error\";\n throw new Error(`MDX Error in: docs/${slug}.mdx\\n${msg}`, { cause: err });\n }\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (builder) {\n frontmatter = await builder.runTransformFrontmatterChain(frontmatter, {\n slug,\n filePath,\n content,\n });\n }\n\n const title = frontmatterField(frontmatter, \"title\") || slug || \"Docs\";\n const description = frontmatterField(frontmatter, \"description\");\n const slugParts = slug ? slug.split(\"/\") : [];\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: docuConfig.repo?.url },\n React.createElement(DocsPage, {\n slug: slugParts,\n title,\n description,\n date: frontmatterField(frontmatter, \"date\") || undefined,\n // Render MDX content as its own root: client hydrates the island as a\n // separate root, so SSR must be root-relative too or useId-based ids\n // (mdx-compiler components) mismatch during hydration.\n content: renderToString(result.content),\n tocs: result.tocs,\n filePath,\n repoUrl: docuConfig.repo?.url,\n mdxSlug: slug,\n })\n );\n\n const body = renderToString(page);\n\n const ctx: PageContext = {\n pageType: \"docs\",\n assets: assetManifest.docs,\n slug,\n filePath,\n frontmatter,\n content,\n config: docuConfig,\n };\n const headExtra = builder?.collectHead(ctx);\n const bodyExtra = builder?.collectBody(ctx);\n\n const depth = slug ? slug.split(\"/\").length : 1;\n const favicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const seo = buildSeoMeta(docuConfig, frontmatter, slug || \"\");\n // MDX content hydrates from the bundled ESM module (mdx-hydrate), not\n // new Function — no 'unsafe-eval' needed in the CSP.\n const csp = nonce ? cspHeader(nonce) : undefined;\n let html = htmlShell({\n title,\n description,\n body,\n favicon,\n seo,\n csp,\n css: ctx.assets.css,\n js: ctx.assets.js,\n nonce,\n themeCss: inlineThemeCss,\n depth,\n headExtra,\n bodyExtra,\n });\n\n if (builder) {\n html = await builder.runTransformHtmlChain(html, ctx);\n }\n\n return html;\n}\n\nasync function copyDirectoryRecursive(src: string, dest: string): Promise<void> {\n if (!existsSync(src)) return;\n await mkdir(dest, { recursive: true });\n const entries = await readdir(src, { withFileTypes: true });\n for (const entry of entries) {\n const srcPath = join(src, entry.name);\n const destPath = join(dest, entry.name);\n if (entry.isDirectory()) {\n await copyDirectoryRecursive(srcPath, destPath);\n } else {\n await copyFile(srcPath, destPath);\n }\n }\n}\n\nexport async function runBuild(): Promise<void> {\n const docuConfig = loadDocuConfig();\n const args = parseArgs();\n\n hookMemoryPressure(clearDerivedPageCaches);\n\n logger.buildStart();\n\n if (args.clean) {\n const { rm } = await import(\"node:fs/promises\");\n try {\n await rm(DIST_DIR, { recursive: true, force: true });\n } catch (err) {\n console.error(\"Failed to clean dist directory:\", (err as Error).message);\n }\n }\n\n await mkdir(DIST_DIR, { recursive: true });\n await mkdir(ASSETS_DIR, { recursive: true });\n\n await copyDirectoryRecursive(DOCS_ASSETS_DIR, join(DIST_DIR, \"docs\", \"assets\"));\n\n const mdxFiles = await scanMdxFiles(DOCS_DIR);\n const cache = args.force ? {} : await readCache();\n let built = 0;\n let skipped = 0;\n\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n await builder.runOnStart();\n }\n\n // Pre-compile every page's MDX to an ESM module (program format) so the\n // client bundle can hydrate the content island statically — no new Function.\n // Mirrors the page loop's transform + plugin chain so SSR and client trees\n // match. Runs for all files regardless of cache; the bundle is shared by\n // every page, so a content change invalidates the page cache anyway.\n const mdxSources: Record<string, string> = {};\n const prePassTasks = mdxFiles.map(async (file) => {\n let raw: string;\n try {\n raw = await readFile(file.absPath, \"utf-8\");\n } catch {\n return;\n }\n // Cache the original content so the page loop does not re-read the file\n // (one disk read per file — the frontmatter is parsed once here too).\n registerPageContent(`/${file.path}`, raw);\n let content = raw;\n if (builder) {\n const relPath = file.absPath.replace(PROJECT_ROOT + \"/\", \"\");\n const transformed = await builder.runOnLoad(relPath, content);\n if (transformed?.contents) content = transformed.contents;\n }\n const remarkPlugins = builder?.collectRemarkPlugins();\n const rehypePlugins = builder?.collectRehypePlugins();\n mdxSources[file.path] = await compileMdxModule(\n content,\n remarkPlugins,\n rehypePlugins,\n `/${file.path}`\n );\n });\n await Promise.all(prePassTasks);\n\n // The docs root (index.mdx, or index.md) renders with slug \"\" — mirror that\n // key so the index page hydrates too. Its render has its own try/catch; skip\n // on error.\n const indexSource = resolveDocsIndexSource(DOCS_DIR);\n if (indexSource) {\n try {\n const indexRaw = await readFile(indexSource, \"utf-8\");\n let indexContent = indexRaw;\n if (builder) {\n const relPath = indexSource.replace(PROJECT_ROOT + \"/\", \"\");\n const transformed = await builder.runOnLoad(relPath, indexContent);\n if (transformed?.contents) indexContent = transformed.contents;\n }\n mdxSources[\"\"] = await compileMdxModule(\n indexContent,\n builder?.collectRemarkPlugins(),\n builder?.collectRehypePlugins()\n );\n } catch {\n // ignore — the index render reports its own error\n }\n }\n\n logger.bundleStart();\n let t = performance.now();\n // Skip the JS bundle when compiled MDX sources are unchanged: the bundle\n // is shared by every page, so its hash doubles as the content fingerprint.\n // CSS still builds via its own content-keyed cache inside the hydrator.\n const bundleHash = hashMdxSources(mdxSources);\n const lastBundle = cache[\"__bundle__\"];\n const bundleHit =\n isCacheEntry(lastBundle) &&\n lastBundle.hash === bundleHash &&\n existsSync(join(ASSETS_DIR, \"manifest.json\"));\n assetManifest = await resolveAssetManifest(bundleHit, mdxSources);\n logger.bundleDone(Math.round(performance.now() - t));\n\n inlineThemeCss = computeInlineThemeCss();\n\n const lastManifest = cache[\"__assets__\"];\n const assetHash = JSON.stringify(assetManifest);\n const assetsChanged = !isCacheEntry(lastManifest) || lastManifest.hash !== assetHash;\n if (assetsChanged) {\n cache[\"__assets__\"] = { hash: assetHash, mtime: 0, builtAt: Date.now() };\n }\n if (!bundleHit) {\n cache[\"__bundle__\"] = { hash: bundleHash, mtime: 0, builtAt: Date.now() };\n }\n\n logger.spinner.start(\"Building pages...\");\n t = performance.now();\n\n const allRelPaths = mdxFiles.map((f) => f.absPath.replace(PROJECT_ROOT + \"/\", \"\"));\n\n if (indexSource) {\n allRelPaths.push(indexSource.replace(PROJECT_ROOT + \"/\", \"\"));\n }\n const gitDates = await getGitLastModifiedBatch(allRelPaths);\n\n const CONCURRENCY = parseConcurrency();\n const buildTasks = [];\n const errors: string[] = [];\n\n for (const file of mdxFiles) {\n const rebuildDecision = shouldRebuild(file.path, file.mtime, cache);\n\n if (rebuildDecision === \"no\" && !builder) {\n const outputPath = join(DIST_DIR, \"docs\", `${file.path}.html`);\n if (existsSync(outputPath) && !assetsChanged) {\n skipped++;\n continue;\n }\n }\n\n let rawMdx = getPageContent(`/${file.path}`);\n if (rawMdx === undefined) {\n try {\n rawMdx = await readFile(file.absPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n continue;\n }\n }\n\n if (rebuildDecision === \"hash_check\" && !builder) {\n const contentHash = hashContent(rawMdx);\n const cached = cache[file.path];\n if (isCacheEntry(cached) && cached.hash === contentHash) {\n if (!assetsChanged) {\n const outputPath = join(DIST_DIR, \"docs\", `${file.path}.html`);\n if (existsSync(outputPath)) {\n cache[file.path] = { ...cached, mtime: file.mtime, builtAt: Date.now() };\n skipped++;\n continue;\n }\n }\n }\n }\n\n const relPath = file.absPath.replace(PROJECT_ROOT + \"/\", \"\");\n const capturedRawMdx = rawMdx;\n const capturedFile = file;\n\n buildTasks.push(async () => {\n try {\n const pageNonce = generateNonce();\n const html = await renderDocsPage(\n docuConfig,\n capturedFile.path,\n capturedRawMdx,\n relPath,\n gitDates,\n builder,\n pageNonce\n );\n const outputPath = join(DIST_DIR, \"docs\", `${capturedFile.path}.html`);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, html);\n cache[capturedFile.path] = {\n hash: hashContent(capturedRawMdx),\n mtime: capturedFile.mtime,\n builtAt: Date.now(),\n };\n built++;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push(msg);\n console.error(`\\n\\u274C ${msg}\\n`);\n }\n });\n }\n\n for (let i = 0; i < buildTasks.length; i += CONCURRENCY) {\n await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));\n }\n\n if (indexSource) {\n try {\n const indexRaw = await readFile(indexSource, \"utf-8\");\n const indexRelPath = indexSource.replace(PROJECT_ROOT + \"/\", \"\");\n const indexHtml = await renderDocsPage(\n docuConfig,\n \"\",\n indexRaw,\n indexRelPath,\n gitDates,\n builder,\n generateNonce()\n );\n await mkdir(join(DIST_DIR, \"docs\"), { recursive: true });\n await writeFile(join(DIST_DIR, \"docs\", \"index.html\"), indexHtml);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push(`index: ${msg}`);\n console.error(`\\n❌ Failed to build index: ${msg}\\n`);\n }\n } else {\n const msg = \"docs root index: docs/index.mdx (or docs/index.md) not found\";\n errors.push(`index: ${msg}`);\n console.error(`\\n❌ Failed to build index: ${msg}\\n`);\n }\n\n const landingPage = React.createElement(IndexPage);\n const landingFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const landingSeo = buildSeoMeta(\n docuConfig,\n docuConfig.meta as unknown as Record<string, unknown>,\n \"\"\n );\n const landingNonce = generateNonce();\n const landingContext: PageContext = {\n pageType: \"home\",\n assets: assetManifest.home,\n slug: \"\",\n filePath: join(PAGES_DIR, \"index.tsx\"),\n frontmatter: { ...docuConfig.meta } as unknown as Record<string, unknown>,\n config: docuConfig,\n };\n let landingHtml = htmlShell({\n title: docuConfig.meta?.title || \"DocuBook\",\n description: docuConfig.meta?.description || \"\",\n body: renderToString(landingPage),\n favicon: landingFavicon,\n seo: landingSeo,\n csp: cspHeader(landingNonce),\n css: landingContext.assets.css,\n js: landingContext.assets.js,\n nonce: landingNonce,\n themeCss: inlineThemeCss,\n headExtra: builder?.collectHead(landingContext),\n bodyExtra: builder?.collectBody(landingContext),\n });\n if (builder) landingHtml = await builder.runTransformHtmlChain(landingHtml, landingContext);\n await writeFile(join(DIST_DIR, \"index.html\"), landingHtml);\n\n const notFoundPage = React.createElement(NotFoundPage);\n const notFoundFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const notFoundNonce = generateNonce();\n const notFoundContext: PageContext = {\n pageType: \"notFound\",\n assets: assetManifest.notFound,\n slug: \"404\",\n filePath: join(PAGES_DIR, \"404.tsx\"),\n frontmatter: {},\n config: docuConfig,\n };\n let notFoundHtml = htmlShell({\n title: \"404 - Not Found\",\n description: \"\",\n body: renderToString(notFoundPage),\n favicon: notFoundFavicon,\n headExtra: [\n '<meta name=\"robots\" content=\"noindex,follow\">',\n ...(builder?.collectHead(notFoundContext) ?? []),\n ],\n bodyExtra: builder?.collectBody(notFoundContext),\n csp: cspHeader(notFoundNonce),\n css: notFoundContext.assets.css,\n js: notFoundContext.assets.js,\n nonce: notFoundNonce,\n themeCss: inlineThemeCss,\n // Served as the static-host fallback at ANY requested path — relative\n // depth can never be right there, so use root-absolute asset URLs.\n absoluteAssets: true,\n });\n if (builder) notFoundHtml = await builder.runTransformHtmlChain(notFoundHtml, notFoundContext);\n await writeFile(join(DIST_DIR, \"404.html\"), notFoundHtml);\n\n logger.spinner.stop(\n `Built ${built} pages (${skipped} cached) \\x1b[90m(${Math.round(performance.now() - t)}ms)\\x1b[0m`\n );\n\n if (builder) {\n const pages: PageMeta[] = mdxFiles.map((f) => ({\n slug: f.path,\n title: f.path.split(\"/\").pop() || f.path,\n filePath: join(DOCS_DIR, f.path),\n outputPath: join(DIST_DIR, \"docs\", `${f.path}.html`),\n }));\n await builder.runOnEnd(pages, { assetManifest, outDir: DIST_DIR });\n }\n\n logger.indexStart();\n t = performance.now();\n // No content changed (all pages cached) → the aggregated index is\n // unchanged too; reuse the existing file instead of regenerating it.\n const indexSkipped = built === 0 && existsSync(join(ASSETS_DIR, \"search-index.json\"));\n const indexCount = indexSkipped ? 0 : await generateSearchIndex();\n logger.indexDone(indexCount, Math.round(performance.now() - t), indexSkipped);\n\n logger.routes();\n\n await writeCache(cache);\n\n if (errors.length > 0) {\n console.error(`\\n\\u274C Build completed with ${errors.length} error(s)\\n`);\n process.exit(1);\n }\n}\n\nexport async function runBuildCli(): Promise<void> {\n try {\n await initSentry();\n await runBuild();\n } catch (err) {\n captureException(err);\n console.error(\"Build failed:\", err);\n process.exit(1);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build.impl-CA7XPLz0.js","names":[],"sources":["../node/types.ts","../node/seo.ts","../node/build.impl.ts"],"sourcesContent":["import type { ThemeConfig } from \"@docubook/themes-colors\";\nimport type { PluginEntry } from \"./plugin\";\n\nexport interface DocuRouteContext {\n title?: string;\n icon?: string;\n description?: string;\n}\n\nexport interface DocuRoute {\n title: string;\n href: string;\n noLink?: boolean;\n items?: DocuRoute[];\n context?: DocuRouteContext;\n}\n\nexport interface DocuMeta {\n title: string;\n description: string;\n baseURL: string;\n favicon?: string;\n /** Default OG image path (e.g. /docs/assets/images/og.png). Used when page frontmatter has no image. */\n ogImage?: string;\n}\n\nexport interface SocialLink {\n name: string;\n url: string;\n}\n\nexport interface DocuNavbar {\n logoText: string;\n logo?: { src?: string; alt?: string };\n menu: { title: string; href: string }[];\n}\n\nexport interface DocuFooter {\n social: SocialLink[];\n}\n\nexport interface DocuSidebar {\n /** How context sections are displayed.\n * \"dropdown\" — context switcher dropdown (default)\n * \"separator\" — inline group headers in sidebar */\n context?: \"dropdown\" | \"separator\";\n}\n\nexport interface RepoConfig {\n url: string;\n path: string;\n edit: boolean;\n}\n\nexport interface HeroAction {\n text: string;\n link: string;\n theme?: \"primary\" | \"secondary\" | \"ghost\";\n icon?: string;\n}\n\nexport interface Hero {\n tagline?: string;\n headline: string;\n description?: string;\n actions?: HeroAction[];\n}\n\nexport interface HomeFeature {\n icon?: string;\n title: string;\n description: string;\n link?: string;\n}\n\nexport interface HomeConfig {\n hero?: Hero;\n features?: HomeFeature[];\n}\n\nexport interface DocuConfig {\n meta: DocuMeta;\n home?: HomeConfig;\n navbar: DocuNavbar;\n footer: DocuFooter;\n repo: RepoConfig;\n sidebar?: DocuSidebar;\n routes: DocuRoute[];\n themes?: {\n colors: ThemeConfig;\n };\n /** List of DocuBook plugins to load. Empty by default — no-op when absent. */\n plugins?: PluginEntry[];\n}\n\nexport interface AssetEntry {\n css: string;\n js?: string;\n}\n\nexport interface AssetManifest {\n docs: AssetEntry;\n home: AssetEntry;\n notFound: AssetEntry;\n}\n\nexport interface BuildCacheEntry {\n hash: string;\n mtime: number;\n builtAt: number;\n}\n\n/** Version stamp stored as `__meta__` — extends entry so the index signature stays valid. */\nexport interface BuildCacheMeta extends BuildCacheEntry {\n version: number;\n runtime: string;\n}\n\nexport interface BuildCache {\n [path: string]: BuildCacheEntry | undefined;\n __meta__?: BuildCacheMeta;\n __assets__?: BuildCacheEntry;\n __bundle__?: BuildCacheEntry;\n}\n\n/** Narrow a cache slot to a real page/asset entry (guards `__meta__` shape). */\nexport function isCacheEntry(value: unknown): value is BuildCacheEntry {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.hash === \"string\" && typeof v.mtime === \"number\" && typeof v.builtAt === \"number\";\n}\n\nexport interface CliArgs {\n force?: boolean;\n clean?: boolean;\n ssr?: boolean;\n}\n\nexport interface ParsedDoc {\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n raw: string;\n scope?: Record<string, unknown>;\n}\n\nexport type { TocItem } from \"@docubook/core\";\n","import type { DocuConfig } from \"./types\";\nimport { frontmatterField } from \"./mdx\";\n\nexport interface SeoMeta {\n /** Absolute canonical URL */\n url: string;\n /** Site name for og:site_name */\n siteName: string;\n /** Absolute OG image URL (from frontmatter.image, if set) */\n image?: string;\n}\n\n/**\n * Build SEO metadata from config and per-page frontmatter.\n * All fields are derived from existing data — no extra config required.\n */\nexport function buildSeoMeta(\n config: DocuConfig,\n frontmatter: Record<string, unknown>,\n slug: string\n): SeoMeta {\n const baseURL = config.meta?.baseURL?.replace(/\\/+$/, \"\") || \"\";\n const url = slug ? `${baseURL}/docs/${slug}` : `${baseURL}/`;\n\n const result: SeoMeta = {\n url,\n siteName: config.meta?.title || \"\",\n };\n\n // Per-page image from frontmatter, fallback to global default from config\n const image = frontmatterField(frontmatter, \"image\") || config.meta?.ogImage;\n if (image) {\n // Resolve using URL constructor — handles absolute, root-relative, and relative paths\n try {\n result.image = new URL(image, image.startsWith(\"/\") ? baseURL : `${baseURL}/docs/`).href;\n } catch {\n result.image = image;\n }\n }\n\n return result;\n}\n","/**\n * Runtime-neutral static build — mirror of `build.ts` (Bun-only via its\n * transitive imports, protected) with the Bun-coupled modules swapped for\n * their neutral counterparts: `html.shared` (pure escaping) and\n * `hydrate.node` (esbuild client bundling). Everything else is identical.\n * The Node/Deno build entries call `runBuildCli()`.\n */\n\nimport { readFile, writeFile, mkdir, readdir, copyFile, rename, unlink } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { join, dirname } from \"node:path\";\nimport React from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport {\n compileMdx,\n compileMdxModule,\n frontmatterField,\n getGitLastModifiedBatch,\n getPageContent,\n getPageFrontmatter,\n getPageStripped,\n registerPageContent,\n} from \"./mdx\";\nimport {\n DOCS_DIR,\n DIST_DIR,\n ASSETS_DIR,\n CACHE_FILE,\n DOCS_ASSETS_DIR,\n PROJECT_ROOT,\n PAGES_DIR,\n loadDocuConfig,\n} from \"./paths\";\nimport { htmlShell } from \"./html.shared\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { scanMdxFiles, resolveDocsIndexSource, DEFAULT_FAVICON } from \"./utils\";\nimport type { AssetManifest, BuildCache, BuildCacheMeta, CliArgs } from \"./types\";\nimport { isCacheEntry } from \"./types\";\nimport {\n BUILD_CACHE_VERSION,\n atomicWriteFile,\n hashMdxSources,\n hookMemoryPressure,\n runtimeStamp,\n} from \"./cache-key\";\nimport { clearDerivedPageCaches } from \"./mdx\";\nimport { generateNonce, cspHeader } from \"./security\";\nimport type { PageMeta, PageContext } from \"./plugin\";\nimport { buildSeoMeta } from \"./seo\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport IndexPage from \"../pages/index\";\nimport NotFoundPage from \"../pages/404\";\nimport { DocsLayout } from \"../components/DocsLayout\";\n\nfunction parseArgs(): CliArgs {\n const args = process.argv.slice(2);\n return {\n force: args.includes(\"--force\") || args.includes(\"-f\"),\n clean: args.includes(\"--clean\") || args.includes(\"-c\"),\n };\n}\n\nfunction hashContent(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nasync function readCache(): Promise<BuildCache> {\n try {\n if (existsSync(CACHE_FILE)) {\n const data = await readFile(CACHE_FILE, \"utf-8\");\n const parsed = JSON.parse(data) as BuildCache;\n const meta = parsed.__meta__ as BuildCacheMeta | undefined;\n if (!meta || meta.version !== BUILD_CACHE_VERSION || meta.runtime !== runtimeStamp()) {\n return {};\n }\n return parsed;\n }\n } catch (err) {\n console.error(\"Failed to load build cache:\", (err as Error).message);\n }\n return {};\n}\n\nfunction stampCache(cache: BuildCache): void {\n cache.__meta__ = {\n hash: `${BUILD_CACHE_VERSION}:${runtimeStamp()}`,\n mtime: 0,\n builtAt: Date.now(),\n version: BUILD_CACHE_VERSION,\n runtime: runtimeStamp(),\n };\n}\n\nasync function writeCache(cache: BuildCache): Promise<void> {\n stampCache(cache);\n await atomicWriteFile(writeFile, rename, unlink, CACHE_FILE, JSON.stringify(cache, null, 2));\n}\n\nfunction parseConcurrency(): number {\n return Math.max(1, parseInt(process.env.BUILD_CONCURRENCY || \"4\", 10) || 4);\n}\n\ntype RebuildDecision = \"yes\" | \"hash_check\" | \"no\";\n\nfunction shouldRebuild(path: string, mtime: number, cache: BuildCache): RebuildDecision {\n const cached = cache[path];\n if (!isCacheEntry(cached)) return \"yes\";\n // Any mtime drift vs the recorded one — a newer edit, or mtime moving\n // backwards (rsync -a, cp -p, snapshot restore) — falls through to the\n // hash check: hashing an unchanged file is cheap, serving a stale page\n // is not. Only an untouched mtime skips without reading the file.\n if (mtime !== cached.mtime) return \"hash_check\";\n return \"no\";\n}\n\nlet assetManifest: AssetManifest = {\n docs: { js: \"client.js\", css: \"docs.css\" },\n home: { js: \"home-client.js\", css: \"site.css\" },\n notFound: { css: \"site.css\" },\n};\n\n/**\n * Reuse manifest.json on bundle cache hit; fall back to a full rebuild\n * when the manifest is missing or malformed.\n */\nasync function resolveAssetManifest(\n bundleHit: boolean,\n mdxSources: Record<string, string>\n): Promise<AssetManifest> {\n if (!bundleHit) return buildClientBundle(mdxSources);\n try {\n const manifest = JSON.parse(\n await readFile(join(ASSETS_DIR, \"manifest.json\"), \"utf-8\")\n ) as Partial<AssetManifest>;\n if (\n typeof manifest.docs?.js === \"string\" &&\n typeof manifest.docs.css === \"string\" &&\n typeof manifest.home?.js === \"string\" &&\n typeof manifest.home.css === \"string\" &&\n typeof manifest.notFound?.css === \"string\"\n ) {\n return manifest as AssetManifest;\n }\n } catch {\n // corrupt/missing manifest — rebuild below\n }\n return buildClientBundle(mdxSources);\n}\n\nlet inlineThemeCss: string | undefined;\n\nasync function renderDocsPage(\n docuConfig: ReturnType<typeof loadDocuConfig>,\n slug: string,\n rawMdx: string,\n filePath: string,\n gitDates?: Map<string, string>,\n builder?: BuildPluginBuilder | null,\n nonce?: string\n): Promise<string> {\n let content = rawMdx;\n if (builder) {\n const transformed = await builder.runOnLoad(filePath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n let result;\n try {\n const remarkPlugins = builder?.collectRemarkPlugins();\n const rehypePlugins = builder?.collectRehypePlugins();\n // Parse-once: reuse the prePass frontmatter + stripped content so the\n // SSR phase does not re-extract them. Only when the prePass actually\n // registered them — otherwise compileMdx does its own extraction.\n const preFm = getPageFrontmatter(`/${slug}`);\n const preStripped = getPageStripped(`/${slug}`);\n const pre =\n preFm !== undefined && preStripped !== undefined\n ? { frontmatter: preFm, strippedContent: preStripped }\n : undefined;\n result = await compileMdx(\n content,\n filePath,\n gitDates,\n remarkPlugins,\n rehypePlugins,\n undefined,\n pre\n );\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown MDX error\";\n throw new Error(`MDX Error in: docs/${slug}.mdx\\n${msg}`, { cause: err });\n }\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (builder) {\n frontmatter = await builder.runTransformFrontmatterChain(frontmatter, {\n slug,\n filePath,\n content,\n });\n }\n\n const title = frontmatterField(frontmatter, \"title\") || slug || \"Docs\";\n const description = frontmatterField(frontmatter, \"description\");\n const slugParts = slug ? slug.split(\"/\") : [];\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: docuConfig.repo?.url },\n React.createElement(DocsPage, {\n slug: slugParts,\n title,\n description,\n date: frontmatterField(frontmatter, \"date\") || undefined,\n // Render MDX content as its own root: client hydrates the island as a\n // separate root, so SSR must be root-relative too or useId-based ids\n // (mdx-compiler components) mismatch during hydration.\n content: renderToString(result.content),\n tocs: result.tocs,\n filePath,\n repoUrl: docuConfig.repo?.url,\n mdxSlug: slug,\n })\n );\n\n const body = renderToString(page);\n\n const ctx: PageContext = {\n pageType: \"docs\",\n assets: assetManifest.docs,\n slug,\n filePath,\n frontmatter,\n content,\n config: docuConfig,\n };\n const headExtra = builder?.collectHead(ctx);\n const bodyExtra = builder?.collectBody(ctx);\n\n const depth = slug ? slug.split(\"/\").length : 1;\n const favicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const seo = buildSeoMeta(docuConfig, frontmatter, slug || \"\");\n // MDX content hydrates from the bundled ESM module (mdx-hydrate), not\n // new Function — no 'unsafe-eval' needed in the CSP.\n const csp = nonce ? cspHeader(nonce) : undefined;\n let html = htmlShell({\n title,\n description,\n body,\n favicon,\n seo,\n csp,\n css: ctx.assets.css,\n js: ctx.assets.js,\n nonce,\n themeCss: inlineThemeCss,\n depth,\n headExtra,\n bodyExtra,\n });\n\n if (builder) {\n html = await builder.runTransformHtmlChain(html, ctx);\n }\n\n return html;\n}\n\nasync function copyDirectoryRecursive(src: string, dest: string): Promise<void> {\n if (!existsSync(src)) return;\n await mkdir(dest, { recursive: true });\n const entries = await readdir(src, { withFileTypes: true });\n for (const entry of entries) {\n const srcPath = join(src, entry.name);\n const destPath = join(dest, entry.name);\n if (entry.isDirectory()) {\n await copyDirectoryRecursive(srcPath, destPath);\n } else {\n await copyFile(srcPath, destPath);\n }\n }\n}\n\nexport async function runBuild(): Promise<void> {\n const docuConfig = loadDocuConfig();\n const args = parseArgs();\n\n hookMemoryPressure(clearDerivedPageCaches);\n\n logger.buildStart();\n\n if (args.clean) {\n const { rm } = await import(\"node:fs/promises\");\n try {\n await rm(DIST_DIR, { recursive: true, force: true });\n } catch (err) {\n console.error(\"Failed to clean dist directory:\", (err as Error).message);\n }\n }\n\n await mkdir(DIST_DIR, { recursive: true });\n await mkdir(ASSETS_DIR, { recursive: true });\n\n await copyDirectoryRecursive(DOCS_ASSETS_DIR, join(DIST_DIR, \"docs\", \"assets\"));\n\n const mdxFiles = await scanMdxFiles(DOCS_DIR);\n const cache = args.force ? {} : await readCache();\n let built = 0;\n let skipped = 0;\n\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n await builder.runOnStart();\n }\n\n // Pre-compile every page's MDX to an ESM module (program format) so the\n // client bundle can hydrate the content island statically — no new Function.\n // Mirrors the page loop's transform + plugin chain so SSR and client trees\n // match. Runs for all files regardless of cache; the bundle is shared by\n // every page, so a content change invalidates the page cache anyway.\n const mdxSources: Record<string, string> = {};\n const prePassTasks = mdxFiles.map(async (file) => {\n let raw: string;\n try {\n raw = await readFile(file.absPath, \"utf-8\");\n } catch {\n return;\n }\n // Cache the original content so the page loop does not re-read the file\n // (one disk read per file — the frontmatter is parsed once here too).\n registerPageContent(`/${file.path}`, raw);\n let content = raw;\n if (builder) {\n const relPath = file.absPath.replace(PROJECT_ROOT + \"/\", \"\");\n const transformed = await builder.runOnLoad(relPath, content);\n if (transformed?.contents) content = transformed.contents;\n }\n const remarkPlugins = builder?.collectRemarkPlugins();\n const rehypePlugins = builder?.collectRehypePlugins();\n mdxSources[file.path] = await compileMdxModule(\n content,\n remarkPlugins,\n rehypePlugins,\n `/${file.path}`\n );\n });\n await Promise.all(prePassTasks);\n\n // The docs root (index.mdx, or index.md) renders with slug \"\" — mirror that\n // key so the index page hydrates too. Its render has its own try/catch; skip\n // on error.\n const indexSource = resolveDocsIndexSource(DOCS_DIR);\n if (indexSource) {\n try {\n const indexRaw = await readFile(indexSource, \"utf-8\");\n let indexContent = indexRaw;\n if (builder) {\n const relPath = indexSource.replace(PROJECT_ROOT + \"/\", \"\");\n const transformed = await builder.runOnLoad(relPath, indexContent);\n if (transformed?.contents) indexContent = transformed.contents;\n }\n mdxSources[\"\"] = await compileMdxModule(\n indexContent,\n builder?.collectRemarkPlugins(),\n builder?.collectRehypePlugins()\n );\n } catch {\n // ignore — the index render reports its own error\n }\n }\n\n logger.bundleStart();\n let t = performance.now();\n // Skip the JS bundle when compiled MDX sources are unchanged: the bundle\n // is shared by every page, so its hash doubles as the content fingerprint.\n // CSS still builds via its own content-keyed cache inside the hydrator.\n const bundleHash = hashMdxSources(mdxSources);\n const lastBundle = cache[\"__bundle__\"];\n const bundleHit =\n isCacheEntry(lastBundle) &&\n lastBundle.hash === bundleHash &&\n existsSync(join(ASSETS_DIR, \"manifest.json\"));\n assetManifest = await resolveAssetManifest(bundleHit, mdxSources);\n logger.bundleDone(Math.round(performance.now() - t));\n\n inlineThemeCss = computeInlineThemeCss();\n\n const lastManifest = cache[\"__assets__\"];\n const assetHash = JSON.stringify(assetManifest);\n const assetsChanged = !isCacheEntry(lastManifest) || lastManifest.hash !== assetHash;\n if (assetsChanged) {\n cache[\"__assets__\"] = { hash: assetHash, mtime: 0, builtAt: Date.now() };\n }\n if (!bundleHit) {\n cache[\"__bundle__\"] = { hash: bundleHash, mtime: 0, builtAt: Date.now() };\n }\n\n logger.spinner.start(\"Building pages...\");\n t = performance.now();\n\n const allRelPaths = mdxFiles.map((f) => f.absPath.replace(PROJECT_ROOT + \"/\", \"\"));\n\n if (indexSource) {\n allRelPaths.push(indexSource.replace(PROJECT_ROOT + \"/\", \"\"));\n }\n const gitDates = await getGitLastModifiedBatch(allRelPaths);\n\n const CONCURRENCY = parseConcurrency();\n const buildTasks = [];\n const errors: string[] = [];\n\n for (const file of mdxFiles) {\n const rebuildDecision = shouldRebuild(file.path, file.mtime, cache);\n\n if (rebuildDecision === \"no\" && !builder) {\n const outputPath = join(DIST_DIR, \"docs\", `${file.path}.html`);\n if (existsSync(outputPath) && !assetsChanged) {\n skipped++;\n continue;\n }\n }\n\n let rawMdx = getPageContent(`/${file.path}`);\n if (rawMdx === undefined) {\n try {\n rawMdx = await readFile(file.absPath, \"utf-8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n continue;\n }\n }\n\n if (rebuildDecision === \"hash_check\" && !builder) {\n const contentHash = hashContent(rawMdx);\n const cached = cache[file.path];\n if (isCacheEntry(cached) && cached.hash === contentHash) {\n if (!assetsChanged) {\n const outputPath = join(DIST_DIR, \"docs\", `${file.path}.html`);\n if (existsSync(outputPath)) {\n cache[file.path] = { ...cached, mtime: file.mtime, builtAt: Date.now() };\n skipped++;\n continue;\n }\n }\n }\n }\n\n const relPath = file.absPath.replace(PROJECT_ROOT + \"/\", \"\");\n const capturedRawMdx = rawMdx;\n const capturedFile = file;\n\n buildTasks.push(async () => {\n try {\n const pageNonce = generateNonce();\n const html = await renderDocsPage(\n docuConfig,\n capturedFile.path,\n capturedRawMdx,\n relPath,\n gitDates,\n builder,\n pageNonce\n );\n const outputPath = join(DIST_DIR, \"docs\", `${capturedFile.path}.html`);\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, html);\n cache[capturedFile.path] = {\n hash: hashContent(capturedRawMdx),\n mtime: capturedFile.mtime,\n builtAt: Date.now(),\n };\n built++;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push(msg);\n console.error(`\\n\\u274C ${msg}\\n`);\n }\n });\n }\n\n for (let i = 0; i < buildTasks.length; i += CONCURRENCY) {\n await Promise.all(buildTasks.slice(i, i + CONCURRENCY).map((fn) => fn()));\n }\n\n if (indexSource) {\n try {\n const indexRaw = await readFile(indexSource, \"utf-8\");\n const indexRelPath = indexSource.replace(PROJECT_ROOT + \"/\", \"\");\n const indexHtml = await renderDocsPage(\n docuConfig,\n \"\",\n indexRaw,\n indexRelPath,\n gitDates,\n builder,\n generateNonce()\n );\n await mkdir(join(DIST_DIR, \"docs\"), { recursive: true });\n await writeFile(join(DIST_DIR, \"docs\", \"index.html\"), indexHtml);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n errors.push(`index: ${msg}`);\n console.error(`\\n❌ Failed to build index: ${msg}\\n`);\n }\n } else {\n const msg = \"docs root index: docs/index.mdx (or docs/index.md) not found\";\n errors.push(`index: ${msg}`);\n console.error(`\\n❌ Failed to build index: ${msg}\\n`);\n }\n\n const landingPage = React.createElement(IndexPage);\n const landingFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const landingSeo = buildSeoMeta(\n docuConfig,\n docuConfig.meta as unknown as Record<string, unknown>,\n \"\"\n );\n const landingNonce = generateNonce();\n const landingContext: PageContext = {\n pageType: \"home\",\n assets: assetManifest.home,\n slug: \"\",\n filePath: join(PAGES_DIR, \"index.tsx\"),\n frontmatter: { ...docuConfig.meta } as unknown as Record<string, unknown>,\n config: docuConfig,\n };\n let landingHtml = htmlShell({\n title: docuConfig.meta?.title || \"DocuBook\",\n description: docuConfig.meta?.description || \"\",\n body: renderToString(landingPage),\n favicon: landingFavicon,\n seo: landingSeo,\n csp: cspHeader(landingNonce),\n css: landingContext.assets.css,\n js: landingContext.assets.js,\n nonce: landingNonce,\n themeCss: inlineThemeCss,\n headExtra: builder?.collectHead(landingContext),\n bodyExtra: builder?.collectBody(landingContext),\n });\n if (builder) landingHtml = await builder.runTransformHtmlChain(landingHtml, landingContext);\n await writeFile(join(DIST_DIR, \"index.html\"), landingHtml);\n\n const notFoundPage = React.createElement(NotFoundPage);\n const notFoundFavicon = docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const notFoundNonce = generateNonce();\n const notFoundContext: PageContext = {\n pageType: \"notFound\",\n assets: assetManifest.notFound,\n slug: \"404\",\n filePath: join(PAGES_DIR, \"404.tsx\"),\n frontmatter: {},\n config: docuConfig,\n };\n let notFoundHtml = htmlShell({\n title: \"404 - Not Found\",\n description: \"\",\n body: renderToString(notFoundPage),\n favicon: notFoundFavicon,\n headExtra: [\n '<meta name=\"robots\" content=\"noindex,follow\">',\n ...(builder?.collectHead(notFoundContext) ?? []),\n ],\n bodyExtra: builder?.collectBody(notFoundContext),\n csp: cspHeader(notFoundNonce),\n css: notFoundContext.assets.css,\n js: notFoundContext.assets.js,\n nonce: notFoundNonce,\n themeCss: inlineThemeCss,\n // Served as the static-host fallback at ANY requested path — relative\n // depth can never be right there, so use root-absolute asset URLs.\n absoluteAssets: true,\n });\n if (builder) notFoundHtml = await builder.runTransformHtmlChain(notFoundHtml, notFoundContext);\n await writeFile(join(DIST_DIR, \"404.html\"), notFoundHtml);\n\n logger.spinner.stop(\n `Built ${built} pages (${skipped} cached) \\x1b[90m(${Math.round(performance.now() - t)}ms)\\x1b[0m`\n );\n\n if (builder) {\n const pages: PageMeta[] = mdxFiles.map((f) => ({\n slug: f.path,\n title: f.path.split(\"/\").pop() || f.path,\n filePath: join(DOCS_DIR, f.path),\n outputPath: join(DIST_DIR, \"docs\", `${f.path}.html`),\n }));\n await builder.runOnEnd(pages, { assetManifest, outDir: DIST_DIR });\n }\n\n logger.indexStart();\n t = performance.now();\n // No content changed (all pages cached) → the aggregated index is\n // unchanged too; reuse the existing file instead of regenerating it.\n const indexSkipped = built === 0 && existsSync(join(ASSETS_DIR, \"search-index.json\"));\n const indexCount = indexSkipped ? 0 : await generateSearchIndex();\n logger.indexDone(indexCount, Math.round(performance.now() - t), indexSkipped);\n\n logger.routes();\n\n await writeCache(cache);\n\n if (errors.length > 0) {\n console.error(`\\n\\u274C Build completed with ${errors.length} error(s)\\n`);\n process.exit(1);\n }\n}\n\nexport async function runBuildCli(): Promise<void> {\n try {\n await initSentry();\n await runBuild();\n } catch (err) {\n captureException(err);\n console.error(\"Build failed:\", err);\n process.exit(1);\n }\n}\n"],"mappings":"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"}
|
package/.docu/lib/build.node.js
CHANGED
package/.docu/lib/clean.js
CHANGED
package/.docu/lib/deploy.deno.js
CHANGED
package/.docu/lib/deploy.node.js
CHANGED
|
@@ -81,52 +81,120 @@ var DOCKERIGNORE = `node_modules
|
|
|
81
81
|
.npmrc
|
|
82
82
|
*.log
|
|
83
83
|
`;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
RUN flame build --bun
|
|
89
|
-
|
|
90
|
-
FROM nginx:alpine
|
|
84
|
+
/** Final stage shared by every generated Dockerfile: nginx serves the flat
|
|
85
|
+
* static output produced by the builder stage. */
|
|
86
|
+
var DOCKERFILE_MARKER = "# Generated by @docubook/flame deploy --docker";
|
|
87
|
+
var NGINX_STAGE = `FROM nginx:alpine
|
|
91
88
|
COPY --from=builder /app/.docu/dist /usr/share/nginx/html
|
|
92
89
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
93
90
|
EXPOSE 80
|
|
94
91
|
CMD ["nginx", "-g", "daemon off;"]
|
|
95
92
|
`;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
/** Bun projects build inside the published builder image — the CLI is already
|
|
94
|
+
* installed globally there, so no project dependency install step is needed. */
|
|
95
|
+
var DOCKERFILE_BUN = `${DOCKERFILE_MARKER}
|
|
96
|
+
FROM ghcr.io/docubook/flame:${FLAME_VERSION} AS builder
|
|
97
|
+
ENV NODE_ENV=production
|
|
98
|
+
WORKDIR /app
|
|
99
|
+
COPY . .
|
|
100
|
+
RUN flame build --bun
|
|
101
|
+
|
|
102
|
+
${NGINX_STAGE}`;
|
|
103
|
+
/** Per-package-manager build config. `installCmd` is the locked install and
|
|
104
|
+
* assumes `lockFile` exists — see `LOCKLESS_INSTALL` for projects without one. */
|
|
105
|
+
var PM_CONFIG = {
|
|
106
|
+
bun: {
|
|
99
107
|
baseImage: "oven/bun:1-debian",
|
|
100
|
-
lockFile:
|
|
108
|
+
lockFile: "bun.lock",
|
|
101
109
|
installCmd: "bun install --frozen-lockfile",
|
|
102
110
|
runCmd: "bun",
|
|
103
111
|
cache: "",
|
|
104
112
|
setupAction: "bun"
|
|
105
|
-
}
|
|
106
|
-
|
|
113
|
+
},
|
|
114
|
+
pnpm: {
|
|
107
115
|
baseImage: "node:22-alpine",
|
|
108
116
|
lockFile: "pnpm-lock.yaml",
|
|
109
117
|
installCmd: "corepack enable && pnpm install --frozen-lockfile",
|
|
110
118
|
runCmd: "pnpm",
|
|
111
119
|
cache: "pnpm",
|
|
112
120
|
setupAction: "pnpm"
|
|
113
|
-
}
|
|
114
|
-
|
|
121
|
+
},
|
|
122
|
+
yarn: {
|
|
115
123
|
baseImage: "node:22-alpine",
|
|
116
124
|
lockFile: "yarn.lock",
|
|
117
125
|
installCmd: "yarn install --frozen-lockfile",
|
|
118
126
|
runCmd: "yarn",
|
|
119
127
|
cache: "yarn",
|
|
120
128
|
setupAction: "node"
|
|
121
|
-
}
|
|
122
|
-
|
|
129
|
+
},
|
|
130
|
+
npm: {
|
|
123
131
|
baseImage: "node:22-alpine",
|
|
124
132
|
lockFile: "package-lock.json",
|
|
125
133
|
installCmd: "npm ci",
|
|
126
134
|
runCmd: "npm",
|
|
127
135
|
cache: "npm",
|
|
128
136
|
setupAction: "node"
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
/** Installs for projects with no lockfile, where `npm ci` and
|
|
140
|
+
* `--frozen-lockfile` cannot run. */
|
|
141
|
+
var LOCKLESS_INSTALL = {
|
|
142
|
+
bun: "bun install",
|
|
143
|
+
pnpm: "corepack enable && pnpm install --prod=false",
|
|
144
|
+
yarn: "yarn install --production=false",
|
|
145
|
+
npm: "npm install --include=dev"
|
|
146
|
+
};
|
|
147
|
+
/** Build installs must include tools declared in devDependencies even though
|
|
148
|
+
* the builder stage sets NODE_ENV=production for the eventual site build. */
|
|
149
|
+
function getInstallCommand(pm, hasLockFile) {
|
|
150
|
+
if (pm.runCmd === "npm") return hasLockFile ? "npm ci --include=dev" : LOCKLESS_INSTALL.npm;
|
|
151
|
+
if (pm.runCmd === "pnpm" && hasLockFile) return `${pm.installCmd} --prod=false`;
|
|
152
|
+
if (pm.runCmd === "yarn" && hasLockFile) return `${pm.installCmd} --production=false`;
|
|
153
|
+
return hasLockFile ? pm.installCmd : LOCKLESS_INSTALL[pm.runCmd];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Detect the project's package manager. Lockfiles win — they describe what the
|
|
157
|
+
* project actually committed. `userAgent` (the invoking tool's
|
|
158
|
+
* `npm_config_user_agent`) is only consulted when no lockfile exists, so a
|
|
159
|
+
* lockless project still installs with the package manager the user is running.
|
|
160
|
+
* pnpm/yarn UA strings also contain "npm/?" — match specific tools first.
|
|
161
|
+
*/
|
|
162
|
+
function detectPkgManager(dir, userAgent = "") {
|
|
163
|
+
const bunLockFile = existsSync(join(dir, "bun.lock")) ? "bun.lock" : existsSync(join(dir, "bun.lockb")) ? "bun.lockb" : null;
|
|
164
|
+
if (bunLockFile) return {
|
|
165
|
+
...PM_CONFIG.bun,
|
|
166
|
+
lockFile: bunLockFile
|
|
129
167
|
};
|
|
168
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) return PM_CONFIG.pnpm;
|
|
169
|
+
if (existsSync(join(dir, "yarn.lock"))) return PM_CONFIG.yarn;
|
|
170
|
+
if (existsSync(join(dir, "package-lock.json"))) return PM_CONFIG.npm;
|
|
171
|
+
if (/bun\//.test(userAgent)) return PM_CONFIG.bun;
|
|
172
|
+
if (/pnpm\//.test(userAgent)) return PM_CONFIG.pnpm;
|
|
173
|
+
if (/yarn\//.test(userAgent)) return PM_CONFIG.yarn;
|
|
174
|
+
return PM_CONFIG.npm;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Dockerfile for the project's package manager. Unlike the Bun builder image,
|
|
178
|
+
* node-based images ship no project dependencies — without an install step the
|
|
179
|
+
* build fails resolving imports from node_modules, so the locked dependencies
|
|
180
|
+
* are installed before `flame` is invoked through the project's build script.
|
|
181
|
+
* The lockfile and its frozen install are used only when the lockfile exists;
|
|
182
|
+
* otherwise the install is derived from the invoking package manager.
|
|
183
|
+
*/
|
|
184
|
+
function generateDockerfile(dir = PROJECT_ROOT, userAgent = process.env.npm_config_user_agent || "") {
|
|
185
|
+
const pm = detectPkgManager(dir, userAgent);
|
|
186
|
+
if (pm.runCmd === "bun") return DOCKERFILE_BUN;
|
|
187
|
+
const hasLockFile = existsSync(join(dir, pm.lockFile));
|
|
188
|
+
return `${DOCKERFILE_MARKER}
|
|
189
|
+
FROM ${pm.baseImage} AS builder
|
|
190
|
+
ENV NODE_ENV=production
|
|
191
|
+
WORKDIR /app
|
|
192
|
+
COPY package.json${hasLockFile ? ` ${pm.lockFile}` : ""} ./
|
|
193
|
+
RUN ${getInstallCommand(pm, hasLockFile)}
|
|
194
|
+
COPY . .
|
|
195
|
+
RUN ${pm.runCmd} run build
|
|
196
|
+
|
|
197
|
+
${NGINX_STAGE}`;
|
|
130
198
|
}
|
|
131
199
|
var HEADERS_FILE = `/*
|
|
132
200
|
X-Frame-Options: DENY
|
|
@@ -174,15 +242,19 @@ async function runBuild() {
|
|
|
174
242
|
process.env.FLAME_BUILD_SILENT = "1";
|
|
175
243
|
process.env.LOG_LEVEL = "error";
|
|
176
244
|
}
|
|
177
|
-
const { runBuildCli } = await import("./build.impl-
|
|
245
|
+
const { runBuildCli } = await import("./build.impl-C97QWe1I.js");
|
|
178
246
|
await runBuildCli();
|
|
179
247
|
}
|
|
180
248
|
async function writeDockerFiles() {
|
|
181
249
|
const dockerDir = PROJECT_ROOT;
|
|
182
|
-
|
|
183
|
-
|
|
250
|
+
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
251
|
+
if (!existsSync(dockerfilePath)) {
|
|
252
|
+
await writeFile(dockerfilePath, generateDockerfile(dockerDir));
|
|
184
253
|
log.created("📄 Created Dockerfile");
|
|
185
|
-
}
|
|
254
|
+
} else if (readFileSync(dockerfilePath, "utf-8").startsWith(`# Generated by @docubook/flame deploy --docker\n`)) {
|
|
255
|
+
await writeFile(dockerfilePath, generateDockerfile(dockerDir));
|
|
256
|
+
log.created("📄 Updated generated Dockerfile");
|
|
257
|
+
} else log.info("⚠️ Dockerfile already exists; skipped it. Delete it and rerun flame deploy --docker to regenerate.");
|
|
186
258
|
if (!existsSync(join(dockerDir, "nginx.conf"))) {
|
|
187
259
|
await writeFile(join(dockerDir, "nginx.conf"), NGINX_CONF);
|
|
188
260
|
log.created("📄 Created nginx.conf");
|
|
@@ -193,7 +265,10 @@ async function writeDockerFiles() {
|
|
|
193
265
|
}
|
|
194
266
|
}
|
|
195
267
|
function generateWorkflowYml() {
|
|
196
|
-
const pm = detectPkgManager(PROJECT_ROOT);
|
|
268
|
+
const pm = detectPkgManager(PROJECT_ROOT, process.env.npm_config_user_agent || "");
|
|
269
|
+
const hasLockFile = existsSync(join(PROJECT_ROOT, pm.lockFile));
|
|
270
|
+
const installCmd = getInstallCommand(pm, hasLockFile);
|
|
271
|
+
const cacheLine = hasLockFile && pm.cache ? ` cache: ${pm.cache}` : "";
|
|
197
272
|
const setupSteps = [
|
|
198
273
|
" - uses: actions/checkout@v7",
|
|
199
274
|
" with:",
|
|
@@ -201,8 +276,8 @@ function generateWorkflowYml() {
|
|
|
201
276
|
""
|
|
202
277
|
];
|
|
203
278
|
if (pm.setupAction === "bun") setupSteps.push(" - uses: oven-sh/setup-bun@v2", " with:", " bun-version: latest", "");
|
|
204
|
-
else if (pm.setupAction === "pnpm") setupSteps.push(" - uses: pnpm/action-setup@v6", "", " - uses: actions/setup-node@v6", " with:", " node-version: 22",
|
|
205
|
-
else setupSteps.push(" - uses: actions/setup-node@v6", " with:", " node-version: 22",
|
|
279
|
+
else if (pm.setupAction === "pnpm") setupSteps.push(" - uses: pnpm/action-setup@v6", "", " - uses: actions/setup-node@v6", " with:", " node-version: 22", ...cacheLine ? [cacheLine] : [], "");
|
|
280
|
+
else setupSteps.push(" - uses: actions/setup-node@v6", " with:", " node-version: 22", ...cacheLine ? [cacheLine] : [], "");
|
|
206
281
|
return [
|
|
207
282
|
`name: Deploy to GitHub Pages`,
|
|
208
283
|
"",
|
|
@@ -225,7 +300,7 @@ function generateWorkflowYml() {
|
|
|
225
300
|
" runs-on: ubuntu-latest",
|
|
226
301
|
" steps:",
|
|
227
302
|
...setupSteps,
|
|
228
|
-
` - run: ${
|
|
303
|
+
` - run: ${installCmd}`,
|
|
229
304
|
"",
|
|
230
305
|
` - run: ${pm.runCmd} run build`,
|
|
231
306
|
"",
|
|
@@ -320,4 +395,4 @@ async function runDeploy() {
|
|
|
320
395
|
//#endregion
|
|
321
396
|
export { runDeploy as t };
|
|
322
397
|
|
|
323
|
-
//# sourceMappingURL=deploy.shared-
|
|
398
|
+
//# sourceMappingURL=deploy.shared-DjD3qs7t.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.shared-DjD3qs7t.js","names":[],"sources":["../node/deploy.shared.ts"],"sourcesContent":["/**\n * Runtime-neutral deploy — 3 modes:\n * 1. `flame deploy` → build + generate GitHub Actions workflow\n * 2. `flame deploy --docker` → build + generate Docker deployment files\n * 3. `flame deploy --docker --silent` → same as #2, minimal output\n *\n * Mirror of deploy.ts (Bun-only, protected) for Node.js and Deno.\n * Runs the neutral build in-process, then prepares .docu/dist.\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { DIST_DIR, PROJECT_ROOT, FRAMEWORK_ROOT } from \"./paths\";\n\nconst FLAME_VERSION = JSON.parse(\n readFileSync(resolve(FRAMEWORK_ROOT, \"package.json\"), \"utf-8\")\n).version;\n\nconst WORKFLOW_DIR = join(PROJECT_ROOT, \".github/workflows\");\nconst WORKFLOW_FILE = join(WORKFLOW_DIR, \"deploy.yml\");\n\nexport const NGINX_CONF = `server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n error_page 404 /404.html;\n\n gzip on;\n gzip_types text/html text/css application/javascript image/svg+xml;\n\n # Security headers (HSTS effective when HTTPS is terminated upstream)\n add_header X-Frame-Options \"DENY\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains\" always;\n add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\" always;\n\n # Build metadata and search content use stable URLs, so clients must revalidate them.\n location = /assets/manifest.json {\n expires -1;\n }\n\n location = /assets/search-index.json {\n expires -1;\n }\n\n # Generated JS, CSS, and chunks include content hashes in their filenames.\n location /assets/ {\n expires 1y;\n add_header Cache-Control \"public, immutable\";\n add_header X-Frame-Options \"DENY\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains\" always;\n add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\" always;\n }\n\n location /docs/assets/ {\n expires 7d;\n add_header Cache-Control \"public\";\n add_header X-Frame-Options \"DENY\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains\" always;\n add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\" always;\n }\n\n location = /404.html { }\n\n location / {\n try_files $uri $uri.html $uri/ =404;\n }\n}\n`;\n\nexport const DOCKERIGNORE = `node_modules\n*.DS_Store\n.docu/dist\n.docu/lib\n.env\n.env.*\n.npmrc\n*.log\n`;\n\n/** Final stage shared by every generated Dockerfile: nginx serves the flat\n * static output produced by the builder stage. */\nexport const DOCKERFILE_MARKER = \"# Generated by @docubook/flame deploy --docker\";\n\nconst NGINX_STAGE = `FROM nginx:alpine\nCOPY --from=builder /app/.docu/dist /usr/share/nginx/html\nCOPY nginx.conf /etc/nginx/conf.d/default.conf\nEXPOSE 80\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n`;\n\n/** Bun projects build inside the published builder image — the CLI is already\n * installed globally there, so no project dependency install step is needed. */\nexport const DOCKERFILE_BUN = `${DOCKERFILE_MARKER}\nFROM ghcr.io/docubook/flame:${FLAME_VERSION} AS builder\nENV NODE_ENV=production\nWORKDIR /app\nCOPY . .\nRUN flame build --bun\n\n${NGINX_STAGE}`;\n\nexport type PkgManagerConfig = {\n baseImage: string;\n lockFile: string;\n installCmd: string;\n runCmd: string;\n cache: string;\n setupAction: string;\n};\n\n/** Per-package-manager build config. `installCmd` is the locked install and\n * assumes `lockFile` exists — see `LOCKLESS_INSTALL` for projects without one. */\nconst PM_CONFIG: Record<string, PkgManagerConfig> = {\n bun: {\n baseImage: \"oven/bun:1-debian\",\n lockFile: \"bun.lock\",\n installCmd: \"bun install --frozen-lockfile\",\n runCmd: \"bun\",\n cache: \"\",\n setupAction: \"bun\",\n },\n pnpm: {\n baseImage: \"node:22-alpine\",\n lockFile: \"pnpm-lock.yaml\",\n installCmd: \"corepack enable && pnpm install --frozen-lockfile\",\n runCmd: \"pnpm\",\n cache: \"pnpm\",\n setupAction: \"pnpm\",\n },\n yarn: {\n baseImage: \"node:22-alpine\",\n lockFile: \"yarn.lock\",\n installCmd: \"yarn install --frozen-lockfile\",\n runCmd: \"yarn\",\n cache: \"yarn\",\n setupAction: \"node\",\n },\n npm: {\n baseImage: \"node:22-alpine\",\n lockFile: \"package-lock.json\",\n installCmd: \"npm ci\",\n runCmd: \"npm\",\n cache: \"npm\",\n setupAction: \"node\",\n },\n};\n\n/** Installs for projects with no lockfile, where `npm ci` and\n * `--frozen-lockfile` cannot run. */\nconst LOCKLESS_INSTALL: Record<string, string> = {\n bun: \"bun install\",\n pnpm: \"corepack enable && pnpm install --prod=false\",\n yarn: \"yarn install --production=false\",\n npm: \"npm install --include=dev\",\n};\n\n/** Build installs must include tools declared in devDependencies even though\n * the builder stage sets NODE_ENV=production for the eventual site build. */\nfunction getInstallCommand(pm: PkgManagerConfig, hasLockFile: boolean): string {\n if (pm.runCmd === \"npm\") {\n return hasLockFile ? \"npm ci --include=dev\" : LOCKLESS_INSTALL.npm;\n }\n if (pm.runCmd === \"pnpm\" && hasLockFile) {\n return `${pm.installCmd} --prod=false`;\n }\n if (pm.runCmd === \"yarn\" && hasLockFile) {\n return `${pm.installCmd} --production=false`;\n }\n return hasLockFile ? pm.installCmd : LOCKLESS_INSTALL[pm.runCmd];\n}\n\n/**\n * Detect the project's package manager. Lockfiles win — they describe what the\n * project actually committed. `userAgent` (the invoking tool's\n * `npm_config_user_agent`) is only consulted when no lockfile exists, so a\n * lockless project still installs with the package manager the user is running.\n * pnpm/yarn UA strings also contain \"npm/?\" — match specific tools first.\n */\nexport function detectPkgManager(dir: string, userAgent = \"\"): PkgManagerConfig {\n const bunLockFile = existsSync(join(dir, \"bun.lock\"))\n ? \"bun.lock\"\n : existsSync(join(dir, \"bun.lockb\"))\n ? \"bun.lockb\"\n : null;\n if (bunLockFile) return { ...PM_CONFIG.bun, lockFile: bunLockFile };\n if (existsSync(join(dir, \"pnpm-lock.yaml\"))) return PM_CONFIG.pnpm;\n if (existsSync(join(dir, \"yarn.lock\"))) return PM_CONFIG.yarn;\n if (existsSync(join(dir, \"package-lock.json\"))) return PM_CONFIG.npm;\n\n if (/bun\\//.test(userAgent)) return PM_CONFIG.bun;\n if (/pnpm\\//.test(userAgent)) return PM_CONFIG.pnpm;\n if (/yarn\\//.test(userAgent)) return PM_CONFIG.yarn;\n return PM_CONFIG.npm;\n}\n\n/**\n * Dockerfile for the project's package manager. Unlike the Bun builder image,\n * node-based images ship no project dependencies — without an install step the\n * build fails resolving imports from node_modules, so the locked dependencies\n * are installed before `flame` is invoked through the project's build script.\n * The lockfile and its frozen install are used only when the lockfile exists;\n * otherwise the install is derived from the invoking package manager.\n */\nexport function generateDockerfile(\n dir: string = PROJECT_ROOT,\n userAgent: string = process.env.npm_config_user_agent || \"\"\n): string {\n const pm = detectPkgManager(dir, userAgent);\n if (pm.runCmd === \"bun\") return DOCKERFILE_BUN;\n\n const hasLockFile = existsSync(join(dir, pm.lockFile));\n\n return `${DOCKERFILE_MARKER}\nFROM ${pm.baseImage} AS builder\nENV NODE_ENV=production\nWORKDIR /app\nCOPY package.json${hasLockFile ? ` ${pm.lockFile}` : \"\"} ./\nRUN ${getInstallCommand(pm, hasLockFile)}\nCOPY . .\nRUN ${pm.runCmd} run build\n\n${NGINX_STAGE}`;\n}\n\nexport const HEADERS_FILE = `/*\n X-Frame-Options: DENY\n X-Content-Type-Options: nosniff\n Strict-Transport-Security: max-age=63072000; includeSubDomains; preload\n Referrer-Policy: strict-origin-when-cross-origin\n Permissions-Policy: camera=(), microphone=(), geolocation=()\n Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\n\n/assets/client-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/home-client-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/docs-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/site-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/chunks/*\n Cache-Control: public, max-age=31536000, immutable\n/assets/assets/*\n Cache-Control: public, max-age=31536000, immutable\n/assets/manifest.json\n Cache-Control: no-cache\n/assets/search-index.json\n Cache-Control: no-cache\n`;\n\nconst isDocker = !!process.env.FLAME_DEPLOY_DOCKER;\nconst isSilent = !!process.env.FLAME_DEPLOY_SILENT;\nconst isCi = !!process.env.FLAME_DEPLOY_CI;\n\n/** Logger that no-ops all non-error output in silent mode. */\nconst log = isSilent\n ? { info: () => {}, ok: () => {}, created: () => {}, out: () => {} }\n : {\n info: (m: string) => console.log(m),\n ok: () => console.log(\"\\n✅ Ready to deploy!\"),\n created: (m: string) => console.log(m),\n out: (m: string) => console.log(m),\n };\n\nasync function runBuild() {\n process.env.NODE_ENV = \"production\";\n if (isSilent) {\n process.env.FLAME_BUILD_SILENT = \"1\";\n process.env.LOG_LEVEL = \"error\";\n }\n const { runBuildCli } = await import(\"./build.impl\");\n await runBuildCli();\n}\n\nasync function writeDockerFiles() {\n const dockerDir = PROJECT_ROOT;\n const dockerfilePath = join(dockerDir, \"Dockerfile\");\n\n if (!existsSync(dockerfilePath)) {\n await writeFile(dockerfilePath, generateDockerfile(dockerDir));\n log.created(\"📄 Created Dockerfile\");\n } else if (readFileSync(dockerfilePath, \"utf-8\").startsWith(`${DOCKERFILE_MARKER}\\n`)) {\n await writeFile(dockerfilePath, generateDockerfile(dockerDir));\n log.created(\"📄 Updated generated Dockerfile\");\n } else {\n log.info(\n \"⚠️ Dockerfile already exists; skipped it. Delete it and rerun flame deploy --docker to regenerate.\"\n );\n }\n\n if (!existsSync(join(dockerDir, \"nginx.conf\"))) {\n await writeFile(join(dockerDir, \"nginx.conf\"), NGINX_CONF);\n log.created(\"📄 Created nginx.conf\");\n }\n\n if (!existsSync(join(dockerDir, \".dockerignore\"))) {\n await writeFile(join(dockerDir, \".dockerignore\"), DOCKERIGNORE);\n log.created(\"📄 Created .dockerignore\");\n }\n}\n\nfunction generateWorkflowYml(): string {\n const pm = detectPkgManager(PROJECT_ROOT, process.env.npm_config_user_agent || \"\");\n const hasLockFile = existsSync(join(PROJECT_ROOT, pm.lockFile));\n const installCmd = getInstallCommand(pm, hasLockFile);\n const cacheLine = hasLockFile && pm.cache ? ` cache: ${pm.cache}` : \"\";\n\n const setupSteps: string[] = [\n \" - uses: actions/checkout@v7\",\n \" with:\",\n \" fetch-depth: 0\",\n \"\",\n ];\n\n if (pm.setupAction === \"bun\") {\n setupSteps.push(\n \" - uses: oven-sh/setup-bun@v2\",\n \" with:\",\n \" bun-version: latest\",\n \"\"\n );\n } else if (pm.setupAction === \"pnpm\") {\n setupSteps.push(\n \" - uses: pnpm/action-setup@v6\",\n \"\",\n \" - uses: actions/setup-node@v6\",\n \" with:\",\n \" node-version: 22\",\n ...(cacheLine ? [cacheLine] : []),\n \"\"\n );\n } else {\n // npm / yarn\n setupSteps.push(\n \" - uses: actions/setup-node@v6\",\n \" with:\",\n \" node-version: 22\",\n ...(cacheLine ? [cacheLine] : []),\n \"\"\n );\n }\n\n return [\n `name: Deploy to GitHub Pages`,\n \"\",\n \"on:\",\n \" push:\",\n \" branches: [main]\",\n \" workflow_dispatch:\",\n \"\",\n \"permissions:\",\n \" contents: read\",\n \" pages: write\",\n \" id-token: write\",\n \"\",\n \"concurrency:\",\n ' group: \"pages\"',\n \" cancel-in-progress: false\",\n \"\",\n \"jobs:\",\n \" build:\",\n \" runs-on: ubuntu-latest\",\n \" steps:\",\n ...setupSteps,\n ` - run: ${installCmd}`,\n \"\",\n ` - run: ${pm.runCmd} run build`,\n \"\",\n \" - name: Add .nojekyll\",\n \" run: touch .docu/dist/.nojekyll\",\n \"\",\n \" - uses: actions/upload-pages-artifact@v3\",\n \" with:\",\n \" path: .docu/dist\",\n \"\",\n \" deploy:\",\n \" environment:\",\n \" name: github-pages\",\n \" url: ${{ steps.deployment.outputs.page_url }}\",\n \" runs-on: ubuntu-latest\",\n \" needs: build\",\n \" steps:\",\n \" - id: deployment\",\n \" uses: actions/deploy-pages@v4\",\n ].join(\"\\n\");\n}\n\nfunction generateDockerWorkflowYml(): string {\n const imageName = \"ghcr.io/${{ github.repository }}\";\n return [\n `name: Build & Push Docker Image`,\n \"\",\n \"on:\",\n \" push:\",\n \" branches: [main]\",\n \" workflow_dispatch:\",\n \"\",\n \"permissions:\",\n \" contents: read\",\n \" packages: write\",\n \"\",\n \"jobs:\",\n \" build:\",\n \" runs-on: ubuntu-latest\",\n \" steps:\",\n \" - uses: actions/checkout@v4\",\n \" with:\",\n \" fetch-depth: 0\",\n \"\",\n \" - name: Log in to GHCR\",\n \" uses: docker/login-action@v3\",\n \" with:\",\n \" registry: ghcr.io\",\n \" username: ${{ github.actor }}\",\n \" password: ${{ secrets.GITHUB_TOKEN }}\",\n \"\",\n \" - name: Build & push\",\n \" uses: docker/build-push-action@v5\",\n \" with:\",\n \" context: .\",\n ` tags: ${imageName}:latest`,\n \" push: true\",\n ].join(\"\\n\");\n}\n\nasync function writeGhaWorkflow() {\n if (!existsSync(WORKFLOW_FILE)) {\n await mkdir(WORKFLOW_DIR, { recursive: true });\n await writeFile(WORKFLOW_FILE, generateWorkflowYml());\n log.created(\"📄 Created .github/workflows/deploy.yml\");\n }\n}\n\nconst DOCKER_WORKFLOW_FILE = join(WORKFLOW_DIR, \"deploy-docker.yml\");\n\nasync function writeDockerWorkflow() {\n if (!existsSync(DOCKER_WORKFLOW_FILE)) {\n await mkdir(WORKFLOW_DIR, { recursive: true });\n await writeFile(DOCKER_WORKFLOW_FILE, generateDockerWorkflowYml());\n log.created(\"📄 Created .github/workflows/deploy-docker.yml\");\n }\n}\n\nexport async function runDeploy(): Promise<void> {\n log.info(\"📦 Building for production...\\n\");\n await runBuild();\n\n // Common: .nojekyll + _headers\n await writeFile(join(DIST_DIR, \".nojekyll\"), \"\");\n await writeFile(join(DIST_DIR, \"_headers\"), HEADERS_FILE);\n\n if (isDocker) {\n await writeDockerFiles();\n if (isCi) await writeDockerWorkflow();\n } else {\n await writeGhaWorkflow();\n }\n\n log.ok();\n log.out(\" Output: .docu/dist/\");\n if (isDocker) {\n log.out(\" Build locally: docker build -t my-docs . && docker run -p 80:80 my-docs\");\n if (isCi) {\n log.out(\" Push to GitHub — CI will build & push Docker image to GHCR\");\n log.out(\" Then pull latest image on your hosting platform (Coolify, etc.)\");\n } else {\n log.out(\" For Coolify: connect repo, set build pack to Dockerfile\");\n }\n } else {\n log.out(\" Push to GitHub and enable Pages (Settings → Pages → Source: GitHub Actions)\");\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { f as loadDocuConfig } from "./paths-Cbz-dj4s.js";
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@docubook/flame",
|
|
4
|
-
version: "2.0.
|
|
4
|
+
version: "2.0.2",
|
|
5
5
|
description: "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
|
|
6
6
|
type: "module",
|
|
7
7
|
types: "./.docu/node/plugin.ts",
|
|
@@ -304,4 +304,4 @@ function countRoutes(routes) {
|
|
|
304
304
|
//#endregion
|
|
305
305
|
export { logger as t };
|
|
306
306
|
|
|
307
|
-
//# sourceMappingURL=logger-
|
|
307
|
+
//# sourceMappingURL=logger-_e2TNmEq.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger-DwxYdOL7.js","names":[],"sources":["../../package.json","../node/logger.ts"],"sourcesContent":["{\n \"name\": \"@docubook/flame\",\n \"version\": \"2.0.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACYA,IAAM,aAAa,eAAe;AAClC,IAAM,OAAO,CAAC,EAAE,QAAQ,IAAI,MAAM,QAAQ,IAAI,YAAY,CAAC,QAAQ,OAAO;AAE1E,IAAM,UADa,QAAQ,IAAI,cAAc,cACf;AAG9B,IAAM,SAAmC;CAAE,OAAO;CAAG,MAAM;CAAG,MAAM;CAAG,OAAO;AAAE;AAChF,IAAM,eAAe,OAAQ,QAAQ,IAAI,aAA0B,WAAW,OAAO;AAErF,SAAS,UAAU,OAA0B;CAC3C,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAS,QAAQ,OAAiB,KAAa,MAAgC;CAC7E,IAAI,CAAC,UAAU,KAAK,GAAG;CACvB,MAAM,QAAQ;EAAE,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY;EAAG,GAAG;EAAM;EAAO;CAAI;CAElE,CADY,UAAU,UAAU,QAAQ,QAAQ,UAAU,SAAS,QAAQ,OAAO,QAAQ,IAAA,CACtF,KAAK,UAAU,KAAK,CAAC;AAC3B;;AAGA,SAAS,MAAM,OAAiB,KAAa,MAAyC;CACpF,IAAI,QAAQ;EACV,QAAQ,OAAO,KAAK,IAAI;EACxB,OAAO;CACT;CACA,OAAO,CAAC,UAAU,KAAK;AACzB;AAEA,IAAM,IACJ,QAAQ,SACJ;CACE,OAAO;CACP,MAAM;CACN,KAAK;CACL,OAAO;CACP,MAAM;CACN,SAAS;CACT,QAAQ;CACR,KAAK;CACL,OAAO;CACP,MAAM;AACR,IACA;CACE,OAAO;CACP,MAAM;CACN,KAAK;CACL,OAAO;CACP,MAAM;CACN,SAAS;CACT,QAAQ;CACR,KAAK;CACL,OAAO;CACP,MAAM;AACR;AAEN,IAAM,iBAAiB;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;AAExE,IAAM,UAAN,MAAc;CACZ,QAAgB;CAChB,UAAkB;CAClB,QAAuD;CAEvD,KAAK,UAAkB;EACrB,IAAI,KAAK,OAAO,cAAc,KAAK,KAAK;EACxC,KAAK,QAAQ;EACb,IAAI,MACF,QAAQ,IAAI,KAAK,UAAU;OACtB;GACL,QAAQ,OAAO,MAAM,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;GACjE,QAAQ,OAAO,MAAM,WAAW;EAClC;CACF;CAEA,MAAM,KAAa;EACjB,KAAK,UAAU;EACf,IAAI,MAAM;GACR,QAAQ,IAAI,KAAK,KAAK;GACtB;EACF;EACA,KAAK,QAAQ;EACb,QAAQ,OAAO,MAAM,WAAW;EAChC,KAAK,OAAO;EACZ,KAAK,QAAQ,kBAAkB,KAAK,OAAO,GAAG,EAAE;CAClD;CAEA,KAAK,UAAkB;EACrB,IAAI,KAAK,OAAO,cAAc,KAAK,KAAK;EACxC,KAAK,QAAQ;EACb,IAAI,MAAM;GACR,QAAQ,IAAI,KAAK,UAAU;GAC3B;EACF;EACA,QAAQ,OAAO,MAAM,WAAW,EAAE,MAAM,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG;EAClE,QAAQ,OAAO,MAAM,WAAW;CAClC;CAEA,SAAiB;EACf,MAAM,IAAI,eAAe,KAAK,QAAQ,eAAe;EACrD,QAAQ,OAAO,MAAM,KAAK,EAAE,OAAO,IAAI,EAAE,MAAM,GAAG,KAAK,SAAS;EAChE,KAAK;CACP;AACF;AASA,IAAM,kBAAkB;AACxB,IAAI,iBAAiB;AACrB,IAAI,iBAAiB;AAErB,SAAS,eAAe,QAAqB,SAAS,IAAI,SAAS,MAAM;CACvE,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,IAAI,gBAAgB;EACpB,IAAI,kBAAkB,iBAAiB;GACrC,iBAAiB;GACjB,QAAQ,OAAO,MAAM,GAAG,SAAS,EAAE,IAAI,KAAK,EAAE,MAAM,GAAG;GACvD;EACF;EAEA,MAAM,QAAQ,OAAO;EACrB,MAAM,OAAO,MAAM,OAAO,SAAS;EACnC,MAAM,YAAY,SAAS,KAAK,OAAO,SAAS;EAChD,MAAM,cAAc,SAAS,KAAK,OAAO,SAAS;EAElD,MAAM,OAAO,MAAM,SACf,GAAG,EAAE,MAAM,MAAM,OAAO,EAAE,UAC1B,GAAG,EAAE,QAAQ,MAAM,OAAO,EAAE;EAEhC,MAAM,QAAQ,MAAM,SAAS,GAAG,EAAE,KAAK,SAAS,EAAE,UAAU;EAE5D,QAAQ,OAAO,MAAM,GAAG,SAAS,YAAY,KAAK,GAAG,MAAM,GAAG;EAC9D;EAEA,IAAI,MAAM,OAAO,QACf,eAAe,MAAM,OAAO,SAAS,aAAa,KAAK;CAE3D;AACF;AAEA,IAAa,SAAS;CACpB,SAAS,IAAI,QAAQ;CAErB,aAAa;EACX,IAAI,MAAM,QAAQ,eAAe;GAAE,SAAS;GAAmB,SAAS,gBAAI;EAAQ,CAAC,GAAG;EACxF,QAAQ,IACN,KAAK,EAAE,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,GAAG,EAAE,IAAI,GAAG,gBAAI,UAAU,EAAE,MAAM,GACtF;CACF;CAEA,cAAc;EACZ,IAAI,MAAM,QAAQ,cAAc,GAAG;EACnC,KAAK,QAAQ,MAAM,2BAA2B;CAChD;CAEA,WAAW,IAAY;EACrB,IAAI,MAAM,QAAQ,eAAe,EAAE,aAAa,GAAG,CAAC,GAAG;EACvD,KAAK,QAAQ,KAAK,0BAA0B,EAAE,IAAI,GAAG,GAAG,KAAK,EAAE,OAAO;CACxE;CAEA,aAAa;EACX,IAAI,MAAM,QAAQ,aAAa,GAAG;EAClC,KAAK,QAAQ,MAAM,4BAA4B;CACjD;CAEA,UAAU,SAAiB,IAAY,UAAU,OAAO;EACtD,IAAI,MAAM,QAAQ,cAAc;GAAE;GAAS,aAAa;GAAI;EAAQ,CAAC,GAAG;EACxE,KAAK,QAAQ,KACX,UACI,uBAAuB,EAAE,IAAI,GAAG,GAAG,KAAK,EAAE,UAC1C,0BAA0B,EAAE,IAAI,GAAG,QAAQ,YAAY,GAAG,KAAK,EAAE,OACvE;CACF;CAEA,SAAS;EACP,IAAI,MAAM,SAAS,QAAQ,GAAG;EAC9B,MAAM,SAAS,WAAW;EAC1B,IAAI,CAAC,QAAQ,QAAQ;EAErB,QAAQ,IAAI,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;EAC9F,iBAAiB;EACjB,iBAAiB;EACjB,eAAe,QAAQ,IAAI;CAC7B;CAEA,MAAM,MAAc,MAAM,OAAO;EAC/B,IAAI,MAAM,QAAQ,gBAAgB;GAAE;GAAM;EAAI,CAAC,GAAG;EAClD,QAAQ,IACN,KAAK,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,MAAM,EAAE,KAAK,mBAAmB,KAAK,QAAQ,EAAE,OAC1F;EACA,IAAI,KAAK,QAAQ,IAAI,GAAG,EAAE,IAAI,4CAA4C,EAAE,OAAO;EACnF,QAAQ,IAAI,EAAE;CAChB;CAEA,QAAQ,QAAgB,UAAkB,QAAgB,aAAsB;EAC9E,IAAI,MAAM,QAAQ,gBAAgB;GAAE;GAAQ,MAAM;GAAU;GAAQ;EAAY,CAAC,GAAG;EACpF,MAAM,QAAQ,UAAU,MAAM,EAAE,MAAM,UAAU,MAAM,EAAE,OAAO,EAAE;EACjE,MAAM,sBAAK,IAAI,KAAK,EAAA,CAAE,mBAAmB;EACzC,MAAM,MAAM,eAAe,OAAO,IAAI,EAAE,MAAM,YAAY,IAAI,EAAE,UAAU;EAC1E,QAAQ,IACN,GAAG,EAAE,IAAI,IAAI,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,SAAS,EAAE,QAAQ,KAChF;CACF;CAEA,MAAM,KAAa,MAAgC;EACjD,IAAI,MAAM,SAAS,KAAK,IAAI,GAAG;EAC/B,QAAQ,IAAI,GAAG,EAAE,KAAK,YAAY,MAAM,EAAE,OAAO;CACnD;CAEA,KAAK,KAAa,MAAgC;EAChD,IAAI,MAAM,QAAQ,KAAK,IAAI,GAAG;EAC9B,QAAQ,KAAK,GAAG,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO;CAChD;CAEA,MAAM,KAAa,MAAgC;EACjD,IAAI,MAAM,SAAS,KAAK,IAAI,GAAG;EAC/B,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM,MAAM,EAAE,OAAO;CAC9C;AACF;AAEA,SAAS,YAAY,QAA6B;CAChD,IAAI,QAAQ;CACZ,KAAK,MAAM,KAAK,QAAQ;EACtB,IAAI,CAAC,EAAE,QAAQ;EACf,IAAI,EAAE,OAAO,SAAS,YAAY,EAAE,KAAK;CAC3C;CACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"logger-_e2TNmEq.js","names":[],"sources":["../../package.json","../node/logger.ts"],"sourcesContent":["{\n \"name\": \"@docubook/flame\",\n \"version\": \"2.0.2\",\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,6 +1,6 @@
|
|
|
1
1
|
import { r as DIST_DIR } from "./paths-Cbz-dj4s.js";
|
|
2
2
|
import { a as getContentType, d as cspHeader, h as injectNonce, p as generateNonce, u as SECURITY_HEADERS } from "./utils-B_CoyKie.js";
|
|
3
|
-
import { t as logger } from "./logger-
|
|
3
|
+
import { t as logger } from "./logger-_e2TNmEq.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-DiTu2Coo.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.impl-
|
|
1
|
+
{"version":3,"file":"preview.impl-DiTu2Coo.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 PAGES_DIR, f as loadDocuConfig, l as PROJECT_ROOT, r as DIST_DIR } from "./paths-Cbz-dj4s.js";
|
|
2
2
|
import { D as loadPlugins, E as BuildPluginBuilder, a as IndexPage, b as buildClientBundle, c as captureException, f as compileMdx, i as DocsLayout, l as initSentry, m as frontmatterField, n as hmrScript, o as NotFoundPage, r as htmlShell, s as DocsPage, t as errorHtml, u as generateSearchIndex, x as computeInlineThemeCss } from "./html.shared-CsEFmy_Y.js";
|
|
3
3
|
import { _ as isSlugSafe, a as getContentType, g as isPathSafe, l as stripDocsHtmlSuffix, m as htmlResponse, p as generateNonce, u as SECURITY_HEADERS, y as wrapPluginResponse } from "./utils-B_CoyKie.js";
|
|
4
|
-
import { t as logger } from "./logger-
|
|
4
|
+
import { t as logger } from "./logger-_e2TNmEq.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";
|
|
@@ -346,4 +346,4 @@ async function runServer(adapter) {
|
|
|
346
346
|
//#endregion
|
|
347
347
|
export { runServer as t };
|
|
348
348
|
|
|
349
|
-
//# sourceMappingURL=server.impl-
|
|
349
|
+
//# sourceMappingURL=server.impl-DPBnKdIh.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.impl-Cs6DEy4V.js","names":[],"sources":["../node/server-routes.ts","../node/server.impl.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport React, { type ReactNode } from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport { compileMdx, frontmatterField } from \"./mdx\";\nimport { DEFAULT_FAVICON, getContentType } from \"./utils\";\nimport { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT } from \"./paths\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport type { PageContext, PageType } from \"./plugin\";\nimport type { AssetEntry, AssetManifest, DocuConfig, TocItem } from \"./types\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport NotFoundPage from \"../pages/404\";\nimport IndexPage from \"../pages/index\";\nimport { DocsLayout } from \"../components/DocsLayout\";\nimport { generateNonce, isPathSafe, isSlugSafe, htmlResponse, SECURITY_HEADERS } from \"./security\";\nimport { htmlShell as createHtmlShell, hmrScript, errorHtml } from \"./html.shared\";\n\nexport interface ServerState {\n docuConfig: DocuConfig;\n assetManifest: AssetManifest;\n inlineThemeCss?: string;\n builder: BuildPluginBuilder | null;\n}\n\nasync function createHtmlResponse(\n title: string,\n description: string,\n body: string,\n status: number,\n state: ServerState,\n pageType: PageType,\n page: Omit<PageContext, \"pageType\" | \"assets\" | \"config\">,\n depth = 0\n): Promise<Response> {\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const assets: AssetEntry = state.assetManifest[pageType];\n const context: PageContext = {\n ...page,\n pageType,\n assets,\n config: state.docuConfig,\n };\n let html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: assets.css,\n js: assets.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n headExtra: state.builder?.collectHead(context),\n bodyExtra: state.builder?.collectBody(context),\n depth,\n // 404 pages can be requested at arbitrary depths (e.g. a noLink section\n // path typed in the address bar) — relative asset paths would resolve\n // against the wrong directory and break CSS/JS.\n absoluteAssets: status === 404,\n });\n if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);\n /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */\n return htmlResponse(html, nonce, status, true);\n}\n\n// Dev-only memo: compileMdx per request is ~70ms; repeat navigations with\n// an unchanged file (same mtime) reuse the compiled result. The dev server\n// has no other per-page cache — without this every navigation re-parses MDX.\n// ponytail: unbounded Map is fine — docs sites have a handful of pages; cap\n// with an LRU only if a project exceeds thousands of routes.\nconst docsCache = new Map<\n string,\n { mtimeMs: number; doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>> }\n>();\n\nasync function getDocsForSlug(\n slug: string,\n state: ServerState\n): Promise<{\n content: ReactNode;\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n tocs: TocItem[];\n filePath: string;\n resolvedContent: string;\n} | null> {\n if (!isSlugSafe(slug, DOCS_DIR)) return null;\n\n const paths = [\n resolve(DOCS_DIR, slug, \"index.mdx\"),\n resolve(DOCS_DIR, `${slug}.mdx`),\n resolve(DOCS_DIR, slug, \"index.md\"),\n resolve(DOCS_DIR, `${slug}.md`),\n ];\n\n const resolvedDocsDir = resolve(DOCS_DIR);\n let filePath: string | null = null;\n let raw: string | null = null;\n for (const p of paths) {\n const resolvedCandidate = resolve(p);\n if (\n resolvedCandidate !== resolvedDocsDir &&\n !resolvedCandidate.startsWith(resolvedDocsDir + \"/\")\n ) {\n continue;\n }\n try {\n raw = await readFile(resolvedCandidate, \"utf-8\");\n filePath = resolvedCandidate;\n break;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n if (!filePath || !raw) return null;\n\n const relPath = filePath.replace(PROJECT_ROOT + \"/\", \"\");\n\n const mtimeMs = (await stat(filePath)).mtimeMs;\n const cached = docsCache.get(relPath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.doc;\n }\n\n let content = raw;\n if (state.builder) {\n const transformed = await state.builder.runOnLoad(relPath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n const remarkPlugins = state.builder?.collectRemarkPlugins();\n const rehypePlugins = state.builder?.collectRehypePlugins();\n const result = await compileMdx(content, relPath, undefined, remarkPlugins, rehypePlugins);\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (state.builder) {\n frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {\n slug,\n filePath: relPath,\n content,\n });\n }\n\n const doc = {\n content: result.content,\n compiledSource: result.compiledSource,\n frontmatter,\n tocs: result.tocs,\n filePath: relPath,\n resolvedContent: content,\n };\n docsCache.set(relPath, { mtimeMs, doc });\n return doc;\n}\n\nasync function renderDocsServerPage(\n doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>>,\n slug: string[],\n pathname: string,\n state: ServerState\n): Promise<Response> {\n const title = frontmatterField(doc.frontmatter, \"title\") || slug.join(\"/\") || \"Docs\";\n const description = frontmatterField(doc.frontmatter, \"description\");\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname },\n React.createElement(DocsPage, {\n slug,\n title,\n description,\n date: frontmatterField(doc.frontmatter, \"date\") || undefined,\n // Same root-relative SSR as build — see build.ts.\n content: renderToString(doc.content),\n tocs: doc.tocs,\n filePath: doc.filePath,\n repoUrl: state.docuConfig.repo?.url,\n compiledSource: doc.compiledSource,\n })\n );\n\n const body = renderToString(page);\n\n // Match build.ts depth calculation: slug.split(\"/\").length, fallback to 1 for empty\n const depth = slug.length || 1;\n\n return createHtmlResponse(\n title,\n description,\n body,\n 200,\n state,\n \"docs\",\n {\n slug: slug.join(\"/\"),\n filePath: doc.filePath,\n frontmatter: doc.frontmatter,\n content: doc.resolvedContent,\n },\n depth\n );\n}\n\nasync function renderPage(\n Component: React.ComponentType<Record<string, unknown>>,\n title: string,\n description: string,\n status: number,\n state: ServerState,\n props: Record<string, unknown> = {},\n depth = 0\n): Promise<Response> {\n const body = renderToString(React.createElement(Component, props));\n return createHtmlResponse(\n title,\n description,\n body,\n status,\n state,\n \"notFound\",\n {\n slug: \"404\",\n filePath: resolve(PAGES_DIR, \"404.tsx\"),\n frontmatter: {},\n },\n depth\n );\n}\n\nexport async function handleDocsIndex(state: ServerState): Promise<Response> {\n const doc = await getDocsForSlug(\"\", state);\n if (!doc) return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, 1);\n return renderDocsServerPage(doc, [], \"/docs\", state);\n}\n\nexport async function handleDocsRoute(slug: string[], state: ServerState): Promise<Response> {\n const path = slug.join(\"/\");\n const doc = await getDocsForSlug(path, state);\n if (!doc)\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, slug.length || 1);\n return renderDocsServerPage(doc, slug, `/docs/${path}`, state);\n}\n\nexport async function handleIndex(state: ServerState): Promise<Response> {\n const page = React.createElement(IndexPage);\n const body = renderToString(page);\n return createHtmlResponse(\n state.docuConfig.meta?.title || \"DocuBook\",\n state.docuConfig.meta?.description || \"\",\n body,\n 200,\n state,\n \"home\",\n {\n slug: \"\",\n filePath: resolve(PAGES_DIR, \"index.tsx\"),\n frontmatter: { ...state.docuConfig.meta } as Record<string, unknown>,\n }\n );\n}\n\nexport async function handleNotFound(state: ServerState, depth = 0): Promise<Response> {\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, depth);\n}\n\nexport function serveStatic(pathname: string): Response | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (!isPathSafe(pathname, DIST_DIR)) return null;\n const assetPath = resolve(DIST_DIR, decoded.slice(1));\n try {\n const s = statSync(assetPath);\n if (s.isFile()) {\n return new Response(readFileSync(assetPath), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n\n if (decoded.startsWith(\"/docs/assets/\")) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(\"/docs/assets/\".length);\n const docsAsset = resolve(docsAssetsDir, requestedRelative);\n const docsAssetsDirWithSep = docsAssetsDir.endsWith(\"/\") ? docsAssetsDir : docsAssetsDir + \"/\";\n if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;\n try {\n const s = statSync(docsAsset);\n if (s.isFile()) {\n return new Response(readFileSync(docsAsset), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n return null;\n}\n\nexport function serverErrorResponse(error: unknown): Response {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n const st =\n process.env.NODE_ENV !== \"production\" && error instanceof Error ? error.stack : undefined;\n return new Response(errorHtml(msg, st), {\n status: 500,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": \"default-src 'none'; style-src 'unsafe-inline'\",\n },\n });\n}\n","/**\n * Runtime-neutral dev server — mirror of `server.ts` (Bun-only, protected)\n * driven by a `RuntimeAdapter` (./runtime) instead of `Bun.serve`,\n * with manual route matching instead of `Bun.FileSystemRouter`. The page set\n * is static (`/`, `/docs/[[...slug]]`, `/404`), so a router is unnecessary.\n */\n\nimport { watch, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { DOCS_DIR, loadDocuConfig } from \"./paths\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport {\n serveStatic,\n handleDocsIndex,\n handleDocsRoute,\n handleIndex,\n handleNotFound,\n serverErrorResponse,\n type ServerState,\n} from \"./server-routes\";\nimport { wrapPluginResponse } from \"./security\";\nimport { stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n\n const parsedPort = parseInt(process.env.PORT ?? \"3000\", 10);\n const PORT =\n Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3000;\n\n logger.buildStart();\n\n await initSentry();\n\n logger.bundleStart();\n let t = performance.now();\n const assetManifest = await buildClientBundle();\n logger.bundleDone(Math.round(performance.now() - t));\n\n const inlineThemeCss = computeInlineThemeCss();\n\n logger.indexStart();\n t = performance.now();\n const records = await generateSearchIndex();\n logger.indexDone(records, Math.round(performance.now() - t));\n\n logger.routes();\n\n // Plugin setup — all hooks active (onLoad, remark/rehype, frontmatter, head/body, html transform, handleRequest)\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n await builder.runOnStart();\n }\n\n const state: ServerState = {\n docuConfig,\n assetManifest,\n inlineThemeCss,\n builder,\n };\n\n const hmrClients = new Set<ReadableStreamDefaultController>();\n\n const require = createRequire(import.meta.url);\n /** Locate the installed @docubook/markdown source dir (workspace link), if any. */\n function resolveMdxContentSrc(): string | null {\n try {\n const pkgDir = dirname(require.resolve(\"@docubook/markdown/package.json\"));\n const src = join(pkgDir, \"src\");\n return existsSync(src) ? src : null;\n } catch {\n return null;\n }\n }\n\n let hmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const watcher = watch(DOCS_DIR, { recursive: true }, (_event, filename) => {\n if (!filename || (!filename.endsWith(\".mdx\") && !filename.endsWith(\".md\"))) return;\n if (hmrTimeout) clearTimeout(hmrTimeout);\n hmrTimeout = setTimeout(() => {\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n });\n\n // Rebuild the client bundle when component sources change (e.g. the\n // @docubook/markdown workspace package) so interactive islands pick up\n // edits, then reload connected clients. MDX edits are handled by the watcher\n // above — no rebuild needed, content recompiles per request.\n let srcHmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const mdxContentSrc = resolveMdxContentSrc();\n const srcWatcher = mdxContentSrc\n ? watch(mdxContentSrc, { recursive: true }, (_event, filename) => {\n if (!filename || !/\\.(ts|tsx|js|jsx|css)$/.test(filename)) return;\n if (srcHmrTimeout) clearTimeout(srcHmrTimeout);\n srcHmrTimeout = setTimeout(async () => {\n try {\n state.assetManifest = await buildClientBundle();\n logger.warn(\"[hmr] client bundle rebuilt (component change)\");\n } catch (e) {\n logger.warn(\n `[hmr] bundle rebuild failed: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n })\n : null;\n\n process.on(\"SIGINT\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n\n let handle: ServerHandle | null = null;\n\n const fetchHandler = async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n // Generated links carry `.html` (matching the static build output);\n // route them to the same handler as their extensionless form.\n const pathname = stripDocsHtmlSuffix(url.pathname);\n const startTime = performance.now();\n\n if (builder) {\n const pluginResponse = await builder.runHandleRequest(req, {\n port: handle?.port ?? PORT,\n hostname: handle?.hostname ?? \"localhost\",\n assetManifest: state.assetManifest,\n });\n if (pluginResponse) {\n const securedResponse = wrapPluginResponse(pluginResponse, true);\n logger.request(\n req.method,\n pathname,\n securedResponse.status,\n Math.round(performance.now() - startTime)\n );\n return securedResponse;\n }\n }\n\n try {\n if (pathname === \"/__hmr\") {\n const stream = new ReadableStream({\n start(controller) {\n hmrClients.add(controller);\n controller.enqueue(new TextEncoder().encode(\"data: connected\\n\\n\"));\n },\n cancel(controller) {\n hmrClients.delete(controller);\n },\n });\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n }\n\n if (pathname.startsWith(\"/assets/\") || /\\.\\w+$/.test(pathname)) {\n const staticRes = serveStatic(pathname);\n if (staticRes) return staticRes;\n }\n\n let response: Response;\n\n // Manual route matching — same routes Bun.FileSystemRouter derives\n // from `.docu/pages/` (\"/\", \"/docs/[[...slug]]\", \"/404\").\n if (pathname === \"/\") {\n response = await handleIndex(state);\n } else if (pathname === \"/docs\" || pathname === \"/docs/\") {\n response = await handleDocsIndex(state);\n } else if (pathname.startsWith(\"/docs/\")) {\n const slug = pathname.slice(\"/docs/\".length).split(\"/\").filter(Boolean);\n response =\n slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);\n } else {\n response = await handleNotFound(state);\n }\n\n logger.request(\n req.method,\n pathname,\n response.status,\n Math.round(performance.now() - startTime)\n );\n return response;\n } catch (err) {\n captureException(err, { method: req.method, pathname });\n logger.request(req.method, pathname, 500, Math.round(performance.now() - startTime));\n return serverErrorResponse(err);\n }\n };\n\n handle = await adapter.serve(fetchHandler, { port: PORT, idleTimeout: 255 });\n\n logger.ready(handle.port, true);\n return handle;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.impl-DPBnKdIh.js","names":[],"sources":["../node/server-routes.ts","../node/server.impl.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { readFileSync, statSync } from \"node:fs\";\nimport React, { type ReactNode } from \"react\";\nimport { renderToString } from \"react-dom/server\";\nimport { compileMdx, frontmatterField } from \"./mdx\";\nimport { DEFAULT_FAVICON, getContentType } from \"./utils\";\nimport { DOCS_DIR, DIST_DIR, PAGES_DIR, PROJECT_ROOT } from \"./paths\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport type { PageContext, PageType } from \"./plugin\";\nimport type { AssetEntry, AssetManifest, DocuConfig, TocItem } from \"./types\";\nimport DocsPage from \"../pages/docs/[[...slug]]\";\nimport NotFoundPage from \"../pages/404\";\nimport IndexPage from \"../pages/index\";\nimport { DocsLayout } from \"../components/DocsLayout\";\nimport { generateNonce, isPathSafe, isSlugSafe, htmlResponse, SECURITY_HEADERS } from \"./security\";\nimport { htmlShell as createHtmlShell, hmrScript, errorHtml } from \"./html.shared\";\n\nexport interface ServerState {\n docuConfig: DocuConfig;\n assetManifest: AssetManifest;\n inlineThemeCss?: string;\n builder: BuildPluginBuilder | null;\n}\n\nasync function createHtmlResponse(\n title: string,\n description: string,\n body: string,\n status: number,\n state: ServerState,\n pageType: PageType,\n page: Omit<PageContext, \"pageType\" | \"assets\" | \"config\">,\n depth = 0\n): Promise<Response> {\n const nonce = generateNonce();\n const favicon = state.docuConfig.meta?.favicon || DEFAULT_FAVICON;\n const assets: AssetEntry = state.assetManifest[pageType];\n const context: PageContext = {\n ...page,\n pageType,\n assets,\n config: state.docuConfig,\n };\n let html = createHtmlShell({\n title,\n description,\n body,\n favicon,\n css: assets.css,\n js: assets.js,\n nonce,\n extraScripts: hmrScript(nonce),\n themeCss: state.inlineThemeCss,\n headExtra: state.builder?.collectHead(context),\n bodyExtra: state.builder?.collectBody(context),\n depth,\n // 404 pages can be requested at arbitrary depths (e.g. a noLink section\n // path typed in the address bar) — relative asset paths would resolve\n // against the wrong directory and break CSS/JS.\n absoluteAssets: status === 404,\n });\n if (state.builder) html = await state.builder.runTransformHtmlChain(html, context);\n /** Dev-only: serves compiledSource → MDXRemote eval path (no CSP in production). */\n return htmlResponse(html, nonce, status, true);\n}\n\n// Dev-only memo: compileMdx per request is ~70ms; repeat navigations with\n// an unchanged file (same mtime) reuse the compiled result. The dev server\n// has no other per-page cache — without this every navigation re-parses MDX.\n// ponytail: unbounded Map is fine — docs sites have a handful of pages; cap\n// with an LRU only if a project exceeds thousands of routes.\nconst docsCache = new Map<\n string,\n { mtimeMs: number; doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>> }\n>();\n\nasync function getDocsForSlug(\n slug: string,\n state: ServerState\n): Promise<{\n content: ReactNode;\n compiledSource: string;\n frontmatter: Record<string, unknown>;\n tocs: TocItem[];\n filePath: string;\n resolvedContent: string;\n} | null> {\n if (!isSlugSafe(slug, DOCS_DIR)) return null;\n\n const paths = [\n resolve(DOCS_DIR, slug, \"index.mdx\"),\n resolve(DOCS_DIR, `${slug}.mdx`),\n resolve(DOCS_DIR, slug, \"index.md\"),\n resolve(DOCS_DIR, `${slug}.md`),\n ];\n\n const resolvedDocsDir = resolve(DOCS_DIR);\n let filePath: string | null = null;\n let raw: string | null = null;\n for (const p of paths) {\n const resolvedCandidate = resolve(p);\n if (\n resolvedCandidate !== resolvedDocsDir &&\n !resolvedCandidate.startsWith(resolvedDocsDir + \"/\")\n ) {\n continue;\n }\n try {\n raw = await readFile(resolvedCandidate, \"utf-8\");\n filePath = resolvedCandidate;\n break;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n if (!filePath || !raw) return null;\n\n const relPath = filePath.replace(PROJECT_ROOT + \"/\", \"\");\n\n const mtimeMs = (await stat(filePath)).mtimeMs;\n const cached = docsCache.get(relPath);\n if (cached && cached.mtimeMs === mtimeMs) {\n return cached.doc;\n }\n\n let content = raw;\n if (state.builder) {\n const transformed = await state.builder.runOnLoad(relPath, content);\n if (transformed?.contents) {\n content = transformed.contents;\n }\n }\n\n const remarkPlugins = state.builder?.collectRemarkPlugins();\n const rehypePlugins = state.builder?.collectRehypePlugins();\n const result = await compileMdx(content, relPath, undefined, remarkPlugins, rehypePlugins);\n\n let frontmatter = result.frontmatter as Record<string, unknown>;\n if (state.builder) {\n frontmatter = await state.builder.runTransformFrontmatterChain(frontmatter, {\n slug,\n filePath: relPath,\n content,\n });\n }\n\n const doc = {\n content: result.content,\n compiledSource: result.compiledSource,\n frontmatter,\n tocs: result.tocs,\n filePath: relPath,\n resolvedContent: content,\n };\n docsCache.set(relPath, { mtimeMs, doc });\n return doc;\n}\n\nasync function renderDocsServerPage(\n doc: NonNullable<Awaited<ReturnType<typeof getDocsForSlug>>>,\n slug: string[],\n pathname: string,\n state: ServerState\n): Promise<Response> {\n const title = frontmatterField(doc.frontmatter, \"title\") || slug.join(\"/\") || \"Docs\";\n const description = frontmatterField(doc.frontmatter, \"description\");\n\n const page = React.createElement(\n DocsLayout,\n { repoUrl: state.docuConfig.repo?.url, pathname },\n React.createElement(DocsPage, {\n slug,\n title,\n description,\n date: frontmatterField(doc.frontmatter, \"date\") || undefined,\n // Same root-relative SSR as build — see build.ts.\n content: renderToString(doc.content),\n tocs: doc.tocs,\n filePath: doc.filePath,\n repoUrl: state.docuConfig.repo?.url,\n compiledSource: doc.compiledSource,\n })\n );\n\n const body = renderToString(page);\n\n // Match build.ts depth calculation: slug.split(\"/\").length, fallback to 1 for empty\n const depth = slug.length || 1;\n\n return createHtmlResponse(\n title,\n description,\n body,\n 200,\n state,\n \"docs\",\n {\n slug: slug.join(\"/\"),\n filePath: doc.filePath,\n frontmatter: doc.frontmatter,\n content: doc.resolvedContent,\n },\n depth\n );\n}\n\nasync function renderPage(\n Component: React.ComponentType<Record<string, unknown>>,\n title: string,\n description: string,\n status: number,\n state: ServerState,\n props: Record<string, unknown> = {},\n depth = 0\n): Promise<Response> {\n const body = renderToString(React.createElement(Component, props));\n return createHtmlResponse(\n title,\n description,\n body,\n status,\n state,\n \"notFound\",\n {\n slug: \"404\",\n filePath: resolve(PAGES_DIR, \"404.tsx\"),\n frontmatter: {},\n },\n depth\n );\n}\n\nexport async function handleDocsIndex(state: ServerState): Promise<Response> {\n const doc = await getDocsForSlug(\"\", state);\n if (!doc) return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, 1);\n return renderDocsServerPage(doc, [], \"/docs\", state);\n}\n\nexport async function handleDocsRoute(slug: string[], state: ServerState): Promise<Response> {\n const path = slug.join(\"/\");\n const doc = await getDocsForSlug(path, state);\n if (!doc)\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, slug.length || 1);\n return renderDocsServerPage(doc, slug, `/docs/${path}`, state);\n}\n\nexport async function handleIndex(state: ServerState): Promise<Response> {\n const page = React.createElement(IndexPage);\n const body = renderToString(page);\n return createHtmlResponse(\n state.docuConfig.meta?.title || \"DocuBook\",\n state.docuConfig.meta?.description || \"\",\n body,\n 200,\n state,\n \"home\",\n {\n slug: \"\",\n filePath: resolve(PAGES_DIR, \"index.tsx\"),\n frontmatter: { ...state.docuConfig.meta } as Record<string, unknown>,\n }\n );\n}\n\nexport async function handleNotFound(state: ServerState, depth = 0): Promise<Response> {\n return renderPage(NotFoundPage, \"404 - Not Found\", \"\", 404, state, {}, depth);\n}\n\nexport function serveStatic(pathname: string): Response | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (!isPathSafe(pathname, DIST_DIR)) return null;\n const assetPath = resolve(DIST_DIR, decoded.slice(1));\n try {\n const s = statSync(assetPath);\n if (s.isFile()) {\n return new Response(readFileSync(assetPath), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n\n if (decoded.startsWith(\"/docs/assets/\")) {\n const docsAssetsDir = resolve(DOCS_DIR, \"assets\");\n const requestedRelative = decoded.slice(\"/docs/assets/\".length);\n const docsAsset = resolve(docsAssetsDir, requestedRelative);\n const docsAssetsDirWithSep = docsAssetsDir.endsWith(\"/\") ? docsAssetsDir : docsAssetsDir + \"/\";\n if (docsAsset !== docsAssetsDir && !docsAsset.startsWith(docsAssetsDirWithSep)) return null;\n try {\n const s = statSync(docsAsset);\n if (s.isFile()) {\n return new Response(readFileSync(docsAsset), {\n headers: { \"Content-Type\": getContentType(pathname) },\n });\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n return null;\n}\n\nexport function serverErrorResponse(error: unknown): Response {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n const st =\n process.env.NODE_ENV !== \"production\" && error instanceof Error ? error.stack : undefined;\n return new Response(errorHtml(msg, st), {\n status: 500,\n headers: {\n \"Content-Type\": \"text/html\",\n ...SECURITY_HEADERS,\n \"Content-Security-Policy\": \"default-src 'none'; style-src 'unsafe-inline'\",\n },\n });\n}\n","/**\n * Runtime-neutral dev server — mirror of `server.ts` (Bun-only, protected)\n * driven by a `RuntimeAdapter` (./runtime) instead of `Bun.serve`,\n * with manual route matching instead of `Bun.FileSystemRouter`. The page set\n * is static (`/`, `/docs/[[...slug]]`, `/404`), so a router is unnecessary.\n */\n\nimport { watch, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport type { RuntimeAdapter, ServerHandle } from \"./runtime\";\nimport { DOCS_DIR, loadDocuConfig } from \"./paths\";\nimport { loadPlugins } from \"./plugin-loader\";\nimport { BuildPluginBuilder } from \"./plugin-builder\";\nimport { buildClientBundle, computeInlineThemeCss } from \"./hydrate.node\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { logger } from \"./logger\";\nimport { initSentry, captureException } from \"./sentry\";\nimport {\n serveStatic,\n handleDocsIndex,\n handleDocsRoute,\n handleIndex,\n handleNotFound,\n serverErrorResponse,\n type ServerState,\n} from \"./server-routes\";\nimport { wrapPluginResponse } from \"./security\";\nimport { stripDocsHtmlSuffix } from \"./utils\";\n\nexport async function runServer(adapter: RuntimeAdapter): Promise<ServerHandle> {\n const docuConfig = loadDocuConfig();\n\n const parsedPort = parseInt(process.env.PORT ?? \"3000\", 10);\n const PORT =\n Number.isInteger(parsedPort) && parsedPort > 0 && parsedPort <= 65535 ? parsedPort : 3000;\n\n logger.buildStart();\n\n await initSentry();\n\n logger.bundleStart();\n let t = performance.now();\n const assetManifest = await buildClientBundle();\n logger.bundleDone(Math.round(performance.now() - t));\n\n const inlineThemeCss = computeInlineThemeCss();\n\n logger.indexStart();\n t = performance.now();\n const records = await generateSearchIndex();\n logger.indexDone(records, Math.round(performance.now() - t));\n\n logger.routes();\n\n // Plugin setup — all hooks active (onLoad, remark/rehype, frontmatter, head/body, html transform, handleRequest)\n const pluginsConfig = docuConfig.plugins ?? [];\n const builder = pluginsConfig.length > 0 ? new BuildPluginBuilder(docuConfig) : null;\n\n if (builder) {\n const plugins = await loadPlugins(pluginsConfig);\n\n for (const plugin of plugins) {\n await plugin.setup(builder);\n }\n await builder.runOnStart();\n }\n\n const state: ServerState = {\n docuConfig,\n assetManifest,\n inlineThemeCss,\n builder,\n };\n\n const hmrClients = new Set<ReadableStreamDefaultController>();\n\n const require = createRequire(import.meta.url);\n /** Locate the installed @docubook/markdown source dir (workspace link), if any. */\n function resolveMdxContentSrc(): string | null {\n try {\n const pkgDir = dirname(require.resolve(\"@docubook/markdown/package.json\"));\n const src = join(pkgDir, \"src\");\n return existsSync(src) ? src : null;\n } catch {\n return null;\n }\n }\n\n let hmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const watcher = watch(DOCS_DIR, { recursive: true }, (_event, filename) => {\n if (!filename || (!filename.endsWith(\".mdx\") && !filename.endsWith(\".md\"))) return;\n if (hmrTimeout) clearTimeout(hmrTimeout);\n hmrTimeout = setTimeout(() => {\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n });\n\n // Rebuild the client bundle when component sources change (e.g. the\n // @docubook/markdown workspace package) so interactive islands pick up\n // edits, then reload connected clients. MDX edits are handled by the watcher\n // above — no rebuild needed, content recompiles per request.\n let srcHmrTimeout: ReturnType<typeof setTimeout> | null = null;\n const mdxContentSrc = resolveMdxContentSrc();\n const srcWatcher = mdxContentSrc\n ? watch(mdxContentSrc, { recursive: true }, (_event, filename) => {\n if (!filename || !/\\.(ts|tsx|js|jsx|css)$/.test(filename)) return;\n if (srcHmrTimeout) clearTimeout(srcHmrTimeout);\n srcHmrTimeout = setTimeout(async () => {\n try {\n state.assetManifest = await buildClientBundle();\n logger.warn(\"[hmr] client bundle rebuilt (component change)\");\n } catch (e) {\n logger.warn(\n `[hmr] bundle rebuild failed: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n for (const client of [...hmrClients]) {\n try {\n client.enqueue(new TextEncoder().encode(\"data: reload\\n\\n\"));\n } catch {\n hmrClients.delete(client);\n }\n }\n }, 300);\n })\n : null;\n\n process.on(\"SIGINT\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n process.on(\"SIGTERM\", () => {\n watcher.close();\n srcWatcher?.close();\n process.exit(0);\n });\n\n let handle: ServerHandle | null = null;\n\n const fetchHandler = async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n // Generated links carry `.html` (matching the static build output);\n // route them to the same handler as their extensionless form.\n const pathname = stripDocsHtmlSuffix(url.pathname);\n const startTime = performance.now();\n\n if (builder) {\n const pluginResponse = await builder.runHandleRequest(req, {\n port: handle?.port ?? PORT,\n hostname: handle?.hostname ?? \"localhost\",\n assetManifest: state.assetManifest,\n });\n if (pluginResponse) {\n const securedResponse = wrapPluginResponse(pluginResponse, true);\n logger.request(\n req.method,\n pathname,\n securedResponse.status,\n Math.round(performance.now() - startTime)\n );\n return securedResponse;\n }\n }\n\n try {\n if (pathname === \"/__hmr\") {\n const stream = new ReadableStream({\n start(controller) {\n hmrClients.add(controller);\n controller.enqueue(new TextEncoder().encode(\"data: connected\\n\\n\"));\n },\n cancel(controller) {\n hmrClients.delete(controller);\n },\n });\n return new Response(stream, {\n headers: {\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n });\n }\n\n if (pathname.startsWith(\"/assets/\") || /\\.\\w+$/.test(pathname)) {\n const staticRes = serveStatic(pathname);\n if (staticRes) return staticRes;\n }\n\n let response: Response;\n\n // Manual route matching — same routes Bun.FileSystemRouter derives\n // from `.docu/pages/` (\"/\", \"/docs/[[...slug]]\", \"/404\").\n if (pathname === \"/\") {\n response = await handleIndex(state);\n } else if (pathname === \"/docs\" || pathname === \"/docs/\") {\n response = await handleDocsIndex(state);\n } else if (pathname.startsWith(\"/docs/\")) {\n const slug = pathname.slice(\"/docs/\".length).split(\"/\").filter(Boolean);\n response =\n slug.length === 0 ? await handleDocsIndex(state) : await handleDocsRoute(slug, state);\n } else {\n response = await handleNotFound(state);\n }\n\n logger.request(\n req.method,\n pathname,\n response.status,\n Math.round(performance.now() - startTime)\n );\n return response;\n } catch (err) {\n captureException(err, { method: req.method, pathname });\n logger.request(req.method, pathname, 500, Math.round(performance.now() - startTime));\n return serverErrorResponse(err);\n }\n };\n\n handle = await adapter.serve(fetchHandler, { port: PORT, idleTimeout: 255 });\n\n logger.ready(handle.port, true);\n return handle;\n}\n"],"mappings":"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"}
|
package/.docu/lib/server.node.js
CHANGED
|
@@ -88,71 +88,149 @@ export const DOCKERIGNORE = `node_modules
|
|
|
88
88
|
*.log
|
|
89
89
|
`;
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
COPY . .
|
|
95
|
-
RUN flame build --bun
|
|
91
|
+
/** Final stage shared by every generated Dockerfile: nginx serves the flat
|
|
92
|
+
* static output produced by the builder stage. */
|
|
93
|
+
export const DOCKERFILE_MARKER = "# Generated by @docubook/flame deploy --docker";
|
|
96
94
|
|
|
97
|
-
FROM nginx:alpine
|
|
95
|
+
const NGINX_STAGE = `FROM nginx:alpine
|
|
98
96
|
COPY --from=builder /app/.docu/dist /usr/share/nginx/html
|
|
99
97
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
100
98
|
EXPOSE 80
|
|
101
99
|
CMD ["nginx", "-g", "daemon off;"]
|
|
102
100
|
`;
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
/** Bun projects build inside the published builder image — the CLI is already
|
|
103
|
+
* installed globally there, so no project dependency install step is needed. */
|
|
104
|
+
export const DOCKERFILE_BUN = `${DOCKERFILE_MARKER}
|
|
105
|
+
FROM ghcr.io/docubook/flame:${FLAME_VERSION} AS builder
|
|
106
|
+
ENV NODE_ENV=production
|
|
107
|
+
WORKDIR /app
|
|
108
|
+
COPY . .
|
|
109
|
+
RUN flame build --bun
|
|
110
|
+
|
|
111
|
+
${NGINX_STAGE}`;
|
|
112
|
+
|
|
113
|
+
export type PkgManagerConfig = {
|
|
105
114
|
baseImage: string;
|
|
106
115
|
lockFile: string;
|
|
107
116
|
installCmd: string;
|
|
108
117
|
runCmd: string;
|
|
109
118
|
cache: string;
|
|
110
119
|
setupAction: string;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
installCmd: "yarn install --frozen-lockfile",
|
|
142
|
-
runCmd: "yarn",
|
|
143
|
-
cache: "yarn",
|
|
144
|
-
setupAction: "node",
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// default: npm
|
|
148
|
-
return {
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Per-package-manager build config. `installCmd` is the locked install and
|
|
123
|
+
* assumes `lockFile` exists — see `LOCKLESS_INSTALL` for projects without one. */
|
|
124
|
+
const PM_CONFIG: Record<string, PkgManagerConfig> = {
|
|
125
|
+
bun: {
|
|
126
|
+
baseImage: "oven/bun:1-debian",
|
|
127
|
+
lockFile: "bun.lock",
|
|
128
|
+
installCmd: "bun install --frozen-lockfile",
|
|
129
|
+
runCmd: "bun",
|
|
130
|
+
cache: "",
|
|
131
|
+
setupAction: "bun",
|
|
132
|
+
},
|
|
133
|
+
pnpm: {
|
|
134
|
+
baseImage: "node:22-alpine",
|
|
135
|
+
lockFile: "pnpm-lock.yaml",
|
|
136
|
+
installCmd: "corepack enable && pnpm install --frozen-lockfile",
|
|
137
|
+
runCmd: "pnpm",
|
|
138
|
+
cache: "pnpm",
|
|
139
|
+
setupAction: "pnpm",
|
|
140
|
+
},
|
|
141
|
+
yarn: {
|
|
142
|
+
baseImage: "node:22-alpine",
|
|
143
|
+
lockFile: "yarn.lock",
|
|
144
|
+
installCmd: "yarn install --frozen-lockfile",
|
|
145
|
+
runCmd: "yarn",
|
|
146
|
+
cache: "yarn",
|
|
147
|
+
setupAction: "node",
|
|
148
|
+
},
|
|
149
|
+
npm: {
|
|
149
150
|
baseImage: "node:22-alpine",
|
|
150
151
|
lockFile: "package-lock.json",
|
|
151
152
|
installCmd: "npm ci",
|
|
152
153
|
runCmd: "npm",
|
|
153
154
|
cache: "npm",
|
|
154
155
|
setupAction: "node",
|
|
155
|
-
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** Installs for projects with no lockfile, where `npm ci` and
|
|
160
|
+
* `--frozen-lockfile` cannot run. */
|
|
161
|
+
const LOCKLESS_INSTALL: Record<string, string> = {
|
|
162
|
+
bun: "bun install",
|
|
163
|
+
pnpm: "corepack enable && pnpm install --prod=false",
|
|
164
|
+
yarn: "yarn install --production=false",
|
|
165
|
+
npm: "npm install --include=dev",
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** Build installs must include tools declared in devDependencies even though
|
|
169
|
+
* the builder stage sets NODE_ENV=production for the eventual site build. */
|
|
170
|
+
function getInstallCommand(pm: PkgManagerConfig, hasLockFile: boolean): string {
|
|
171
|
+
if (pm.runCmd === "npm") {
|
|
172
|
+
return hasLockFile ? "npm ci --include=dev" : LOCKLESS_INSTALL.npm;
|
|
173
|
+
}
|
|
174
|
+
if (pm.runCmd === "pnpm" && hasLockFile) {
|
|
175
|
+
return `${pm.installCmd} --prod=false`;
|
|
176
|
+
}
|
|
177
|
+
if (pm.runCmd === "yarn" && hasLockFile) {
|
|
178
|
+
return `${pm.installCmd} --production=false`;
|
|
179
|
+
}
|
|
180
|
+
return hasLockFile ? pm.installCmd : LOCKLESS_INSTALL[pm.runCmd];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Detect the project's package manager. Lockfiles win — they describe what the
|
|
185
|
+
* project actually committed. `userAgent` (the invoking tool's
|
|
186
|
+
* `npm_config_user_agent`) is only consulted when no lockfile exists, so a
|
|
187
|
+
* lockless project still installs with the package manager the user is running.
|
|
188
|
+
* pnpm/yarn UA strings also contain "npm/?" — match specific tools first.
|
|
189
|
+
*/
|
|
190
|
+
export function detectPkgManager(dir: string, userAgent = ""): PkgManagerConfig {
|
|
191
|
+
const bunLockFile = existsSync(join(dir, "bun.lock"))
|
|
192
|
+
? "bun.lock"
|
|
193
|
+
: existsSync(join(dir, "bun.lockb"))
|
|
194
|
+
? "bun.lockb"
|
|
195
|
+
: null;
|
|
196
|
+
if (bunLockFile) return { ...PM_CONFIG.bun, lockFile: bunLockFile };
|
|
197
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) return PM_CONFIG.pnpm;
|
|
198
|
+
if (existsSync(join(dir, "yarn.lock"))) return PM_CONFIG.yarn;
|
|
199
|
+
if (existsSync(join(dir, "package-lock.json"))) return PM_CONFIG.npm;
|
|
200
|
+
|
|
201
|
+
if (/bun\//.test(userAgent)) return PM_CONFIG.bun;
|
|
202
|
+
if (/pnpm\//.test(userAgent)) return PM_CONFIG.pnpm;
|
|
203
|
+
if (/yarn\//.test(userAgent)) return PM_CONFIG.yarn;
|
|
204
|
+
return PM_CONFIG.npm;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Dockerfile for the project's package manager. Unlike the Bun builder image,
|
|
209
|
+
* node-based images ship no project dependencies — without an install step the
|
|
210
|
+
* build fails resolving imports from node_modules, so the locked dependencies
|
|
211
|
+
* are installed before `flame` is invoked through the project's build script.
|
|
212
|
+
* The lockfile and its frozen install are used only when the lockfile exists;
|
|
213
|
+
* otherwise the install is derived from the invoking package manager.
|
|
214
|
+
*/
|
|
215
|
+
export function generateDockerfile(
|
|
216
|
+
dir: string = PROJECT_ROOT,
|
|
217
|
+
userAgent: string = process.env.npm_config_user_agent || ""
|
|
218
|
+
): string {
|
|
219
|
+
const pm = detectPkgManager(dir, userAgent);
|
|
220
|
+
if (pm.runCmd === "bun") return DOCKERFILE_BUN;
|
|
221
|
+
|
|
222
|
+
const hasLockFile = existsSync(join(dir, pm.lockFile));
|
|
223
|
+
|
|
224
|
+
return `${DOCKERFILE_MARKER}
|
|
225
|
+
FROM ${pm.baseImage} AS builder
|
|
226
|
+
ENV NODE_ENV=production
|
|
227
|
+
WORKDIR /app
|
|
228
|
+
COPY package.json${hasLockFile ? ` ${pm.lockFile}` : ""} ./
|
|
229
|
+
RUN ${getInstallCommand(pm, hasLockFile)}
|
|
230
|
+
COPY . .
|
|
231
|
+
RUN ${pm.runCmd} run build
|
|
232
|
+
|
|
233
|
+
${NGINX_STAGE}`;
|
|
156
234
|
}
|
|
157
235
|
|
|
158
236
|
export const HEADERS_FILE = `/*
|
|
@@ -207,10 +285,18 @@ async function runBuild() {
|
|
|
207
285
|
|
|
208
286
|
async function writeDockerFiles() {
|
|
209
287
|
const dockerDir = PROJECT_ROOT;
|
|
288
|
+
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
210
289
|
|
|
211
|
-
if (!existsSync(
|
|
212
|
-
await writeFile(
|
|
290
|
+
if (!existsSync(dockerfilePath)) {
|
|
291
|
+
await writeFile(dockerfilePath, generateDockerfile(dockerDir));
|
|
213
292
|
log.created("📄 Created Dockerfile");
|
|
293
|
+
} else if (readFileSync(dockerfilePath, "utf-8").startsWith(`${DOCKERFILE_MARKER}\n`)) {
|
|
294
|
+
await writeFile(dockerfilePath, generateDockerfile(dockerDir));
|
|
295
|
+
log.created("📄 Updated generated Dockerfile");
|
|
296
|
+
} else {
|
|
297
|
+
log.info(
|
|
298
|
+
"⚠️ Dockerfile already exists; skipped it. Delete it and rerun flame deploy --docker to regenerate."
|
|
299
|
+
);
|
|
214
300
|
}
|
|
215
301
|
|
|
216
302
|
if (!existsSync(join(dockerDir, "nginx.conf"))) {
|
|
@@ -225,7 +311,10 @@ async function writeDockerFiles() {
|
|
|
225
311
|
}
|
|
226
312
|
|
|
227
313
|
function generateWorkflowYml(): string {
|
|
228
|
-
const pm = detectPkgManager(PROJECT_ROOT);
|
|
314
|
+
const pm = detectPkgManager(PROJECT_ROOT, process.env.npm_config_user_agent || "");
|
|
315
|
+
const hasLockFile = existsSync(join(PROJECT_ROOT, pm.lockFile));
|
|
316
|
+
const installCmd = getInstallCommand(pm, hasLockFile);
|
|
317
|
+
const cacheLine = hasLockFile && pm.cache ? ` cache: ${pm.cache}` : "";
|
|
229
318
|
|
|
230
319
|
const setupSteps: string[] = [
|
|
231
320
|
" - uses: actions/checkout@v7",
|
|
@@ -248,7 +337,7 @@ function generateWorkflowYml(): string {
|
|
|
248
337
|
" - uses: actions/setup-node@v6",
|
|
249
338
|
" with:",
|
|
250
339
|
" node-version: 22",
|
|
251
|
-
|
|
340
|
+
...(cacheLine ? [cacheLine] : []),
|
|
252
341
|
""
|
|
253
342
|
);
|
|
254
343
|
} else {
|
|
@@ -257,7 +346,7 @@ function generateWorkflowYml(): string {
|
|
|
257
346
|
" - uses: actions/setup-node@v6",
|
|
258
347
|
" with:",
|
|
259
348
|
" node-version: 22",
|
|
260
|
-
|
|
349
|
+
...(cacheLine ? [cacheLine] : []),
|
|
261
350
|
""
|
|
262
351
|
);
|
|
263
352
|
}
|
|
@@ -284,7 +373,7 @@ function generateWorkflowYml(): string {
|
|
|
284
373
|
" runs-on: ubuntu-latest",
|
|
285
374
|
" steps:",
|
|
286
375
|
...setupSteps,
|
|
287
|
-
` - run: ${
|
|
376
|
+
` - run: ${installCmd}`,
|
|
288
377
|
"",
|
|
289
378
|
` - run: ${pm.runCmd} run build`,
|
|
290
379
|
"",
|
package/.docu/node/deploy.ts
CHANGED
|
@@ -8,12 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { mkdir } from "node:fs/promises";
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { DIST_DIR, PROJECT_ROOT } from "./paths";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
DOCKERFILE_BUN,
|
|
16
|
+
DOCKERFILE_MARKER,
|
|
17
|
+
generateDockerfile,
|
|
18
|
+
HEADERS_FILE,
|
|
19
|
+
NGINX_CONF,
|
|
20
|
+
DOCKERIGNORE,
|
|
21
|
+
} from "./deploy.shared";
|
|
15
22
|
|
|
16
|
-
export { HEADERS_FILE, NGINX_CONF, DOCKERIGNORE };
|
|
23
|
+
export { HEADERS_FILE, NGINX_CONF, DOCKERIGNORE, DOCKERFILE_BUN };
|
|
17
24
|
|
|
18
25
|
const WORKFLOW_DIR = join(PROJECT_ROOT, ".github/workflows");
|
|
19
26
|
const WORKFLOW_FILE = join(WORKFLOW_DIR, "deploy.yml");
|
|
@@ -45,14 +52,20 @@ async function runBuild() {
|
|
|
45
52
|
}
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
export const DOCKERFILE_BUN = DOCKERFILE;
|
|
49
|
-
|
|
50
55
|
async function writeDockerFiles() {
|
|
51
56
|
const dockerDir = PROJECT_ROOT;
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
59
|
+
if (!existsSync(dockerfilePath)) {
|
|
60
|
+
await Bun.write(dockerfilePath, generateDockerfile(dockerDir));
|
|
55
61
|
log.created("📄 Created Dockerfile");
|
|
62
|
+
} else if (readFileSync(dockerfilePath, "utf-8").startsWith(`${DOCKERFILE_MARKER}\n`)) {
|
|
63
|
+
await Bun.write(dockerfilePath, generateDockerfile(dockerDir));
|
|
64
|
+
log.created("📄 Updated generated Dockerfile");
|
|
65
|
+
} else {
|
|
66
|
+
log.info(
|
|
67
|
+
"⚠️ Dockerfile already exists; skipped it. Delete it and rerun flame deploy --docker to regenerate."
|
|
68
|
+
);
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
if (!existsSync(join(dockerDir, "nginx.conf"))) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docubook/flame",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A blazing-fast React + MDX framework powered by Bun, built for modern documentation experiences.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./.docu/node/plugin.ts",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"author-url": "https://wildan.dev",
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@docubook/core": "^2.0.
|
|
50
|
-
"@docubook/markdown": "^2.0.
|
|
51
|
-
"@docubook/themes-colors": "^2.0.
|
|
52
|
-
"@docubook/ui-react": "^2.0.
|
|
49
|
+
"@docubook/core": "^2.0.2",
|
|
50
|
+
"@docubook/markdown": "^2.0.2",
|
|
51
|
+
"@docubook/themes-colors": "^2.0.2",
|
|
52
|
+
"@docubook/ui-react": "^2.0.2",
|
|
53
53
|
"@mdx-js/react": "^3.0.1",
|
|
54
54
|
"@tailwindcss/cli": "4.3.0",
|
|
55
55
|
"@tailwindcss/typography": "0.5.16",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.shared-CvgObu6_.js","names":[],"sources":["../node/deploy.shared.ts"],"sourcesContent":["/**\n * Runtime-neutral deploy — 3 modes:\n * 1. `flame deploy` → build + generate GitHub Actions workflow\n * 2. `flame deploy --docker` → build + generate Docker deployment files\n * 3. `flame deploy --docker --silent` → same as #2, minimal output\n *\n * Mirror of deploy.ts (Bun-only, protected) for Node.js and Deno.\n * Runs the neutral build in-process, then prepares .docu/dist.\n */\n\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { DIST_DIR, PROJECT_ROOT, FRAMEWORK_ROOT } from \"./paths\";\n\nconst FLAME_VERSION = JSON.parse(\n readFileSync(resolve(FRAMEWORK_ROOT, \"package.json\"), \"utf-8\")\n).version;\n\nconst WORKFLOW_DIR = join(PROJECT_ROOT, \".github/workflows\");\nconst WORKFLOW_FILE = join(WORKFLOW_DIR, \"deploy.yml\");\n\nexport const NGINX_CONF = `server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n error_page 404 /404.html;\n\n gzip on;\n gzip_types text/html text/css application/javascript image/svg+xml;\n\n # Security headers (HSTS effective when HTTPS is terminated upstream)\n add_header X-Frame-Options \"DENY\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains\" always;\n add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\" always;\n\n # Build metadata and search content use stable URLs, so clients must revalidate them.\n location = /assets/manifest.json {\n expires -1;\n }\n\n location = /assets/search-index.json {\n expires -1;\n }\n\n # Generated JS, CSS, and chunks include content hashes in their filenames.\n location /assets/ {\n expires 1y;\n add_header Cache-Control \"public, immutable\";\n add_header X-Frame-Options \"DENY\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains\" always;\n add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\" always;\n }\n\n location /docs/assets/ {\n expires 7d;\n add_header Cache-Control \"public\";\n add_header X-Frame-Options \"DENY\" always;\n add_header X-Content-Type-Options \"nosniff\" always;\n add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains\" always;\n add_header Referrer-Policy \"strict-origin-when-cross-origin\" always;\n add_header Permissions-Policy \"camera=(), microphone=(), geolocation=()\" always;\n add_header Content-Security-Policy \"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\" always;\n }\n\n location = /404.html { }\n\n location / {\n try_files $uri $uri.html $uri/ =404;\n }\n}\n`;\n\nexport const DOCKERIGNORE = `node_modules\n*.DS_Store\n.docu/dist\n.docu/lib\n.env\n.env.*\n.npmrc\n*.log\n`;\n\nexport const DOCKERFILE = `FROM ghcr.io/docubook/flame:${FLAME_VERSION} AS builder\nENV NODE_ENV=production\nWORKDIR /app\nCOPY . .\nRUN flame build --bun\n\nFROM nginx:alpine\nCOPY --from=builder /app/.docu/dist /usr/share/nginx/html\nCOPY nginx.conf /etc/nginx/conf.d/default.conf\nEXPOSE 80\nCMD [\"nginx\", \"-g\", \"daemon off;\"]\n`;\n\nexport function detectPkgManager(dir: string): {\n baseImage: string;\n lockFile: string;\n installCmd: string;\n runCmd: string;\n cache: string;\n setupAction: string;\n} {\n const bunLockFile = existsSync(join(dir, \"bun.lock\"))\n ? \"bun.lock\"\n : existsSync(join(dir, \"bun.lockb\"))\n ? \"bun.lockb\"\n : null;\n if (bunLockFile) {\n return {\n baseImage: \"oven/bun:1-debian\",\n lockFile: bunLockFile,\n installCmd: \"bun install --frozen-lockfile\",\n runCmd: \"bun\",\n cache: \"\",\n setupAction: \"bun\",\n };\n }\n if (existsSync(join(dir, \"pnpm-lock.yaml\"))) {\n return {\n baseImage: \"node:22-alpine\",\n lockFile: \"pnpm-lock.yaml\",\n installCmd: \"corepack enable && pnpm install --frozen-lockfile\",\n runCmd: \"pnpm\",\n cache: \"pnpm\",\n setupAction: \"pnpm\",\n };\n }\n if (existsSync(join(dir, \"yarn.lock\"))) {\n return {\n baseImage: \"node:22-alpine\",\n lockFile: \"yarn.lock\",\n installCmd: \"yarn install --frozen-lockfile\",\n runCmd: \"yarn\",\n cache: \"yarn\",\n setupAction: \"node\",\n };\n }\n // default: npm\n return {\n baseImage: \"node:22-alpine\",\n lockFile: \"package-lock.json\",\n installCmd: \"npm ci\",\n runCmd: \"npm\",\n cache: \"npm\",\n setupAction: \"node\",\n };\n}\n\nexport const HEADERS_FILE = `/*\n X-Frame-Options: DENY\n X-Content-Type-Options: nosniff\n Strict-Transport-Security: max-age=63072000; includeSubDomains; preload\n Referrer-Policy: strict-origin-when-cross-origin\n Permissions-Policy: camera=(), microphone=(), geolocation=()\n Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self' data:; connect-src 'self' https:; frame-src https://www.youtube-nocookie.com; frame-ancestors 'none'\n\n/assets/client-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/home-client-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/docs-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/site-*\n Cache-Control: public, max-age=31536000, immutable\n/assets/chunks/*\n Cache-Control: public, max-age=31536000, immutable\n/assets/assets/*\n Cache-Control: public, max-age=31536000, immutable\n/assets/manifest.json\n Cache-Control: no-cache\n/assets/search-index.json\n Cache-Control: no-cache\n`;\n\nconst isDocker = !!process.env.FLAME_DEPLOY_DOCKER;\nconst isSilent = !!process.env.FLAME_DEPLOY_SILENT;\nconst isCi = !!process.env.FLAME_DEPLOY_CI;\n\n/** Logger that no-ops all non-error output in silent mode. */\nconst log = isSilent\n ? { info: () => {}, ok: () => {}, created: () => {}, out: () => {} }\n : {\n info: (m: string) => console.log(m),\n ok: () => console.log(\"\\n✅ Ready to deploy!\"),\n created: (m: string) => console.log(m),\n out: (m: string) => console.log(m),\n };\n\nasync function runBuild() {\n process.env.NODE_ENV = \"production\";\n if (isSilent) {\n process.env.FLAME_BUILD_SILENT = \"1\";\n process.env.LOG_LEVEL = \"error\";\n }\n const { runBuildCli } = await import(\"./build.impl\");\n await runBuildCli();\n}\n\nasync function writeDockerFiles() {\n const dockerDir = PROJECT_ROOT;\n\n if (!existsSync(join(dockerDir, \"Dockerfile\"))) {\n await writeFile(join(dockerDir, \"Dockerfile\"), DOCKERFILE);\n log.created(\"📄 Created Dockerfile\");\n }\n\n if (!existsSync(join(dockerDir, \"nginx.conf\"))) {\n await writeFile(join(dockerDir, \"nginx.conf\"), NGINX_CONF);\n log.created(\"📄 Created nginx.conf\");\n }\n\n if (!existsSync(join(dockerDir, \".dockerignore\"))) {\n await writeFile(join(dockerDir, \".dockerignore\"), DOCKERIGNORE);\n log.created(\"📄 Created .dockerignore\");\n }\n}\n\nfunction generateWorkflowYml(): string {\n const pm = detectPkgManager(PROJECT_ROOT);\n\n const setupSteps: string[] = [\n \" - uses: actions/checkout@v7\",\n \" with:\",\n \" fetch-depth: 0\",\n \"\",\n ];\n\n if (pm.setupAction === \"bun\") {\n setupSteps.push(\n \" - uses: oven-sh/setup-bun@v2\",\n \" with:\",\n \" bun-version: latest\",\n \"\"\n );\n } else if (pm.setupAction === \"pnpm\") {\n setupSteps.push(\n \" - uses: pnpm/action-setup@v6\",\n \"\",\n \" - uses: actions/setup-node@v6\",\n \" with:\",\n \" node-version: 22\",\n ` cache: ${pm.cache}`,\n \"\"\n );\n } else {\n // npm / yarn\n setupSteps.push(\n \" - uses: actions/setup-node@v6\",\n \" with:\",\n \" node-version: 22\",\n ` cache: ${pm.cache}`,\n \"\"\n );\n }\n\n return [\n `name: Deploy to GitHub Pages`,\n \"\",\n \"on:\",\n \" push:\",\n \" branches: [main]\",\n \" workflow_dispatch:\",\n \"\",\n \"permissions:\",\n \" contents: read\",\n \" pages: write\",\n \" id-token: write\",\n \"\",\n \"concurrency:\",\n ' group: \"pages\"',\n \" cancel-in-progress: false\",\n \"\",\n \"jobs:\",\n \" build:\",\n \" runs-on: ubuntu-latest\",\n \" steps:\",\n ...setupSteps,\n ` - run: ${pm.installCmd}`,\n \"\",\n ` - run: ${pm.runCmd} run build`,\n \"\",\n \" - name: Add .nojekyll\",\n \" run: touch .docu/dist/.nojekyll\",\n \"\",\n \" - uses: actions/upload-pages-artifact@v3\",\n \" with:\",\n \" path: .docu/dist\",\n \"\",\n \" deploy:\",\n \" environment:\",\n \" name: github-pages\",\n \" url: ${{ steps.deployment.outputs.page_url }}\",\n \" runs-on: ubuntu-latest\",\n \" needs: build\",\n \" steps:\",\n \" - id: deployment\",\n \" uses: actions/deploy-pages@v4\",\n ].join(\"\\n\");\n}\n\nfunction generateDockerWorkflowYml(): string {\n const imageName = \"ghcr.io/${{ github.repository }}\";\n return [\n `name: Build & Push Docker Image`,\n \"\",\n \"on:\",\n \" push:\",\n \" branches: [main]\",\n \" workflow_dispatch:\",\n \"\",\n \"permissions:\",\n \" contents: read\",\n \" packages: write\",\n \"\",\n \"jobs:\",\n \" build:\",\n \" runs-on: ubuntu-latest\",\n \" steps:\",\n \" - uses: actions/checkout@v4\",\n \" with:\",\n \" fetch-depth: 0\",\n \"\",\n \" - name: Log in to GHCR\",\n \" uses: docker/login-action@v3\",\n \" with:\",\n \" registry: ghcr.io\",\n \" username: ${{ github.actor }}\",\n \" password: ${{ secrets.GITHUB_TOKEN }}\",\n \"\",\n \" - name: Build & push\",\n \" uses: docker/build-push-action@v5\",\n \" with:\",\n \" context: .\",\n ` tags: ${imageName}:latest`,\n \" push: true\",\n ].join(\"\\n\");\n}\n\nasync function writeGhaWorkflow() {\n if (!existsSync(WORKFLOW_FILE)) {\n await mkdir(WORKFLOW_DIR, { recursive: true });\n await writeFile(WORKFLOW_FILE, generateWorkflowYml());\n log.created(\"📄 Created .github/workflows/deploy.yml\");\n }\n}\n\nconst DOCKER_WORKFLOW_FILE = join(WORKFLOW_DIR, \"deploy-docker.yml\");\n\nasync function writeDockerWorkflow() {\n if (!existsSync(DOCKER_WORKFLOW_FILE)) {\n await mkdir(WORKFLOW_DIR, { recursive: true });\n await writeFile(DOCKER_WORKFLOW_FILE, generateDockerWorkflowYml());\n log.created(\"📄 Created .github/workflows/deploy-docker.yml\");\n }\n}\n\nexport async function runDeploy(): Promise<void> {\n log.info(\"📦 Building for production...\\n\");\n await runBuild();\n\n // Common: .nojekyll + _headers\n await writeFile(join(DIST_DIR, \".nojekyll\"), \"\");\n await writeFile(join(DIST_DIR, \"_headers\"), HEADERS_FILE);\n\n if (isDocker) {\n await writeDockerFiles();\n if (isCi) await writeDockerWorkflow();\n } else {\n await writeGhaWorkflow();\n }\n\n log.ok();\n log.out(\" Output: .docu/dist/\");\n if (isDocker) {\n log.out(\" Build locally: docker build -t my-docs . && docker run -p 80:80 my-docs\");\n if (isCi) {\n log.out(\" Push to GitHub — CI will build & push Docker image to GHCR\");\n log.out(\" Then pull latest image on your hosting platform (Coolify, etc.)\");\n } else {\n log.out(\" For Coolify: connect repo, set build pack to Dockerfile\");\n }\n } else {\n log.out(\" Push to GitHub and enable Pages (Settings → Pages → Source: GitHub Actions)\");\n }\n}\n"],"mappings":"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"}
|