@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.
Files changed (60) hide show
  1. package/.docu/lib/build.deno.js +6 -9
  2. package/.docu/lib/build.deno.js.map +1 -0
  3. package/.docu/lib/build.impl-Bz_p421t.js +424 -0
  4. package/.docu/lib/build.impl-Bz_p421t.js.map +1 -0
  5. package/.docu/lib/build.impl-CtPrlYAE.js +2 -0
  6. package/.docu/lib/build.node.js +6 -9
  7. package/.docu/lib/build.node.js.map +1 -0
  8. package/.docu/lib/clean.js +25 -25
  9. package/.docu/lib/clean.js.map +1 -0
  10. package/.docu/lib/deno-DmEaKggj.js +20 -0
  11. package/.docu/lib/deno-DmEaKggj.js.map +1 -0
  12. package/.docu/lib/deploy.deno.js +9 -9
  13. package/.docu/lib/deploy.deno.js.map +1 -0
  14. package/.docu/lib/deploy.node.js +8 -8
  15. package/.docu/lib/deploy.node.js.map +1 -0
  16. package/.docu/lib/deploy.shared-fk8eM-r4.js +295 -0
  17. package/.docu/lib/deploy.shared-fk8eM-r4.js.map +1 -0
  18. package/.docu/lib/html.shared-FwgbE1WG.js +2696 -0
  19. package/.docu/lib/html.shared-FwgbE1WG.js.map +1 -0
  20. package/.docu/lib/logger-CQyNTE6L.js +306 -0
  21. package/.docu/lib/logger-CQyNTE6L.js.map +1 -0
  22. package/.docu/lib/node-DPTySdwG.js +80 -0
  23. package/.docu/lib/node-DPTySdwG.js.map +1 -0
  24. package/.docu/lib/paths-Bl2cdp9E.js +76 -0
  25. package/.docu/lib/paths-Bl2cdp9E.js.map +1 -0
  26. package/.docu/lib/preview.deno.js +7 -11
  27. package/.docu/lib/preview.deno.js.map +1 -0
  28. package/.docu/lib/preview.impl-CnsbLmDA.js +77 -0
  29. package/.docu/lib/preview.impl-CnsbLmDA.js.map +1 -0
  30. package/.docu/lib/preview.node.js +7 -11
  31. package/.docu/lib/preview.node.js.map +1 -0
  32. package/.docu/lib/server.deno.js +7 -12
  33. package/.docu/lib/server.deno.js.map +1 -0
  34. package/.docu/lib/server.impl-CXTzYqbF.js +358 -0
  35. package/.docu/lib/server.impl-CXTzYqbF.js.map +1 -0
  36. package/.docu/lib/server.node.js +7 -12
  37. package/.docu/lib/server.node.js.map +1 -0
  38. package/.docu/lib/utils-DA17MyQG.js +167 -0
  39. package/.docu/lib/utils-DA17MyQG.js.map +1 -0
  40. package/.docu/node/build.impl.ts +76 -11
  41. package/.docu/node/build.ts +83 -9
  42. package/.docu/node/cache-key.ts +311 -0
  43. package/.docu/node/hydrate.node.ts +124 -177
  44. package/.docu/node/hydrate.ts +13 -14
  45. package/.docu/node/mdx.ts +14 -2
  46. package/.docu/node/paths.ts +27 -1
  47. package/.docu/node/types.ts +23 -5
  48. package/bin/cli.js +56 -18
  49. package/bin/compile-lib.mjs +30 -28
  50. package/package.json +10 -16
  51. package/.docu/lib/build.impl-3ZOQORN3.js +0 -12
  52. package/.docu/lib/chunk-4IQXHHPF.js +0 -62
  53. package/.docu/lib/chunk-6EOUQCRM.js +0 -301
  54. package/.docu/lib/chunk-6HFBJUMG.js +0 -434
  55. package/.docu/lib/chunk-EOK6KATZ.js +0 -191
  56. package/.docu/lib/chunk-FERG25YW.js +0 -470
  57. package/.docu/lib/chunk-MQRFIDS4.js +0 -92
  58. package/.docu/lib/chunk-U7M5SK76.js +0 -330
  59. package/.docu/lib/chunk-UISOJ4RW.js +0 -114
  60. package/.docu/lib/chunk-URBATJEC.js +0 -2720
@@ -0,0 +1,2696 @@
1
+ import { a as DOCS_DIR, c as PROJECT_ROOT, d as loadDocuConfig, f as resolveProjectFile, l as STYLES_DIR, o as FRAMEWORK_ROOT, s as LIB_DIR, t as ASSETS_DIR, u as cleanOldBundles } from "./paths-Bl2cdp9E.js";
2
+ import { a as isExternalUrl, h as normalizeImporterPath, n as docsHtmlHref, o as scanMdxFiles, r as formatDate2, t as cn } from "./utils-DA17MyQG.js";
3
+ import { builtinModules, createRequire } from "node:module";
4
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
5
+ import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
6
+ import { mkdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
7
+ import { execFile } from "node:child_process";
8
+ import { promisify } from "node:util";
9
+ import { build } from "vite";
10
+ import { generateThemeCss, presetRegistry, resolveTheme } from "@docubook/themes-colors";
11
+ import { createHash } from "node:crypto";
12
+ import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
13
+ import { z } from "zod";
14
+ import { MDXRemote, createDefaultRehypePlugins, createDefaultRemarkPlugins, extractFrontmatter, extractFrontmatterWithContent, extractTocsFromRawMdx, serialize } from "@docubook/core";
15
+ import { createMdxComponents } from "@docubook/markdown";
16
+ import * as LucideIcons from "lucide-react";
17
+ import { ArrowUpIcon, ArrowUpRight, ChevronDown, ChevronLeft, ChevronRight, ListIcon, SquarePen } from "lucide-react";
18
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "@docubook/ui-react/breadcrumbs";
19
+ import { jsx, jsxs } from "react/jsx-runtime";
20
+ import { PaginationDocs } from "@docubook/ui-react/pagination";
21
+ //#region .docu/node/plugin-loader.ts
22
+ var NPM_PACKAGE_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
23
+ /**
24
+ * Resolve a plugin specifier to an absolute path or npm package name.
25
+ *
26
+ * Resolution rules:
27
+ * 1. Relative path (starts with `.`) → resolve from project root, guard traversal
28
+ * 2. Absolute path (starts with `/`) → guard traversal
29
+ * 3. Anything else → validate as npm package name, then pass to Bun's import
30
+ *
31
+ * Path traversal protection:
32
+ * - All file-system paths (relative & absolute) must resolve within PROJECT_ROOT.
33
+ * - This prevents `../../sensitive-file` or `/etc/passwd` from being imported.
34
+ */
35
+ /** @internal Exported for testing only. */
36
+ function resolveSpecifier(specifier) {
37
+ let resolved;
38
+ if (specifier.startsWith(".")) resolved = resolve(PROJECT_ROOT, specifier);
39
+ else if (specifier.startsWith("/")) resolved = specifier;
40
+ else {
41
+ if (!NPM_PACKAGE_RE.test(specifier)) throw new Error(`[plugin-loader] Invalid plugin specifier "${specifier}": must be a valid npm package name, relative path, or absolute path`);
42
+ return specifier;
43
+ }
44
+ const root = PROJECT_ROOT.endsWith("/") ? PROJECT_ROOT : PROJECT_ROOT + "/";
45
+ if (!resolved.startsWith(root)) throw new Error(`[plugin-loader] Path traversal blocked: "${specifier}" resolves outside project root`);
46
+ return resolved;
47
+ }
48
+ /**
49
+ * Load and instantiate all plugins from a config entries array.
50
+ *
51
+ * @param entries - Array of plugin entries from `docu.json`.
52
+ * Each entry is either:
53
+ * - a `string` (plugin specifier, no options)
54
+ * - a `[string, object]` tuple (plugin specifier + factory options)
55
+ * @returns Array of resolved `DocuBookPlugin` instances.
56
+ *
57
+ * @throws If a plugin specifier cannot be imported.
58
+ * @throws If a plugin's default export lacks a `name` property.
59
+ *
60
+ * @example
61
+ * const plugins = await loadPlugins([
62
+ * "@docubook/plugin-sitemap",
63
+ * ["@docubook/plugin-analytics", { id: "G-XXXXXXX" }],
64
+ * "./plugins/local-reading-time",
65
+ * ]);
66
+ */
67
+ async function loadPlugins(entries = []) {
68
+ const plugins = [];
69
+ for (const entry of entries) {
70
+ const [specifier, options] = Array.isArray(entry) ? entry : [entry, void 0];
71
+ const resolved = resolveSpecifier(specifier);
72
+ let mod;
73
+ try {
74
+ mod = await import(resolved);
75
+ } catch (err) {
76
+ const message = err instanceof Error ? err.message : String(err);
77
+ throw new Error(`[plugin-loader] Failed to import plugin "${specifier}": ${message}`, { cause: err });
78
+ }
79
+ const exported = mod.default;
80
+ let plugin;
81
+ if (typeof exported === "function") try {
82
+ plugin = exported(options);
83
+ } catch (err) {
84
+ const message = err instanceof Error ? err.message : String(err);
85
+ throw new Error(`[plugin-loader] Plugin factory "${specifier}" threw during initialization: ${message}`, { cause: err });
86
+ }
87
+ else if (exported && typeof exported === "object") plugin = exported;
88
+ else throw new Error(`[plugin-loader] Plugin "${specifier}" must export a default function or object. Got: ${typeof exported}`);
89
+ if (!plugin.name || typeof plugin.name !== "string") throw new Error(`[plugin-loader] Plugin "${specifier}" must have a valid 'name' property (string). Got: ${typeof plugin.name}`);
90
+ if (typeof plugin.setup !== "function") throw new Error(`[plugin-loader] Plugin "${specifier}" (name: "${plugin.name}") must have a 'setup(build)' function.`);
91
+ plugins.push(plugin);
92
+ }
93
+ return plugins;
94
+ }
95
+ //#endregion
96
+ //#region .docu/node/plugin-builder.ts
97
+ var BuildPluginBuilder = class {
98
+ config;
99
+ _handleRequest = [];
100
+ _injectBody = [];
101
+ _injectHead = [];
102
+ _onEnd = [];
103
+ _onLoad = [];
104
+ _onStart = [];
105
+ _rehypePlugins = [];
106
+ _remarkPlugins = [];
107
+ _transformFrontmatter = [];
108
+ _transformHtml = [];
109
+ constructor(config) {
110
+ this.config = config;
111
+ }
112
+ /**
113
+ * Collect and deduplicate all `<body>` injection snippets from registered plugins.
114
+ * Each callback is executed in registration order; plugin errors are wrapped
115
+ * with a descriptive message.
116
+ *
117
+ * @param context - Current page context passed to each injectBody callback.
118
+ * @returns Deduplicated array of HTML strings to inject before `</body>`.
119
+ * @throws Error if any injectBody callback throws — wraps original error as cause.
120
+ */
121
+ collectBody(context) {
122
+ const items = [];
123
+ for (const cb of this._injectBody) try {
124
+ const result = cb(context);
125
+ if (result) this.collectItems(items, result, "injectBody");
126
+ } catch (err) {
127
+ throw new Error(`[plugin] injectBody callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
128
+ }
129
+ return [...new Set(items)];
130
+ }
131
+ /**
132
+ * Collect and deduplicate all `<head>` injection snippets from registered plugins.
133
+ * Each callback is executed in registration order; plugin errors are wrapped
134
+ * with a descriptive message.
135
+ *
136
+ * @param context - Current page context passed to each injectHead callback.
137
+ * @returns Deduplicated array of HTML strings to inject before `</head>`.
138
+ * @throws Error if any injectHead callback throws — wraps original error as cause.
139
+ */
140
+ collectHead(context) {
141
+ const items = [];
142
+ for (const cb of this._injectHead) try {
143
+ const result = cb(context);
144
+ if (result) this.collectItems(items, result, "injectHead");
145
+ } catch (err) {
146
+ throw new Error(`[plugin] injectHead callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
147
+ }
148
+ return [...new Set(items)];
149
+ }
150
+ /**
151
+ * Collect all rehype plugin arrays from registered rehypePlugins callbacks.
152
+ * Results from all plugins are flattened into a single array.
153
+ *
154
+ * @returns Flattened array of rehype plugin instances applied after default set.
155
+ * @throws Error if any rehypePlugins callback throws — wraps original error as cause.
156
+ */
157
+ collectRehypePlugins() {
158
+ const plugins = [];
159
+ for (const cb of this._rehypePlugins) try {
160
+ plugins.push(...cb());
161
+ } catch (err) {
162
+ throw new Error(`[plugin] rehypePlugins callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
163
+ }
164
+ return plugins;
165
+ }
166
+ /**
167
+ * Collect all remark plugin arrays from registered remarkPlugins callbacks.
168
+ * Results from all plugins are flattened into a single array.
169
+ *
170
+ * @returns Flattened array of remark plugin instances applied after default set.
171
+ * @throws Error if any remarkPlugins callback throws — wraps original error as cause.
172
+ */
173
+ collectRemarkPlugins() {
174
+ const plugins = [];
175
+ for (const cb of this._remarkPlugins) try {
176
+ plugins.push(...cb());
177
+ } catch (err) {
178
+ throw new Error(`[plugin] remarkPlugins callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
179
+ }
180
+ return plugins;
181
+ }
182
+ /**
183
+ * Register a callback to intercept incoming requests during development.
184
+ * The **first** callback to return a `Response` short-circuits all subsequent handlers.
185
+ * Errors inside callbacks are caught and logged — execution continues to next handler.
186
+ *
187
+ * @param callback - Receives the Request and dev server context. Return Response or void.
188
+ *
189
+ * @example
190
+ * build.handleRequest((req, ctx) => {
191
+ * if (new URL(req.url).pathname === "/api/status") {
192
+ * return new Response(JSON.stringify({ ok: true }), {
193
+ * headers: { "Content-Type": "application/json" },
194
+ * });
195
+ * }
196
+ * });
197
+ */
198
+ handleRequest(callback) {
199
+ this._handleRequest.push(callback);
200
+ }
201
+ /**
202
+ * Register a callback that returns HTML strings to inject before `</body>`.
203
+ * Results from all plugins are merged, deduplicated, and served via `collectBody()`.
204
+ *
205
+ * @param callback - Returns a single HTML string or an array. Called once per page.
206
+ *
207
+ * @example
208
+ * build.injectBody(() => `<div id="chat-widget"></div>`);
209
+ */
210
+ injectBody(callback) {
211
+ this._injectBody.push(callback);
212
+ }
213
+ /**
214
+ * Register a callback that returns HTML strings to inject inside `<head>`.
215
+ * Results from all plugins are merged, deduplicated, and served via `collectHead()`.
216
+ *
217
+ * @param callback - Returns a single HTML string or an array. Called once per page.
218
+ *
219
+ * @example
220
+ * build.injectHead(() => `<script async src="https://cdn.example.com/analytics.js"><\/script>`);
221
+ */
222
+ injectHead(callback) {
223
+ this._injectHead.push(callback);
224
+ }
225
+ /**
226
+ * Register a callback to run once after all pages are built.
227
+ * Receives the resolved config and aggregated page metadata.
228
+ * Errors thrown by the callback propagate to the caller via `runOnEnd()`.
229
+ *
230
+ * @param callback - Receives config and page metadata array. May return a Promise.
231
+ *
232
+ * @example
233
+ * build.onEnd(async (config, pages) => {
234
+ * const xml = generateSitemap(pages, config.meta.baseURL);
235
+ * const out = ".docu/dist/sitemap.xml";
236
+ * // Bun.write on Bun for speed, writeFile on Node/Deno
237
+ * await (typeof Bun !== "undefined"
238
+ * ? Bun.write(out, xml)
239
+ * : writeFile(out, xml));
240
+ * });
241
+ */
242
+ onEnd(callback) {
243
+ this._onEnd.push(callback);
244
+ }
245
+ /**
246
+ * Register a callback to transform raw file content before MDX compilation.
247
+ * Filtered by regex against the file's relative path — only the **first** matching
248
+ * handler's result is used.
249
+ * Errors thrown by the callback propagate to the caller via `runOnLoad()`.
250
+ *
251
+ * @param args.filter - RegExp matched against the file's relative path.
252
+ * @param args.namespace - Optional namespace prefix (reserved for future use).
253
+ * @param callback - Receives file path and raw content. Return new contents or void.
254
+ *
255
+ * @example
256
+ * build.onLoad({ filter: /\.md$/ }, ({ path, content }) => {
257
+ * return { contents: `<!-- preprocessed -->\n${content}`, loader: "mdx" };
258
+ * });
259
+ */
260
+ onLoad(args, callback) {
261
+ this._onLoad.push({
262
+ ...args,
263
+ fn: callback
264
+ });
265
+ }
266
+ /**
267
+ * Register a callback to run once before the build starts.
268
+ * Receives the resolved DocuConfig for validation or resource initialization.
269
+ * Errors thrown by the callback propagate to the caller via `runOnStart()`.
270
+ *
271
+ * @param callback - Receives the resolved config. May return a Promise.
272
+ *
273
+ * @example
274
+ * build.onStart((config) => {
275
+ * if (!config.meta.baseURL) throw new Error("baseURL required");
276
+ * });
277
+ */
278
+ onStart(callback) {
279
+ this._onStart.push(callback);
280
+ }
281
+ /**
282
+ * Register additional rehype (HTML) plugins for the MDX compilation pipeline.
283
+ * Results from all plugins are merged and applied **after** the default set.
284
+ *
285
+ * @param callback - Returns an array of rehype plugins.
286
+ *
287
+ * @example
288
+ * build.rehypePlugins(() => [require("rehype-autolink-headings")]);
289
+ */
290
+ rehypePlugins(callback) {
291
+ this._rehypePlugins.push(callback);
292
+ }
293
+ /**
294
+ * Register additional remark (Markdown) plugins for the MDX compilation pipeline.
295
+ * Results from all plugins are merged and applied **after** the default set.
296
+ *
297
+ * @param callback - Returns an array of remark plugins.
298
+ *
299
+ * @example
300
+ * build.remarkPlugins(() => [require("remark-custom-heading-id")]);
301
+ */
302
+ remarkPlugins(callback) {
303
+ this._remarkPlugins.push(callback);
304
+ }
305
+ /**
306
+ * Execute all registered handleRequest callbacks sequentially.
307
+ * Stops and returns the **first** `Response` returned by any callback.
308
+ * Errors inside individual callbacks are caught and logged — execution
309
+ * continues to the next callback without throwing.
310
+ *
311
+ * @param req - The incoming HTTP Request.
312
+ * @param context - Dev server context (port, hostname).
313
+ * @returns A Response if a callback intercepted the request, or null if none did.
314
+ */
315
+ async runHandleRequest(req, context) {
316
+ for (let i = 0; i < this._handleRequest.length; i++) try {
317
+ const result = await this._handleRequest[i](req, context);
318
+ if (result instanceof Response) return result;
319
+ } catch (err) {
320
+ console.error(`[plugin] handleRequest callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
321
+ }
322
+ return null;
323
+ }
324
+ /**
325
+ * Execute all registered onEnd callbacks sequentially with the resolved
326
+ * config and aggregated page metadata.
327
+ * Errors inside individual callbacks are caught and logged — execution
328
+ * continues to the next callback without throwing.
329
+ *
330
+ * @param pages - Array of metadata for every built page.
331
+ */
332
+ async runOnEnd(pages) {
333
+ for (let i = 0; i < this._onEnd.length; i++) try {
334
+ await this._onEnd[i](this.config, pages);
335
+ } catch (err) {
336
+ console.error(`[plugin] onEnd callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
337
+ }
338
+ }
339
+ /**
340
+ * Execute registered onLoad handlers in registration order against a file.
341
+ * Only the **first** handler whose `filter` regex matches the path and returns
342
+ * a result is applied. If a matching handler throws, the error is logged and
343
+ * subsequent handlers are tried.
344
+ *
345
+ * @param path - Relative path of the file being loaded.
346
+ * @param content - Raw file content.
347
+ * @returns Transformed content if a matching handler returned it, or null.
348
+ */
349
+ async runOnLoad(path, content) {
350
+ for (const handler of this._onLoad) if (handler.filter.test(path)) try {
351
+ const result = await handler.fn({
352
+ path,
353
+ content
354
+ });
355
+ if (result) return result;
356
+ } catch (err) {
357
+ console.error(`[plugin] onLoad handler for filter ${handler.filter} error: ${err instanceof Error ? err.message : String(err)}`);
358
+ }
359
+ return null;
360
+ }
361
+ /**
362
+ * Execute all registered onStart callbacks sequentially.
363
+ * Each callback receives the resolved DocuConfig.
364
+ * Errors inside individual callbacks are caught and logged — execution
365
+ * continues to the next callback without throwing.
366
+ */
367
+ async runOnStart() {
368
+ for (let i = 0; i < this._onStart.length; i++) try {
369
+ await this._onStart[i](this.config);
370
+ } catch (err) {
371
+ console.error(`[plugin] onStart callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
372
+ }
373
+ }
374
+ /**
375
+ * Execute the transformFrontmatter chain in waterfall pattern.
376
+ * Each callback receives the **previous** callback's return value (or the
377
+ * original frontmatter for the first). Callbacks that return `undefined` or
378
+ * `null` pass the current value through unchanged.
379
+ * Callbacks that return a non-object (string, number, array) are skipped
380
+ * with a console warning — only plain objects are accepted.
381
+ * Errors inside individual callbacks are caught and logged — the current
382
+ * frontmatter passes through unchanged for that step.
383
+ *
384
+ * @param frontmatter - Initial frontmatter object parsed from MDX.
385
+ * @param context - Page context with slug, filePath, and raw content.
386
+ * @returns The final transformed frontmatter object.
387
+ */
388
+ async runTransformFrontmatterChain(frontmatter, context) {
389
+ let result = frontmatter;
390
+ for (let i = 0; i < this._transformFrontmatter.length; i++) try {
391
+ const next = await this._transformFrontmatter[i](result, context);
392
+ if (next !== void 0 && next !== null) {
393
+ if (typeof next === "object" && !Array.isArray(next)) result = next;
394
+ else console.warn(`[plugin] transformFrontmatter callback #${i + 1} returned invalid type (expected a plain object), skipping`);
395
+ }
396
+ } catch (err) {
397
+ console.error(`[plugin] transformFrontmatter callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
398
+ }
399
+ return result;
400
+ }
401
+ /**
402
+ * Execute the transformHtml chain in pipeline pattern.
403
+ * Each callback receives the **previous** callback's return value (or the
404
+ * original HTML for the first). Every callback **must** return a string.
405
+ * Errors inside individual callbacks are caught and logged — the current
406
+ * HTML passes through unchanged for that step.
407
+ *
408
+ * @param html - The initial HTML string.
409
+ * @param context - Full page context (slug, filePath, frontmatter, content, config).
410
+ * @returns The final transformed HTML string.
411
+ */
412
+ async runTransformHtmlChain(html, context) {
413
+ let result = html;
414
+ for (let i = 0; i < this._transformHtml.length; i++) try {
415
+ result = await this._transformHtml[i](result, context);
416
+ } catch (err) {
417
+ console.error(`[plugin] transformHtml callback #${i + 1} error: ${err instanceof Error ? err.message : String(err)}`);
418
+ }
419
+ return result;
420
+ }
421
+ /**
422
+ * Register a callback to mutate frontmatter before MDX compilation.
423
+ * Callbacks are chained in a waterfall: the return value of one is passed
424
+ * as input to the next. Return `undefined` to pass through unchanged.
425
+ *
426
+ * **Note:** Only plain objects are accepted as return values. Returning
427
+ * a string, number, or array will be silently skipped with a warning.
428
+ * Plugin authors should validate their return values before returning.
429
+ *
430
+ * @param callback - Receives frontmatter object and page context.
431
+ *
432
+ * @example
433
+ * build.transformFrontmatter((fm, ctx) => {
434
+ * const wordCount = ctx.content!.split(/\s+/).length;
435
+ * return { ...fm, readingTime: `${Math.ceil(wordCount / 200)} min read` };
436
+ * });
437
+ */
438
+ transformFrontmatter(callback) {
439
+ this._transformFrontmatter.push(callback);
440
+ }
441
+ /**
442
+ * Register a callback to transform the final HTML string per page.
443
+ * This is the **last** hook before the HTML is written to disk.
444
+ * Callbacks are chained in a pipeline: each receives the previous callback's output.
445
+ *
446
+ * @param callback - Receives HTML string and full page context. Must return HTML.
447
+ *
448
+ * @example
449
+ * build.transformHtml((html, ctx) => {
450
+ * return html.replace(/https?:\/\/old-domain\.com\//g, "/");
451
+ * });
452
+ */
453
+ transformHtml(callback) {
454
+ this._transformHtml.push(callback);
455
+ }
456
+ /**
457
+ * Collect items from a callback result, filtering only valid strings.
458
+ * Non-string items and unexpected types are logged as warnings.
459
+ */
460
+ collectItems(items, result, hookName) {
461
+ if (Array.isArray(result)) for (const item of result) if (typeof item === "string") items.push(item);
462
+ else console.warn(`[plugin] ${hookName} callback returned non-string item (got ${typeof item}), skipping`);
463
+ else if (typeof result === "string") items.push(result);
464
+ else console.warn(`[plugin] ${hookName} callback returned unexpected type (got ${typeof result}), expected string or string[], skipping`);
465
+ }
466
+ };
467
+ /** Toolchain fingerprint: Bun version on Bun, Deno version on Deno, Node elsewhere. */
468
+ function runtimeStamp() {
469
+ const g = globalThis;
470
+ const bun = g.Bun;
471
+ if (typeof bun?.version === "string" && bun.version.length > 0) return `bun-${bun.version}`;
472
+ const deno = g.Deno;
473
+ if (typeof deno?.version?.deno === "string" && deno.version.deno.length > 0) return `deno-${deno.version.deno}`;
474
+ const proc = g.process;
475
+ if (typeof proc?.version === "string" && proc.version.length > 0) return `node-${proc.version}`;
476
+ return "node-unknown";
477
+ }
478
+ /**
479
+ * True when a CSS file can change Tailwind v4 output.
480
+ * v4 is CSS-first: `@theme`, `@source`, `@plugin`, `@custom-variant`,
481
+ * `@utility`, `@config`, `@apply`/`@variant`, and `@import "tailwindcss"`
482
+ * all live in CSS. Plain CSS without these cannot affect the CLI output,
483
+ * so it is excluded to avoid false cache busts. Broad match deliberate:
484
+ * false positive busts safe, false negative serves stale CSS.
485
+ */
486
+ function isTailwindRelevantCss(content) {
487
+ return content.includes("@theme") || content.includes("@source") || content.includes("@plugin") || content.includes("@custom-variant") || content.includes("@utility") || content.includes("@config") || content.includes("@apply") || content.includes("@variant") || content.includes("@layer") || content.includes("tailwindcss");
488
+ }
489
+ var IMPORT_RE = /@import\s+(?:url\()?["']([^"']+)["']/g;
490
+ var MAX_IMPORT_DEPTH = 10;
491
+ /** Resolve `./` + `../` imports only — bare specifiers are version-pinned deps. */
492
+ function resolveRelativeImport(spec, fromDir) {
493
+ if (!spec.startsWith(".")) return void 0;
494
+ const clean = spec.split("?")[0].split("#")[0];
495
+ if (clean.length === 0) return void 0;
496
+ return join(fromDir, clean);
497
+ }
498
+ /**
499
+ * Read a CSS file plus transitively imported relative files.
500
+ * Non-relevant files contribute "" themselves, but their imports are still
501
+ * followed (nested file may carry `@theme`). `visited` breaks import cycles.
502
+ * Missing/unreadable files resolve to "" — never throws.
503
+ */
504
+ function readCssWithImports(path, visited, depth = 0) {
505
+ if (depth > MAX_IMPORT_DEPTH || visited.has(path)) return "";
506
+ visited.add(path);
507
+ let content = "";
508
+ try {
509
+ if (!existsSync(path)) return "";
510
+ content = readFileSync(path, "utf-8");
511
+ } catch {
512
+ return "";
513
+ }
514
+ let out = isTailwindRelevantCss(content) ? content : "";
515
+ try {
516
+ const dir = join(path, "..");
517
+ for (const m of content.matchAll(IMPORT_RE)) {
518
+ const resolved = resolveRelativeImport(m[1] ?? "", dir);
519
+ if (resolved) out += readCssWithImports(resolved, visited, depth + 1);
520
+ }
521
+ } catch {}
522
+ return out;
523
+ }
524
+ function scanCssDir(dir, visited) {
525
+ let out = "";
526
+ try {
527
+ const entries = readdirSync(dir, { withFileTypes: true });
528
+ for (const e of entries) {
529
+ const full = join(dir, e.name);
530
+ if (e.isDirectory()) {
531
+ if (e.name === "assets" || e.name.startsWith(".") || e.name === "node_modules") continue;
532
+ out += scanCssDir(full, visited);
533
+ } else if (e.name.endsWith(".css")) out += readCssWithImports(full, visited);
534
+ }
535
+ } catch {}
536
+ return out;
537
+ }
538
+ function scanRootCss(root, visited) {
539
+ let out = "";
540
+ try {
541
+ const entries = readdirSync(root, { withFileTypes: true });
542
+ for (const e of entries) if (e.isFile() && e.name.endsWith(".css")) out += readCssWithImports(join(root, e.name), visited);
543
+ } catch {}
544
+ return out;
545
+ }
546
+ /** Hash @theme/@source/@plugin-bearing CSS in project docs/ + root *.css. */
547
+ function tailwindCssThemeInputs(root) {
548
+ const visited = /* @__PURE__ */ new Set();
549
+ return scanCssDir(join(root, "docs"), visited) + scanRootCss(root, visited);
550
+ }
551
+ var TAILWIND_CONFIG_FILES = [
552
+ "tailwind.config.ts",
553
+ "tailwind.config.js",
554
+ "tailwind.config.mjs",
555
+ "tailwind.config.cjs",
556
+ "tailwind.config.mts",
557
+ "tailwind.config.cts",
558
+ "postcss.config.js",
559
+ "postcss.config.mjs",
560
+ "postcss.config.cjs"
561
+ ];
562
+ /**
563
+ * JS config still affects v4 when referenced via `@config`
564
+ * (plus PostCSS pipeline config). Content hashed when present.
565
+ */
566
+ function tailwindJsConfigInputs(root) {
567
+ let out = "";
568
+ for (const name of TAILWIND_CONFIG_FILES) try {
569
+ const p = join(root, name);
570
+ if (existsSync(p)) out += readFileSync(p, "utf-8") + "\0";
571
+ } catch {}
572
+ return out;
573
+ }
574
+ function readInstalledVersion(pkg, roots) {
575
+ for (const root of roots) try {
576
+ const p = join(root, "node_modules", ...pkg.split("/"), "package.json");
577
+ if (existsSync(p)) {
578
+ const parsed = JSON.parse(readFileSync(p, "utf-8"));
579
+ if (typeof parsed.version === "string" && parsed.version.length > 0) return parsed.version;
580
+ }
581
+ } catch {}
582
+ return "";
583
+ }
584
+ /** Pin resolved CSS-affecting deps into the key (installed version wins). */
585
+ function tailwindVersionPins(root) {
586
+ const names = [
587
+ "tailwindcss",
588
+ "@tailwindcss/cli",
589
+ "@tailwindcss/typography",
590
+ "daisyui"
591
+ ];
592
+ let extra = "";
593
+ const roots = [root, FRAMEWORK_ROOT];
594
+ for (const name of names) {
595
+ const installed = readInstalledVersion(name, roots);
596
+ if (installed) extra += `${name}@${installed}\0`;
597
+ }
598
+ try {
599
+ const pkgPath = join(root, "package.json");
600
+ if (existsSync(pkgPath)) {
601
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
602
+ for (const name of names) {
603
+ const range = pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? "";
604
+ if (range) extra += `${name}:${range}\0`;
605
+ }
606
+ }
607
+ } catch {}
608
+ return extra;
609
+ }
610
+ /**
611
+ * Extra Tailwind inputs beyond globals.css + theme.
612
+ * v4 is CSS-first: user overrides live in `@theme`/`@source`/`@plugin`
613
+ * blocks inside CSS files under the project root (e.g. docs slash star dot css),
614
+ * plus JS config referenced via `@config` and installed plugin versions.
615
+ * Missing files resolve to "" — never throws.
616
+ */
617
+ function tailwindExtraInputs(root = resolveProjectFile()) {
618
+ return tailwindCssThemeInputs(root) + "\0" + tailwindJsConfigInputs(root) + tailwindVersionPins(root);
619
+ }
620
+ /**
621
+ * Shared Tailwind cache key: globals.css + theme + tailwind config +
622
+ * toolchain version. Used by both hydrate.ts (Bun) and hydrate.node.ts
623
+ * (Vite) so the two runtimes agree on filenames. Segments joined with NUL
624
+ * so `("ab","c")` and `("a","bc")` hash differently.
625
+ */
626
+ function computeTailwindCacheKey(globals, themeSuffix, projectRoot = resolveProjectFile()) {
627
+ const h = createHash("sha256");
628
+ h.update(globals);
629
+ h.update("\0");
630
+ h.update(themeSuffix);
631
+ h.update("\0");
632
+ h.update(tailwindExtraInputs(projectRoot));
633
+ h.update("\0");
634
+ h.update(runtimeStamp());
635
+ h.update("\0");
636
+ h.update(`v3`);
637
+ return h.digest("hex").slice(0, 16);
638
+ }
639
+ /** Read globals.css content ("" when missing). Shared by both hydrators. */
640
+ function readGlobalsCss() {
641
+ const globalsPath = join(STYLES_DIR, "globals.css");
642
+ return existsSync(globalsPath) ? readFileSync(globalsPath, "utf-8") : "";
643
+ }
644
+ /**
645
+ * Atomic file write: tmp + rename so a crash mid-write never leaves a
646
+ * half-written cache/CSS behind. Tmp name includes pid so parallel
647
+ * builds (`bun run --parallel`) do not clobber each other.
648
+ */
649
+ async function atomicWriteFile(writeFile, rename, unlink, target, data) {
650
+ const proc = globalThis.process;
651
+ const tmp = `${target}.tmp-${typeof proc?.pid === "number" ? proc.pid : Math.floor(Math.random() * 1e9)}-${Date.now()}`;
652
+ try {
653
+ await writeFile(tmp, data);
654
+ await rename(tmp, target);
655
+ } catch (err) {
656
+ try {
657
+ await unlink(tmp);
658
+ } catch {}
659
+ throw err;
660
+ }
661
+ }
662
+ /** Wire memory-pressure hook once: OS low-memory → drop parsed page maps. */
663
+ var memoryPressureHooked = false;
664
+ function hookMemoryPressure(clear) {
665
+ if (memoryPressureHooked) return;
666
+ memoryPressureHooked = true;
667
+ try {
668
+ globalThis.process?.on?.("memoryPressure", () => {
669
+ try {
670
+ clear();
671
+ } catch {}
672
+ });
673
+ } catch {}
674
+ }
675
+ /**
676
+ * Hash of compiled MDX module sources (sorted keys + content).
677
+ * Used to skip the JS bundle rebuild when no page content changed.
678
+ */
679
+ function hashMdxSources(mdxSources) {
680
+ const h = createHash("sha256");
681
+ const slugs = Object.keys(mdxSources).sort();
682
+ h.update(`v3:${runtimeStamp()}:`);
683
+ for (const slug of slugs) {
684
+ h.update(slug);
685
+ h.update("\0");
686
+ h.update(mdxSources[slug]);
687
+ h.update("\0");
688
+ }
689
+ return h.digest("hex").slice(0, 16);
690
+ }
691
+ //#endregion
692
+ //#region .docu/node/fs-scanner.ts
693
+ function toTitleCase$1(str) {
694
+ return str.replace(/-/g, " ").replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
695
+ }
696
+ function isDocFile(filename) {
697
+ const ext = extname(filename).toLowerCase();
698
+ return [".mdx", ".md"].includes(ext);
699
+ }
700
+ function normalizePath(path) {
701
+ return path.split(sep).join("/");
702
+ }
703
+ /**
704
+ * Recursively scan a directory and return file tree
705
+ *
706
+ * @param dirPath - Absolute path to scan
707
+ * @param docsRoot - Root docs folder for relative path calculation
708
+ */
709
+ function scanDir(dirPath, docsRoot) {
710
+ const nodes = [];
711
+ let entries;
712
+ try {
713
+ entries = readdirSync(dirPath).sort();
714
+ } catch (err) {
715
+ if (err.code !== "ENOENT") throw err;
716
+ return nodes;
717
+ }
718
+ for (const entry of entries) {
719
+ if (entry.startsWith(".") || entry === "assets") continue;
720
+ const absPath = join(dirPath, entry);
721
+ try {
722
+ const stat = statSync(absPath);
723
+ if (stat.isDirectory()) {
724
+ const children = scanDir(absPath, docsRoot);
725
+ if (children.length > 0) nodes.push({
726
+ name: entry,
727
+ relPath: normalizePath(relative(docsRoot, absPath)),
728
+ absPath,
729
+ isDirectory: true,
730
+ children
731
+ });
732
+ } else if (stat.isFile() && isDocFile(entry)) nodes.push({
733
+ name: entry,
734
+ relPath: normalizePath(relative(docsRoot, absPath).replace(/\.(mdx|md)$/, "")),
735
+ absPath,
736
+ isDirectory: false
737
+ });
738
+ } catch (err) {
739
+ if (err.code !== "ENOENT") throw err;
740
+ continue;
741
+ }
742
+ }
743
+ return nodes;
744
+ }
745
+ function fileNodesToRoutes(nodes, parentHref = "") {
746
+ const routes = [];
747
+ for (const node of nodes) if (!node.isDirectory) {
748
+ const baseName = node.name.replace(/\.(mdx|md)$/, "");
749
+ if (/^(index|readme)$/i.test(baseName)) continue;
750
+ const href = `/${node.relPath.split("/").pop()}`;
751
+ routes.push({
752
+ title: toTitleCase$1(baseName),
753
+ href
754
+ });
755
+ } else {
756
+ const dirTitle = toTitleCase$1(node.name);
757
+ const dirHref = `/${node.relPath.split("/").pop()}`;
758
+ const children = fileNodesToRoutes(node.children || [], dirHref);
759
+ if (children.length === 0) continue;
760
+ if ((node.children || []).some((c) => !c.isDirectory && /^(index|readme)\.(mdx|md)$/i.test(c.name))) routes.push({
761
+ title: dirTitle,
762
+ href: dirHref,
763
+ ...parentHref === "" && { context: {
764
+ title: dirTitle,
765
+ icon: "CircleHelp",
766
+ description: dirTitle
767
+ } },
768
+ items: children
769
+ });
770
+ else routes.push({
771
+ title: dirTitle,
772
+ href: dirHref,
773
+ noLink: true,
774
+ ...parentHref === "" && { context: {
775
+ title: dirTitle,
776
+ icon: "CircleHelp",
777
+ description: dirTitle
778
+ } },
779
+ items: children
780
+ });
781
+ }
782
+ return routes;
783
+ }
784
+ /**
785
+ * Scan docs folder and convert to DocuRoute[]
786
+ *
787
+ * @param docsPath - Path to docs folder (default: "./docs")
788
+ */
789
+ function scanDocsFolder(docsPath = "./docs") {
790
+ const absDocsPath = join(process.cwd(), docsPath);
791
+ return fileNodesToRoutes(scanDir(absDocsPath, absDocsPath));
792
+ }
793
+ /**
794
+ * Resolve routes:
795
+ * 1. If docu.json has routes, use them (manual priority)
796
+ * 2. Else, scan docs folder (auto-detect)
797
+ *
798
+ * @param docuJsonRoutes - Routes from docu.json (optional)
799
+ */
800
+ function resolveRoutes(docuJsonRoutes) {
801
+ if (docuJsonRoutes && docuJsonRoutes.length > 0) return docuJsonRoutes;
802
+ return scanDocsFolder();
803
+ }
804
+ //#endregion
805
+ //#region .docu/node/hydrate.ts
806
+ var themeRegistry = presetRegistry;
807
+ /**
808
+ * Read the effective theme config with this priority:
809
+ * 1. FLAME_THEME env var (CLI --theme flag)
810
+ * 2. docu.json theme.colors field
811
+ */
812
+ function getThemeConfig() {
813
+ if (process.env.FLAME_THEME) return process.env.FLAME_THEME;
814
+ return loadDocuConfig().themes?.colors;
815
+ }
816
+ /**
817
+ * Append theme CSS to compiled Tailwind output based on theme config.
818
+ */
819
+ function buildThemeCss(baseCss, themeConfig) {
820
+ try {
821
+ const resolved = resolveTheme(themeConfig, themeRegistry);
822
+ return baseCss + "\n" + generateThemeCss(resolved);
823
+ } catch (err) {
824
+ console.warn(`[flame] Failed to resolve theme CSS: ${err instanceof Error ? err.message : String(err)}`);
825
+ return baseCss;
826
+ }
827
+ }
828
+ /**
829
+ * Compute inline theme CSS for FOUC prevention.
830
+ * Returns undefined if no theme is configured or on error.
831
+ */
832
+ function computeInlineThemeCss() {
833
+ try {
834
+ const themeColors = getThemeConfig();
835
+ if (themeColors) {
836
+ const resolved = resolveTheme(themeColors, themeRegistry);
837
+ return generateThemeCss(resolved);
838
+ }
839
+ } catch (err) {
840
+ console.warn(`[flame] Failed to compute inline theme CSS: ${err instanceof Error ? err.message : String(err)}`);
841
+ }
842
+ }
843
+ //#endregion
844
+ //#region .docu/node/hydrate.node.ts
845
+ /**
846
+ * Client bundle builder for Node/Deno runtimes.
847
+ *
848
+ * Uses Vite with Rolldown plugins to produce a browser-ready client
849
+ * bundle (JS + CSS) from the same components Bun.build handles natively.
850
+ * Theme helpers (getThemeConfig, buildThemeCss, computeInlineThemeCss)
851
+ * are re-exported from `hydrate.ts` — that module's `buildClientBundle`
852
+ * is Bun-only and unused here.
853
+ */
854
+ /** Extract Lucide icon names from user docu.json configuration. */
855
+ function extractConfigIcons(config) {
856
+ const icons = [];
857
+ const pushIf = (s) => {
858
+ if (s) icons.push(s);
859
+ };
860
+ config.home?.hero?.actions?.forEach((a) => pushIf(a.icon));
861
+ config.home?.features?.forEach((f) => pushIf(f.icon));
862
+ (function walk(routes) {
863
+ for (const r of routes) {
864
+ pushIf(r.context?.icon);
865
+ if (r.items) walk(r.items);
866
+ }
867
+ })(config.routes ?? []);
868
+ return [...new Set(icons.filter((n) => /^[A-Z]/.test(n)))];
869
+ }
870
+ var execFileAsync = promisify(execFile);
871
+ /** Resolve the @tailwindcss/cli binary path from the installed package. */
872
+ function resolveTailwindBin() {
873
+ const require = createRequire(import.meta.url);
874
+ const pkgPath = require.resolve("@tailwindcss/cli/package.json");
875
+ const pkg = require(pkgPath);
876
+ const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin.tailwindcss;
877
+ return join(dirname(pkgPath), binRel);
878
+ }
879
+ /** Compute a cache key from globals.css + theme + config + toolchain. */
880
+ function tailwindCacheKey() {
881
+ const globalsContent = readGlobalsCss();
882
+ let themeSuffix = "";
883
+ try {
884
+ const themeColors = getThemeConfig();
885
+ if (themeColors) themeSuffix = JSON.stringify(themeColors);
886
+ } catch {}
887
+ return computeTailwindCacheKey(globalsContent, themeSuffix);
888
+ }
889
+ /**
890
+ * Build Tailwind CSS with content-based caching.
891
+ * If a CSS file for the current input hash already exists, skip the subprocess.
892
+ * Returns the filename (e.g. "client-abc123.css") and CSS content.
893
+ */
894
+ async function buildTailwindCss(key) {
895
+ const cachedFile = `client-${key}.css`;
896
+ const cachedPath = join(ASSETS_DIR, cachedFile);
897
+ if (existsSync(cachedPath)) return {
898
+ file: cachedFile,
899
+ content: readFileSync(cachedPath, "utf-8")
900
+ };
901
+ const tmpCss = join(ASSETS_DIR, `_tmp-${key}.css`);
902
+ const bin = resolveTailwindBin();
903
+ const twArgs = [
904
+ "-i",
905
+ join(STYLES_DIR, "globals.css"),
906
+ "-o",
907
+ tmpCss,
908
+ "--minify"
909
+ ];
910
+ const args = "Deno" in globalThis ? [
911
+ "run",
912
+ "-A",
913
+ bin,
914
+ ...twArgs
915
+ ] : [bin, ...twArgs];
916
+ try {
917
+ await execFileAsync(process.execPath, args, { maxBuffer: 16777216 });
918
+ } catch (err) {
919
+ const stderr = err.stderr ?? String(err);
920
+ throw new Error(`Tailwind CSS build failed:\n${stderr}`, { cause: err });
921
+ }
922
+ let cssContent = await readFile(tmpCss, "utf-8");
923
+ await unlink(tmpCss).catch(() => {});
924
+ try {
925
+ const themeColors = getThemeConfig();
926
+ if (themeColors) cssContent = buildThemeCss(cssContent, themeColors);
927
+ } catch (err) {
928
+ console.warn(`[flame] Failed to resolve theme config, falling back to globals.css only: ${err instanceof Error ? err.message : String(err)}`);
929
+ }
930
+ const cssFile = `client-${key}.css`;
931
+ const outPath = join(ASSETS_DIR, cssFile);
932
+ if (!existsSync(outPath)) await atomicWriteFile(writeFile, rename, unlink, outPath, cssContent);
933
+ return {
934
+ file: cssFile,
935
+ content: cssContent
936
+ };
937
+ }
938
+ var NODE_BUILTINS_RE = new RegExp(`^(node:.*|${builtinModules.map((m) => m.replace(/\//g, "\\/")).join("|")})$`);
939
+ var lucideRealEntry;
940
+ /** Resolve the real lucide-react entry path once (cached). */
941
+ function getLucideRealEntry() {
942
+ if (!lucideRealEntry) lucideRealEntry = createRequire(import.meta.url).resolve("lucide-react");
943
+ return lucideRealEntry;
944
+ }
945
+ var LUCIDE_IMPORT_RE = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
946
+ var LUCIDE_ICON_RE = /^[A-Z]/;
947
+ /** Walk a directory scanning JS/TS/TSX files for `lucide-react` named imports. */
948
+ function scanDirLucideIcons(dir, set) {
949
+ if (!existsSync(dir)) return;
950
+ try {
951
+ const entries = readdirSync(dir, { withFileTypes: true });
952
+ for (const e of entries) {
953
+ const full = join(dir, e.name);
954
+ if (e.isDirectory()) {
955
+ if (e.name !== "node_modules") scanDirLucideIcons(full, set);
956
+ } else if (/\.(js|ts|tsx)$/.test(e.name)) {
957
+ const content = readFileSync(full, "utf-8");
958
+ for (const m of content.matchAll(LUCIDE_IMPORT_RE)) for (const s of m[1].split(",")) {
959
+ const name = s.trim().split(/\s+as\s+/)[0].trim();
960
+ if (LUCIDE_ICON_RE.test(name)) set.add(name);
961
+ }
962
+ }
963
+ }
964
+ } catch (err) {
965
+ console.warn(`[flame] Failed to scan lucide icons: ${err instanceof Error ? err.message : String(err)}`);
966
+ }
967
+ }
968
+ /** Collect every lucide icon name imported across flame sources and deps. */
969
+ function collectAllLucideIcons() {
970
+ const icons = /* @__PURE__ */ new Set();
971
+ scanDirLucideIcons(join(FRAMEWORK_ROOT, ".docu/components"), icons);
972
+ scanDirLucideIcons(join(FRAMEWORK_ROOT, ".docu/pages"), icons);
973
+ const depDirs = [
974
+ join(FRAMEWORK_ROOT, "..", "mdx-content", "dist"),
975
+ join(FRAMEWORK_ROOT, "..", "ui-react", "dist"),
976
+ join(FRAMEWORK_ROOT, "..", "core", "dist"),
977
+ join(FRAMEWORK_ROOT, "..", "themes-colors", "dist")
978
+ ];
979
+ for (const d of depDirs) scanDirLucideIcons(resolve(d), icons);
980
+ return [...icons];
981
+ }
982
+ /** Build the client JS bundle and Tailwind CSS. */
983
+ async function buildClientBundle(mdxSources = {}) {
984
+ await mkdir(ASSETS_DIR, { recursive: true });
985
+ const twKey = tailwindCacheKey();
986
+ await cleanOldBundles(/* @__PURE__ */ new Set([`client-${twKey}.css`]));
987
+ const nodeEnv = process.env.NODE_ENV || "development";
988
+ const entryPath = join(LIB_DIR, "client.ts");
989
+ const bundle = await build({
990
+ configFile: false,
991
+ publicDir: false,
992
+ define: { "process.env.NODE_ENV": JSON.stringify(nodeEnv) },
993
+ plugins: [
994
+ {
995
+ name: "node-builtin-stub",
996
+ resolveId(id) {
997
+ if (NODE_BUILTINS_RE.test(id)) return `\0node-stub:${id}`;
998
+ return null;
999
+ },
1000
+ load(id) {
1001
+ if (!id.startsWith("\0node-stub:")) return null;
1002
+ return "export default {}; export {};";
1003
+ }
1004
+ },
1005
+ {
1006
+ name: "lucide-optimize",
1007
+ resolveId(id, importer) {
1008
+ if (id !== "lucide-react") return null;
1009
+ if (importer) {
1010
+ if (normalizeImporterPath(importer).includes("/markdown/dist/")) return null;
1011
+ }
1012
+ return "\0lucide-virt";
1013
+ },
1014
+ load(id) {
1015
+ if (id !== "\0lucide-virt") return null;
1016
+ const scanned = collectAllLucideIcons();
1017
+ const configured = extractConfigIcons(loadDocuConfig());
1018
+ return `export { ${[.../* @__PURE__ */ new Set([...scanned, ...configured])].join(", ")} } from ${JSON.stringify(getLucideRealEntry())};`;
1019
+ }
1020
+ },
1021
+ {
1022
+ name: "docu-config",
1023
+ resolveId(id) {
1024
+ if (!/client-routes$/.test(id)) return null;
1025
+ return "\0client-routes";
1026
+ },
1027
+ load(id) {
1028
+ if (id !== "\0client-routes") return null;
1029
+ const config = loadDocuConfig();
1030
+ const resolved = {
1031
+ ...config,
1032
+ routes: resolveRoutes(config.routes)
1033
+ };
1034
+ return [
1035
+ `const docuConfig = ${JSON.stringify(resolved)};`,
1036
+ `export const routes = docuConfig.routes || [];`,
1037
+ `export const config = docuConfig;`
1038
+ ].join("\n");
1039
+ }
1040
+ },
1041
+ {
1042
+ name: "mdx-hydrate",
1043
+ resolveId(id) {
1044
+ if (/mdx-manifest$/.test(id)) return "\0mdx-manifest";
1045
+ if (id.startsWith("mdx-module:")) return `\0${id}`;
1046
+ return null;
1047
+ },
1048
+ load(id) {
1049
+ if (id === "\0mdx-manifest") {
1050
+ const slugs = Object.keys(mdxSources).sort();
1051
+ return `${slugs.map((slug, i) => {
1052
+ const key = slug.replace(/["\\]/g, "");
1053
+ return `import * as _mdx${i} from ${JSON.stringify(`mdx-module:${key}`)};`;
1054
+ }).join("\n")}\nexport const mdxModules = { ${slugs.map((slug, i) => `${JSON.stringify(slug)}: _mdx${i}`).join(", ")} };\n`;
1055
+ }
1056
+ if (!id.startsWith("\0mdx-module:")) return null;
1057
+ const slug = id.slice(12);
1058
+ const contents = mdxSources[slug];
1059
+ if (contents == null) throw new Error(`unknown mdx module: ${slug}`);
1060
+ return contents;
1061
+ }
1062
+ }
1063
+ ],
1064
+ build: {
1065
+ outDir: ASSETS_DIR,
1066
+ emptyOutDir: false,
1067
+ sourcemap: true,
1068
+ minify: nodeEnv === "production",
1069
+ target: "es2020",
1070
+ rollupOptions: {
1071
+ input: entryPath,
1072
+ output: {
1073
+ format: "es",
1074
+ entryFileNames: "client-[hash].js",
1075
+ chunkFileNames: "chunks/[name]-[hash].js",
1076
+ assetFileNames: "assets/[name]-[hash][extname]"
1077
+ }
1078
+ }
1079
+ }
1080
+ });
1081
+ const outputs = Array.isArray(bundle) ? bundle : [bundle];
1082
+ let jsFile;
1083
+ for (const item of outputs) {
1084
+ if (!("output" in item)) continue;
1085
+ for (const output of item.output) if (output.type === "chunk" && output.isEntry && output.facadeModuleId && resolve(output.facadeModuleId) === entryPath) {
1086
+ jsFile = basename(output.fileName);
1087
+ break;
1088
+ }
1089
+ if (jsFile) break;
1090
+ }
1091
+ if (!jsFile) throw new Error("Client bundle produced no output files");
1092
+ const { file: cssFile } = await buildTailwindCss(twKey);
1093
+ await writeFile(join(ASSETS_DIR, "manifest.json"), JSON.stringify({
1094
+ js: jsFile,
1095
+ css: cssFile
1096
+ }));
1097
+ return {
1098
+ js: jsFile,
1099
+ css: cssFile
1100
+ };
1101
+ }
1102
+ //#endregion
1103
+ //#region .docu/node/git.ts
1104
+ /**
1105
+ * Runtime-neutral git helpers — same behavior as the git functions in
1106
+ * `utils.ts` (Bun-only, protected) but spawning via `node:child_process`,
1107
+ * which works on Bun, Node.js, and Deno. `mdx.ts` imports from here.
1108
+ */
1109
+ function runGit(args) {
1110
+ return new Promise((resolve, reject) => {
1111
+ execFile("git", args, { maxBuffer: 16777216 }, (err, stdout) => {
1112
+ if (err) reject(err);
1113
+ else resolve(stdout);
1114
+ });
1115
+ });
1116
+ }
1117
+ function sanitizePath(filePath) {
1118
+ const cleanPath = filePath.replace(/^\//, "");
1119
+ if (!cleanPath || !/^[a-zA-Z0-9\-_/.\s]+$/.test(cleanPath) || /(^|\/)\.\.($|\/)/.test(cleanPath)) return null;
1120
+ return cleanPath;
1121
+ }
1122
+ /** Get git last modified date for a file */
1123
+ async function getGitLastModified(filePath) {
1124
+ const cleanPath = sanitizePath(filePath);
1125
+ if (!cleanPath) return null;
1126
+ try {
1127
+ return (await runGit([
1128
+ "log",
1129
+ "-1",
1130
+ "--format=%cI",
1131
+ "--",
1132
+ cleanPath
1133
+ ])).trim() || null;
1134
+ } catch {
1135
+ return null;
1136
+ }
1137
+ }
1138
+ /**
1139
+ * Fallback to filesystem mtime when git is unavailable (shallow clone, no .git, etc.).
1140
+ * Returns ISO 8601 date string or null.
1141
+ */
1142
+ async function getFilesystemMtime(filePath) {
1143
+ try {
1144
+ return (await stat(filePath)).mtime.toISOString();
1145
+ } catch {
1146
+ return null;
1147
+ }
1148
+ }
1149
+ /** Batch git last modified dates for multiple files in a single spawn */
1150
+ async function getGitLastModifiedBatch(filePaths) {
1151
+ const result = /* @__PURE__ */ new Map();
1152
+ if (filePaths.length === 0) return result;
1153
+ const safePaths = [];
1154
+ for (const fp of filePaths) {
1155
+ const cleanPath = sanitizePath(fp);
1156
+ if (!cleanPath) {
1157
+ console.warn(`[git] getGitLastModifiedBatch: skipping invalid path "${fp}"`);
1158
+ continue;
1159
+ }
1160
+ safePaths.push(cleanPath);
1161
+ }
1162
+ if (safePaths.length === 0) return result;
1163
+ try {
1164
+ const text = await runGit([
1165
+ "log",
1166
+ "--format=%cI",
1167
+ "--name-only",
1168
+ "--diff-filter=ACMR",
1169
+ ...safePaths
1170
+ ]);
1171
+ let currentDate = "";
1172
+ for (const line of text.split("\n")) {
1173
+ const trimmed = line.trim();
1174
+ if (!trimmed) continue;
1175
+ if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) currentDate = trimmed;
1176
+ else if (currentDate && !result.has(trimmed)) result.set(trimmed, currentDate);
1177
+ }
1178
+ } catch (err) {
1179
+ console.error("Failed to get git last modified batch for", filePaths, err);
1180
+ }
1181
+ return result;
1182
+ }
1183
+ //#endregion
1184
+ //#region .docu/node/mdx.ts
1185
+ /**
1186
+ * Return the value with `.html` appended, or null if the value should be left
1187
+ * unchanged. Rules:
1188
+ * - Must be a string
1189
+ * - Must start with /docs/ (the /docs root index needs no suffix)
1190
+ * - Must not be an external URL, contain a fragment, or already end in .html
1191
+ */
1192
+ function appendHtml(value) {
1193
+ if (typeof value !== "string") return null;
1194
+ if (/^https?:\/\//.test(value)) return null;
1195
+ if (!value.startsWith("/docs/")) return null;
1196
+ if (value.includes("#")) return null;
1197
+ if (value.endsWith(".html")) return null;
1198
+ return `${value}.html`;
1199
+ }
1200
+ /**
1201
+ * Rehype plugin: append `.html` to internal `/docs/` hrefs on HTML `<a>` nodes.
1202
+ *
1203
+ * This covers standard markdown links: `[text](/docs/page)` → `<a href="…">`.
1204
+ * It runs in the HAST (HTML AST) phase, where `<a>` elements are real nodes.
1205
+ *
1206
+ * Skips: external URLs, anchor-only links, paths that already end in `.html`,
1207
+ * and the `/docs` root index (no trailing slash segment).
1208
+ */
1209
+ function rehypeDocsHtmlLinks() {
1210
+ return (tree) => {
1211
+ function walk(node) {
1212
+ if (node.type === "element" && node.tagName === "a") {
1213
+ const fixed = appendHtml(node.properties?.href);
1214
+ if (fixed) node.properties.href = fixed;
1215
+ }
1216
+ if (node.children) for (const child of node.children) walk(child);
1217
+ }
1218
+ walk(tree);
1219
+ return tree;
1220
+ };
1221
+ }
1222
+ /**
1223
+ * Remark plugin: append `.html` to internal `/docs/` hrefs on MDX JSX nodes.
1224
+ *
1225
+ * MDX JSX elements (`<Card href="…">`, `<LinkCard href="…">`, etc.) live in
1226
+ * the MDAST as `mdxJsxFlowElement` / `mdxJsxTextElement` nodes. They are
1227
+ * compiled directly to JavaScript by the MDX compiler *before* rehype runs,
1228
+ * so a rehype plugin can never see them as `<a>` elements. This remark plugin
1229
+ * intercepts them at the MDAST phase where their `attributes` array is still
1230
+ * accessible and mutable.
1231
+ *
1232
+ * Skips: same rules as `appendHtml` (external URLs, anchors, already `.html`).
1233
+ */
1234
+ function remarkMdxJsxDocsHtmlLinks() {
1235
+ return (tree) => {
1236
+ function walk(node) {
1237
+ if ((node.type === "mdxJsxFlowElement" || node.type === "mdxJsxTextElement") && node.attributes) {
1238
+ for (const attr of node.attributes) if (attr.type === "mdxJsxAttribute" && attr.name === "href") {
1239
+ const fixed = appendHtml(attr.value);
1240
+ if (fixed) attr.value = fixed;
1241
+ }
1242
+ }
1243
+ if (node.children) for (const child of node.children) walk(child);
1244
+ }
1245
+ walk(tree);
1246
+ return tree;
1247
+ };
1248
+ }
1249
+ z.object({
1250
+ title: z.coerce.string().optional(),
1251
+ description: z.coerce.string().optional(),
1252
+ image: z.coerce.string().optional(),
1253
+ date: z.coerce.string().optional()
1254
+ }).passthrough();
1255
+ /**
1256
+ * Read a string field from frontmatter after the plugin transform chain
1257
+ * (which widens the type to `Record<string, unknown>`). Returns "" when
1258
+ * missing or not a string.
1259
+ */
1260
+ function frontmatterField(frontmatter, key) {
1261
+ return typeof frontmatter[key] === "string" ? frontmatter[key] : "";
1262
+ }
1263
+ /**
1264
+ * Compile MDX/MD content into a React element and compiled source.
1265
+ *
1266
+ * @param rawMdx - Raw MDX/MD file content
1267
+ * @param filePath - Relative file path for git date lookup
1268
+ * @param gitDates - Optional pre-fetched git last-modified map
1269
+ * @param remarkPlugins - Additional remark plugins (merged after defaults, optional)
1270
+ * @param rehypePlugins - Additional rehype plugins (merged after defaults, optional)
1271
+ * @param frontmatterSchema - Custom schema overriding the default contract
1272
+ */
1273
+ /**
1274
+ * Shared compile core used by `compileMdx` (SSR) and `compileMdxModule`
1275
+ * (static hydration). Strips frontmatter, merges the doc plugin chain
1276
+ * (defaults + .html link fixes + user plugins) and runs `serialize()`.
1277
+ */
1278
+ async function serializeWithDocPlugins(rawMdx, opts = {}, pre) {
1279
+ const { strippedContent, frontmatter } = pre ?? (opts.frontmatterSchema ? extractFrontmatterWithContent(rawMdx, opts.frontmatterSchema) : extractFrontmatterWithContent(rawMdx));
1280
+ const defaultRemark = createDefaultRemarkPlugins();
1281
+ const defaultRehype = createDefaultRehypePlugins();
1282
+ const finalRemark = [
1283
+ ...defaultRemark,
1284
+ remarkMdxJsxDocsHtmlLinks,
1285
+ ...opts.remarkPlugins ?? []
1286
+ ];
1287
+ const finalRehype = [
1288
+ ...defaultRehype,
1289
+ rehypeDocsHtmlLinks,
1290
+ ...opts.rehypePlugins ?? []
1291
+ ];
1292
+ return serialize(strippedContent, {
1293
+ outputFormat: opts.outputFormat,
1294
+ format: "md",
1295
+ mdxOptions: {
1296
+ rehypePlugins: finalRehype,
1297
+ remarkPlugins: finalRemark
1298
+ }
1299
+ }).then((serialized) => ({
1300
+ ...serialized,
1301
+ frontmatter,
1302
+ strippedContent
1303
+ }));
1304
+ }
1305
+ /**
1306
+ * Compile MDX/MD content into a React element and compiled source.
1307
+ *
1308
+ * @param rawMdx - Raw MDX/MD file content
1309
+ * @param filePath - Relative file path for git date lookup
1310
+ * @param gitDates - Optional pre-fetched git last-modified map
1311
+ * @param remarkPlugins - Additional remark plugins (merged after defaults, optional)
1312
+ * @param rehypePlugins - Additional rehype plugins (merged after defaults, optional)
1313
+ * @param frontmatterSchema - Custom schema overriding the default contract
1314
+ */
1315
+ async function compileMdx(rawMdx, filePath, gitDates, remarkPlugins, rehypePlugins, frontmatterSchema, pre) {
1316
+ const tocs = extractTocsFromRawMdx(rawMdx);
1317
+ const frontmatter = pre?.frontmatter ?? (frontmatterSchema ? extractFrontmatterWithContent(rawMdx, frontmatterSchema).frontmatter : extractFrontmatterWithContent(rawMdx).frontmatter);
1318
+ const serialized = await serializeWithDocPlugins(rawMdx, {
1319
+ remarkPlugins,
1320
+ rehypePlugins,
1321
+ frontmatterSchema
1322
+ }, pre);
1323
+ const components = createMdxComponents();
1324
+ const content = React.createElement(MDXRemote, {
1325
+ compiledSource: serialized.compiledSource,
1326
+ scope: {},
1327
+ frontmatter: {},
1328
+ components
1329
+ });
1330
+ const date = frontmatter.date || gitDates?.get(filePath) || await getGitLastModified(filePath) || await getFilesystemMtime(filePath) || void 0;
1331
+ return {
1332
+ content,
1333
+ compiledSource: serialized.compiledSource,
1334
+ frontmatter: {
1335
+ ...frontmatter,
1336
+ date
1337
+ },
1338
+ tocs
1339
+ };
1340
+ }
1341
+ /**
1342
+ * Compile MDX to a real ESM module source (program format) for static
1343
+ * client-side hydration — the browser imports and executes it via the bundler
1344
+ * instead of `new Function(compiledSource)`. Uses the same plugin chain as
1345
+ * `compileMdx` so the hydrated tree matches the SSR output.
1346
+ */
1347
+ /**
1348
+ * Frontmatter records collected once during compilation — pagination title /
1349
+ * description and other metadata consumers read from here instead of
1350
+ * re-reading + re-parsing files (parse-once contract: the frontmatter is
1351
+ * already parsed by `serializeWithDocPlugins`).
1352
+ */
1353
+ var pageFrontmatter = /* @__PURE__ */ new Map();
1354
+ /** Register a page's frontmatter, keyed by its href. */
1355
+ function registerPageFrontmatter(href, frontmatter) {
1356
+ pageFrontmatter.set(href, frontmatter);
1357
+ }
1358
+ /** Look up a page's frontmatter (undefined when not yet compiled). */
1359
+ function getPageFrontmatter(href) {
1360
+ return pageFrontmatter.get(href);
1361
+ }
1362
+ /**
1363
+ * Frontmatter-stripped content from the prePass — lets the SSR compile
1364
+ * phase skip its own `extractFrontmatterWithContent` (parse-once across
1365
+ * both compile phases).
1366
+ */
1367
+ var pageStripped = /* @__PURE__ */ new Map();
1368
+ function registerPageStripped(href, stripped) {
1369
+ pageStripped.set(href, stripped);
1370
+ }
1371
+ function getPageStripped(href) {
1372
+ return pageStripped.get(href);
1373
+ }
1374
+ /**
1375
+ * Original (pre-transform) file content, cached during the prePass so the
1376
+ * page loop does not re-read the file from disk — one read per file.
1377
+ */
1378
+ var pageContent = /* @__PURE__ */ new Map();
1379
+ function registerPageContent(href, raw) {
1380
+ pageContent.set(href, raw);
1381
+ }
1382
+ function getPageContent(href) {
1383
+ return pageContent.get(href);
1384
+ }
1385
+ /** Drop derived page maps under OS memory pressure (Bun 1.4 `memoryPressure`). */
1386
+ function clearDerivedPageCaches() {
1387
+ pageContent.clear();
1388
+ pageStripped.clear();
1389
+ pageFrontmatter.clear();
1390
+ }
1391
+ async function compileMdxModule(rawMdx, remarkPlugins, rehypePlugins, href) {
1392
+ const serialized = await serializeWithDocPlugins(rawMdx, {
1393
+ outputFormat: "program",
1394
+ remarkPlugins,
1395
+ rehypePlugins
1396
+ });
1397
+ if (href) {
1398
+ registerPageFrontmatter(href, serialized.frontmatter);
1399
+ registerPageStripped(href, serialized.strippedContent);
1400
+ }
1401
+ return serialized.compiledSource;
1402
+ }
1403
+ //#endregion
1404
+ //#region .docu/node/search-indexer.ts
1405
+ /**
1406
+ * Search Index Generator
1407
+ *
1408
+ * Scans MDX files at build-time and produces a search-index.json
1409
+ * with hierarchy-based records (similar to Algolia DocSearch crawler).
1410
+ *
1411
+ * Hierarchy levels:
1412
+ * lvl0 = section (parent route title)
1413
+ * lvl1 = page title (frontmatter title or h1)
1414
+ * lvl2-lvl6 = headings h2-h6
1415
+ * content = first paragraph/list items after each heading
1416
+ */
1417
+ var docuConfig$5 = loadDocuConfig();
1418
+ function getSectionTitle(filePath) {
1419
+ const parts = filePath.split("/");
1420
+ if (parts.length > 1) {
1421
+ const section = docuConfig$5.routes?.find((r) => r.href === `/${parts[0]}` || r.href === parts[0]);
1422
+ if (section) return section.title;
1423
+ }
1424
+ return docuConfig$5.meta?.title || "Docs";
1425
+ }
1426
+ function slugify(text) {
1427
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1428
+ }
1429
+ function stripJsx(content) {
1430
+ let result = content;
1431
+ let prev = "";
1432
+ while (result !== prev) {
1433
+ prev = result;
1434
+ result = result.replace(/<[A-Z][\w.]*[\s\S]*?\/>/g, "").replace(/<[A-Z][\w.]*[\s\S]*?>([\s\S]*?)<\/[A-Z][\w.]*>/g, "$1");
1435
+ }
1436
+ return result;
1437
+ }
1438
+ function extractRecords(filePath, raw) {
1439
+ const { frontmatter, strippedContent: content } = extractFrontmatterWithContent(raw);
1440
+ const records = [];
1441
+ const url = docsHtmlHref(`/docs/${filePath}`);
1442
+ const lvl0 = getSectionTitle(filePath);
1443
+ const lvl1 = frontmatterField(frontmatter, "title") || null;
1444
+ const hierarchy = {
1445
+ lvl0,
1446
+ lvl1,
1447
+ lvl2: null,
1448
+ lvl3: null,
1449
+ lvl4: null,
1450
+ lvl5: null,
1451
+ lvl6: null
1452
+ };
1453
+ if (lvl1) records.push({
1454
+ url,
1455
+ hierarchy: { ...hierarchy },
1456
+ content: frontmatterField(frontmatter, "description") || null,
1457
+ type: "lvl1"
1458
+ });
1459
+ const lines = stripJsx(content).split("\n");
1460
+ let currentParagraph = [];
1461
+ let inCodeBlock = false;
1462
+ const flushParagraph = () => {
1463
+ if (currentParagraph.length > 0) {
1464
+ const text = currentParagraph.join(" ").trim();
1465
+ if (text) records.push({
1466
+ url: buildAnchorUrl(),
1467
+ hierarchy: { ...hierarchy },
1468
+ content: text,
1469
+ type: "content"
1470
+ });
1471
+ currentParagraph = [];
1472
+ }
1473
+ };
1474
+ const buildAnchorUrl = () => {
1475
+ for (let i = 6; i >= 2; i--) {
1476
+ const key = `lvl${i}`;
1477
+ if (hierarchy[key]) return `${url}#${slugify(hierarchy[key])}`;
1478
+ }
1479
+ return url;
1480
+ };
1481
+ for (const line of lines) {
1482
+ const trimmed = line.trim();
1483
+ if (trimmed.startsWith("```")) {
1484
+ inCodeBlock = !inCodeBlock;
1485
+ continue;
1486
+ }
1487
+ if (inCodeBlock) continue;
1488
+ if (/^import\s+[\w{*]/.test(trimmed) || /^export\s+[\w{*]/.test(trimmed)) continue;
1489
+ const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
1490
+ if (headingMatch) {
1491
+ flushParagraph();
1492
+ const level = headingMatch[1].length;
1493
+ const title = headingMatch[2].replace(/[*`[\]]/g, "").trim();
1494
+ for (let i = level; i <= 6; i++) hierarchy[`lvl${i}`] = null;
1495
+ hierarchy[`lvl${level}`] = title;
1496
+ if (level === 1 && !hierarchy.lvl1) hierarchy.lvl1 = title;
1497
+ if (level >= 2) records.push({
1498
+ url: `${url}#${slugify(title)}`,
1499
+ hierarchy: { ...hierarchy },
1500
+ content: null,
1501
+ type: `lvl${level}`
1502
+ });
1503
+ continue;
1504
+ }
1505
+ if (trimmed === "" || trimmed === "---") {
1506
+ flushParagraph();
1507
+ continue;
1508
+ }
1509
+ if (/^\|.+\|/.test(trimmed)) continue;
1510
+ const cleaned = trimmed.replace(/^[-*+]\s+/, "").replace(/^\d+\.\s+/, "").replace(/^>\s+/, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").trim();
1511
+ if (cleaned) currentParagraph.push(cleaned);
1512
+ }
1513
+ flushParagraph();
1514
+ return records;
1515
+ }
1516
+ async function generateSearchIndex(docsDir, outputDir) {
1517
+ const docs = resolve(docsDir || DOCS_DIR);
1518
+ const dist = resolve(outputDir || ASSETS_DIR);
1519
+ await mkdir(dist, { recursive: true });
1520
+ const mdxFiles = await scanMdxFiles(docs);
1521
+ const allRecords = (await Promise.all(mdxFiles.map(async (file) => {
1522
+ const raw = getPageContent(`/${file.path}`) ?? await readFile(file.absPath, "utf-8");
1523
+ return extractRecords(file.path, raw);
1524
+ }))).flat();
1525
+ await writeFile(join(dist, "search-index.json"), JSON.stringify(allRecords));
1526
+ return allRecords.length;
1527
+ }
1528
+ //#endregion
1529
+ //#region .docu/node/sentry.ts
1530
+ /**
1531
+ * Optional Sentry error tracking integration.
1532
+ * Active only when SENTRY_DSN environment variable is set.
1533
+ * Requires @sentry/bun as an optional peer dependency.
1534
+ */
1535
+ var sentry = null;
1536
+ var initialized = false;
1537
+ async function initSentry() {
1538
+ const dsn = process.env.SENTRY_DSN;
1539
+ if (!dsn) return;
1540
+ try {
1541
+ sentry = await import("@sentry/bun");
1542
+ sentry.init({
1543
+ dsn,
1544
+ environment: process.env.NODE_ENV || "development",
1545
+ release: process.env.SENTRY_RELEASE || void 0
1546
+ });
1547
+ initialized = true;
1548
+ } catch {
1549
+ sentry = null;
1550
+ initialized = false;
1551
+ }
1552
+ }
1553
+ function captureException(err, context) {
1554
+ if (!initialized || !sentry) return;
1555
+ sentry.captureException(err, context ? { extra: context } : void 0);
1556
+ }
1557
+ //#endregion
1558
+ //#region .docu/components/Breadcrumb.tsx
1559
+ function toTitleCase(input) {
1560
+ return input.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1561
+ }
1562
+ function DocsBreadcrumb({ paths }) {
1563
+ return /* @__PURE__ */ jsx(Breadcrumb, {
1564
+ className: "py-4",
1565
+ children: /* @__PURE__ */ jsxs(BreadcrumbList, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, { children: /* @__PURE__ */ jsx("span", {
1566
+ className: "text-muted-foreground",
1567
+ children: "Docs"
1568
+ }) }), paths.map((path, index) => /* @__PURE__ */ jsx(BreadcrumbItem, { children: index < paths.length - 1 ? /* @__PURE__ */ jsx("span", {
1569
+ className: "text-muted-foreground",
1570
+ children: toTitleCase(path)
1571
+ }) : /* @__PURE__ */ jsx(BreadcrumbPage, {
1572
+ className: "text-base-content",
1573
+ children: toTitleCase(path)
1574
+ }) }, `${path}-${index}`))] })
1575
+ });
1576
+ }
1577
+ var routes$1 = resolveRoutes(loadDocuConfig().routes);
1578
+ function flattenRoutes() {
1579
+ const paths = [];
1580
+ function traverse(route, section = "") {
1581
+ const fullPath = route.href.startsWith(section) ? route.href : `${section}${route.href}`.replace(/\/+/g, "/");
1582
+ if (route.href && !route.noLink) paths.push(fullPath);
1583
+ if (route.items) route.items.forEach((item) => traverse(item, fullPath));
1584
+ }
1585
+ routes$1.forEach((route) => traverse(route));
1586
+ return paths;
1587
+ }
1588
+ function getRouteMap() {
1589
+ const map = /* @__PURE__ */ new Map();
1590
+ function traverse(route, section = "") {
1591
+ const fullPath = route.href.startsWith(section) ? route.href : `${section}${route.href}`.replace(/\/+/g, "/");
1592
+ map.set(fullPath, route.title);
1593
+ if (route.items) route.items.forEach((item) => traverse(item, fullPath));
1594
+ }
1595
+ routes$1.forEach((route) => traverse(route));
1596
+ return map;
1597
+ }
1598
+ /**
1599
+ * Frontmatter for a page — read from the parse-once registry (populated
1600
+ * during compilation) instead of re-reading + re-parsing the file. Falls
1601
+ * back to a direct read only for pages the dev server has not compiled yet,
1602
+ * and caches the result back into the registry.
1603
+ */
1604
+ function readPageFrontmatter(href) {
1605
+ const registered = getPageFrontmatter(href);
1606
+ if (registered) return registered;
1607
+ let fm = {};
1608
+ const rel = href.replace(/^\/|$/g, "");
1609
+ for (const ext of [".mdx", ".md"]) {
1610
+ for (const file of [join(DOCS_DIR, `${rel}${ext}`), join(DOCS_DIR, `${rel}/index${ext}`)]) try {
1611
+ fm = extractFrontmatter(readFileSync(file, "utf-8"));
1612
+ break;
1613
+ } catch {}
1614
+ if (Object.keys(fm).length) break;
1615
+ }
1616
+ registerPageFrontmatter(href, fm);
1617
+ return fm;
1618
+ }
1619
+ function getPreviousNext(pathname) {
1620
+ const normalizedPath = pathname.replace(/^\/|$/g, "");
1621
+ if (normalizedPath === "docs" || normalizedPath === "") {
1622
+ const paths = flattenRoutes();
1623
+ const routeMap = getRouteMap();
1624
+ const first = paths[0];
1625
+ if (!first) return {
1626
+ prev: null,
1627
+ next: null
1628
+ };
1629
+ const fm = readPageFrontmatter(first);
1630
+ return {
1631
+ prev: null,
1632
+ next: {
1633
+ href: first,
1634
+ title: fm.title || routeMap.get(first) || "",
1635
+ description: fm.description || ""
1636
+ }
1637
+ };
1638
+ }
1639
+ const paths = flattenRoutes();
1640
+ const index = paths.findIndex((href) => href === `/${normalizedPath}` || href === normalizedPath);
1641
+ if (index === -1) return {
1642
+ prev: null,
1643
+ next: null
1644
+ };
1645
+ const routeMap = getRouteMap();
1646
+ const prevHref = index > 0 ? paths[index - 1] : null;
1647
+ const nextHref = index < paths.length - 1 ? paths[index + 1] : null;
1648
+ const prevFm = prevHref ? readPageFrontmatter(prevHref) : null;
1649
+ const nextFm = nextHref ? readPageFrontmatter(nextHref) : null;
1650
+ return {
1651
+ prev: prevHref ? {
1652
+ href: prevHref,
1653
+ title: prevFm?.title || routeMap.get(prevHref) || ""
1654
+ } : null,
1655
+ next: nextHref ? {
1656
+ href: nextHref,
1657
+ title: nextFm?.title || routeMap.get(nextHref) || "",
1658
+ description: nextFm?.description || ""
1659
+ } : null
1660
+ };
1661
+ }
1662
+ //#endregion
1663
+ //#region .docu/components/Pagination.tsx
1664
+ function Pagination({ pathname, className, prevIcon, nextIcon, linkClassName }) {
1665
+ const { prev, next } = getPreviousNext(pathname);
1666
+ if (!prev && !next) return null;
1667
+ return /* @__PURE__ */ jsx(PaginationDocs, {
1668
+ prev: prev ? {
1669
+ href: docsHtmlHref(`/docs${prev.href}`),
1670
+ title: prev.title
1671
+ } : void 0,
1672
+ next: next ? {
1673
+ href: docsHtmlHref(`/docs${next.href}`),
1674
+ title: next.title,
1675
+ description: next.description
1676
+ } : void 0,
1677
+ className,
1678
+ prevIcon,
1679
+ nextIcon,
1680
+ linkClassName
1681
+ });
1682
+ }
1683
+ //#endregion
1684
+ //#region .docu/components/Typography.tsx
1685
+ function Typography({ children }) {
1686
+ return /* @__PURE__ */ jsx("div", {
1687
+ className: "prose prose-zinc dark:prose-invert prose-code:font-code dark:prose-code:bg-stone-900/25 prose-code:bg-stone-50 prose-pre:bg-background max-lg:prose-headings:scroll-mt-16 prose-headings:scroll-mt-4 prose-code:text-sm prose-code:leading-6 dark:prose-code:text-white prose-code:text-stone-800 prose-code:p-1 prose-code:rounded-md prose-code:border prose-img:rounded-md prose-img:border prose-code:before:content-none prose-code:after:content-none prose-code:px-1.5 prose-code:overflow-x-auto prose-img:my-3 prose-h2:my-4 prose-h2:mt-8 w-full max-w-full! pt-2",
1688
+ children
1689
+ });
1690
+ }
1691
+ //#endregion
1692
+ //#region .docu/node/helpers.ts
1693
+ var docuConfig$3 = loadDocuConfig();
1694
+ function getEditLink(url, filePath) {
1695
+ const configPath = docuConfig$3?.repo?.path || detectPlatformPath(url);
1696
+ const encodedPath = filePath.replace(/^\//, "").split("/").map(encodeURIComponent).join("/");
1697
+ return `${url}/${configPath}`.replace("{filePath}", encodedPath);
1698
+ }
1699
+ /**
1700
+ * Detect the default edit path template from the repo URL hostname.
1701
+ * Supports GitHub, GitLab, Bitbucket, Gitea (cloud + self-hosted),
1702
+ * Gogs, Forgejo, and Codeberg.
1703
+ * Falls back to Gitea-style for unknown hosts — override via repo.path.
1704
+ */
1705
+ function detectPlatformPath(url) {
1706
+ try {
1707
+ const host = new URL(url).hostname;
1708
+ if (host === "github.com") return "blob/main/{filePath}";
1709
+ if (host === "gitlab.com") return "-/blob/main/{filePath}";
1710
+ if (host === "bitbucket.org") return "src/main/{filePath}";
1711
+ if (host === "gitea.com") return "src/branch/main/{filePath}";
1712
+ if (host === "codeberg.org") return "src/branch/main/{filePath}";
1713
+ return "src/branch/main/{filePath}";
1714
+ } catch {
1715
+ return "blob/main/{filePath}";
1716
+ }
1717
+ }
1718
+ function isEditEnabled() {
1719
+ return docuConfig$3?.repo?.edit ?? false;
1720
+ }
1721
+ function getRepoUrl() {
1722
+ return docuConfig$3?.repo?.url || "";
1723
+ }
1724
+ function getSocialLinks() {
1725
+ return docuConfig$3?.footer?.social || [];
1726
+ }
1727
+ //#endregion
1728
+ //#region .docu/components/EditWith.tsx
1729
+ function EditWith({ filePath, text = "Edit this page", className = "" }) {
1730
+ if (!isEditEnabled()) return null;
1731
+ const repoUrl = getRepoUrl();
1732
+ if (!repoUrl) return null;
1733
+ const editUrl = getEditLink(repoUrl, filePath);
1734
+ return /* @__PURE__ */ jsx("div", {
1735
+ className: `text-right text-sm ${className}`,
1736
+ children: /* @__PURE__ */ jsxs("a", {
1737
+ href: editUrl,
1738
+ target: "_blank",
1739
+ rel: "noopener noreferrer",
1740
+ "aria-label": "Edit this page",
1741
+ className: "flex items-center gap-1 no-underline",
1742
+ children: [/* @__PURE__ */ jsx(SquarePen, { className: "h-4 w-4" }), text]
1743
+ })
1744
+ });
1745
+ }
1746
+ //#endregion
1747
+ //#region .docu/components/Social.tsx
1748
+ function createIcon(pathData) {
1749
+ return function Icon({ className = "", size = 20 }) {
1750
+ return /* @__PURE__ */ jsx("svg", {
1751
+ className,
1752
+ width: size,
1753
+ height: size,
1754
+ viewBox: "0 0 24 24",
1755
+ xmlns: "http://www.w3.org/2000/svg",
1756
+ fill: "currentColor",
1757
+ children: /* @__PURE__ */ jsx("path", { d: pathData })
1758
+ });
1759
+ };
1760
+ }
1761
+ var GithubIcon = createIcon("M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12");
1762
+ var BitbucketIcon = createIcon("M.778 1.213a.768.768 0 00-.768.892l3.263 19.81c.084.5.515.868 1.022.873H19.95a.772.772 0 00.77-.646l3.27-20.03a.768.768 0 00-.768-.891zM14.52 15.53H9.522L8.17 8.466h7.561z");
1763
+ var GitlabIcon = createIcon("m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z");
1764
+ var NpmIcon = createIcon("M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z");
1765
+ var YoutubeIcon = createIcon("M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z");
1766
+ var TwitterIcon = createIcon("M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z");
1767
+ var iconMap = {
1768
+ github: GithubIcon,
1769
+ gitlab: GitlabIcon,
1770
+ bitbucket: BitbucketIcon,
1771
+ npm: NpmIcon,
1772
+ youtube: YoutubeIcon,
1773
+ twitter: TwitterIcon,
1774
+ x: TwitterIcon,
1775
+ instagram: createIcon("M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077"),
1776
+ linkedin: createIcon("M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.528zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.226.792 24 1.771 24h20.451C23.2 24 24 23.226 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"),
1777
+ facebook: createIcon("M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"),
1778
+ telegram: createIcon("M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"),
1779
+ discord: createIcon("M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"),
1780
+ threads: createIcon("M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.964-.065-1.19.408-2.285 1.33-3.082.88-.76 2.119-1.207 3.583-1.291a13.853 13.853 0 0 1 3.02.142c-.126-.742-.375-1.332-.75-1.757-.513-.586-1.308-.883-2.359-.89h-.029c-.844 0-1.992.232-2.721 1.32L7.734 7.847c.98-1.454 2.568-2.256 4.478-2.256h.044c3.194.02 5.097 1.975 5.287 5.388.108.046.216.094.321.142 1.49.7 2.58 1.761 3.154 3.07.797 1.82.871 4.79-1.548 7.158-1.85 1.81-4.094 2.628-7.277 2.65Zm1.003-11.69c-.242 0-.487.007-.739.021-1.836.103-2.98.946-2.916 2.143.067 1.256 1.452 1.839 2.784 1.767 1.224-.065 2.818-.543 3.086-3.71a10.5 10.5 0 0 0-2.215-.221z"),
1781
+ mastodon: createIcon("M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z")
1782
+ };
1783
+ function getSocialIcon(name) {
1784
+ return iconMap[name.toLowerCase().replace(/\s+/g, "")] || null;
1785
+ }
1786
+ function Social({ className = "", size = 16 }) {
1787
+ const socialLinks = getSocialLinks();
1788
+ if (!socialLinks?.length) return null;
1789
+ return /* @__PURE__ */ jsx("div", {
1790
+ className: `flex gap-1 ${className}`,
1791
+ children: socialLinks.map((link) => {
1792
+ const Icon = getSocialIcon(link.name);
1793
+ return /* @__PURE__ */ jsx("a", {
1794
+ href: link.url,
1795
+ target: "_blank",
1796
+ rel: "noopener noreferrer",
1797
+ "aria-label": link.name,
1798
+ className: "btn-xs btn-circle btn-ghost text-muted-foreground",
1799
+ children: Icon && /* @__PURE__ */ jsx(Icon, { size })
1800
+ }, link.name);
1801
+ })
1802
+ });
1803
+ }
1804
+ //#endregion
1805
+ //#region .docu/components/Footer.tsx
1806
+ function Footer() {
1807
+ return /* @__PURE__ */ jsxs("footer", {
1808
+ className: "text-muted-foreground mt-auto flex w-full flex-col items-start gap-4 py-6 sm:flex-row sm:items-center sm:justify-between",
1809
+ children: [/* @__PURE__ */ jsx(Social, {}), /* @__PURE__ */ jsx("aside", {
1810
+ className: "sm:ml-auto",
1811
+ children: /* @__PURE__ */ jsxs("p", {
1812
+ className: "text-xs",
1813
+ children: [
1814
+ "Made with",
1815
+ " ",
1816
+ /* @__PURE__ */ jsx("a", {
1817
+ href: "https://docubook.pro",
1818
+ target: "_blank",
1819
+ rel: "noopener noreferrer",
1820
+ className: "link link-hover text-muted-foreground font-medium",
1821
+ children: "DocuBook"
1822
+ })
1823
+ ]
1824
+ })
1825
+ })]
1826
+ });
1827
+ }
1828
+ //#endregion
1829
+ //#region .docu/components/ScrollTo.tsx
1830
+ function ScrollTo({ className, showIcon = true, onScrollToTop }) {
1831
+ const [isVisible, setIsVisible] = useState(false);
1832
+ const checkScroll = useCallback(() => {
1833
+ const container = document.getElementById("scroll-container");
1834
+ const shouldShow = (container ? container.scrollTop : window.scrollY) > (container ? container.scrollHeight : document.documentElement.scrollHeight) * .3;
1835
+ if (shouldShow !== isVisible) setIsVisible(shouldShow);
1836
+ }, [isVisible]);
1837
+ useEffect(() => {
1838
+ let timeoutId;
1839
+ const handleScroll = () => {
1840
+ if (timeoutId) clearTimeout(timeoutId);
1841
+ timeoutId = setTimeout(checkScroll, 100);
1842
+ };
1843
+ const container = document.getElementById("scroll-container") || window;
1844
+ container.addEventListener("scroll", handleScroll, { passive: true });
1845
+ return () => {
1846
+ container.removeEventListener("scroll", handleScroll);
1847
+ if (timeoutId) clearTimeout(timeoutId);
1848
+ };
1849
+ }, [checkScroll]);
1850
+ const scrollToTop = useCallback((e) => {
1851
+ e.preventDefault();
1852
+ onScrollToTop?.();
1853
+ history.replaceState(null, "", "#top");
1854
+ const container = document.getElementById("scroll-container");
1855
+ if (container) container.scrollTo({
1856
+ top: 0,
1857
+ behavior: "smooth"
1858
+ });
1859
+ else window.scrollTo({
1860
+ top: 0,
1861
+ behavior: "smooth"
1862
+ });
1863
+ }, [onScrollToTop]);
1864
+ return /* @__PURE__ */ jsx("div", {
1865
+ className: cn("border-base-300 mt-4 border-t pt-4", "transition-opacity duration-300", isVisible ? "opacity-100" : "pointer-events-none opacity-0", className),
1866
+ children: /* @__PURE__ */ jsxs("a", {
1867
+ href: "#top",
1868
+ onClick: scrollToTop,
1869
+ className: cn("inline-flex items-center text-sm", "link link-hover text-base-content/60 hover:text-base-content", "transition-all duration-200 hover:translate-y-px"),
1870
+ "aria-label": "Scroll to top",
1871
+ children: [showIcon && /* @__PURE__ */ jsx(ArrowUpIcon, { className: "mr-1 h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ jsx("span", { children: "Scroll to Top" })]
1872
+ })
1873
+ });
1874
+ }
1875
+ //#endregion
1876
+ //#region .docu/components/Toc.tsx
1877
+ function Toc({ tocs }) {
1878
+ const [activeId, setActiveId] = useState(null);
1879
+ const clickedIdRef = useRef(null);
1880
+ const clickTimerRef = useRef(null);
1881
+ const activeIdRef = useRef(null);
1882
+ useEffect(() => {
1883
+ activeIdRef.current = activeId;
1884
+ }, [activeId]);
1885
+ const getAnchorOffset = useCallback(() => {
1886
+ if (typeof window === "undefined") return 100;
1887
+ const first = tocs[0] ? document.getElementById(tocs[0].href.slice(1)) : null;
1888
+ return (first ? parseFloat(getComputedStyle(first).scrollMarginTop) : 0) || 100;
1889
+ }, [tocs]);
1890
+ useEffect(() => {
1891
+ if (typeof window === "undefined" || window.innerWidth < 1024) return;
1892
+ if (!tocs.length) return;
1893
+ const container = window.innerWidth >= 1024 ? document.getElementById("scroll-container") : null;
1894
+ const scrollTarget = container || window;
1895
+ const offset = getAnchorOffset();
1896
+ const handleScroll = () => {
1897
+ if (clickedIdRef.current) return;
1898
+ let currentId = null;
1899
+ for (const toc of tocs) {
1900
+ const id = toc.href.slice(1);
1901
+ const el = document.getElementById(id);
1902
+ if (!el) continue;
1903
+ if ((container ? el.offsetTop - container.scrollTop : el.getBoundingClientRect().top) <= offset) currentId = id;
1904
+ else break;
1905
+ }
1906
+ if (currentId !== activeIdRef.current) {
1907
+ setActiveId(currentId);
1908
+ history.replaceState(null, "", currentId ? `#${currentId}` : "#top");
1909
+ }
1910
+ };
1911
+ handleScroll();
1912
+ let throttleTimer = null;
1913
+ const listener = tocs.length > 30 ? () => {
1914
+ if (throttleTimer) return;
1915
+ throttleTimer = setTimeout(() => {
1916
+ throttleTimer = null;
1917
+ handleScroll();
1918
+ }, 50);
1919
+ } : handleScroll;
1920
+ scrollTarget.addEventListener("scroll", listener, { passive: true });
1921
+ return () => {
1922
+ scrollTarget.removeEventListener("scroll", listener);
1923
+ if (throttleTimer) clearTimeout(throttleTimer);
1924
+ };
1925
+ }, [tocs, getAnchorOffset]);
1926
+ const handleLinkClick = useCallback((id) => {
1927
+ clickedIdRef.current = id;
1928
+ setActiveId(id);
1929
+ history.replaceState(null, "", `#${id}`);
1930
+ if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
1931
+ clickTimerRef.current = setTimeout(() => {
1932
+ clickedIdRef.current = null;
1933
+ }, 1e3);
1934
+ }, []);
1935
+ const handleScrollToTop = useCallback(() => {
1936
+ clickedIdRef.current = "__top__";
1937
+ setActiveId(null);
1938
+ history.replaceState(null, "", "#top");
1939
+ if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
1940
+ clickTimerRef.current = setTimeout(() => {
1941
+ clickedIdRef.current = null;
1942
+ }, 1e3);
1943
+ }, []);
1944
+ useEffect(() => {
1945
+ return () => {
1946
+ if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
1947
+ };
1948
+ }, []);
1949
+ const activeItemRef = useRef(null);
1950
+ useEffect(() => {
1951
+ if (activeId && activeItemRef.current && !clickedIdRef.current) activeItemRef.current.scrollIntoView({
1952
+ block: "nearest",
1953
+ behavior: "smooth"
1954
+ });
1955
+ }, [activeId]);
1956
+ if (!tocs.length) return null;
1957
+ return /* @__PURE__ */ jsxs("div", {
1958
+ className: "flex h-full min-h-0 w-full flex-col gap-2",
1959
+ children: [/* @__PURE__ */ jsxs("div", {
1960
+ className: "flex shrink-0 items-center gap-2",
1961
+ children: [/* @__PURE__ */ jsx(ListIcon, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("h3", {
1962
+ className: "text-sm font-medium",
1963
+ children: "On this page"
1964
+ })]
1965
+ }), /* @__PURE__ */ jsxs("div", {
1966
+ className: "relative min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1",
1967
+ children: [/* @__PURE__ */ jsxs("div", {
1968
+ className: "relative text-sm",
1969
+ children: [/* @__PURE__ */ jsx("div", { className: "bg-base-300 absolute top-0 left-0 h-full w-px" }), /* @__PURE__ */ jsx("div", {
1970
+ className: "flex flex-col",
1971
+ children: tocs.map(({ href, level, text }) => {
1972
+ const id = href.slice(1);
1973
+ const isActive = activeId === id;
1974
+ const levelPadding = (level - 2) * 16;
1975
+ return /* @__PURE__ */ jsxs("div", {
1976
+ ref: isActive ? activeItemRef : void 0,
1977
+ className: cn("relative flex items-center transition-all duration-200", isActive && "bg-primary/5"),
1978
+ children: [/* @__PURE__ */ jsxs("div", {
1979
+ className: cn("flex shrink-0 items-center px-1 py-2 transition-all duration-200", isActive && "border-primary -ml-px border-l-[3px]"),
1980
+ children: [/* @__PURE__ */ jsx("div", { className: cn("h-px transition-colors duration-200", isActive ? "bg-primary w-3" : "bg-base-300 w-2") }), /* @__PURE__ */ jsx("div", { className: cn("h-1.5 w-1.5 shrink-0 rounded-full transition-colors duration-300", isActive ? "bg-primary" : "bg-base-300") })]
1981
+ }), /* @__PURE__ */ jsx("a", {
1982
+ href,
1983
+ onClick: (e) => {
1984
+ e.preventDefault();
1985
+ handleLinkClick(id);
1986
+ const el = document.getElementById(id);
1987
+ if (el) {
1988
+ const offset = getAnchorOffset();
1989
+ const scroller = window.innerWidth >= 1024 ? document.getElementById("scroll-container") : null;
1990
+ const elTop = el.getBoundingClientRect().top;
1991
+ if (scroller) scroller.scrollTo({
1992
+ top: elTop - scroller.getBoundingClientRect().top + scroller.scrollTop - offset,
1993
+ behavior: "smooth"
1994
+ });
1995
+ else window.scrollTo({
1996
+ top: elTop + window.scrollY - offset,
1997
+ behavior: "smooth"
1998
+ });
1999
+ }
2000
+ },
2001
+ className: cn("flex flex-1 items-center py-2 transition-all duration-200", isActive ? "text-primary font-medium" : "text-base-content/60 hover:text-base-content"),
2002
+ style: { paddingLeft: `${levelPadding + 6}px` },
2003
+ children: /* @__PURE__ */ jsx("span", {
2004
+ className: "line-clamp-2 text-sm break-words",
2005
+ children: text
2006
+ })
2007
+ })]
2008
+ }, href);
2009
+ })
2010
+ })]
2011
+ }), /* @__PURE__ */ jsxs("div", {
2012
+ className: "mt-2 grid grid-cols-[28px_minmax(0,1fr)] items-start px-4 text-sm",
2013
+ children: [/* @__PURE__ */ jsx("div", { "aria-hidden": "true" }), /* @__PURE__ */ jsx(ScrollTo, {
2014
+ className: "mt-0",
2015
+ onScrollToTop: handleScrollToTop
2016
+ })]
2017
+ })]
2018
+ })]
2019
+ });
2020
+ }
2021
+ //#endregion
2022
+ //#region .docu/pages/docs/[[...slug]].tsx
2023
+ function DocsPage({ slug, title, description, date, content, tocs, filePath, repoUrl, mdxSlug, compiledSource }) {
2024
+ const pathname = slug.join("/");
2025
+ const tocsJson = JSON.stringify(tocs);
2026
+ return /* @__PURE__ */ jsx("div", {
2027
+ className: "flex w-full flex-1 px-0 pb-4 lg:h-[calc(100vh-4rem)] lg:px-8 lg:pb-8",
2028
+ children: /* @__PURE__ */ jsxs("div", {
2029
+ id: "scroll-container",
2030
+ className: "bg-base-100 border-base-300 relative flex w-full flex-col items-start rounded-b-3xl border shadow-md max-lg:scroll-p-54 lg:h-full lg:flex-row lg:overflow-y-auto lg:rounded-xl",
2031
+ children: [/* @__PURE__ */ jsx("div", {
2032
+ id: "mobile-bar-island",
2033
+ className: "sticky top-0 z-50 w-full lg:hidden",
2034
+ "data-tocs": tocsJson,
2035
+ "data-title": title,
2036
+ "data-repo": repoUrl || ""
2037
+ }), /* @__PURE__ */ jsxs("div", {
2038
+ className: "flex w-full flex-col lg:flex-row 2xl:mx-auto 2xl:max-w-[1300px]",
2039
+ children: [/* @__PURE__ */ jsxs("div", {
2040
+ className: `w-full min-w-0 ${tocs.length > 0 ? "flex-[7]" : "mx-auto max-w-[820px]"} px-4 py-6 lg:px-12 lg:py-10`,
2041
+ children: [/* @__PURE__ */ jsx(DocsBreadcrumb, { paths: slug }), /* @__PURE__ */ jsxs(Typography, { children: [
2042
+ /* @__PURE__ */ jsx("h1", {
2043
+ className: "-mt-0.5 text-3xl",
2044
+ children: title
2045
+ }),
2046
+ description && /* @__PURE__ */ jsx("p", {
2047
+ className: "text-muted-foreground -mt-4 text-[16.5px]",
2048
+ children: description
2049
+ }),
2050
+ /* @__PURE__ */ jsx("div", {
2051
+ id: "mdx-content-island",
2052
+ "data-mdx-slug": mdxSlug,
2053
+ dangerouslySetInnerHTML: { __html: content }
2054
+ }),
2055
+ compiledSource && /* @__PURE__ */ jsx("script", {
2056
+ id: "mdx-compiled-source",
2057
+ type: "application/json",
2058
+ dangerouslySetInnerHTML: { __html: JSON.stringify(compiledSource).replace(/<\//g, "\\u003C/") }
2059
+ }),
2060
+ /* @__PURE__ */ jsxs("div", {
2061
+ className: "border-base-300 my-8 flex items-center border-b-2 border-dashed",
2062
+ children: [/* @__PURE__ */ jsx(EditWith, {
2063
+ className: "text-muted-foreground",
2064
+ filePath
2065
+ }), date && /* @__PURE__ */ jsxs("p", {
2066
+ className: "text-muted-foreground ml-auto text-[13px]",
2067
+ children: ["Last updated ", formatDate2(date)]
2068
+ })]
2069
+ }),
2070
+ /* @__PURE__ */ jsx(Pagination, {
2071
+ pathname,
2072
+ prevIcon: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-3 w-3" }),
2073
+ nextIcon: /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3" })
2074
+ }),
2075
+ /* @__PURE__ */ jsx(Footer, {})
2076
+ ] })]
2077
+ }), tocs.length > 0 && /* @__PURE__ */ jsx("div", {
2078
+ id: "toc-island",
2079
+ "data-tocs": tocsJson,
2080
+ className: "sticky top-4 hidden h-[calc(100vh-8rem)] min-w-[240px] flex-[3] self-start lg:flex lg:px-4 lg:py-6",
2081
+ children: /* @__PURE__ */ jsx(Toc, { tocs })
2082
+ })]
2083
+ })]
2084
+ })
2085
+ });
2086
+ }
2087
+ //#endregion
2088
+ //#region .docu/pages/404.tsx
2089
+ function NotFoundPage() {
2090
+ return /* @__PURE__ */ jsx("div", {
2091
+ className: "flex w-full flex-1 px-4 py-8 lg:h-[calc(100vh-4rem)] lg:px-8 lg:py-4",
2092
+ children: /* @__PURE__ */ jsxs("div", {
2093
+ className: "bg-base-100 border-base-300 flex min-h-[50vh] w-full flex-col items-center justify-center rounded-xl border shadow-md lg:min-h-0 lg:flex-1",
2094
+ children: [
2095
+ /* @__PURE__ */ jsx("h1", {
2096
+ className: "text-6xl font-bold",
2097
+ children: "404"
2098
+ }),
2099
+ /* @__PURE__ */ jsx("p", {
2100
+ className: "text-base-content/60 py-4 text-xl",
2101
+ children: "Page not found"
2102
+ }),
2103
+ /* @__PURE__ */ jsx("a", {
2104
+ href: "/docs/",
2105
+ className: "btn btn-primary mt-2",
2106
+ children: "Go to Docs"
2107
+ })
2108
+ ]
2109
+ })
2110
+ });
2111
+ }
2112
+ //#endregion
2113
+ //#region .docu/components/Lucide.tsx
2114
+ /**
2115
+ * Get Lucide icon component by name
2116
+ * Icon names must match Lucide's export names exactly (e.g., "Zap", "BookOpen", "Search")
2117
+ */
2118
+ function getLucideIcon(name) {
2119
+ if (!name) return null;
2120
+ return LucideIcons[name] || null;
2121
+ }
2122
+ /**
2123
+ * Render Lucide icon component
2124
+ */
2125
+ function renderLucideIcon(name, className) {
2126
+ const Icon = getLucideIcon(name);
2127
+ return Icon ? /* @__PURE__ */ jsx(Icon, { className }) : null;
2128
+ }
2129
+ //#endregion
2130
+ //#region .docu/components/home/Hero.tsx
2131
+ function isExternalLink(link) {
2132
+ return /^https?:\/\//.test(link);
2133
+ }
2134
+ function renderActionButtonIcon(iconName, className) {
2135
+ if (!iconName) return null;
2136
+ const lucideIcon = renderLucideIcon(iconName, className);
2137
+ if (lucideIcon) return lucideIcon;
2138
+ const SocialIcon = getSocialIcon(iconName);
2139
+ if (SocialIcon) return /* @__PURE__ */ jsx(SocialIcon, { className });
2140
+ return null;
2141
+ }
2142
+ function ActionButton({ action }) {
2143
+ const themeClasses = {
2144
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
2145
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90",
2146
+ ghost: "bg-transparent text-muted-foreground border border-base-300 hover:bg-base-200"
2147
+ };
2148
+ const isExternal = isExternalLink(action.link);
2149
+ return /* @__PURE__ */ jsxs("a", {
2150
+ href: action.link,
2151
+ target: isExternal ? "_blank" : void 0,
2152
+ rel: isExternal ? "noopener noreferrer" : void 0,
2153
+ className: cn("inline-flex items-center gap-2 rounded-lg px-6 py-3 text-sm font-medium transition-colors", themeClasses[action.theme || "primary"]),
2154
+ children: [renderActionButtonIcon(action.icon, "h-4 w-4"), action.text]
2155
+ });
2156
+ }
2157
+ function Hero({ hero, className }) {
2158
+ const { tagline, headline, description, actions } = hero;
2159
+ return /* @__PURE__ */ jsx("div", {
2160
+ className: cn("mx-auto max-w-4xl px-6 py-32 sm:py-44", className),
2161
+ children: /* @__PURE__ */ jsxs("div", {
2162
+ className: "text-center",
2163
+ children: [
2164
+ tagline && /* @__PURE__ */ jsx("p", {
2165
+ className: "text-primary mb-4 text-lg font-semibold",
2166
+ children: tagline
2167
+ }),
2168
+ /* @__PURE__ */ jsx("h1", {
2169
+ className: "text-5xl font-semibold tracking-tight text-balance sm:text-7xl",
2170
+ children: headline
2171
+ }),
2172
+ description && /* @__PURE__ */ jsx("p", {
2173
+ className: "text-muted-foreground mt-8 text-lg text-pretty sm:text-xl",
2174
+ children: description
2175
+ }),
2176
+ actions && actions.length > 0 && /* @__PURE__ */ jsx("div", {
2177
+ className: "mt-10 flex flex-wrap items-center justify-center gap-4",
2178
+ children: actions.map((action, index) => /* @__PURE__ */ jsx(ActionButton, { action }, index))
2179
+ })
2180
+ ]
2181
+ })
2182
+ });
2183
+ }
2184
+ //#endregion
2185
+ //#region .docu/components/home/Features.tsx
2186
+ function FeatureCard({ feature, index }) {
2187
+ const Wrapper = feature.link ? "a" : "div";
2188
+ const wrapperProps = feature.link ? { href: feature.link } : {};
2189
+ const patternId = `grid-${index}`;
2190
+ return /* @__PURE__ */ jsxs(Wrapper, {
2191
+ ...wrapperProps,
2192
+ className: cn("border-base-200 bg-base-100 hover:border-primary/40 group relative overflow-hidden rounded-2xl border p-6 transition-all hover:shadow-lg", feature.link && "cursor-pointer"),
2193
+ children: [/* @__PURE__ */ jsxs("svg", {
2194
+ className: "absolute inset-0 h-full w-full",
2195
+ xmlns: "http://www.w3.org/2000/svg",
2196
+ style: { color: "var(--color-primary)" },
2197
+ "aria-hidden": "true",
2198
+ children: [/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("pattern", {
2199
+ id: patternId,
2200
+ width: "40",
2201
+ height: "40",
2202
+ patternUnits: "userSpaceOnUse",
2203
+ children: /* @__PURE__ */ jsx("path", {
2204
+ d: "M 40 0 L 0 0 0 40",
2205
+ fill: "none",
2206
+ stroke: "currentColor",
2207
+ strokeWidth: "0.5",
2208
+ opacity: "0.15"
2209
+ })
2210
+ }) }), /* @__PURE__ */ jsx("rect", {
2211
+ width: "100%",
2212
+ height: "100%",
2213
+ fill: `url(#${patternId})`
2214
+ })]
2215
+ }), /* @__PURE__ */ jsxs("div", {
2216
+ className: "relative z-10",
2217
+ children: [
2218
+ feature.icon && /* @__PURE__ */ jsx("div", {
2219
+ className: "bg-primary/10 mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg",
2220
+ children: renderLucideIcon(feature.icon, "h-6 w-6 text-primary")
2221
+ }),
2222
+ /* @__PURE__ */ jsx("h3", {
2223
+ className: "mb-2 text-lg font-semibold",
2224
+ children: feature.title
2225
+ }),
2226
+ /* @__PURE__ */ jsx("p", {
2227
+ className: "text-muted-foreground text-sm",
2228
+ children: feature.description
2229
+ })
2230
+ ]
2231
+ })]
2232
+ });
2233
+ }
2234
+ function Features({ features, className }) {
2235
+ if (!features || features.length === 0) return null;
2236
+ return /* @__PURE__ */ jsx("div", {
2237
+ className: cn("mx-auto max-w-5xl px-6 pb-24", className),
2238
+ children: /* @__PURE__ */ jsx("div", {
2239
+ className: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3",
2240
+ children: features.map((feature, index) => /* @__PURE__ */ jsx(FeatureCard, {
2241
+ feature,
2242
+ index
2243
+ }, index))
2244
+ })
2245
+ });
2246
+ }
2247
+ //#endregion
2248
+ //#region .docu/pages/index.tsx
2249
+ var docuConfig$2 = loadDocuConfig();
2250
+ function IndexPage() {
2251
+ const { meta, home } = docuConfig$2;
2252
+ const routes = docuConfig$2.routes || [];
2253
+ const linkWithHtml = (link) => {
2254
+ if (isExternalUrl(link)) return link;
2255
+ if (link.startsWith("/docs/")) return `${link}.html`;
2256
+ return link;
2257
+ };
2258
+ const features = home?.features?.map((f) => ({
2259
+ ...f,
2260
+ link: f.link ? linkWithHtml(f.link) : void 0
2261
+ })) || routes.filter((r) => r.context).map((route) => ({
2262
+ icon: route.context?.icon,
2263
+ title: route.context?.title || route.title,
2264
+ description: route.context?.description || "",
2265
+ link: docsHtmlHref(`/docs${route.href}${route.items?.[0]?.href || ""}`)
2266
+ }));
2267
+ const hero = home?.hero ? {
2268
+ ...home.hero,
2269
+ actions: home.hero.actions?.map((a) => ({
2270
+ ...a,
2271
+ link: linkWithHtml(a.link)
2272
+ }))
2273
+ } : {
2274
+ headline: meta.title,
2275
+ description: meta.description
2276
+ };
2277
+ return /* @__PURE__ */ jsxs("div", {
2278
+ className: "bg-base-100 relative isolate min-h-screen overflow-hidden",
2279
+ children: [
2280
+ /* @__PURE__ */ jsx("div", {
2281
+ className: "absolute top-4 right-4 z-10",
2282
+ id: "theme-island"
2283
+ }),
2284
+ /* @__PURE__ */ jsx("div", {
2285
+ "aria-hidden": "true",
2286
+ className: "pointer-events-none absolute -top-40 left-1/2 -z-10 -translate-x-1/2 blur-3xl sm:-top-80",
2287
+ children: /* @__PURE__ */ jsx("div", {
2288
+ style: { clipPath: "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)" },
2289
+ className: "from-primary to-accent h-[40rem] w-[80rem] bg-gradient-to-tr opacity-20"
2290
+ })
2291
+ }),
2292
+ /* @__PURE__ */ jsx(Hero, { hero }),
2293
+ /* @__PURE__ */ jsx(Features, { features }),
2294
+ /* @__PURE__ */ jsx("div", {
2295
+ "aria-hidden": "true",
2296
+ className: "pointer-events-none absolute bottom-0 left-1/2 -z-10 translate-x-1/4 blur-3xl",
2297
+ children: /* @__PURE__ */ jsx("div", {
2298
+ style: { clipPath: "polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)" },
2299
+ className: "from-accent to-primary h-[30rem] w-[70rem] bg-gradient-to-tr opacity-20"
2300
+ })
2301
+ })
2302
+ ]
2303
+ });
2304
+ }
2305
+ //#endregion
2306
+ //#region .docu/components/Anchor.tsx
2307
+ function Anchor({ href = "", className = "", activeClassName = "", activeWhen, disabled = false, children, ...props }) {
2308
+ const isActive = (() => {
2309
+ if (!activeWhen || typeof window === "undefined") return false;
2310
+ const pathname = window.location.pathname;
2311
+ if (typeof activeWhen === "string") return pathname === activeWhen || pathname.endsWith(activeWhen);
2312
+ if (activeWhen instanceof RegExp) return activeWhen.test(pathname);
2313
+ if (typeof activeWhen === "function") return activeWhen(pathname);
2314
+ return false;
2315
+ })();
2316
+ const isExternal = isExternalUrl(href);
2317
+ const baseClasses = cn("hover:underline transition-colors", className, isActive ? activeClassName : "", disabled && "cursor-not-allowed opacity-50");
2318
+ if (disabled) return /* @__PURE__ */ jsx("span", {
2319
+ className: baseClasses,
2320
+ children
2321
+ });
2322
+ if (isExternal) return /* @__PURE__ */ jsxs("a", {
2323
+ href,
2324
+ className: baseClasses,
2325
+ target: "_blank",
2326
+ rel: "noopener noreferrer",
2327
+ ...props,
2328
+ children: [children, /* @__PURE__ */ jsx(ArrowUpRight, { className: "ml-0.5 inline-block h-3.5 w-3.5" })]
2329
+ });
2330
+ return /* @__PURE__ */ jsx("a", {
2331
+ href,
2332
+ className: baseClasses,
2333
+ ...props,
2334
+ children
2335
+ });
2336
+ }
2337
+ //#endregion
2338
+ //#region .docu/node/client-routes.ts
2339
+ var docuConfig$1 = loadDocuConfig();
2340
+ resolveRoutes(docuConfig$1.routes || []);
2341
+ var config = docuConfig$1;
2342
+ //#endregion
2343
+ //#region .docu/components/Sublink.tsx
2344
+ /** Exclusive accordion for level >= 2 sidebar groups — opening one group
2345
+ * closes the previously open one. All level >= 2 groups default to closed
2346
+ * and expand only when the header is clicked. `open` is used to auto-expand
2347
+ * the group containing the active page. */
2348
+ var GroupAccordionContext = createContext({
2349
+ openId: null,
2350
+ open: () => {},
2351
+ toggle: () => {}
2352
+ });
2353
+ function GroupAccordionProvider({ children }) {
2354
+ const [openId, setOpenId] = useState(null);
2355
+ const open = useCallback((id) => setOpenId(id), []);
2356
+ const toggle = useCallback((id) => setOpenId((prev) => prev === id ? null : id), []);
2357
+ return /* @__PURE__ */ jsx(GroupAccordionContext.Provider, {
2358
+ value: {
2359
+ openId,
2360
+ open,
2361
+ toggle
2362
+ },
2363
+ children
2364
+ });
2365
+ }
2366
+ function Sublink({ title, href, items, noLink, level, onNavigate, parentHref = "", pathname: pathnameProp }) {
2367
+ const fullHref = parentHref ? `${parentHref}${href}` : `/docs${href}`;
2368
+ const currentPathname = pathnameProp || (typeof window !== "undefined" ? window.location.pathname : "/docs");
2369
+ const isSeparator = config.sidebar?.context === "separator";
2370
+ const { openId, open, toggle } = useContext(GroupAccordionContext);
2371
+ const isAccordionGroup = Boolean(items) && (isSeparator || level >= 1);
2372
+ const treeLevel = level + (isSeparator ? 2 : 1);
2373
+ const [isOpen, setIsOpen] = useState(() => {
2374
+ if (isAccordionGroup) return false;
2375
+ if (level === 0) return true;
2376
+ return false;
2377
+ });
2378
+ const effectiveOpen = isAccordionGroup ? openId === fullHref : isOpen;
2379
+ const handleToggle = () => {
2380
+ if (isAccordionGroup) toggle(fullHref);
2381
+ else setIsOpen((o) => !o);
2382
+ };
2383
+ const isInsideActive = currentPathname.startsWith(fullHref) && currentPathname !== fullHref;
2384
+ useEffect(() => {
2385
+ if (isAccordionGroup && isInsideActive) open(fullHref);
2386
+ }, [
2387
+ isAccordionGroup,
2388
+ isInsideActive,
2389
+ fullHref,
2390
+ open
2391
+ ]);
2392
+ const levelPadding = cn(level === 1 && "pl-4", level === 2 && "pl-8", level >= 3 && "pl-12");
2393
+ const isActive = currentPathname === fullHref || currentPathname === `${fullHref}.html`;
2394
+ const activeRef = useRef(null);
2395
+ useEffect(() => {
2396
+ if (isActive && activeRef.current) activeRef.current.scrollIntoView({ block: "nearest" });
2397
+ }, [isActive]);
2398
+ if (!items) {
2399
+ const link = /* @__PURE__ */ jsx(Anchor, {
2400
+ href: docsHtmlHref(fullHref),
2401
+ className: "text-foreground hover:text-foreground/80 text-sm transition-colors",
2402
+ activeClassName: "text-primary font-medium",
2403
+ activeWhen: (path) => path === fullHref || path === `${fullHref}.html`,
2404
+ onClick: onNavigate,
2405
+ children: title
2406
+ });
2407
+ if (level >= 1) {
2408
+ if (config.sidebar?.context === "separator") {
2409
+ const sectionOffsets = {
2410
+ 1: 16,
2411
+ 2: 32
2412
+ };
2413
+ let overlap = 14;
2414
+ for (let l = 1; l < level; l++) overlap += sectionOffsets[l] ?? 48;
2415
+ return /* @__PURE__ */ jsx("div", {
2416
+ ref: activeRef,
2417
+ className: cn("border-l-2", isActive ? "border-primary" : "border-transparent"),
2418
+ style: { marginLeft: `-${overlap}px` },
2419
+ children: /* @__PURE__ */ jsx("div", {
2420
+ className: cn("py-1 pl-3", levelPadding),
2421
+ children: link
2422
+ })
2423
+ });
2424
+ }
2425
+ return /* @__PURE__ */ jsx("div", {
2426
+ ref: activeRef,
2427
+ className: cn("py-1", levelPadding, "border-l-2", isActive ? "border-primary" : "border-base-300"),
2428
+ children: link
2429
+ });
2430
+ }
2431
+ return /* @__PURE__ */ jsx("div", {
2432
+ ref: activeRef,
2433
+ className: cn("py-1", levelPadding),
2434
+ children: link
2435
+ });
2436
+ }
2437
+ return /* @__PURE__ */ jsxs("div", {
2438
+ ref: isActive ? activeRef : void 0,
2439
+ className: cn("flex flex-col", levelPadding),
2440
+ children: [/* @__PURE__ */ jsxs("button", {
2441
+ type: "button",
2442
+ onClick: handleToggle,
2443
+ className: cn("flex w-full cursor-pointer items-center justify-between py-1 text-left text-sm transition-colors", noLink && treeLevel === 1 ? "text-base-content font-semibold" : "text-foreground hover:text-foreground/80"),
2444
+ children: [noLink ? /* @__PURE__ */ jsx("span", { children: title }) : /* @__PURE__ */ jsx(Anchor, {
2445
+ href: docsHtmlHref(fullHref),
2446
+ className: "text-foreground hover:text-foreground/80 transition-colors",
2447
+ activeClassName: "text-primary",
2448
+ activeWhen: (path) => path === fullHref || path === `${fullHref}.html`,
2449
+ onClick: onNavigate,
2450
+ children: title
2451
+ }), /* @__PURE__ */ jsx(ChevronDown, { className: cn("text-base-content/40 h-4 w-4 shrink-0 transition-transform duration-200", effectiveOpen ? "rotate-0" : "-rotate-90") })]
2452
+ }), effectiveOpen && /* @__PURE__ */ jsx("div", {
2453
+ className: "flex flex-col py-1",
2454
+ children: items.map((item) => /* @__PURE__ */ jsx(Sublink, {
2455
+ ...item,
2456
+ href: item.href,
2457
+ level: level + 1,
2458
+ onNavigate,
2459
+ parentHref: fullHref,
2460
+ pathname: pathnameProp
2461
+ }, `${fullHref}${item.href}`))
2462
+ })]
2463
+ });
2464
+ }
2465
+ //#endregion
2466
+ //#region .docu/components/SidebarGroupHeader.tsx
2467
+ function SidebarGroupHeader({ icon, title }) {
2468
+ return /* @__PURE__ */ jsxs("div", {
2469
+ className: "sidebar-group-header mb-1.5 flex items-center gap-2.5 font-medium text-gray-900 dark:text-gray-200",
2470
+ children: [icon && /* @__PURE__ */ jsx("span", {
2471
+ className: "flex h-4 w-4 shrink-0 items-center justify-center",
2472
+ children: renderLucideIcon(icon, "h-3.5 w-3.5")
2473
+ }), /* @__PURE__ */ jsx("h3", {
2474
+ className: "sidebar-title font-[inherit] text-[length:inherit] leading-[inherit]",
2475
+ children: /* @__PURE__ */ jsx("span", { children: title })
2476
+ })]
2477
+ });
2478
+ }
2479
+ //#endregion
2480
+ //#region .docu/components/Menu.tsx
2481
+ function getCurrentContext(path) {
2482
+ if (!path.startsWith("/docs")) return void 0;
2483
+ const match = path.match(/^\/docs\/([^/]+)/);
2484
+ return match ? match[1] : void 0;
2485
+ }
2486
+ function getContextRoute(contextPath, routeList) {
2487
+ return routeList.find((route) => {
2488
+ return route.href.replace(/^\/+|\/+$/, "") === contextPath;
2489
+ });
2490
+ }
2491
+ function Menu({ onNavigate, className = "", pathname, routes = [] }) {
2492
+ const menuRoutes = routes;
2493
+ const [currentPath] = useState(() => pathname || (typeof window !== "undefined" ? window.location.pathname : "/docs"));
2494
+ if (!currentPath.startsWith("/docs")) return null;
2495
+ const mode = config.sidebar?.context || "dropdown";
2496
+ const navProps = {
2497
+ "aria-label": "Documentation navigation",
2498
+ className: cn("transition-all duration-200", className)
2499
+ };
2500
+ const renderBorderItem = (item, parentRouteHref, key) => {
2501
+ const fullHref = `/docs${parentRouteHref}${item.href}`;
2502
+ const isActive = currentPath === fullHref || currentPath === `${fullHref}.html`;
2503
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("div", {
2504
+ className: cn("-ml-[14px] border-l-2", isActive ? "border-primary" : "border-transparent"),
2505
+ children: /* @__PURE__ */ jsx("div", {
2506
+ className: "pl-3",
2507
+ children: /* @__PURE__ */ jsx(Sublink, {
2508
+ ...item,
2509
+ href: item.href,
2510
+ level: 0,
2511
+ onNavigate,
2512
+ parentHref: `/docs${parentRouteHref}`
2513
+ })
2514
+ })
2515
+ }) }, key);
2516
+ };
2517
+ const sharedUlClasses = "border-base-300 flex flex-col gap-0.5 border-l-2 pb-0.5 pl-3 pt-0.5";
2518
+ if (mode === "separator") {
2519
+ const contextRoutes = menuRoutes.filter((r) => r.context);
2520
+ if (contextRoutes.length === 0) return /* @__PURE__ */ jsx(GroupAccordionProvider, { children: /* @__PURE__ */ jsx("nav", {
2521
+ ...navProps,
2522
+ children: /* @__PURE__ */ jsx("ul", {
2523
+ className: sharedUlClasses,
2524
+ children: menuRoutes.map((route) => renderBorderItem(route, "", route.href))
2525
+ })
2526
+ }) });
2527
+ return /* @__PURE__ */ jsx(GroupAccordionProvider, { children: /* @__PURE__ */ jsx("nav", {
2528
+ ...navProps,
2529
+ children: contextRoutes.map((route, i) => /* @__PURE__ */ jsxs("div", {
2530
+ className: i > 0 ? "mt-6 lg:mt-8" : "",
2531
+ children: [/* @__PURE__ */ jsx(SidebarGroupHeader, {
2532
+ icon: route.context?.icon,
2533
+ title: route.context?.title || route.title
2534
+ }), /* @__PURE__ */ jsx("ul", {
2535
+ className: sharedUlClasses,
2536
+ children: route.items?.map((item) => renderBorderItem(item, route.href, item.href))
2537
+ })]
2538
+ }, route.href))
2539
+ }) });
2540
+ }
2541
+ const isDocsRoot = currentPath === "/docs" || currentPath === "/docs/";
2542
+ const currentContext = isDocsRoot ? menuRoutes[0]?.href.replace(/^\/+|\/+$/, "") : getCurrentContext(currentPath);
2543
+ const contextRoute = isDocsRoot && menuRoutes[0] ? currentContext ? getContextRoute(currentContext, menuRoutes) : menuRoutes[0] : currentContext ? getContextRoute(currentContext, menuRoutes) : void 0;
2544
+ if (!contextRoute) return null;
2545
+ return /* @__PURE__ */ jsx(GroupAccordionProvider, { children: /* @__PURE__ */ jsx("nav", {
2546
+ ...navProps,
2547
+ children: /* @__PURE__ */ jsx("ul", {
2548
+ className: "flex flex-col gap-0.5 py-4",
2549
+ children: /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Sublink, {
2550
+ ...contextRoute,
2551
+ href: contextRoute.href,
2552
+ level: 0,
2553
+ onNavigate,
2554
+ parentHref: "/docs"
2555
+ }) }, contextRoute.title)
2556
+ })
2557
+ }) });
2558
+ }
2559
+ //#endregion
2560
+ //#region .docu/components/DocsLayout.tsx
2561
+ var docuConfig = loadDocuConfig();
2562
+ function DocsLayout({ children, repoUrl, pathname = "/docs" }) {
2563
+ return React.createElement("div", { className: "docs-layout flex flex-col min-h-screen w-full" }, React.createElement("div", { className: "flex flex-1 items-start w-full" }, React.createElement("aside", {
2564
+ id: "sidebar-island",
2565
+ className: "sticky top-0 hidden h-screen w-[280px] shrink-0 flex-col lg:flex border-r border-base-200 bg-base-100",
2566
+ "data-tocs": "[]",
2567
+ "data-title": "",
2568
+ "data-repo": repoUrl || ""
2569
+ }, React.createElement("div", { className: "flex h-full flex-col overflow-y-auto px-4" }, React.createElement(Menu, {
2570
+ pathname,
2571
+ routes: docuConfig.routes || []
2572
+ }))), React.createElement("main", { className: "flex-1 min-w-0 min-h-screen flex flex-col" }, React.createElement("div", { className: "hidden lg:flex items-center justify-end gap-6 h-14 px-8" }, React.createElement("nav", { className: "flex items-center gap-6 text-sm font-medium text-base-content/80" }, ...(docuConfig.navbar?.menu || []).map((item) => {
2573
+ const isExternal = /^https?:\/\//.test(item.href);
2574
+ const isDocsActive = item.href === "/docs";
2575
+ return React.createElement("a", {
2576
+ key: item.title,
2577
+ href: item.href,
2578
+ className: `flex items-center gap-1 hover:text-base-content transition-colors${isDocsActive ? " text-primary font-semibold" : ""}`,
2579
+ ...isExternal ? {
2580
+ target: "_blank",
2581
+ rel: "noopener noreferrer"
2582
+ } : {}
2583
+ }, item.title, isExternal ? React.createElement("svg", {
2584
+ xmlns: "http://www.w3.org/2000/svg",
2585
+ width: "14",
2586
+ height: "14",
2587
+ viewBox: "0 0 24 24",
2588
+ fill: "none",
2589
+ stroke: "currentColor",
2590
+ strokeWidth: "2",
2591
+ strokeLinecap: "round",
2592
+ strokeLinejoin: "round"
2593
+ }, React.createElement("path", { d: "M7 7h10v10" }), React.createElement("path", { d: "M7 17 17 7" })) : null);
2594
+ }))), React.createElement("div", { className: "flex-1 w-full" }, children))));
2595
+ }
2596
+ //#endregion
2597
+ //#region .docu/node/escapeHtml.ts
2598
+ var ESCAPE_RE = /[&<>"']/g;
2599
+ var ESCAPE_MAP = {
2600
+ "&": "&amp;",
2601
+ "<": "&lt;",
2602
+ ">": "&gt;",
2603
+ "\"": "&quot;",
2604
+ "'": "&#x27;"
2605
+ };
2606
+ /**
2607
+ * Runtime-neutral HTML escaping — same character set as `Bun.escapeHTML`
2608
+ * (& < > " '). Used by the shared HTML shell so Node/Deno entries never
2609
+ * touch the Bun global.
2610
+ */
2611
+ function escapeHtml(input) {
2612
+ return input.replace(ESCAPE_RE, (ch) => ESCAPE_MAP[ch]);
2613
+ }
2614
+ //#endregion
2615
+ //#region .docu/node/html.shared.ts
2616
+ /**
2617
+ * Runtime-neutral HTML shell — identical templates to `html.ts` (Bun-only,
2618
+ * protected) but escaping via the pure `escapeHtml()` so it runs on
2619
+ * Node.js and Deno as well as Bun. Shared modules (`server-routes.ts`) and
2620
+ * the non-Bun entries import from here; `build.ts`/`server.ts` keep using
2621
+ * `html.ts` untouched.
2622
+ */
2623
+ function htmlShell(opts) {
2624
+ const { title, description, body, favicon, css, js, nonce, csp, extraScripts, themeCss, depth = 0, headExtra, bodyExtra, absoluteAssets = false } = opts;
2625
+ const nonceAttr = nonce ? ` nonce="${escapeHtml(nonce)}"` : "";
2626
+ const themeStyle = themeCss ? `\n <style${nonceAttr}>${escapeHtml(themeCss)}</style>` : "";
2627
+ const headInjection = headExtra?.length ? `\n ${headExtra.join("\n ")}` : "";
2628
+ const bodyInjection = bodyExtra?.length ? `\n ${bodyExtra.join("\n ")}` : "";
2629
+ const depthPrefix = depth === 0 ? "" : "../".repeat(depth);
2630
+ const assetPrefix = absoluteAssets ? "/assets/" : depthPrefix + "assets/";
2631
+ const resolvePath = (path) => absoluteAssets ? path : path.startsWith("/") ? depthPrefix + path.slice(1) : path;
2632
+ let seoTags = "";
2633
+ if (opts.seo) {
2634
+ const s = opts.seo;
2635
+ const e = escapeHtml;
2636
+ seoTags = `\n <meta property="og:title" content="${e(title)}" />\n <meta property="og:description" content="${e(description)}" />\n <meta property="og:url" content="${e(s.url)}" />\n <meta property="og:type" content="website" />\n <meta property="og:site_name" content="${e(s.siteName)}" />\n <meta name="twitter:card" content="summary_large_image" />\n <link rel="canonical" href="${e(s.url)}" />`;
2637
+ if (s.image) seoTags += `\n <meta property="og:image" content="${e(s.image)}" />`;
2638
+ }
2639
+ return `<!DOCTYPE html>
2640
+ <html lang="en">
2641
+ <head>
2642
+ <meta charset="UTF-8">
2643
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2644
+ <title>${escapeHtml(title)}</title>
2645
+ <meta name="description" content="${escapeHtml(description)}">
2646
+ ${favicon ? `<link rel="icon" type="image/x-icon" href="${escapeHtml(resolvePath(favicon))}">` : ""}${themeStyle}
2647
+ <link rel="preload" href="${escapeHtml(assetPrefix + css)}" as="style">
2648
+ <link rel="stylesheet" href="${escapeHtml(assetPrefix + css)}">
2649
+ ${csp ? `<meta http-equiv="Content-Security-Policy" content="${escapeHtml(csp)}">` : ""}
2650
+ ${seoTags}
2651
+ <script${nonceAttr}>try{if(localStorage.getItem("theme")==="dark")document.documentElement.classList.add("dark")}catch(e){}<\/script>${headInjection}
2652
+ </head>
2653
+ <body>
2654
+ <div id="root">${body}</div>
2655
+ <link rel="modulepreload" href="${escapeHtml(assetPrefix + js)}">
2656
+ <script type="module"${nonceAttr} src="${escapeHtml(assetPrefix + js)}"><\/script>${extraScripts ? `\n ${extraScripts}` : ""}${bodyInjection}
2657
+ </body>
2658
+ </html>`;
2659
+ }
2660
+ function errorHtml(message, stack) {
2661
+ const msg = escapeHtml(message || "Unknown error");
2662
+ const st = escapeHtml(stack || "");
2663
+ return `<!DOCTYPE html>
2664
+ <html lang="en">
2665
+ <head>
2666
+ <meta charset="utf-8">
2667
+ <title>Server Error</title>
2668
+ <style>
2669
+ *{margin:0;padding:0;box-sizing:border-box}
2670
+ body{padding:2rem;font-family:ui-monospace,monospace;background:#1a1a2e;color:#e0e0e0}
2671
+ h1{color:#ff6b6b;font-size:1.5rem;margin-bottom:1rem}
2672
+ pre{background:#0d0d1a;border:1px solid #333;border-radius:8px;padding:1.5rem;overflow-x:auto;font-size:14px;line-height:1.6;white-space:pre-wrap;word-break:break-word}
2673
+ .msg{color:#ff6b6b;font-weight:bold}
2674
+ </style>
2675
+ </head>
2676
+ <body>
2677
+ <h1>🔥 Server Error</h1>
2678
+ <pre><span class="msg">${msg}</span>${st ? `\n\n${st}` : ""}</pre>
2679
+ </body>
2680
+ </html>`;
2681
+ }
2682
+ function hmrScript(nonce) {
2683
+ return `<script nonce="${escapeHtml(nonce)}">
2684
+ (function(){
2685
+ const es = new EventSource("/__hmr");
2686
+ es.onmessage = function(e) {
2687
+ if (e.data === "reload") window.location.reload();
2688
+ };
2689
+ es.onerror = function() { es.close(); setTimeout(() => { window.location.reload(); }, 2000); };
2690
+ })();
2691
+ <\/script>`;
2692
+ }
2693
+ //#endregion
2694
+ export { hashMdxSources as C, loadPlugins as D, BuildPluginBuilder as E, atomicWriteFile as S, runtimeStamp 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, 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, hookMemoryPressure as w, computeInlineThemeCss as x, getGitLastModifiedBatch as y };
2695
+
2696
+ //# sourceMappingURL=html.shared-FwgbE1WG.js.map