@docubook/flame 2.1.1 → 2.1.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-BaIcsV7P.js +2 -0
- package/.docu/lib/{build.impl-oTVkE6D9.js → build.impl-CAMyXJH7.js} +7 -7
- package/.docu/lib/build.impl-CAMyXJH7.js.map +1 -0
- package/.docu/lib/build.node.js +1 -1
- package/.docu/lib/clean.js +1 -1
- package/.docu/lib/deploy.deno.js +1 -1
- package/.docu/lib/deploy.node.js +1 -1
- package/.docu/lib/{deploy.shared-p8UHVvAw.js → deploy.shared-Bo7qnRdv.js} +2 -2
- package/.docu/lib/{deploy.shared-p8UHVvAw.js.map → deploy.shared-Bo7qnRdv.js.map} +1 -1
- package/.docu/lib/{html.shared-RPCM1y5Q.js → html.shared-DFiCcRc9.js} +89 -22
- package/.docu/lib/html.shared-DFiCcRc9.js.map +1 -0
- package/.docu/lib/{logger-NdMEl8u7.js → logger-B4tneSEM.js} +2 -2
- package/.docu/lib/{logger-NdMEl8u7.js.map → logger-B4tneSEM.js.map} +1 -1
- package/.docu/lib/preview.deno.js +1 -1
- package/.docu/lib/{preview.impl-CLHIPgry.js → preview.impl-CJFSqI73.js} +2 -2
- package/.docu/lib/{preview.impl-CLHIPgry.js.map → preview.impl-CJFSqI73.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-C4t3Xqb5.js → server.impl-BLpoFB2Y.js} +3 -3
- package/.docu/lib/{server.impl-C4t3Xqb5.js.map → server.impl-BLpoFB2Y.js.map} +1 -1
- package/.docu/lib/server.node.js +1 -1
- package/.docu/node/build.impl.ts +5 -5
- package/.docu/node/build.ts +5 -5
- package/.docu/node/cache-key.ts +104 -20
- package/.docu/node/hydrate.node.ts +10 -0
- package/.docu/node/hydrate.ts +16 -0
- package/.docu/styles/base.css +3 -0
- package/package.json +3 -3
- package/.docu/lib/build.impl-DxI2YYqL.js +0 -2
- package/.docu/lib/build.impl-oTVkE6D9.js.map +0 -1
- package/.docu/lib/html.shared-RPCM1y5Q.js.map +0 -1
package/.docu/lib/build.deno.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { D as resolveDeployPath, E as resolveBasePath, S as docsDepth, T as rebaseContentPath, _ as loadDocuConfig, a as DOCS_DIR, f as assertValidBasePath, g as deployPath, h as defaultFavicon, i as DOCS_ASSETS_DIR, l as PAGES_DIR, n as CACHE_FILE, o as DOCS_OUT_DIR, r as DIST_DIR, t as ASSETS_DIR, u as PROJECT_ROOT, y as servedBasePath } from "./paths-CHXABfXa.js";
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as atomicWriteFile, D as runtimeStamp, E as renderToolchainStamp, O as BuildPluginBuilder, S as computeInlineThemeCss, T as hookMemoryPressure, _ 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, k as loadPlugins, 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 hashMdxSources, x as cssBundleStamp, y as getGitLastModifiedBatch } from "./html.shared-DFiCcRc9.js";
|
|
3
3
|
import { a as cspHeader, n as resolveDocsIndexSource, r as scanMdxFiles, s as generateNonce } from "./server-utils-BYWB7A6T.js";
|
|
4
|
-
import { t as logger } from "./logger-
|
|
4
|
+
import { t as logger } from "./logger-B4tneSEM.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";
|
|
@@ -81,7 +81,7 @@ async function readCache() {
|
|
|
81
81
|
const data = await readFile(CACHE_FILE, "utf-8");
|
|
82
82
|
const parsed = JSON.parse(data);
|
|
83
83
|
const meta = parsed.__meta__;
|
|
84
|
-
if (!meta || meta.version !==
|
|
84
|
+
if (!meta || meta.version !== 7 || meta.runtime !== runtimeStamp() || meta.render !== renderToolchainStamp()) return {};
|
|
85
85
|
return parsed;
|
|
86
86
|
}
|
|
87
87
|
} catch (err) {
|
|
@@ -91,10 +91,10 @@ async function readCache() {
|
|
|
91
91
|
}
|
|
92
92
|
function stampCache(cache) {
|
|
93
93
|
cache.__meta__ = {
|
|
94
|
-
hash: `
|
|
94
|
+
hash: `7:${runtimeStamp()}`,
|
|
95
95
|
mtime: 0,
|
|
96
96
|
builtAt: Date.now(),
|
|
97
|
-
version:
|
|
97
|
+
version: 7,
|
|
98
98
|
runtime: runtimeStamp(),
|
|
99
99
|
render: renderToolchainStamp()
|
|
100
100
|
};
|
|
@@ -290,7 +290,7 @@ async function runBuild() {
|
|
|
290
290
|
} catch {}
|
|
291
291
|
logger.bundleStart();
|
|
292
292
|
let t = performance.now();
|
|
293
|
-
const bundleHash = hashMdxSources(mdxSources);
|
|
293
|
+
const bundleHash = hashMdxSources(mdxSources, cssBundleStamp());
|
|
294
294
|
const lastBundle = cache["__bundle__"];
|
|
295
295
|
const bundleHit = isCacheEntry(lastBundle) && lastBundle.hash === bundleHash && existsSync(join(ASSETS_DIR, "manifest.json"));
|
|
296
296
|
assetManifest = await resolveAssetManifest(bundleHit, mdxSources);
|
|
@@ -482,4 +482,4 @@ async function runBuildCli() {
|
|
|
482
482
|
//#endregion
|
|
483
483
|
export { runBuildCli as n, runBuild as t };
|
|
484
484
|
|
|
485
|
-
//# sourceMappingURL=build.impl-
|
|
485
|
+
//# sourceMappingURL=build.impl-CAMyXJH7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.impl-CAMyXJH7.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 /** Origin + deployment root, e.g. `https://docubook.pro` or `https://user.github.io`. */\n baseURL: string;\n /**\n * URL prefix the docs site is served under, e.g. \"/docs\" (default) or\n * \"/repo\" for a GitHub Pages project site. An empty string serves the site\n * from the deployment root. Set it independently of `baseURL`: the prefix is\n * appended to `baseURL` when building canonical/OG URLs.\n */\n basePath?: 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 * Fingerprint of the framework's rendering sources. Optional so a cache file\n * written before this field existed is treated as a mismatch and discarded.\n */\n render?: 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\";\nimport { resolveBasePath, resolveDeployPath } from \"./paths\";\nimport { rebaseContentPath } from \"./utils\";\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 *\n * All paths are prefixed with the configured base path so canonical and OG\n * URLs stay correct when the site is served under a subpath.\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 prefix = resolveBasePath(config);\n const url = slug ? `${baseURL}${prefix}/${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 result.image = resolveOgImage(image, baseURL, prefix, resolveDeployPath(config));\n }\n\n return result;\n}\n\n/**\n * Resolve an OG image path to an absolute URL.\n *\n * Handles four shapes:\n * - absolute (`https://…`) — used verbatim\n * - root-relative already carrying the prefix (`/repo/assets/og.png`) — used as is\n * - root-relative still carrying the default prefix (`/docs/assets/og.png`) —\n * re-based onto `prefix`, so an existing `docu.json` keeps working after the\n * site moves to a different subpath\n * - bare relative (`og.png`) — resolved against the prefix directory\n */\nfunction resolveOgImage(\n image: string,\n baseURL: string,\n prefix: string,\n deploymentPath: string\n): string {\n try {\n if (/^[a-zA-Z][a-zA-Z\\d+.-]*:\\/\\//.test(image)) return new URL(image).href;\n // Content assets live under the docs prefix *inside* the deployment root,\n // which `baseURL` already spells out (schema: \"origin plus deployment\n // root\"), so resolve the path relative to it. The deployment path is only\n // used to spot an author who spelled the host's segment out themselves.\n const rebased = rebaseContentPath(image.startsWith(\"/\") ? image : `${prefix}/${image}`, prefix);\n const withinRoot = rebased.startsWith(`${deploymentPath}/`)\n ? rebased.slice(deploymentPath.length)\n : rebased;\n return new URL(withinRoot.replace(/^\\/+/, \"\"), `${baseURL}/`).href;\n } catch {\n return image;\n }\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 DOCS_OUT_DIR,\n ASSETS_DIR,\n CACHE_FILE,\n DOCS_ASSETS_DIR,\n PROJECT_ROOT,\n PAGES_DIR,\n loadDocuConfig,\n servedBasePath,\n docsDepth,\n deployPath,\n assertValidBasePath,\n} from \"./paths\";\nimport { htmlShell } from \"./html.shared\";\nimport { generateSearchIndex } from \"./search-indexer\";\nimport { buildClientBundle, computeInlineThemeCss, cssBundleStamp } 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 } from \"./server-utils\";\nimport { defaultFavicon } from \"./paths\";\nimport type { AssetManifest, BuildCache, BuildCacheMeta, CliArgs } from \"./types\";\nimport { isCacheEntry } from \"./types\";\nimport {\n BUILD_CACHE_VERSION,\n atomicWriteFile,\n hashMdxSources,\n hookMemoryPressure,\n renderToolchainStamp,\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 // `render` covers the framework's own rendering sources: a template or\n // meta-tag edit changes output without touching any MDX file, so an\n // older cache must not be trusted after one.\n if (\n !meta ||\n meta.version !== BUILD_CACHE_VERSION ||\n meta.runtime !== runtimeStamp() ||\n meta.render !== renderToolchainStamp()\n ) {\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 render: renderToolchainStamp(),\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 = docsDepth(slug, servedBasePath());\n const favicon = docuConfig.meta?.favicon || defaultFavicon();\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 basePath: servedBasePath(),\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 // Normalized prefixes (case, whitespace, unservable characters) and values\n // that cannot be honored would otherwise only surface after the whole build —\n // report them, or stop before anything is written.\n for (const warning of assertValidBasePath()) logger.warn(warning.message);\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(DOCS_OUT_DIR, \"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 const docsIndexPath = join(DOCS_OUT_DIR, \"index.html\");\n // If the source was removed since the previous build, don't ship stale docs\n // output. At the deployment root, this path belongs to the landing page.\n if (!indexSource && DOCS_OUT_DIR !== DIST_DIR) {\n const { rm } = await import(\"node:fs/promises\");\n await rm(docsIndexPath, { force: true });\n }\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 the compiled MDX sources *and* the stylesheet\n // fingerprint are unchanged: the bundle is shared by every page, so its hash\n // doubles as the content fingerprint.\n const bundleHash = hashMdxSources(mdxSources, cssBundleStamp());\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(DOCS_OUT_DIR, `${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(DOCS_OUT_DIR, `${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(DOCS_OUT_DIR, `${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 if (DOCS_OUT_DIR !== DIST_DIR) {\n await mkdir(DOCS_OUT_DIR, { recursive: true });\n await writeFile(docsIndexPath, indexHtml);\n }\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 }\n\n const landingPage = React.createElement(IndexPage);\n const landingFavicon = docuConfig.meta?.favicon || defaultFavicon();\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 basePath: servedBasePath(),\n headExtra: builder?.collectHead(landingContext),\n bodyExtra: builder?.collectBody(landingContext),\n });\n if (builder) landingHtml = await builder.runTransformHtmlChain(landingHtml, landingContext);\n // The landing page always owns `/`. At a root deployment the docs index\n // would collide with it, so the index write above is skipped there — the\n // docs root page simply does not exist.\n await writeFile(join(DIST_DIR, \"index.html\"), landingHtml);\n\n const notFoundPage = React.createElement(NotFoundPage);\n const notFoundFavicon = docuConfig.meta?.favicon || defaultFavicon();\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 basePath: servedBasePath(),\n deployPath: deployPath(),\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(DOCS_OUT_DIR, `${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
|
@@ -261,7 +261,7 @@ async function runBuild() {
|
|
|
261
261
|
process.env.FLAME_BUILD_SILENT = "1";
|
|
262
262
|
process.env.LOG_LEVEL = "error";
|
|
263
263
|
}
|
|
264
|
-
const { runBuildCli } = await import("./build.impl-
|
|
264
|
+
const { runBuildCli } = await import("./build.impl-BaIcsV7P.js");
|
|
265
265
|
await runBuildCli();
|
|
266
266
|
}
|
|
267
267
|
async function writeDockerFiles() {
|
|
@@ -414,4 +414,4 @@ async function runDeploy() {
|
|
|
414
414
|
//#endregion
|
|
415
415
|
export { runDeploy as t };
|
|
416
416
|
|
|
417
|
-
//# sourceMappingURL=deploy.shared-
|
|
417
|
+
//# sourceMappingURL=deploy.shared-Bo7qnRdv.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.shared-p8UHVvAw.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, servedBasePath } 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\n/**\n * Nginx content-assets block.\n *\n * Two distinct asset trees:\n * - `/assets/` — hash-named client bundles, written to the dist root, immutable.\n * - `<prefix>/assets/` — user content assets copied from `docs/assets`,\n * revalidated after 7d.\n *\n * When the prefix is empty (root deployment) both trees land on `/assets/`, and\n * nginx rejects duplicate location blocks — so the content tree is omitted and\n * the immutable bundle rules win. Non-hashed content assets then inherit the\n * 1y immutable header; that is a deliberate trade for a valid config, and root\n * deployments are the uncommon case.\n */\nexport function buildNginxConf(basePath: string): string {\n // nginx `location` names must be plain URI prefixes; anything else (spaces,\n // braces, semicolons) fails `nginx -t` and the container never starts.\n // Unsafe prefixes simply lose the content-assets cache block — the default\n // location still serves the files, the same trade root deployments make.\n const safePrefix = /^\\/[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/.test(basePath);\n if (basePath.length > 0 && !safePrefix) {\n console.warn(\n `[flame] basePath \"${basePath}\" contains characters nginx cannot use in a location block; ` +\n \"omitting the content-assets caching block (assets still served)\"\n );\n }\n const contentAssetsBlock =\n safePrefix && basePath.length > 0\n ? `\n location ${basePath}/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 : \"\";\n\n return `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${contentAssetsBlock}\n location = /404.html { }\n\n location / {\n try_files $uri $uri.html $uri/ =404;\n }\n}\n`;\n}\n\n/** Backward-compatible default config (root-level `/docs` prefix). */\nexport const NGINX_CONF = buildNginxConf(\"/docs\");\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;\n\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\"), buildNginxConf(servedBasePath()));\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
|
+
{"version":3,"file":"deploy.shared-Bo7qnRdv.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, servedBasePath } 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\n/**\n * Nginx content-assets block.\n *\n * Two distinct asset trees:\n * - `/assets/` — hash-named client bundles, written to the dist root, immutable.\n * - `<prefix>/assets/` — user content assets copied from `docs/assets`,\n * revalidated after 7d.\n *\n * When the prefix is empty (root deployment) both trees land on `/assets/`, and\n * nginx rejects duplicate location blocks — so the content tree is omitted and\n * the immutable bundle rules win. Non-hashed content assets then inherit the\n * 1y immutable header; that is a deliberate trade for a valid config, and root\n * deployments are the uncommon case.\n */\nexport function buildNginxConf(basePath: string): string {\n // nginx `location` names must be plain URI prefixes; anything else (spaces,\n // braces, semicolons) fails `nginx -t` and the container never starts.\n // Unsafe prefixes simply lose the content-assets cache block — the default\n // location still serves the files, the same trade root deployments make.\n const safePrefix = /^\\/[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/.test(basePath);\n if (basePath.length > 0 && !safePrefix) {\n console.warn(\n `[flame] basePath \"${basePath}\" contains characters nginx cannot use in a location block; ` +\n \"omitting the content-assets caching block (assets still served)\"\n );\n }\n const contentAssetsBlock =\n safePrefix && basePath.length > 0\n ? `\n location ${basePath}/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 : \"\";\n\n return `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${contentAssetsBlock}\n location = /404.html { }\n\n location / {\n try_files $uri $uri.html $uri/ =404;\n }\n}\n`;\n}\n\n/** Backward-compatible default config (root-level `/docs` prefix). */\nexport const NGINX_CONF = buildNginxConf(\"/docs\");\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;\n\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\"), buildNginxConf(servedBasePath()));\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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"}
|
|
@@ -542,7 +542,7 @@ var renderStampCache = null;
|
|
|
542
542
|
function renderToolchainStamp() {
|
|
543
543
|
if (renderStampCache !== null) return renderStampCache;
|
|
544
544
|
const h = createHash("sha256");
|
|
545
|
-
h.update(`
|
|
545
|
+
h.update(`v7`);
|
|
546
546
|
for (const rel of RENDER_SOURCE_FILES) {
|
|
547
547
|
const file = join(FRAMEWORK_ROOT, rel);
|
|
548
548
|
try {
|
|
@@ -566,7 +566,7 @@ function isTailwindRelevantCss(content) {
|
|
|
566
566
|
}
|
|
567
567
|
var IMPORT_RE = /@import\s+(?:url\()?["']([^"']+)["']/g;
|
|
568
568
|
var MAX_IMPORT_DEPTH = 10;
|
|
569
|
-
/** Resolve `./` + `../` imports
|
|
569
|
+
/** Resolve `./` + `../` imports (bare specifiers go through the package resolver). */
|
|
570
570
|
function resolveRelativeImport(spec, fromDir) {
|
|
571
571
|
if (!spec.startsWith(".")) return void 0;
|
|
572
572
|
const clean = spec.split("?")[0].split("#")[0];
|
|
@@ -574,12 +574,51 @@ function resolveRelativeImport(spec, fromDir) {
|
|
|
574
574
|
return join(fromDir, clean);
|
|
575
575
|
}
|
|
576
576
|
/**
|
|
577
|
-
*
|
|
578
|
-
*
|
|
577
|
+
* Resolve a bare specifier (`@docubook/markdown/styles.css`) to the file it
|
|
578
|
+
* names in `node_modules`.
|
|
579
|
+
*
|
|
580
|
+
* A package stylesheet imported by the entry is emitted verbatim, so its
|
|
581
|
+
* content belongs in the cache key — otherwise editing the package's CSS keeps
|
|
582
|
+
* serving the previous bundle. The lookup walks up from the file that wrote the
|
|
583
|
+
* `@import`, checking `node_modules` at each level (Node's own order), which
|
|
584
|
+
* covers both the flat layout and pnpm's virtual store, where a package sits at
|
|
585
|
+
* `.../.pnpm/@docubook+flame@1/node_modules/@docubook/flame` and its deps
|
|
586
|
+
* resolve from the store directory above it. Directory targets such as
|
|
587
|
+
* `@import "tailwindcss"` are not files and resolve to undefined, so the
|
|
588
|
+
* vendored Tailwind tree never enters the key.
|
|
589
|
+
*/
|
|
590
|
+
function resolvePackageImport(spec, fromDir, root) {
|
|
591
|
+
if (spec.startsWith(".")) return void 0;
|
|
592
|
+
const clean = spec.split("?")[0].split("#")[0];
|
|
593
|
+
if (clean.length === 0) return void 0;
|
|
594
|
+
const isFile = (candidate) => {
|
|
595
|
+
try {
|
|
596
|
+
return existsSync(candidate) && statSync(candidate).isFile();
|
|
597
|
+
} catch {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
const bases = [];
|
|
602
|
+
let dir = fromDir;
|
|
603
|
+
for (let depth = 0; depth < 12; depth++) {
|
|
604
|
+
bases.push(dir);
|
|
605
|
+
const parent = join(dir, "..");
|
|
606
|
+
if (parent === dir) break;
|
|
607
|
+
dir = parent;
|
|
608
|
+
}
|
|
609
|
+
bases.push(root, FRAMEWORK_ROOT);
|
|
610
|
+
for (const base of bases) {
|
|
611
|
+
const candidate = join(base, "node_modules", clean);
|
|
612
|
+
if (isFile(candidate)) return candidate;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Read a CSS file plus transitively imported files.
|
|
617
|
+
* Filtered files contribute "" themselves, but their imports are still
|
|
579
618
|
* followed (nested file may carry `@theme`). `visited` breaks import cycles.
|
|
580
619
|
* Missing/unreadable files resolve to "" — never throws.
|
|
581
620
|
*/
|
|
582
|
-
function readCssWithImports(path, visited, depth = 0) {
|
|
621
|
+
function readCssWithImports(path, visited, opts, depth = 0) {
|
|
583
622
|
if (depth > MAX_IMPORT_DEPTH || visited.has(path)) return "";
|
|
584
623
|
visited.add(path);
|
|
585
624
|
let content = "";
|
|
@@ -589,17 +628,18 @@ function readCssWithImports(path, visited, depth = 0) {
|
|
|
589
628
|
} catch {
|
|
590
629
|
return "";
|
|
591
630
|
}
|
|
592
|
-
let out = isTailwindRelevantCss(content) ? content : "";
|
|
631
|
+
let out = opts.keepPlain || isTailwindRelevantCss(content) ? content : "";
|
|
593
632
|
try {
|
|
594
633
|
const dir = join(path, "..");
|
|
595
634
|
for (const m of content.matchAll(IMPORT_RE)) {
|
|
596
|
-
const
|
|
597
|
-
|
|
635
|
+
const spec = m[1] ?? "";
|
|
636
|
+
const resolved = resolveRelativeImport(spec, dir) ?? resolvePackageImport(spec, dir, opts.root);
|
|
637
|
+
if (resolved) out += readCssWithImports(resolved, visited, opts, depth + 1);
|
|
598
638
|
}
|
|
599
639
|
} catch {}
|
|
600
640
|
return out;
|
|
601
641
|
}
|
|
602
|
-
function scanCssDir(dir, visited) {
|
|
642
|
+
function scanCssDir(dir, visited, opts) {
|
|
603
643
|
let out = "";
|
|
604
644
|
try {
|
|
605
645
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -607,24 +647,28 @@ function scanCssDir(dir, visited) {
|
|
|
607
647
|
const full = join(dir, e.name);
|
|
608
648
|
if (e.isDirectory()) {
|
|
609
649
|
if (e.name === "assets" || e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
610
|
-
out += scanCssDir(full, visited);
|
|
611
|
-
} else if (e.name.endsWith(".css")) out += readCssWithImports(full, visited);
|
|
650
|
+
out += scanCssDir(full, visited, opts);
|
|
651
|
+
} else if (e.name.endsWith(".css")) out += readCssWithImports(full, visited, opts);
|
|
612
652
|
}
|
|
613
653
|
} catch {}
|
|
614
654
|
return out;
|
|
615
655
|
}
|
|
616
|
-
function scanRootCss(root, visited) {
|
|
656
|
+
function scanRootCss(root, visited, opts) {
|
|
617
657
|
let out = "";
|
|
618
658
|
try {
|
|
619
659
|
const entries = readdirSync(root, { withFileTypes: true });
|
|
620
|
-
for (const e of entries) if (e.isFile() && e.name.endsWith(".css")) out += readCssWithImports(join(root, e.name), visited);
|
|
660
|
+
for (const e of entries) if (e.isFile() && e.name.endsWith(".css")) out += readCssWithImports(join(root, e.name), visited, opts);
|
|
621
661
|
} catch {}
|
|
622
662
|
return out;
|
|
623
663
|
}
|
|
624
664
|
/** Hash @theme/@source/@plugin-bearing CSS in project docs/ + root *.css. */
|
|
625
665
|
function tailwindCssThemeInputs(root) {
|
|
626
666
|
const visited = /* @__PURE__ */ new Set();
|
|
627
|
-
|
|
667
|
+
const opts = {
|
|
668
|
+
root,
|
|
669
|
+
keepPlain: false
|
|
670
|
+
};
|
|
671
|
+
return scanCssDir(join(root, "docs"), visited, opts) + scanRootCss(root, visited, opts);
|
|
628
672
|
}
|
|
629
673
|
var TAILWIND_CONFIG_FILES = [
|
|
630
674
|
"tailwind.config.ts",
|
|
@@ -713,12 +757,23 @@ function computeTailwindCacheKey(globals, themeSuffix, projectRoot = resolveProj
|
|
|
713
757
|
h.update("\0");
|
|
714
758
|
h.update(basePathStamp());
|
|
715
759
|
h.update("\0");
|
|
716
|
-
h.update(`
|
|
760
|
+
h.update(`v7`);
|
|
717
761
|
return h.digest("hex").slice(0, 16);
|
|
718
762
|
}
|
|
719
|
-
/**
|
|
720
|
-
|
|
721
|
-
|
|
763
|
+
/**
|
|
764
|
+
* Read a Tailwind entry plus everything it imports for cache invalidation.
|
|
765
|
+
*
|
|
766
|
+
* `keepPlain`: the entry graph is hashed verbatim (Tailwind copies its plain
|
|
767
|
+
* rules into the output), and bare package imports are followed into
|
|
768
|
+
* `node_modules` — a stylesheet shipped by a dependency, e.g.
|
|
769
|
+
* `@docubook/markdown/styles.css`, changes the bundle like any other input.
|
|
770
|
+
* `dir`/`root` are overridable for tests.
|
|
771
|
+
*/
|
|
772
|
+
function readStyleCss(file = "globals.css", dir = STYLES_DIR, root = resolveProjectFile()) {
|
|
773
|
+
return readCssWithImports(join(dir, file), /* @__PURE__ */ new Set(), {
|
|
774
|
+
root,
|
|
775
|
+
keepPlain: true
|
|
776
|
+
});
|
|
722
777
|
}
|
|
723
778
|
/**
|
|
724
779
|
* Atomic file write: tmp + rename so a crash mid-write never leaves a
|
|
@@ -754,11 +809,14 @@ function hookMemoryPressure(clear) {
|
|
|
754
809
|
/**
|
|
755
810
|
* Hash of compiled MDX module sources (sorted keys + content).
|
|
756
811
|
* Used to skip the JS bundle rebuild when no page content changed.
|
|
812
|
+
*
|
|
813
|
+
* `extraStamp` folds inputs the MDX sources cannot see — the stylesheet
|
|
814
|
+
* fingerprint (`cssBundleStamp()`) — so a CSS-only edit still rebuilds assets.
|
|
757
815
|
*/
|
|
758
|
-
function hashMdxSources(mdxSources) {
|
|
816
|
+
function hashMdxSources(mdxSources, extraStamp = "") {
|
|
759
817
|
const h = createHash("sha256");
|
|
760
818
|
const slugs = Object.keys(mdxSources).sort();
|
|
761
|
-
h.update(`
|
|
819
|
+
h.update(`v7:${runtimeStamp()}:${basePathStamp()}:${renderToolchainStamp()}:${extraStamp}:`);
|
|
762
820
|
for (const slug of slugs) {
|
|
763
821
|
h.update(slug);
|
|
764
822
|
h.update("\0");
|
|
@@ -961,6 +1019,15 @@ function themeCacheSuffix() {
|
|
|
961
1019
|
function tailwindCacheKey(styleFile) {
|
|
962
1020
|
return computeTailwindCacheKey(readStyleCss(styleFile), `${styleFile}\0${themeCacheSuffix()}`);
|
|
963
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Fingerprint of every style input that shapes the emitted CSS: the route
|
|
1024
|
+
* entries (`globals.css`, `site.css`) plus everything they import — relative
|
|
1025
|
+
* files and package stylesheets — and the resolved theme. Mirrors
|
|
1026
|
+
* `hydrate.ts` so both runtimes agree on when the bundle must rebuild.
|
|
1027
|
+
*/
|
|
1028
|
+
function cssBundleStamp() {
|
|
1029
|
+
return `${tailwindCacheKey("globals.css")}\0${tailwindCacheKey("site.css")}`;
|
|
1030
|
+
}
|
|
964
1031
|
async function buildTailwindCss(name, styleFile) {
|
|
965
1032
|
const key = tailwindCacheKey(styleFile);
|
|
966
1033
|
const cachedFile = `${name}-${key}.css`;
|
|
@@ -2917,6 +2984,6 @@ function hmrScript(nonce) {
|
|
|
2917
2984
|
<\/script>`;
|
|
2918
2985
|
}
|
|
2919
2986
|
//#endregion
|
|
2920
|
-
export {
|
|
2987
|
+
export { atomicWriteFile as C, runtimeStamp as D, renderToolchainStamp as E, BuildPluginBuilder as O, computeInlineThemeCss as S, hookMemoryPressure as T, getPageStripped as _, IndexPage as a, buildClientBundle as b, captureException as c, clearDerivedPageCaches as d, compileMdx as f, getPageFrontmatter as g, getPageContent as h, DocsLayout as i, loadPlugins as k, initSentry as l, frontmatterField as m, hmrScript as n, NotFoundPage as o, compileMdxModule as p, htmlShell as r, DocsPage as s, errorHtml as t, generateSearchIndex as u, registerPageContent as v, hashMdxSources as w, cssBundleStamp as x, getGitLastModifiedBatch as y };
|
|
2921
2988
|
|
|
2922
|
-
//# sourceMappingURL=html.shared-
|
|
2989
|
+
//# sourceMappingURL=html.shared-DFiCcRc9.js.map
|