@docubook/flame 2.0.0-beta.1 → 2.0.0-beta.3
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 +6 -9
- package/.docu/lib/build.deno.js.map +1 -0
- package/.docu/lib/build.impl-Bz_p421t.js +424 -0
- package/.docu/lib/build.impl-Bz_p421t.js.map +1 -0
- package/.docu/lib/build.impl-CtPrlYAE.js +2 -0
- package/.docu/lib/build.node.js +6 -9
- package/.docu/lib/build.node.js.map +1 -0
- package/.docu/lib/clean.js +25 -25
- package/.docu/lib/clean.js.map +1 -0
- package/.docu/lib/deno-DmEaKggj.js +20 -0
- package/.docu/lib/deno-DmEaKggj.js.map +1 -0
- package/.docu/lib/deploy.deno.js +9 -9
- package/.docu/lib/deploy.deno.js.map +1 -0
- package/.docu/lib/deploy.node.js +8 -8
- package/.docu/lib/deploy.node.js.map +1 -0
- package/.docu/lib/deploy.shared-fk8eM-r4.js +295 -0
- package/.docu/lib/deploy.shared-fk8eM-r4.js.map +1 -0
- package/.docu/lib/html.shared-FwgbE1WG.js +2696 -0
- package/.docu/lib/html.shared-FwgbE1WG.js.map +1 -0
- package/.docu/lib/logger-CQyNTE6L.js +306 -0
- package/.docu/lib/logger-CQyNTE6L.js.map +1 -0
- package/.docu/lib/node-DPTySdwG.js +80 -0
- package/.docu/lib/node-DPTySdwG.js.map +1 -0
- package/.docu/lib/paths-Bl2cdp9E.js +76 -0
- package/.docu/lib/paths-Bl2cdp9E.js.map +1 -0
- package/.docu/lib/preview.deno.js +7 -11
- package/.docu/lib/preview.deno.js.map +1 -0
- package/.docu/lib/preview.impl-CnsbLmDA.js +77 -0
- package/.docu/lib/preview.impl-CnsbLmDA.js.map +1 -0
- package/.docu/lib/preview.node.js +7 -11
- package/.docu/lib/preview.node.js.map +1 -0
- package/.docu/lib/server.deno.js +7 -12
- package/.docu/lib/server.deno.js.map +1 -0
- package/.docu/lib/server.impl-CXTzYqbF.js +358 -0
- package/.docu/lib/server.impl-CXTzYqbF.js.map +1 -0
- package/.docu/lib/server.node.js +7 -12
- package/.docu/lib/server.node.js.map +1 -0
- package/.docu/lib/utils-DA17MyQG.js +167 -0
- package/.docu/lib/utils-DA17MyQG.js.map +1 -0
- package/.docu/node/build.impl.ts +76 -11
- package/.docu/node/build.ts +83 -9
- package/.docu/node/cache-key.ts +311 -0
- package/.docu/node/hydrate.node.ts +124 -177
- package/.docu/node/hydrate.ts +13 -14
- package/.docu/node/mdx.ts +14 -2
- package/.docu/node/paths.ts +27 -1
- package/.docu/node/types.ts +23 -5
- package/bin/cli.js +56 -18
- package/bin/compile-lib.mjs +30 -28
- package/package.json +10 -16
- package/.docu/lib/build.impl-3ZOQORN3.js +0 -12
- package/.docu/lib/chunk-4IQXHHPF.js +0 -62
- package/.docu/lib/chunk-6EOUQCRM.js +0 -301
- package/.docu/lib/chunk-6HFBJUMG.js +0 -434
- package/.docu/lib/chunk-EOK6KATZ.js +0 -191
- package/.docu/lib/chunk-FERG25YW.js +0 -470
- package/.docu/lib/chunk-MQRFIDS4.js +0 -92
- package/.docu/lib/chunk-U7M5SK76.js +0 -330
- package/.docu/lib/chunk-UISOJ4RW.js +0 -114
- package/.docu/lib/chunk-URBATJEC.js +0 -2720
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client bundle builder for Node/Deno runtimes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Uses Vite with Rolldown plugins to produce a browser-ready client
|
|
5
5
|
* bundle (JS + CSS) from the same components Bun.build handles natively.
|
|
6
6
|
* Theme helpers (getThemeConfig, buildThemeCss, computeInlineThemeCss)
|
|
7
7
|
* are re-exported from `hydrate.ts` — that module's `buildClientBundle`
|
|
@@ -13,8 +13,8 @@ import { builtinModules, createRequire } from "node:module";
|
|
|
13
13
|
import { basename, dirname, join, resolve } from "node:path";
|
|
14
14
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
15
15
|
import { promisify } from "node:util";
|
|
16
|
-
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
17
|
-
import {
|
|
16
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
17
|
+
import { build as viteBuild } from "vite";
|
|
18
18
|
import {
|
|
19
19
|
ASSETS_DIR,
|
|
20
20
|
FRAMEWORK_ROOT,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
loadDocuConfig,
|
|
25
25
|
} from "./paths";
|
|
26
26
|
import { buildThemeCss, getThemeConfig } from "./hydrate";
|
|
27
|
+
import { atomicWriteFile, computeTailwindCacheKey, readGlobalsCss } from "./cache-key";
|
|
27
28
|
import { resolveRoutes } from "./fs-scanner";
|
|
28
29
|
import { normalizeImporterPath } from "./security";
|
|
29
30
|
import type { DocuConfig, DocuRoute } from "./types";
|
|
@@ -58,10 +59,9 @@ function resolveTailwindBin(): string {
|
|
|
58
59
|
return join(dirname(pkgPath), binRel);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
/** Compute a cache key from globals.css + theme config
|
|
62
|
+
/** Compute a cache key from globals.css + theme + config + toolchain. */
|
|
62
63
|
function tailwindCacheKey(): string {
|
|
63
|
-
const
|
|
64
|
-
const globalsContent = existsSync(globalsPath) ? readFileSync(globalsPath, "utf-8") : "";
|
|
64
|
+
const globalsContent = readGlobalsCss();
|
|
65
65
|
let themeSuffix = "";
|
|
66
66
|
try {
|
|
67
67
|
const themeColors = getThemeConfig();
|
|
@@ -71,10 +71,7 @@ function tailwindCacheKey(): string {
|
|
|
71
71
|
} catch {
|
|
72
72
|
// theme config unavailable — proceed without it
|
|
73
73
|
}
|
|
74
|
-
return
|
|
75
|
-
.update(globalsContent + themeSuffix)
|
|
76
|
-
.digest("hex")
|
|
77
|
-
.slice(0, 16);
|
|
74
|
+
return computeTailwindCacheKey(globalsContent, themeSuffix);
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
/**
|
|
@@ -104,7 +101,7 @@ async function buildTailwindCss(key: string): Promise<{ file: string; content: s
|
|
|
104
101
|
}
|
|
105
102
|
|
|
106
103
|
let cssContent = await readFile(tmpCss, "utf-8");
|
|
107
|
-
await unlink(tmpCss);
|
|
104
|
+
await unlink(tmpCss).catch(() => {});
|
|
108
105
|
|
|
109
106
|
try {
|
|
110
107
|
const themeColors = getThemeConfig();
|
|
@@ -119,11 +116,12 @@ async function buildTailwindCss(key: string): Promise<{ file: string; content: s
|
|
|
119
116
|
|
|
120
117
|
// Use the same input-derived key for lookup and output — if inputs change,
|
|
121
118
|
// the key changes, cache busting works without a separate content hash.
|
|
119
|
+
// Atomic tmp+rename with pid suffix: parallel builds never clobber each other.
|
|
122
120
|
const cssFile = `client-${key}.css`;
|
|
123
121
|
const outPath = join(ASSETS_DIR, cssFile);
|
|
124
122
|
|
|
125
123
|
if (!existsSync(outPath)) {
|
|
126
|
-
await writeFile
|
|
124
|
+
await atomicWriteFile(writeFile, rename, unlink, outPath, cssContent);
|
|
127
125
|
}
|
|
128
126
|
|
|
129
127
|
return { file: cssFile, content: cssContent };
|
|
@@ -203,180 +201,129 @@ export async function buildClientBundle(
|
|
|
203
201
|
await cleanOldBundles(new Set([`client-${twKey}.css`]));
|
|
204
202
|
|
|
205
203
|
const nodeEnv = process.env.NODE_ENV || "development";
|
|
206
|
-
const esbuild = await import("esbuild");
|
|
207
|
-
const { build } = esbuild;
|
|
208
204
|
|
|
209
205
|
const entryPath = join(LIB_DIR, "client.ts");
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
minify: nodeEnv === "production",
|
|
221
|
-
define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
|
|
222
|
-
jsx: "automatic",
|
|
223
|
-
jsxDev: nodeEnv !== "production",
|
|
224
|
-
metafile: true,
|
|
225
|
-
logLevel: "silent",
|
|
226
|
-
plugins: [
|
|
227
|
-
{
|
|
228
|
-
name: "node-builtin-stub",
|
|
229
|
-
setup(build) {
|
|
230
|
-
build.onResolve({ filter: NODE_BUILTINS_RE }, (args) => ({
|
|
231
|
-
path: args.path,
|
|
232
|
-
namespace: "node-stub",
|
|
233
|
-
}));
|
|
234
|
-
build.onLoad({ filter: /.*/, namespace: "node-stub" }, () => ({
|
|
235
|
-
contents: "module.exports = {};",
|
|
236
|
-
loader: "js",
|
|
237
|
-
}));
|
|
238
|
-
},
|
|
206
|
+
const bundle = await viteBuild({
|
|
207
|
+
configFile: false,
|
|
208
|
+
publicDir: false,
|
|
209
|
+
define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
|
|
210
|
+
plugins: [
|
|
211
|
+
{
|
|
212
|
+
name: "node-builtin-stub",
|
|
213
|
+
resolveId(id) {
|
|
214
|
+
if (NODE_BUILTINS_RE.test(id)) return `\0node-stub:${id}`;
|
|
215
|
+
return null;
|
|
239
216
|
},
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
build.onResolve({ filter: /^lucide-react$/ }, (args) => {
|
|
244
|
-
// Imports from within our virtual module go to the real package.
|
|
245
|
-
if (args.namespace === "lucide-virt") {
|
|
246
|
-
return { path: getLucideRealEntry(), namespace: "file" };
|
|
247
|
-
}
|
|
248
|
-
// markdown Icon.tsx uses namespace import for arbitrary
|
|
249
|
-
// user-provided icon names in MDX — keep full barrel there.
|
|
250
|
-
if (args.importer) {
|
|
251
|
-
const normalized = normalizeImporterPath(args.importer);
|
|
252
|
-
if (normalized.includes("/markdown/dist/")) {
|
|
253
|
-
return { path: getLucideRealEntry(), namespace: "file" };
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
// All other files get tree-shaken via the virtual module.
|
|
257
|
-
// Lucide.tsx renders only config-defined icons, which are
|
|
258
|
-
// collected by extractConfigIcons() + collectAllLucideIcons().
|
|
259
|
-
return { path: args.path, namespace: "lucide-virt" };
|
|
260
|
-
});
|
|
261
|
-
build.onLoad({ filter: /.*/, namespace: "lucide-virt" }, () => {
|
|
262
|
-
const scanned = collectAllLucideIcons();
|
|
263
|
-
const configured = extractConfigIcons(loadDocuConfig());
|
|
264
|
-
const allIcons = [...new Set([...scanned, ...configured])];
|
|
265
|
-
return {
|
|
266
|
-
contents: `export { ${allIcons.join(", ")} } from "lucide-react";`,
|
|
267
|
-
loader: "js",
|
|
268
|
-
};
|
|
269
|
-
});
|
|
270
|
-
},
|
|
217
|
+
load(id) {
|
|
218
|
+
if (!id.startsWith("\0node-stub:")) return null;
|
|
219
|
+
return "export default {}; export {};";
|
|
271
220
|
},
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const config = loadDocuConfig();
|
|
283
|
-
const resolved = {
|
|
284
|
-
...config,
|
|
285
|
-
routes: resolveRoutes(config.routes as DocuRoute[] | undefined),
|
|
286
|
-
};
|
|
287
|
-
return {
|
|
288
|
-
contents: [
|
|
289
|
-
`import type { DocuRoute, DocuConfig } from "./types";`,
|
|
290
|
-
`const docuConfig = ${JSON.stringify(resolved)};`,
|
|
291
|
-
`export const routes = docuConfig.routes || [];`,
|
|
292
|
-
`export const config = docuConfig;`,
|
|
293
|
-
].join("\n"),
|
|
294
|
-
loader: "ts",
|
|
295
|
-
};
|
|
296
|
-
});
|
|
297
|
-
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "lucide-optimize",
|
|
224
|
+
resolveId(id, importer) {
|
|
225
|
+
if (id !== "lucide-react") return null;
|
|
226
|
+
if (importer) {
|
|
227
|
+
const normalized = normalizeImporterPath(importer);
|
|
228
|
+
if (normalized.includes("/markdown/dist/")) return null;
|
|
229
|
+
}
|
|
230
|
+
return "\0lucide-virt";
|
|
298
231
|
},
|
|
299
|
-
{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// Compiled MDX (program format) imports the JSX runtime and the
|
|
306
|
-
// MDX provider. Virtual modules have no real directory, so esbuild
|
|
307
|
-
// cannot resolve bare specifiers from them — map them to the
|
|
308
|
-
// installed real paths explicitly (same pattern as lucide-optimize).
|
|
309
|
-
const require = createRequire(import.meta.url);
|
|
310
|
-
const mdxModuleDeps: Record<string, string> = {
|
|
311
|
-
"react/jsx-runtime": require.resolve("react/jsx-runtime"),
|
|
312
|
-
"react/jsx-dev-runtime": require.resolve("react/jsx-dev-runtime"),
|
|
313
|
-
"@mdx-js/react": require.resolve("@mdx-js/react"),
|
|
314
|
-
};
|
|
315
|
-
build.onResolve(
|
|
316
|
-
{ filter: /^(react\/jsx-runtime|react\/jsx-dev-runtime|@mdx-js\/react)$/ },
|
|
317
|
-
(args) => {
|
|
318
|
-
if (args.namespace !== "mdx-module") return;
|
|
319
|
-
return { path: mdxModuleDeps[args.path], namespace: "file" };
|
|
320
|
-
}
|
|
321
|
-
);
|
|
322
|
-
build.onResolve({ filter: /^mdx-module:/ }, (args) => ({
|
|
323
|
-
path: args.path,
|
|
324
|
-
namespace: "mdx-module",
|
|
325
|
-
}));
|
|
326
|
-
build.onLoad({ filter: /.*/, namespace: "mdx-module" }, (args) => {
|
|
327
|
-
const slug = args.path.slice("mdx-module:".length);
|
|
328
|
-
const contents = mdxSources[slug];
|
|
329
|
-
if (contents == null) {
|
|
330
|
-
return {
|
|
331
|
-
errors: [{ text: `unknown mdx module: ${slug}` }],
|
|
332
|
-
contents: "",
|
|
333
|
-
loader: "js",
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
return { contents, loader: "js" };
|
|
337
|
-
});
|
|
338
|
-
build.onResolve({ filter: /mdx-manifest$/ }, (args) => ({
|
|
339
|
-
path: args.path,
|
|
340
|
-
namespace: "mdx-manifest",
|
|
341
|
-
}));
|
|
342
|
-
build.onLoad({ filter: /.*/, namespace: "mdx-manifest" }, () => {
|
|
343
|
-
// Sort keys: the prePass fills mdxSources via Promise.all, so
|
|
344
|
-
// insertion order = resolution order (non-deterministic across
|
|
345
|
-
// processes). Stable key order keeps the bundle hash stable so
|
|
346
|
-
// the build cache (`assetsChanged`) actually hits.
|
|
347
|
-
const slugs = Object.keys(mdxSources).sort();
|
|
348
|
-
const imports = slugs
|
|
349
|
-
.map((slug, i) => {
|
|
350
|
-
const key = slug.replace(/["\\]/g, "");
|
|
351
|
-
return `import * as _mdx${i} from "mdx-module:${key}";`;
|
|
352
|
-
})
|
|
353
|
-
.join("\n");
|
|
354
|
-
const map = slugs.map((slug, i) => `${JSON.stringify(slug)}: _mdx${i}`).join(", ");
|
|
355
|
-
return {
|
|
356
|
-
contents: `${imports}\nexport const mdxModules = { ${map} };\n`,
|
|
357
|
-
loader: "js",
|
|
358
|
-
};
|
|
359
|
-
});
|
|
360
|
-
},
|
|
232
|
+
load(id) {
|
|
233
|
+
if (id !== "\0lucide-virt") return null;
|
|
234
|
+
const scanned = collectAllLucideIcons();
|
|
235
|
+
const configured = extractConfigIcons(loadDocuConfig());
|
|
236
|
+
const allIcons = [...new Set([...scanned, ...configured])];
|
|
237
|
+
return `export { ${allIcons.join(", ")} } from ${JSON.stringify(getLucideRealEntry())};`;
|
|
361
238
|
},
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
name: "docu-config",
|
|
242
|
+
resolveId(id) {
|
|
243
|
+
if (!/client-routes$/.test(id)) return null;
|
|
244
|
+
return "\0client-routes";
|
|
245
|
+
},
|
|
246
|
+
load(id) {
|
|
247
|
+
if (id !== "\0client-routes") return null;
|
|
248
|
+
const config = loadDocuConfig();
|
|
249
|
+
const resolved = {
|
|
250
|
+
...config,
|
|
251
|
+
routes: resolveRoutes(config.routes as DocuRoute[] | undefined),
|
|
252
|
+
};
|
|
253
|
+
return [
|
|
254
|
+
`const docuConfig = ${JSON.stringify(resolved)};`,
|
|
255
|
+
`export const routes = docuConfig.routes || [];`,
|
|
256
|
+
`export const config = docuConfig;`,
|
|
257
|
+
].join("\n");
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "mdx-hydrate",
|
|
262
|
+
resolveId(id) {
|
|
263
|
+
if (/mdx-manifest$/.test(id)) return "\0mdx-manifest";
|
|
264
|
+
if (id.startsWith("mdx-module:")) return `\0${id}`;
|
|
265
|
+
return null;
|
|
266
|
+
},
|
|
267
|
+
load(id) {
|
|
268
|
+
if (id === "\0mdx-manifest") {
|
|
269
|
+
const slugs = Object.keys(mdxSources).sort();
|
|
270
|
+
const imports = slugs
|
|
271
|
+
.map((slug, i) => {
|
|
272
|
+
const key = slug.replace(/["\\]/g, "");
|
|
273
|
+
return `import * as _mdx${i} from ${JSON.stringify(`mdx-module:${key}`)};`;
|
|
274
|
+
})
|
|
275
|
+
.join("\n");
|
|
276
|
+
const map = slugs.map((slug, i) => `${JSON.stringify(slug)}: _mdx${i}`).join(", ");
|
|
277
|
+
return `${imports}\nexport const mdxModules = { ${map} };\n`;
|
|
278
|
+
}
|
|
279
|
+
if (!id.startsWith("\0mdx-module:")) return null;
|
|
280
|
+
const slug = id.slice("\0mdx-module:".length);
|
|
281
|
+
const contents = mdxSources[slug];
|
|
282
|
+
if (contents == null) {
|
|
283
|
+
throw new Error(`unknown mdx module: ${slug}`);
|
|
284
|
+
}
|
|
285
|
+
return contents;
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
build: {
|
|
290
|
+
outDir: ASSETS_DIR,
|
|
291
|
+
emptyOutDir: false,
|
|
292
|
+
sourcemap: true,
|
|
293
|
+
minify: nodeEnv === "production",
|
|
294
|
+
target: "es2020",
|
|
295
|
+
rollupOptions: {
|
|
296
|
+
input: entryPath,
|
|
297
|
+
output: {
|
|
298
|
+
format: "es",
|
|
299
|
+
entryFileNames: "client-[hash].js",
|
|
300
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
301
|
+
assetFileNames: "assets/[name]-[hash][extname]",
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
375
305
|
});
|
|
376
|
-
|
|
306
|
+
|
|
307
|
+
const outputs = Array.isArray(bundle) ? bundle : [bundle];
|
|
308
|
+
let jsFile: string | undefined;
|
|
309
|
+
for (const item of outputs) {
|
|
310
|
+
if (!("output" in item)) continue;
|
|
311
|
+
for (const output of item.output) {
|
|
312
|
+
if (
|
|
313
|
+
output.type === "chunk" &&
|
|
314
|
+
output.isEntry &&
|
|
315
|
+
output.facadeModuleId &&
|
|
316
|
+
resolve(output.facadeModuleId) === entryPath
|
|
317
|
+
) {
|
|
318
|
+
jsFile = basename(output.fileName);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (jsFile) break;
|
|
323
|
+
}
|
|
324
|
+
if (!jsFile) {
|
|
377
325
|
throw new Error("Client bundle produced no output files");
|
|
378
326
|
}
|
|
379
|
-
const jsFile = basename(jsOutput);
|
|
380
327
|
|
|
381
328
|
const { file: cssFile } = await buildTailwindCss(twKey);
|
|
382
329
|
|
package/.docu/node/hydrate.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { mkdir, unlink } from "node:fs/promises";
|
|
3
|
-
import { existsSync
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, unlink, rename } from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
5
4
|
import { resolveTheme, generateThemeCss, presetRegistry } from "@docubook/themes-colors";
|
|
6
5
|
import { ASSETS_DIR, cleanOldBundles, LIB_DIR, STYLES_DIR, loadDocuConfig } from "./paths";
|
|
6
|
+
import { atomicWriteFile, computeTailwindCacheKey, readGlobalsCss } from "./cache-key";
|
|
7
7
|
import { resolveRoutes } from "./fs-scanner";
|
|
8
8
|
import type { DocuRoute } from "./types";
|
|
9
9
|
import type { ThemeConfig } from "@docubook/themes-colors";
|
|
@@ -57,10 +57,9 @@ export function computeInlineThemeCss(): string | undefined {
|
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/** Compute Tailwind cache key from globals.css + theme config. */
|
|
60
|
+
/** Compute Tailwind cache key from globals.css + theme + config + toolchain. */
|
|
61
61
|
function twCacheKey(): string {
|
|
62
|
-
const
|
|
63
|
-
const globals = existsSync(globalsPath) ? readFileSync(globalsPath, "utf-8") : "";
|
|
62
|
+
const globals = readGlobalsCss();
|
|
64
63
|
let themeSuffix = "";
|
|
65
64
|
try {
|
|
66
65
|
const themeColors = getThemeConfig();
|
|
@@ -68,10 +67,7 @@ function twCacheKey(): string {
|
|
|
68
67
|
} catch {
|
|
69
68
|
// theme config unavailable — proceed without
|
|
70
69
|
}
|
|
71
|
-
return
|
|
72
|
-
.update(globals + themeSuffix)
|
|
73
|
-
.digest("hex")
|
|
74
|
-
.slice(0, 16);
|
|
70
|
+
return computeTailwindCacheKey(globals, themeSuffix);
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
/** Run Tailwind CLI, caching by content hash. */
|
|
@@ -105,7 +101,7 @@ async function buildTailwindCss(key: string): Promise<{ file: string; content: s
|
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
let cssContent = await Bun.file(tmpCss).text();
|
|
108
|
-
await unlink(tmpCss);
|
|
104
|
+
await unlink(tmpCss).catch(() => {});
|
|
109
105
|
|
|
110
106
|
try {
|
|
111
107
|
const themeColors = getThemeConfig();
|
|
@@ -116,11 +112,14 @@ async function buildTailwindCss(key: string): Promise<{ file: string; content: s
|
|
|
116
112
|
);
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
// Use the same input-derived key for lookup and output — if inputs change,
|
|
120
|
-
// the key changes, cache busting works without a separate content hash.
|
|
121
115
|
const cssFile = `client-${key}.css`;
|
|
122
116
|
const outPath = join(ASSETS_DIR, cssFile);
|
|
123
|
-
|
|
117
|
+
// Atomic tmp+rename with pid suffix: parallel builds never clobber each
|
|
118
|
+
// other, and a crash cannot leave a half-written CSS file behind.
|
|
119
|
+
if (!existsSync(outPath)) {
|
|
120
|
+
const { writeFile } = await import("node:fs/promises");
|
|
121
|
+
await atomicWriteFile(writeFile, rename, unlink, outPath, cssContent);
|
|
122
|
+
}
|
|
124
123
|
|
|
125
124
|
return { file: cssFile, content: cssContent };
|
|
126
125
|
}
|
package/.docu/node/mdx.ts
CHANGED
|
@@ -174,7 +174,10 @@ async function serializeWithDocPlugins(
|
|
|
174
174
|
// Parse-once: when the prePass already extracted the frontmatter + stripped
|
|
175
175
|
// content, reuse it instead of re-parsing (the SSR phase skips extraction).
|
|
176
176
|
const { strippedContent, frontmatter } =
|
|
177
|
-
pre ??
|
|
177
|
+
pre ??
|
|
178
|
+
(opts.frontmatterSchema
|
|
179
|
+
? extractFrontmatterWithContent<Frontmatter>(rawMdx, opts.frontmatterSchema)
|
|
180
|
+
: extractFrontmatterWithContent<Frontmatter>(rawMdx));
|
|
178
181
|
|
|
179
182
|
const defaultRemark = createDefaultRemarkPlugins();
|
|
180
183
|
const defaultRehype = createDefaultRehypePlugins();
|
|
@@ -221,7 +224,9 @@ export async function compileMdx(
|
|
|
221
224
|
const tocs = extractTocsFromRawMdx(rawMdx);
|
|
222
225
|
const frontmatter =
|
|
223
226
|
pre?.frontmatter ??
|
|
224
|
-
|
|
227
|
+
(frontmatterSchema
|
|
228
|
+
? extractFrontmatterWithContent<Frontmatter>(rawMdx, frontmatterSchema).frontmatter
|
|
229
|
+
: extractFrontmatterWithContent<Frontmatter>(rawMdx).frontmatter);
|
|
225
230
|
const serialized = await serializeWithDocPlugins(
|
|
226
231
|
rawMdx,
|
|
227
232
|
{ remarkPlugins, rehypePlugins, frontmatterSchema },
|
|
@@ -304,6 +309,13 @@ export function getPageContent(href: string): string | undefined {
|
|
|
304
309
|
return pageContent.get(href);
|
|
305
310
|
}
|
|
306
311
|
|
|
312
|
+
/** Drop derived page maps under OS memory pressure (Bun 1.4 `memoryPressure`). */
|
|
313
|
+
export function clearDerivedPageCaches(): void {
|
|
314
|
+
pageContent.clear();
|
|
315
|
+
pageStripped.clear();
|
|
316
|
+
pageFrontmatter.clear();
|
|
317
|
+
}
|
|
318
|
+
|
|
307
319
|
export async function compileMdxModule(
|
|
308
320
|
rawMdx: string,
|
|
309
321
|
remarkPlugins?: Pluggable[],
|
package/.docu/node/paths.ts
CHANGED
|
@@ -10,7 +10,28 @@ import type { DocuConfig } from "./types";
|
|
|
10
10
|
|
|
11
11
|
// .docu/node/paths.ts → package root is 2 levels up
|
|
12
12
|
export const FRAMEWORK_ROOT = resolve(import.meta.dirname, "../..");
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Find the project root by walking up from cwd until docu.json is found.
|
|
16
|
+
* Falls back to cwd when not found (tests, ad-hoc scripts). Bun 1.4
|
|
17
|
+
* `run --parallel --filter` may change cwd per workspace task, so a bare
|
|
18
|
+
* process.cwd() can point at the wrong package.
|
|
19
|
+
*/
|
|
20
|
+
function findProjectRoot(): string {
|
|
21
|
+
let dir = process.cwd();
|
|
22
|
+
for (let i = 0; i < 6; i++) {
|
|
23
|
+
try {
|
|
24
|
+
if (existsSync(join(dir, "docu.json"))) return dir;
|
|
25
|
+
} catch {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
const parent = resolve(dir, "..");
|
|
29
|
+
if (parent === dir) break;
|
|
30
|
+
dir = parent;
|
|
31
|
+
}
|
|
32
|
+
return process.cwd();
|
|
33
|
+
}
|
|
34
|
+
export const PROJECT_ROOT = findProjectRoot();
|
|
14
35
|
|
|
15
36
|
// Framework paths (internal)
|
|
16
37
|
export const PAGES_DIR = join(FRAMEWORK_ROOT, ".docu/pages");
|
|
@@ -29,6 +50,11 @@ export const DOCS_DIR = join(PROJECT_ROOT, "docs");
|
|
|
29
50
|
export const DOCS_ASSETS_DIR = join(PROJECT_ROOT, "docs/assets");
|
|
30
51
|
export const DOCU_CONFIG_PATH = join(PROJECT_ROOT, "docu.json");
|
|
31
52
|
|
|
53
|
+
/** Resolve a project-relative file against the discovered PROJECT_ROOT. */
|
|
54
|
+
export function resolveProjectFile(...segments: string[]): string {
|
|
55
|
+
return join(PROJECT_ROOT, ...segments);
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
// Config singleton
|
|
33
59
|
let _config: DocuConfig | null = null;
|
|
34
60
|
|
package/.docu/node/types.ts
CHANGED
|
@@ -93,12 +93,30 @@ export interface DocuConfig {
|
|
|
93
93
|
plugins?: PluginEntry[];
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
export interface BuildCacheEntry {
|
|
97
|
+
hash: string;
|
|
98
|
+
mtime: number;
|
|
99
|
+
builtAt: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Version stamp stored as `__meta__` — extends entry so the index signature stays valid. */
|
|
103
|
+
export interface BuildCacheMeta extends BuildCacheEntry {
|
|
104
|
+
version: number;
|
|
105
|
+
runtime: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
96
108
|
export interface BuildCache {
|
|
97
|
-
[path: string]:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
[path: string]: BuildCacheEntry;
|
|
110
|
+
__meta__?: BuildCacheMeta;
|
|
111
|
+
__assets__?: BuildCacheEntry;
|
|
112
|
+
__bundle__?: BuildCacheEntry;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Narrow a cache slot to a real page/asset entry (guards `__meta__` shape). */
|
|
116
|
+
export function isCacheEntry(value: unknown): value is BuildCacheEntry {
|
|
117
|
+
if (typeof value !== "object" || value === null) return false;
|
|
118
|
+
const v = value as Record<string, unknown>;
|
|
119
|
+
return typeof v.hash === "string" && typeof v.mtime === "number" && typeof v.builtAt === "number";
|
|
102
120
|
}
|
|
103
121
|
|
|
104
122
|
export interface CliArgs {
|
package/bin/cli.js
CHANGED
|
@@ -7,9 +7,35 @@ import { spawnSync } from "node:child_process";
|
|
|
7
7
|
|
|
8
8
|
const __dirname = import.meta.dirname;
|
|
9
9
|
|
|
10
|
+
// Detect the package manager that invoked this binary. npm, pnpm, bun, and
|
|
11
|
+
// yarn set npm_config_user_agent when they run a bin; fall back to lockfiles
|
|
12
|
+
// in the target directory for direct invocations (e.g. `node bin/cli.js`).
|
|
13
|
+
function detectPkgManager(dir) {
|
|
14
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
15
|
+
// pnpm/yarn UA strings also contain "npm/?" — match specific tools first.
|
|
16
|
+
if (/bun\//.test(userAgent)) return "bun";
|
|
17
|
+
if (/pnpm\//.test(userAgent)) return "pnpm";
|
|
18
|
+
if (/yarn\//.test(userAgent)) return "yarn";
|
|
19
|
+
if (/npm\//.test(userAgent)) return "npm";
|
|
20
|
+
if (existsSync(join(dir, "bun.lock"))) return "bun";
|
|
21
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
22
|
+
if (existsSync(join(dir, "yarn.lock"))) return "yarn";
|
|
23
|
+
if (existsSync(join(dir, "package-lock.json"))) return "npm";
|
|
24
|
+
return "npm";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Explicit `--bun` on the flame CLI itself (e.g. `flame dev --bun`).
|
|
28
|
+
// Distinct from `bun run --bun dev` (consumed by bun, never reaches here).
|
|
29
|
+
// Stripped so downstream entries don't see an unknown flag.
|
|
30
|
+
const bunFlagIndex = process.argv.indexOf("--bun");
|
|
31
|
+
const wantBun = bunFlagIndex !== -1;
|
|
32
|
+
if (wantBun) process.argv.splice(bunFlagIndex, 1);
|
|
33
|
+
|
|
10
34
|
// Runtime detection — override with FLAME_RUNTIME=bun|node|deno for testing.
|
|
11
35
|
// Deno's npm compat layer may expose `Bun` via globals, check execPath first.
|
|
12
|
-
|
|
36
|
+
// The shebang (#!/usr/bin/env node) hides `typeof Bun` under `bun run`, so a
|
|
37
|
+
// bun PM also selects the bun runtime; the re-exec below lands on it.
|
|
38
|
+
let runtime =
|
|
13
39
|
process.env.FLAME_RUNTIME ||
|
|
14
40
|
(process.execPath.includes("deno")
|
|
15
41
|
? "deno"
|
|
@@ -18,6 +44,35 @@ const runtime =
|
|
|
18
44
|
: typeof Deno !== "undefined"
|
|
19
45
|
? "deno"
|
|
20
46
|
: "node");
|
|
47
|
+
if (wantBun) runtime = "bun";
|
|
48
|
+
else if (
|
|
49
|
+
!process.env.FLAME_RUNTIME &&
|
|
50
|
+
runtime === "node" &&
|
|
51
|
+
detectPkgManager(process.cwd()) === "bun"
|
|
52
|
+
)
|
|
53
|
+
runtime = "bun";
|
|
54
|
+
|
|
55
|
+
// Re-exec under `bun` when selected but this process is node. One spawn per
|
|
56
|
+
// command; FLAME_REEXEC guards the loop; silent node fallback when `bun` is
|
|
57
|
+
// missing unless explicitly requested.
|
|
58
|
+
if (
|
|
59
|
+
runtime === "bun" &&
|
|
60
|
+
typeof Bun === "undefined" &&
|
|
61
|
+
!process.execPath.includes("deno") &&
|
|
62
|
+
!process.env.FLAME_REEXEC
|
|
63
|
+
) {
|
|
64
|
+
const result = spawnSync("bun", [process.argv[1], ...process.argv.slice(2)], {
|
|
65
|
+
stdio: "inherit",
|
|
66
|
+
env: { ...process.env, FLAME_REEXEC: "1" },
|
|
67
|
+
});
|
|
68
|
+
if (result.error?.code === "ENOENT") {
|
|
69
|
+
if (wantBun || process.env.FLAME_RUNTIME)
|
|
70
|
+
console.error("flame: --bun requested but `bun` not found in PATH, falling back to node.");
|
|
71
|
+
runtime = "node";
|
|
72
|
+
} else {
|
|
73
|
+
process.exit(result.status ?? 0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
21
76
|
|
|
22
77
|
const COMMAND_MAP = {
|
|
23
78
|
bun: {
|
|
@@ -50,23 +105,6 @@ if (!COMMAND_MAP) {
|
|
|
50
105
|
|
|
51
106
|
const command = process.argv[2];
|
|
52
107
|
|
|
53
|
-
// Detect the package manager that invoked this binary. npm, pnpm, bun, and
|
|
54
|
-
// yarn set npm_config_user_agent when they run a bin; fall back to lockfiles
|
|
55
|
-
// in the target directory for direct invocations (e.g. `node bin/cli.js`).
|
|
56
|
-
function detectPkgManager(dir) {
|
|
57
|
-
const userAgent = process.env.npm_config_user_agent || "";
|
|
58
|
-
// pnpm/yarn UA strings also contain "npm/?" — match specific tools first.
|
|
59
|
-
if (/bun\//.test(userAgent)) return "bun";
|
|
60
|
-
if (/pnpm\//.test(userAgent)) return "pnpm";
|
|
61
|
-
if (/yarn\//.test(userAgent)) return "yarn";
|
|
62
|
-
if (/npm\//.test(userAgent)) return "npm";
|
|
63
|
-
if (existsSync(join(dir, "bun.lock"))) return "bun";
|
|
64
|
-
if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
65
|
-
if (existsSync(join(dir, "yarn.lock"))) return "yarn";
|
|
66
|
-
if (existsSync(join(dir, "package-lock.json"))) return "npm";
|
|
67
|
-
return "npm";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
108
|
// Parse flags
|
|
71
109
|
const themeIndex = process.argv.indexOf("--theme");
|
|
72
110
|
if (themeIndex !== -1 && themeIndex + 1 < process.argv.length) {
|