@ox-content/vite-plugin 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,2515 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DEFAULT_HTML_TEMPLATE: () => DEFAULT_HTML_TEMPLATE,
34
+ buildSearchIndex: () => buildSearchIndex,
35
+ buildSsg: () => buildSsg,
36
+ createMarkdownEnvironment: () => createMarkdownEnvironment,
37
+ extractDocs: () => extractDocs,
38
+ generateMarkdown: () => generateMarkdown,
39
+ oxContent: () => oxContent2,
40
+ resolveDocsOptions: () => resolveDocsOptions,
41
+ resolveSearchOptions: () => resolveSearchOptions,
42
+ resolveSsgOptions: () => resolveSsgOptions,
43
+ transformMarkdown: () => transformMarkdown,
44
+ writeDocs: () => writeDocs,
45
+ writeSearchIndex: () => writeSearchIndex
46
+ });
47
+ module.exports = __toCommonJS(index_exports);
48
+ var path5 = __toESM(require("path"), 1);
49
+
50
+ // src/environment.ts
51
+ function createMarkdownEnvironment(options) {
52
+ return {
53
+ // Consumer type for this environment
54
+ consumer: "server",
55
+ // Build configuration
56
+ build: {
57
+ // Output to a separate directory
58
+ outDir: `${options.outDir}/.markdown`,
59
+ // Emit assets for SSG
60
+ emitAssets: true,
61
+ // Create manifest for asset tracking
62
+ manifest: true,
63
+ // SSR-like externalization
64
+ rollupOptions: {
65
+ external: [
66
+ // Externalize Node.js built-ins
67
+ /^node:/,
68
+ // Externalize native modules
69
+ /\.node$/
70
+ ]
71
+ }
72
+ },
73
+ // Resolve configuration
74
+ resolve: {
75
+ // Handle .md files
76
+ extensions: [".md", ".markdown"],
77
+ // Conditions for module resolution
78
+ conditions: ["markdown", "node", "import"],
79
+ // Don't dedupe - each environment gets its own modules
80
+ dedupe: []
81
+ },
82
+ // Optimize dependencies
83
+ optimizeDeps: {
84
+ // Include ox-content dependencies
85
+ include: [],
86
+ // Exclude native modules
87
+ exclude: ["@ox-content/napi"]
88
+ }
89
+ };
90
+ }
91
+
92
+ // src/highlight.ts
93
+ var import_unified = require("unified");
94
+ var import_rehype_parse = __toESM(require("rehype-parse"), 1);
95
+ var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
96
+ var import_shiki = require("shiki");
97
+ var highlighterPromise = null;
98
+ async function getHighlighter(theme) {
99
+ if (!highlighterPromise) {
100
+ highlighterPromise = (0, import_shiki.createHighlighter)({
101
+ themes: [theme],
102
+ langs: [
103
+ "javascript",
104
+ "typescript",
105
+ "jsx",
106
+ "tsx",
107
+ "vue",
108
+ "svelte",
109
+ "html",
110
+ "css",
111
+ "scss",
112
+ "json",
113
+ "yaml",
114
+ "markdown",
115
+ "bash",
116
+ "shell",
117
+ "rust",
118
+ "python",
119
+ "go",
120
+ "java",
121
+ "c",
122
+ "cpp",
123
+ "sql",
124
+ "graphql",
125
+ "diff",
126
+ "toml"
127
+ ]
128
+ });
129
+ }
130
+ return highlighterPromise;
131
+ }
132
+ function rehypeShikiHighlight(options) {
133
+ const { theme } = options;
134
+ return async (tree) => {
135
+ const highlighter = await getHighlighter(theme);
136
+ const visit = async (node) => {
137
+ if ("children" in node) {
138
+ for (let i = 0; i < node.children.length; i++) {
139
+ const child = node.children[i];
140
+ if (child.type === "element" && child.tagName === "pre") {
141
+ const codeElement = child.children.find(
142
+ (c) => c.type === "element" && c.tagName === "code"
143
+ );
144
+ if (codeElement) {
145
+ const className = codeElement.properties?.className;
146
+ let lang = "text";
147
+ if (Array.isArray(className)) {
148
+ const langClass = className.find(
149
+ (c) => typeof c === "string" && c.startsWith("language-")
150
+ );
151
+ if (langClass && typeof langClass === "string") {
152
+ lang = langClass.replace("language-", "");
153
+ }
154
+ }
155
+ const codeText = getTextContent(codeElement);
156
+ try {
157
+ const highlighted = highlighter.codeToHtml(codeText, {
158
+ lang,
159
+ theme
160
+ });
161
+ const parsed = (0, import_unified.unified)().use(import_rehype_parse.default, { fragment: true }).parse(highlighted);
162
+ if (parsed.children[0]) {
163
+ node.children[i] = parsed.children[0];
164
+ }
165
+ } catch {
166
+ }
167
+ }
168
+ } else if (child.type === "element") {
169
+ await visit(child);
170
+ }
171
+ }
172
+ }
173
+ };
174
+ await visit(tree);
175
+ };
176
+ }
177
+ function getTextContent(node) {
178
+ let text = "";
179
+ if ("children" in node) {
180
+ for (const child of node.children) {
181
+ if (child.type === "text") {
182
+ text += child.value;
183
+ } else if (child.type === "element") {
184
+ text += getTextContent(child);
185
+ }
186
+ }
187
+ }
188
+ return text;
189
+ }
190
+ async function highlightCode(html, theme = "github-dark") {
191
+ const result = await (0, import_unified.unified)().use(import_rehype_parse.default, { fragment: true }).use(rehypeShikiHighlight, { theme }).use(import_rehype_stringify.default).process(html);
192
+ return String(result);
193
+ }
194
+
195
+ // src/mermaid.ts
196
+ var import_unified2 = require("unified");
197
+ var import_rehype_parse2 = __toESM(require("rehype-parse"), 1);
198
+ var import_rehype_stringify2 = __toESM(require("rehype-stringify"), 1);
199
+ function getTextContent2(node) {
200
+ let text = "";
201
+ if ("children" in node) {
202
+ for (const child of node.children) {
203
+ if (child.type === "text") {
204
+ text += child.value;
205
+ } else if (child.type === "element") {
206
+ text += getTextContent2(child);
207
+ }
208
+ }
209
+ }
210
+ return text;
211
+ }
212
+ function rehypeMermaid() {
213
+ return (tree) => {
214
+ const visit = (node) => {
215
+ if ("children" in node) {
216
+ for (let i = 0; i < node.children.length; i++) {
217
+ const child = node.children[i];
218
+ if (child.type === "element" && child.tagName === "pre") {
219
+ const codeElement = child.children.find(
220
+ (c) => c.type === "element" && c.tagName === "code"
221
+ );
222
+ if (codeElement) {
223
+ const className = codeElement.properties?.className;
224
+ let isMermaid = false;
225
+ if (Array.isArray(className)) {
226
+ isMermaid = className.some(
227
+ (c) => typeof c === "string" && c.includes("mermaid")
228
+ );
229
+ }
230
+ if (isMermaid) {
231
+ const mermaidCode = getTextContent2(codeElement);
232
+ const wrapper = {
233
+ type: "element",
234
+ tagName: "div",
235
+ properties: {
236
+ className: ["ox-mermaid"],
237
+ "data-mermaid": mermaidCode
238
+ },
239
+ children: [
240
+ {
241
+ type: "element",
242
+ tagName: "pre",
243
+ properties: {
244
+ className: ["ox-mermaid-source"]
245
+ },
246
+ children: [
247
+ {
248
+ type: "text",
249
+ value: mermaidCode
250
+ }
251
+ ]
252
+ }
253
+ ]
254
+ };
255
+ node.children[i] = wrapper;
256
+ }
257
+ }
258
+ } else if (child.type === "element") {
259
+ visit(child);
260
+ }
261
+ }
262
+ }
263
+ };
264
+ visit(tree);
265
+ };
266
+ }
267
+ async function transformMermaid(html) {
268
+ const result = await (0, import_unified2.unified)().use(import_rehype_parse2.default, { fragment: true }).use(rehypeMermaid).use(import_rehype_stringify2.default).process(html);
269
+ return String(result);
270
+ }
271
+
272
+ // src/transform.ts
273
+ var napiBindings;
274
+ var napiLoadAttempted = false;
275
+ async function loadNapiBindings() {
276
+ if (napiLoadAttempted) {
277
+ return napiBindings ?? null;
278
+ }
279
+ napiLoadAttempted = true;
280
+ try {
281
+ const mod = await import("@ox-content/napi");
282
+ napiBindings = mod;
283
+ return mod;
284
+ } catch (error) {
285
+ if (process.env.DEBUG) {
286
+ console.debug("[ox-content] NAPI bindings load failed:", error);
287
+ }
288
+ napiBindings = null;
289
+ return null;
290
+ }
291
+ }
292
+ async function transformMarkdown(source, filePath, options, ssgOptions) {
293
+ const napi = await loadNapiBindings();
294
+ if (!napi) {
295
+ throw new Error("[ox-content] NAPI bindings not available. Please ensure @ox-content/napi is built.");
296
+ }
297
+ const result = napi.transform(source, {
298
+ gfm: options.gfm,
299
+ footnotes: options.footnotes,
300
+ taskLists: options.taskLists,
301
+ tables: options.tables,
302
+ strikethrough: options.strikethrough,
303
+ tocMaxDepth: options.tocMaxDepth,
304
+ convertMdLinks: ssgOptions?.convertMdLinks,
305
+ baseUrl: ssgOptions?.baseUrl
306
+ });
307
+ if (result.errors.length > 0) {
308
+ console.warn("[ox-content] Transform warnings:", result.errors);
309
+ }
310
+ let html = result.html;
311
+ let frontmatter;
312
+ try {
313
+ frontmatter = JSON.parse(result.frontmatter);
314
+ } catch {
315
+ frontmatter = {};
316
+ }
317
+ const flatToc = result.toc.map((item) => ({
318
+ ...item,
319
+ children: []
320
+ }));
321
+ const toc = options.toc ? buildTocTree(flatToc) : [];
322
+ if (options.highlight) {
323
+ html = await highlightCode(html, options.highlightTheme);
324
+ }
325
+ if (options.mermaid) {
326
+ html = await transformMermaid(html);
327
+ }
328
+ const code = generateModuleCode(html, frontmatter, toc, filePath, options);
329
+ return {
330
+ code,
331
+ html,
332
+ frontmatter,
333
+ toc
334
+ };
335
+ }
336
+ function buildTocTree(entries) {
337
+ const root = [];
338
+ const stack = [];
339
+ for (const entry of entries) {
340
+ while (stack.length > 0 && stack[stack.length - 1].depth >= entry.depth) {
341
+ stack.pop();
342
+ }
343
+ if (stack.length === 0) {
344
+ root.push(entry);
345
+ } else {
346
+ stack[stack.length - 1].children.push(entry);
347
+ }
348
+ stack.push(entry);
349
+ }
350
+ return root;
351
+ }
352
+ function generateModuleCode(html, frontmatter, toc, filePath, _options) {
353
+ const htmlJson = JSON.stringify(html);
354
+ const frontmatterJson = JSON.stringify(frontmatter);
355
+ const tocJson = JSON.stringify(toc);
356
+ return `
357
+ // Generated by vite-plugin-ox-content
358
+ // Source: ${filePath}
359
+
360
+ /**
361
+ * Rendered HTML content.
362
+ */
363
+ export const html = ${htmlJson};
364
+
365
+ /**
366
+ * Parsed frontmatter.
367
+ */
368
+ export const frontmatter = ${frontmatterJson};
369
+
370
+ /**
371
+ * Table of contents.
372
+ */
373
+ export const toc = ${tocJson};
374
+
375
+ /**
376
+ * Default export with all data.
377
+ */
378
+ export default {
379
+ html,
380
+ frontmatter,
381
+ toc,
382
+ };
383
+
384
+ // HMR support
385
+ if (import.meta.hot) {
386
+ import.meta.hot.accept((newModule) => {
387
+ if (newModule) {
388
+ // Trigger re-render with new content
389
+ import.meta.hot.invalidate();
390
+ }
391
+ });
392
+ }
393
+ `;
394
+ }
395
+ async function generateOgImageSvg(data, config) {
396
+ const napi = await loadNapiBindings();
397
+ if (!napi) {
398
+ return null;
399
+ }
400
+ const napiConfig = config ? {
401
+ width: config.width,
402
+ height: config.height,
403
+ backgroundColor: config.backgroundColor,
404
+ textColor: config.textColor,
405
+ titleFontSize: config.titleFontSize,
406
+ descriptionFontSize: config.descriptionFontSize
407
+ } : void 0;
408
+ return napi.generateOgImageSvg(data, napiConfig);
409
+ }
410
+
411
+ // src/docs.ts
412
+ var fs = __toESM(require("fs"), 1);
413
+ var path2 = __toESM(require("path"), 1);
414
+
415
+ // src/nav-generator.ts
416
+ var import_path = __toESM(require("path"), 1);
417
+ function generateNavMetadata(docs, basePath = "/api") {
418
+ const sortedDocs = [...docs].sort((a, b) => {
419
+ const aName = getDocDisplayName(a.file);
420
+ const bName = getDocDisplayName(b.file);
421
+ return aName.localeCompare(bName);
422
+ });
423
+ return sortedDocs.map((doc) => ({
424
+ title: getDocDisplayName(doc.file),
425
+ path: `${basePath}/${getDocFileName(doc.file)}`
426
+ }));
427
+ }
428
+ function getDocDisplayName(filePath) {
429
+ const fileName = import_path.default.basename(filePath, import_path.default.extname(filePath));
430
+ if (fileName === "index" || fileName === "index-module") {
431
+ return "Overview";
432
+ }
433
+ return fileName.replace(/[-_]([a-z])/g, (_, char) => " " + char.toUpperCase()).replace(/^[a-z]/, (char) => char.toUpperCase());
434
+ }
435
+ function getDocFileName(filePath) {
436
+ const fileName = import_path.default.basename(filePath, import_path.default.extname(filePath));
437
+ if (fileName === "index") {
438
+ return "index";
439
+ }
440
+ return fileName;
441
+ }
442
+ function generateNavCode(navItems, exportName = "apiNav") {
443
+ const json = JSON.stringify(navItems, null, 2);
444
+ return `/**
445
+ * Auto-generated API documentation navigation.
446
+ * This file is automatically generated by the docs plugin.
447
+ * Do not edit manually.
448
+ */
449
+
450
+ export interface NavItem {
451
+ title: string;
452
+ path: string;
453
+ children?: NavItem[];
454
+ }
455
+
456
+ export const ${exportName}: NavItem[] = ${json} as const;
457
+ `;
458
+ }
459
+
460
+ // src/docs.ts
461
+ var JSDOC_BLOCK = /^[ \t]*\/\*\*\s*([\s\S]*?)\s*\*\//gm;
462
+ async function extractDocs(srcDirs, options) {
463
+ const results = [];
464
+ for (const srcDir of srcDirs) {
465
+ const files = await findFiles(srcDir, options);
466
+ for (const file of files) {
467
+ const content = await fs.promises.readFile(file, "utf-8");
468
+ const entries = extractFromContent(content, file, options);
469
+ if (entries.length > 0) {
470
+ results.push({ file, entries });
471
+ }
472
+ }
473
+ }
474
+ return results;
475
+ }
476
+ async function findFiles(dir, options) {
477
+ const files = [];
478
+ async function walk(currentDir) {
479
+ let entries;
480
+ try {
481
+ entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
482
+ } catch {
483
+ return;
484
+ }
485
+ for (const entry of entries) {
486
+ const fullPath = path2.join(currentDir, entry.name);
487
+ if (entry.isDirectory()) {
488
+ if (!isExcluded(fullPath, options.exclude)) {
489
+ await walk(fullPath);
490
+ }
491
+ } else if (entry.isFile()) {
492
+ if (isIncluded(fullPath, options.include) && !isExcluded(fullPath, options.exclude)) {
493
+ files.push(fullPath);
494
+ }
495
+ }
496
+ }
497
+ }
498
+ await walk(dir);
499
+ return files;
500
+ }
501
+ function isIncluded(file, patterns) {
502
+ return patterns.some((pattern) => {
503
+ if (pattern.includes("**")) {
504
+ const ext = pattern.split(".").pop();
505
+ return file.endsWith(`.${ext}`);
506
+ }
507
+ return file.endsWith(pattern.replace("*", ""));
508
+ });
509
+ }
510
+ function isExcluded(file, patterns) {
511
+ return patterns.some((pattern) => {
512
+ if (pattern.includes("node_modules")) {
513
+ return file.includes("node_modules");
514
+ }
515
+ if (pattern.includes(".test.") || pattern.includes(".spec.")) {
516
+ return file.includes(".test.") || file.includes(".spec.");
517
+ }
518
+ return false;
519
+ });
520
+ }
521
+ function extractFromContent(content, file, options) {
522
+ const entries = [];
523
+ let match;
524
+ JSDOC_BLOCK.lastIndex = 0;
525
+ while ((match = JSDOC_BLOCK.exec(content)) !== null) {
526
+ const jsdocContent = match[1];
527
+ const jsdocEnd = match.index + match[0].length;
528
+ const afterJsdoc = content.slice(jsdocEnd).trim();
529
+ const lineNumber = content.slice(0, match.index).split("\n").length;
530
+ const entry = parseJsdocBlock(jsdocContent, afterJsdoc, file, lineNumber);
531
+ if (entry && (options.private || !entry.private)) {
532
+ entries.push(entry);
533
+ }
534
+ }
535
+ return entries;
536
+ }
537
+ function extractFunctionSignature(signature) {
538
+ const match = signature.match(
539
+ /(?:export\s+)?(?:async\s+)?(?:function\s+\w+|\w+\s*=\s*(?:async\s*)?\()\([^{]*?\)(?:\s*:\s*[^{;]+)?/s
540
+ );
541
+ if (match) {
542
+ let sig = match[0].trim();
543
+ sig = sig.split("\n").map((line) => line.trim()).filter((line) => line).join("\n ");
544
+ return sig;
545
+ }
546
+ return void 0;
547
+ }
548
+ function extractTypesFromSignature(signature, params) {
549
+ const paramTypes = [];
550
+ const paramListMatch = signature.match(/\(([^)]*)\)(?:\s*:\s*([^{=>]+))?/s);
551
+ if (paramListMatch && paramListMatch[1]) {
552
+ const paramListStr = paramListMatch[1];
553
+ const paramParts = splitParameters(paramListStr);
554
+ for (const part of paramParts) {
555
+ const trimmed = part.trim();
556
+ if (!trimmed) continue;
557
+ const typeMatch = /:\s*(.+?)(?:\s*=|$)/.exec(trimmed);
558
+ if (typeMatch) {
559
+ let typeStr = typeMatch[1].trim();
560
+ if (typeStr.includes("=")) {
561
+ typeStr = typeStr.split("=")[0].trim();
562
+ }
563
+ paramTypes.push(typeStr);
564
+ }
565
+ }
566
+ }
567
+ let returnType;
568
+ const returnTypeMatch = signature.match(/\)\s*:\s*(.+?)(?={|$)/);
569
+ if (returnTypeMatch) {
570
+ returnType = returnTypeMatch[1].trim();
571
+ }
572
+ return {
573
+ paramTypes,
574
+ returnType
575
+ };
576
+ }
577
+ function splitParameters(paramListStr) {
578
+ const parts = [];
579
+ let current = "";
580
+ let depth = 0;
581
+ for (const char of paramListStr) {
582
+ if (char === "<") {
583
+ depth++;
584
+ current += char;
585
+ } else if (char === ">") {
586
+ depth--;
587
+ current += char;
588
+ } else if (char === "," && depth === 0) {
589
+ parts.push(current);
590
+ current = "";
591
+ } else {
592
+ current += char;
593
+ }
594
+ }
595
+ if (current) {
596
+ parts.push(current);
597
+ }
598
+ return parts;
599
+ }
600
+ function parseJsdocBlock(jsdoc, declaration, file, line) {
601
+ const params = [];
602
+ const examples = [];
603
+ const tags = {};
604
+ let description = "";
605
+ let returns;
606
+ let isPrivate = false;
607
+ const rawLines = jsdoc.split("\n").map((l) => l.replace(/^\s*\*\s?/, ""));
608
+ const cleanedLines = rawLines.map((l) => l.trim()).filter((l) => l);
609
+ let currentExample = "";
610
+ let inExample = false;
611
+ let rawLineIndex = 0;
612
+ for (const lineText of cleanedLines) {
613
+ while (rawLineIndex < rawLines.length && rawLines[rawLineIndex].trim() !== lineText) {
614
+ rawLineIndex++;
615
+ }
616
+ const rawLine = rawLineIndex < rawLines.length ? rawLines[rawLineIndex] : lineText;
617
+ rawLineIndex++;
618
+ if (lineText.startsWith("@")) {
619
+ if (inExample) {
620
+ examples.push(currentExample.trim());
621
+ currentExample = "";
622
+ inExample = false;
623
+ }
624
+ const tagMatch = /@(\w+)\s*(?:\{([^}]*)\})?(.*)/.exec(lineText);
625
+ if (tagMatch) {
626
+ const [, tagName, tagType, tagRest] = tagMatch;
627
+ switch (tagName) {
628
+ case "param":
629
+ const paramMatch = /(\w+)\s*-?\s*(.*)/.exec(tagRest.trim());
630
+ if (paramMatch) {
631
+ params.push({
632
+ name: paramMatch[1],
633
+ type: tagType || "unknown",
634
+ description: paramMatch[2]
635
+ });
636
+ }
637
+ break;
638
+ case "returns":
639
+ case "return":
640
+ returns = {
641
+ type: tagType || "unknown",
642
+ description: tagRest.trim()
643
+ };
644
+ break;
645
+ case "example":
646
+ inExample = true;
647
+ break;
648
+ case "private":
649
+ isPrivate = true;
650
+ break;
651
+ default:
652
+ tags[tagName] = tagRest.trim();
653
+ }
654
+ }
655
+ } else if (inExample) {
656
+ currentExample += rawLine + "\n";
657
+ } else if (!description) {
658
+ description = lineText;
659
+ } else {
660
+ description += "\n" + lineText;
661
+ }
662
+ }
663
+ if (inExample && currentExample) {
664
+ examples.push(currentExample.trim());
665
+ }
666
+ const firstFewLines = declaration.split("\n").slice(0, 5).join("\n");
667
+ let name = "";
668
+ let kind = "function";
669
+ const ANCHORED_FUNCTION = /^(?:export\s+)?(?:async\s+)?function\s+(\w+)/;
670
+ const ANCHORED_CONST_FUNC = /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\(/;
671
+ const ANCHORED_CLASS = /^(?:export\s+)?class\s+(\w+)/;
672
+ const ANCHORED_INTERFACE = /^(?:export\s+)?interface\s+(\w+)/;
673
+ const ANCHORED_TYPE = /^(?:export\s+)?type\s+(\w+)/;
674
+ let declMatch;
675
+ if (declMatch = ANCHORED_FUNCTION.exec(firstFewLines)) {
676
+ name = declMatch[1];
677
+ kind = "function";
678
+ } else if (declMatch = ANCHORED_CONST_FUNC.exec(firstFewLines)) {
679
+ name = declMatch[1];
680
+ kind = "function";
681
+ } else if (declMatch = ANCHORED_CLASS.exec(firstFewLines)) {
682
+ name = declMatch[1];
683
+ kind = "class";
684
+ } else if (declMatch = ANCHORED_INTERFACE.exec(firstFewLines)) {
685
+ name = declMatch[1];
686
+ kind = "interface";
687
+ } else if (declMatch = ANCHORED_TYPE.exec(firstFewLines)) {
688
+ name = declMatch[1];
689
+ kind = "type";
690
+ }
691
+ if (!name) return null;
692
+ let signature;
693
+ if (kind === "function") {
694
+ const signatureTypes = extractTypesFromSignature(firstFewLines, params);
695
+ if (signatureTypes.paramTypes.length > 0) {
696
+ for (let i = 0; i < params.length && i < signatureTypes.paramTypes.length; i++) {
697
+ if (params[i].type === "unknown") {
698
+ params[i].type = signatureTypes.paramTypes[i];
699
+ }
700
+ }
701
+ }
702
+ if (signatureTypes.returnType && (!returns || returns.type === "unknown")) {
703
+ if (returns) {
704
+ returns.type = signatureTypes.returnType;
705
+ } else {
706
+ returns = {
707
+ type: signatureTypes.returnType,
708
+ description: ""
709
+ };
710
+ }
711
+ }
712
+ signature = extractFunctionSignature(firstFewLines);
713
+ }
714
+ return {
715
+ name,
716
+ kind,
717
+ description,
718
+ params: params.length > 0 ? params : void 0,
719
+ returns,
720
+ examples: examples.length > 0 ? examples : void 0,
721
+ tags: Object.keys(tags).length > 0 ? tags : void 0,
722
+ private: isPrivate,
723
+ file,
724
+ line,
725
+ signature
726
+ };
727
+ }
728
+ function generateMarkdown(docs, options) {
729
+ const result = {};
730
+ const symbolMap = buildSymbolMap(docs);
731
+ if (options.groupBy === "file") {
732
+ const docToFile = /* @__PURE__ */ new Map();
733
+ for (const doc of docs) {
734
+ let fileName = path2.basename(doc.file, path2.extname(doc.file));
735
+ if (fileName === "index") {
736
+ fileName = "index-module";
737
+ }
738
+ docToFile.set(doc, fileName);
739
+ const markdown = generateFileMarkdown(doc, options, fileName, symbolMap);
740
+ result[`${fileName}.md`] = markdown;
741
+ }
742
+ result["index.md"] = generateIndex(docs, docToFile);
743
+ } else {
744
+ const byKind = /* @__PURE__ */ new Map();
745
+ for (const doc of docs) {
746
+ for (const entry of doc.entries) {
747
+ const existing = byKind.get(entry.kind) || [];
748
+ existing.push(entry);
749
+ byKind.set(entry.kind, existing);
750
+ }
751
+ }
752
+ for (const [kind, entries] of byKind) {
753
+ result[`${kind}s.md`] = generateCategoryMarkdown(kind, entries, options, symbolMap);
754
+ }
755
+ result["index.md"] = generateCategoryIndex(byKind);
756
+ }
757
+ return result;
758
+ }
759
+ function generateFileMarkdown(doc, options, currentFileName, symbolMap) {
760
+ const displayName = path2.basename(doc.file);
761
+ let md = `# ${displayName}
762
+
763
+ `;
764
+ if (options.githubUrl) {
765
+ const sourceLink = generateSourceLink(doc.file, options.githubUrl);
766
+ if (sourceLink) {
767
+ md += sourceLink + "\n\n";
768
+ }
769
+ }
770
+ for (const entry of doc.entries) {
771
+ md += generateEntryMarkdown(entry, options, currentFileName, symbolMap);
772
+ }
773
+ return md;
774
+ }
775
+ function generateEntryMarkdown(entry, options, currentFileName, symbolMap) {
776
+ let md = `## ${entry.name}
777
+
778
+ `;
779
+ md += `\`${entry.kind}\`
780
+
781
+ `;
782
+ if (entry.description) {
783
+ const processedDescription = currentFileName && symbolMap ? convertSymbolLinks(entry.description, currentFileName, symbolMap) : entry.description;
784
+ md += `${processedDescription}
785
+
786
+ `;
787
+ }
788
+ if (options?.githubUrl) {
789
+ const sourceLink = generateSourceLink(entry.file, options.githubUrl, entry.line);
790
+ if (sourceLink) {
791
+ md += sourceLink + "\n\n";
792
+ }
793
+ }
794
+ if (entry.signature && entry.kind === "function") {
795
+ md += "```typescript\n";
796
+ md += entry.signature + "\n";
797
+ md += "```\n\n";
798
+ }
799
+ if (entry.params && entry.params.length > 0) {
800
+ md += "### Parameters\n\n";
801
+ md += "| Name | Type | Description |\n";
802
+ md += "|------|------|-------------|\n";
803
+ for (const param of entry.params) {
804
+ md += `| \`${param.name}\` | \`${param.type}\` | ${param.description} |
805
+ `;
806
+ }
807
+ md += "\n";
808
+ }
809
+ if (entry.returns) {
810
+ md += "### Returns\n\n";
811
+ md += `\`${entry.returns.type}\` - ${entry.returns.description}
812
+
813
+ `;
814
+ }
815
+ if (entry.examples && entry.examples.length > 0) {
816
+ md += "### Examples\n\n";
817
+ for (const example of entry.examples) {
818
+ md += "```ts\n";
819
+ md += example.replace(/^```\w*\n?/, "").replace(/\n?```$/, "");
820
+ md += "\n```\n\n";
821
+ }
822
+ }
823
+ md += "---\n\n";
824
+ return md;
825
+ }
826
+ function generateIndex(docs, docToFile) {
827
+ let md = "# API Documentation\n\n";
828
+ md += "Generated by [Ox Content](https://github.com/ubugeeei/ox-content)\n\n";
829
+ md += "## Modules\n\n";
830
+ for (const doc of docs) {
831
+ const displayName = path2.basename(doc.file, path2.extname(doc.file));
832
+ let fileName = displayName;
833
+ if (docToFile && docToFile.has(doc)) {
834
+ fileName = docToFile.get(doc);
835
+ } else if (fileName === "index") {
836
+ fileName = "index-module";
837
+ }
838
+ md += `### [${displayName}](./${fileName}.md)
839
+
840
+ `;
841
+ for (const entry of doc.entries) {
842
+ const desc = entry.description?.slice(0, 80) || "";
843
+ const ellipsis = entry.description && entry.description.length > 80 ? "..." : "";
844
+ md += `- \`${entry.kind}\` **${entry.name}** - ${desc}${ellipsis}
845
+ `;
846
+ }
847
+ md += "\n";
848
+ }
849
+ return md;
850
+ }
851
+ function generateCategoryMarkdown(kind, entries, options, symbolMap) {
852
+ const categoryFileName = `${kind}s`;
853
+ let md = `# ${kind.charAt(0).toUpperCase() + kind.slice(1)}s
854
+
855
+ `;
856
+ for (const entry of entries) {
857
+ md += generateEntryMarkdown(entry, options, categoryFileName, symbolMap);
858
+ }
859
+ return md;
860
+ }
861
+ function generateCategoryIndex(byKind) {
862
+ let md = "# API Documentation\n\n";
863
+ md += "Generated by [Ox Content](https://github.com/ubugeeei/ox-content)\n\n";
864
+ for (const [kind, entries] of byKind) {
865
+ const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
866
+ md += `## [${kindTitle}](./${kind}s.md)
867
+
868
+ `;
869
+ for (const entry of entries) {
870
+ const desc = entry.description?.slice(0, 60) || "";
871
+ md += `- **${entry.name}** - ${desc}...
872
+ `;
873
+ }
874
+ md += "\n";
875
+ }
876
+ return md;
877
+ }
878
+ function convertSymbolLinks(text, currentFileName, symbolMap) {
879
+ return text.replace(/\[([A-Z_]\w*)\](?!\()/g, (match, symbolName) => {
880
+ const location = symbolMap.get(symbolName);
881
+ if (!location) {
882
+ return match;
883
+ }
884
+ if (location.fileName === currentFileName) {
885
+ return `[${symbolName}](#${symbolName.toLowerCase()})`;
886
+ } else {
887
+ return `[${symbolName}](./${location.fileName}.md#${symbolName.toLowerCase()})`;
888
+ }
889
+ });
890
+ }
891
+ function buildSymbolMap(docs) {
892
+ const map = /* @__PURE__ */ new Map();
893
+ for (const doc of docs) {
894
+ let fileName = path2.basename(doc.file, path2.extname(doc.file));
895
+ if (fileName === "index") {
896
+ fileName = "index-module";
897
+ }
898
+ for (const entry of doc.entries) {
899
+ map.set(entry.name, {
900
+ name: entry.name,
901
+ file: doc.file,
902
+ fileName
903
+ });
904
+ }
905
+ }
906
+ return map;
907
+ }
908
+ async function writeDocs(docs, outDir, extractedDocs, options) {
909
+ await fs.promises.mkdir(outDir, { recursive: true });
910
+ for (const [fileName, content] of Object.entries(docs)) {
911
+ const filePath = path2.join(outDir, fileName);
912
+ await fs.promises.writeFile(filePath, content, "utf-8");
913
+ }
914
+ if (extractedDocs && options?.generateNav && options.groupBy === "file") {
915
+ const navItems = generateNavMetadata(extractedDocs, "/api");
916
+ const navCode = generateNavCode(navItems, "apiNav");
917
+ const navFilePath = path2.join(outDir, "nav.ts");
918
+ await fs.promises.writeFile(navFilePath, navCode, "utf-8");
919
+ }
920
+ }
921
+ function generateSourceLink(filePath, githubUrl, lineNumber) {
922
+ const relativePath = filePath.replace(/^.*?\/(npm|packages|crates|src)\//, "$1/");
923
+ const fragment = lineNumber ? `#L${lineNumber}` : "";
924
+ const link = `${githubUrl}/blob/main/${relativePath}${fragment}`;
925
+ return `**[Source](${link})**`;
926
+ }
927
+ function resolveDocsOptions(options) {
928
+ if (options === false) {
929
+ return false;
930
+ }
931
+ const opts = options || {};
932
+ return {
933
+ enabled: opts.enabled ?? true,
934
+ src: opts.src ?? ["./src"],
935
+ out: opts.out ?? "docs/api",
936
+ include: opts.include ?? ["**/*.ts", "**/*.tsx"],
937
+ exclude: opts.exclude ?? ["**/*.test.*", "**/*.spec.*", "node_modules"],
938
+ format: opts.format ?? "markdown",
939
+ private: opts.private ?? false,
940
+ toc: false,
941
+ groupBy: opts.groupBy ?? "file",
942
+ githubUrl: opts.githubUrl,
943
+ generateNav: opts.generateNav ?? true
944
+ };
945
+ }
946
+
947
+ // src/ssg.ts
948
+ var fs2 = __toESM(require("fs/promises"), 1);
949
+ var path3 = __toESM(require("path"), 1);
950
+ var import_glob = require("glob");
951
+ var DEFAULT_HTML_TEMPLATE = `<!DOCTYPE html>
952
+ <html lang="en">
953
+ <head>
954
+ <meta charset="UTF-8">
955
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
956
+ <title>{{title}}{{#siteName}} - {{siteName}}{{/siteName}}</title>
957
+ {{#description}}<meta name="description" content="{{description}}">{{/description}}
958
+ <!-- Open Graph -->
959
+ <meta property="og:type" content="website">
960
+ <meta property="og:title" content="{{title}}{{#siteName}} - {{siteName}}{{/siteName}}">
961
+ {{#description}}<meta property="og:description" content="{{description}}">{{/description}}
962
+ {{#ogImage}}<meta property="og:image" content="{{ogImage}}">{{/ogImage}}
963
+ <!-- Twitter Card -->
964
+ <meta name="twitter:card" content="summary_large_image">
965
+ <meta name="twitter:title" content="{{title}}{{#siteName}} - {{siteName}}{{/siteName}}">
966
+ {{#description}}<meta name="twitter:description" content="{{description}}">{{/description}}
967
+ {{#ogImage}}<meta name="twitter:image" content="{{ogImage}}">{{/ogImage}}
968
+ <style>
969
+ :root {
970
+ --sidebar-width: 260px;
971
+ --header-height: 60px;
972
+ --max-content-width: 960px;
973
+ --font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
974
+ --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
975
+ --color-bg: #ffffff;
976
+ --color-bg-alt: #f8f9fa;
977
+ --color-text: #1a1a1a;
978
+ --color-text-muted: #666666;
979
+ --color-border: #e5e7eb;
980
+ --color-primary: #b7410e;
981
+ --color-primary-hover: #ce5937;
982
+ --color-code-bg: #1e293b;
983
+ --color-code-text: #e2e8f0;
984
+ }
985
+ [data-theme="dark"] {
986
+ --color-bg: #0f172a;
987
+ --color-bg-alt: #1e293b;
988
+ --color-text: #e2e8f0;
989
+ --color-text-muted: #94a3b8;
990
+ --color-border: #334155;
991
+ --color-primary: #e67e4d;
992
+ --color-primary-hover: #f4a07a;
993
+ --color-code-bg: #0f172a;
994
+ --color-code-text: #e2e8f0;
995
+ }
996
+ @media (prefers-color-scheme: dark) {
997
+ :root:not([data-theme="light"]) {
998
+ --color-bg: #0f172a;
999
+ --color-bg-alt: #1e293b;
1000
+ --color-text: #e2e8f0;
1001
+ --color-text-muted: #94a3b8;
1002
+ --color-border: #334155;
1003
+ --color-primary: #e67e4d;
1004
+ --color-primary-hover: #f4a07a;
1005
+ --color-code-bg: #0f172a;
1006
+ --color-code-text: #e2e8f0;
1007
+ }
1008
+ }
1009
+ * { box-sizing: border-box; margin: 0; padding: 0; }
1010
+ html { scroll-behavior: smooth; }
1011
+ body {
1012
+ font-family: var(--font-sans);
1013
+ line-height: 1.7;
1014
+ color: var(--color-text);
1015
+ background: var(--color-bg);
1016
+ }
1017
+ a { color: var(--color-primary); text-decoration: none; }
1018
+ a:hover { color: var(--color-primary-hover); text-decoration: underline; }
1019
+
1020
+ /* Header */
1021
+ .header {
1022
+ position: fixed;
1023
+ top: 0;
1024
+ left: 0;
1025
+ right: 0;
1026
+ height: var(--header-height);
1027
+ background: var(--color-bg);
1028
+ border-bottom: 1px solid var(--color-border);
1029
+ display: flex;
1030
+ align-items: center;
1031
+ padding: 0 1.5rem;
1032
+ z-index: 100;
1033
+ }
1034
+ .header-title {
1035
+ font-size: 1.25rem;
1036
+ font-weight: 600;
1037
+ color: var(--color-text);
1038
+ }
1039
+ .header-title:hover { text-decoration: none; }
1040
+ .menu-toggle {
1041
+ display: none;
1042
+ background: none;
1043
+ border: none;
1044
+ cursor: pointer;
1045
+ padding: 0.5rem;
1046
+ margin-right: 0.75rem;
1047
+ }
1048
+ .menu-toggle svg { display: block; }
1049
+ .menu-toggle path { stroke: var(--color-text); }
1050
+ .header-actions { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }
1051
+ .search-button {
1052
+ display: flex;
1053
+ align-items: center;
1054
+ gap: 0.5rem;
1055
+ padding: 0.5rem 0.75rem;
1056
+ background: var(--color-bg-alt);
1057
+ border: 1px solid var(--color-border);
1058
+ border-radius: 6px;
1059
+ color: var(--color-text-muted);
1060
+ cursor: pointer;
1061
+ font-size: 0.875rem;
1062
+ transition: border-color 0.15s, color 0.15s;
1063
+ }
1064
+ .search-button:hover { border-color: var(--color-primary); color: var(--color-text); }
1065
+ .search-button svg { width: 16px; height: 16px; }
1066
+ .search-button kbd {
1067
+ padding: 0.125rem 0.375rem;
1068
+ background: var(--color-bg);
1069
+ border: 1px solid var(--color-border);
1070
+ border-radius: 4px;
1071
+ font-family: var(--font-mono);
1072
+ font-size: 0.75rem;
1073
+ }
1074
+ @media (max-width: 640px) {
1075
+ .search-button span, .search-button kbd { display: none; }
1076
+ .search-button { padding: 0.5rem; }
1077
+ }
1078
+ .search-modal-overlay {
1079
+ display: none;
1080
+ position: fixed;
1081
+ inset: 0;
1082
+ z-index: 200;
1083
+ background: rgba(0,0,0,0.6);
1084
+ backdrop-filter: blur(4px);
1085
+ justify-content: center;
1086
+ padding-top: 10vh;
1087
+ }
1088
+ .search-modal-overlay.open { display: flex; }
1089
+ .search-modal {
1090
+ width: 100%;
1091
+ max-width: 560px;
1092
+ margin: 0 1rem;
1093
+ background: var(--color-bg);
1094
+ border: 1px solid var(--color-border);
1095
+ border-radius: 12px;
1096
+ overflow: hidden;
1097
+ box-shadow: 0 25px 50px -12px rgba(0,0,0,0.4);
1098
+ max-height: 70vh;
1099
+ display: flex;
1100
+ flex-direction: column;
1101
+ }
1102
+ .search-header {
1103
+ display: flex;
1104
+ align-items: center;
1105
+ gap: 0.75rem;
1106
+ padding: 1rem;
1107
+ border-bottom: 1px solid var(--color-border);
1108
+ }
1109
+ .search-header svg { flex-shrink: 0; color: var(--color-text-muted); }
1110
+ .search-input {
1111
+ flex: 1;
1112
+ background: none;
1113
+ border: none;
1114
+ outline: none;
1115
+ font-size: 1rem;
1116
+ color: var(--color-text);
1117
+ }
1118
+ .search-input::placeholder { color: var(--color-text-muted); }
1119
+ .search-close {
1120
+ padding: 0.25rem 0.5rem;
1121
+ background: var(--color-bg-alt);
1122
+ border: 1px solid var(--color-border);
1123
+ border-radius: 4px;
1124
+ color: var(--color-text-muted);
1125
+ font-family: var(--font-mono);
1126
+ font-size: 0.75rem;
1127
+ cursor: pointer;
1128
+ }
1129
+ .search-results {
1130
+ flex: 1;
1131
+ overflow-y: auto;
1132
+ padding: 0.5rem;
1133
+ }
1134
+ .search-result {
1135
+ display: block;
1136
+ padding: 0.75rem 1rem;
1137
+ border-radius: 8px;
1138
+ color: var(--color-text);
1139
+ text-decoration: none;
1140
+ }
1141
+ .search-result:hover, .search-result.selected { background: var(--color-bg-alt); text-decoration: none; }
1142
+ .search-result-title { font-weight: 600; font-size: 0.875rem; margin-bottom: 0.25rem; }
1143
+ .search-result-snippet { font-size: 0.8125rem; color: var(--color-text-muted); }
1144
+ .search-empty { padding: 2rem 1rem; text-align: center; color: var(--color-text-muted); }
1145
+ .search-footer {
1146
+ display: flex;
1147
+ align-items: center;
1148
+ justify-content: center;
1149
+ gap: 1rem;
1150
+ padding: 0.75rem 1rem;
1151
+ border-top: 1px solid var(--color-border);
1152
+ background: var(--color-bg-alt);
1153
+ font-size: 0.75rem;
1154
+ color: var(--color-text-muted);
1155
+ }
1156
+ .search-footer kbd {
1157
+ padding: 0.125rem 0.375rem;
1158
+ background: var(--color-bg);
1159
+ border: 1px solid var(--color-border);
1160
+ border-radius: 4px;
1161
+ font-family: var(--font-mono);
1162
+ }
1163
+ .theme-toggle {
1164
+ background: none;
1165
+ border: none;
1166
+ cursor: pointer;
1167
+ padding: 0.5rem;
1168
+ border-radius: 6px;
1169
+ color: var(--color-text-muted);
1170
+ transition: background 0.15s, color 0.15s;
1171
+ }
1172
+ .theme-toggle:hover { background: var(--color-bg-alt); color: var(--color-text); }
1173
+ .theme-toggle svg { display: block; width: 20px; height: 20px; }
1174
+ .theme-toggle .icon-sun { display: none; }
1175
+ .theme-toggle .icon-moon { display: block; }
1176
+ [data-theme="dark"] .theme-toggle .icon-sun { display: block; }
1177
+ [data-theme="dark"] .theme-toggle .icon-moon { display: none; }
1178
+ @media (prefers-color-scheme: dark) {
1179
+ :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
1180
+ :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
1181
+ }
1182
+
1183
+ /* Layout */
1184
+ .layout {
1185
+ display: flex;
1186
+ padding-top: var(--header-height);
1187
+ min-height: 100vh;
1188
+ }
1189
+
1190
+ /* Sidebar */
1191
+ .sidebar {
1192
+ position: fixed;
1193
+ top: var(--header-height);
1194
+ left: 0;
1195
+ bottom: 0;
1196
+ width: var(--sidebar-width);
1197
+ background: var(--color-bg-alt);
1198
+ border-right: 1px solid var(--color-border);
1199
+ overflow-y: auto;
1200
+ padding: 1.5rem 1rem;
1201
+ }
1202
+ .nav-section { margin-bottom: 1.5rem; }
1203
+ .nav-title {
1204
+ font-size: 0.75rem;
1205
+ font-weight: 600;
1206
+ text-transform: uppercase;
1207
+ letter-spacing: 0.05em;
1208
+ color: var(--color-text-muted);
1209
+ margin-bottom: 0.5rem;
1210
+ padding: 0 0.75rem;
1211
+ }
1212
+ .nav-list { list-style: none; }
1213
+ .nav-item { margin: 0.125rem 0; }
1214
+ .nav-link {
1215
+ display: block;
1216
+ padding: 0.5rem 0.75rem;
1217
+ border-radius: 6px;
1218
+ color: var(--color-text);
1219
+ font-size: 0.875rem;
1220
+ transition: background 0.15s;
1221
+ }
1222
+ .nav-link:hover {
1223
+ background: var(--color-border);
1224
+ text-decoration: none;
1225
+ }
1226
+ .nav-link.active {
1227
+ background: var(--color-primary);
1228
+ color: white;
1229
+ }
1230
+
1231
+ /* Main content */
1232
+ .main {
1233
+ flex: 1;
1234
+ margin-left: var(--sidebar-width);
1235
+ padding: 2rem;
1236
+ min-width: 0;
1237
+ overflow-x: hidden;
1238
+ }
1239
+ .content {
1240
+ max-width: var(--max-content-width);
1241
+ margin: 0 auto;
1242
+ overflow-wrap: break-word;
1243
+ word-wrap: break-word;
1244
+ word-break: break-word;
1245
+ }
1246
+
1247
+ /* TOC (right sidebar) */
1248
+ .toc {
1249
+ position: fixed;
1250
+ top: calc(var(--header-height) + 2rem);
1251
+ right: 2rem;
1252
+ width: 200px;
1253
+ font-size: 0.8125rem;
1254
+ }
1255
+ .toc-title {
1256
+ font-weight: 600;
1257
+ margin-bottom: 0.75rem;
1258
+ color: var(--color-text-muted);
1259
+ }
1260
+ .toc-list { list-style: none; }
1261
+ .toc-item { margin: 0.375rem 0; }
1262
+ .toc-link {
1263
+ color: var(--color-text-muted);
1264
+ display: block;
1265
+ padding-left: calc((var(--depth, 1) - 1) * 0.75rem);
1266
+ }
1267
+ .toc-link:hover { color: var(--color-primary); }
1268
+ @media (max-width: 1200px) { .toc { display: none; } }
1269
+
1270
+ /* Typography */
1271
+ .content h1 { font-size: 2.25rem; margin-bottom: 1rem; line-height: 1.2; }
1272
+ .content h2 { font-size: 1.5rem; margin-top: 2.5rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-border); }
1273
+ .content h3 { font-size: 1.25rem; margin-top: 2rem; margin-bottom: 0.75rem; }
1274
+ .content h4 { font-size: 1rem; margin-top: 1.5rem; margin-bottom: 0.5rem; }
1275
+ .content p { margin-bottom: 1rem; }
1276
+ .content ul, .content ol { margin: 1rem 0; padding-left: 1.5rem; }
1277
+ .content li { margin: 0.375rem 0; }
1278
+ .content blockquote {
1279
+ border-left: 4px solid var(--color-primary);
1280
+ padding: 0.5rem 1rem;
1281
+ margin: 1rem 0;
1282
+ background: var(--color-bg-alt);
1283
+ border-radius: 0 6px 6px 0;
1284
+ }
1285
+ .content code {
1286
+ font-family: var(--font-mono);
1287
+ font-size: 0.875em;
1288
+ background: var(--color-bg-alt);
1289
+ padding: 0.2em 0.4em;
1290
+ border-radius: 4px;
1291
+ word-break: break-all;
1292
+ }
1293
+ .content pre {
1294
+ background: var(--color-code-bg);
1295
+ color: var(--color-code-text);
1296
+ padding: 1rem 1.25rem;
1297
+ border-radius: 8px;
1298
+ overflow-x: auto;
1299
+ margin: 1.5rem 0;
1300
+ line-height: 1.5;
1301
+ }
1302
+ .content pre code {
1303
+ background: transparent;
1304
+ padding: 0;
1305
+ font-size: 0.8125rem;
1306
+ }
1307
+ .content table {
1308
+ width: 100%;
1309
+ border-collapse: collapse;
1310
+ margin: 1.5rem 0;
1311
+ font-size: 0.875rem;
1312
+ }
1313
+ .content th, .content td {
1314
+ border: 1px solid var(--color-border);
1315
+ padding: 0.75rem 1rem;
1316
+ text-align: left;
1317
+ }
1318
+ .content th { background: var(--color-bg-alt); font-weight: 600; }
1319
+ .content img { max-width: 100%; height: auto; border-radius: 8px; display: block; }
1320
+ .content img[alt*="Logo"] { max-width: 200px; display: block; margin: 1rem 0; }
1321
+ .content img[alt*="Architecture"] { max-width: 600px; }
1322
+ .content img[alt*="Benchmark"] { max-width: 680px; }
1323
+ .content hr { border: none; border-top: 1px solid var(--color-border); margin: 2rem 0; }
1324
+
1325
+ /* Responsive */
1326
+ @media (max-width: 768px) {
1327
+ .menu-toggle { display: block; }
1328
+ .sidebar {
1329
+ transform: translateX(-100%);
1330
+ transition: transform 0.3s ease;
1331
+ z-index: 99;
1332
+ width: 280px;
1333
+ }
1334
+ .sidebar.open { transform: translateX(0); }
1335
+ .main { margin-left: 0; padding: 1rem 0.75rem; }
1336
+ .content { padding: 0 0.25rem; }
1337
+ .content h1 { font-size: 1.5rem; line-height: 1.3; margin-bottom: 0.75rem; }
1338
+ .content h2 { font-size: 1.2rem; margin-top: 2rem; }
1339
+ .content h3 { font-size: 1.1rem; }
1340
+ .content p { font-size: 0.9375rem; margin-bottom: 0.875rem; }
1341
+ .content ul, .content ol { padding-left: 1.25rem; font-size: 0.9375rem; }
1342
+ .content pre {
1343
+ padding: 0.75rem;
1344
+ font-size: 0.75rem;
1345
+ margin: 1rem -0.75rem;
1346
+ border-radius: 0;
1347
+ overflow-x: auto;
1348
+ -webkit-overflow-scrolling: touch;
1349
+ }
1350
+ .content code { font-size: 0.8125em; }
1351
+ .content table {
1352
+ display: block;
1353
+ overflow-x: auto;
1354
+ -webkit-overflow-scrolling: touch;
1355
+ font-size: 0.8125rem;
1356
+ margin: 1rem -0.75rem;
1357
+ width: calc(100% + 1.5rem);
1358
+ }
1359
+ .content th, .content td { padding: 0.5rem 0.75rem; white-space: nowrap; }
1360
+ .content img { margin: 1rem 0; }
1361
+ .content img[alt*="Logo"] { max-width: 150px; }
1362
+ .content img[alt*="Architecture"] { max-width: 100%; }
1363
+ .content img[alt*="Benchmark"] { max-width: 100%; }
1364
+ .content blockquote { padding: 0.5rem 0.75rem; margin: 1rem 0; font-size: 0.9375rem; }
1365
+ .header { padding: 0 1rem; }
1366
+ .header-title { font-size: 1rem; }
1367
+ .header-title img { width: 24px; height: 24px; }
1368
+ .overlay {
1369
+ display: none;
1370
+ position: fixed;
1371
+ inset: 0;
1372
+ background: rgba(0,0,0,0.5);
1373
+ z-index: 98;
1374
+ }
1375
+ .overlay.open { display: block; }
1376
+ }
1377
+
1378
+ /* Extra small devices */
1379
+ @media (max-width: 480px) {
1380
+ .main { padding: 0.75rem 0.5rem; }
1381
+ .content h1 { font-size: 1.35rem; }
1382
+ .content pre { font-size: 0.6875rem; padding: 0.625rem; }
1383
+ .content table { font-size: 0.75rem; }
1384
+ .content th, .content td { padding: 0.375rem 0.5rem; }
1385
+ }
1386
+ </style>
1387
+ </head>
1388
+ <body>
1389
+ <header class="header">
1390
+ <button class="menu-toggle" aria-label="Toggle menu">
1391
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round">
1392
+ <path d="M3 12h18M3 6h18M3 18h18"/>
1393
+ </svg>
1394
+ </button>
1395
+ <a href="{{base}}index.html" class="header-title">
1396
+ <img src="{{base}}logo.svg" alt="" width="28" height="28" style="margin-right: 8px; vertical-align: middle;" />
1397
+ {{siteName}}
1398
+ </a>
1399
+ <div class="header-actions">
1400
+ <button class="search-button" aria-label="Search">
1401
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
1402
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
1403
+ </svg>
1404
+ <span>Search</span>
1405
+ <kbd>/</kbd>
1406
+ </button>
1407
+ <button class="theme-toggle" aria-label="Toggle theme">
1408
+ <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
1409
+ <circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
1410
+ </svg>
1411
+ <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
1412
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
1413
+ </svg>
1414
+ </button>
1415
+ </div>
1416
+ </header>
1417
+ <div class="search-modal-overlay">
1418
+ <div class="search-modal">
1419
+ <div class="search-header">
1420
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
1421
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
1422
+ </svg>
1423
+ <input type="text" class="search-input" placeholder="Search documentation..." />
1424
+ <button class="search-close">Esc</button>
1425
+ </div>
1426
+ <div class="search-results"></div>
1427
+ <div class="search-footer">
1428
+ <span><kbd>\u2191</kbd><kbd>\u2193</kbd> to navigate</span>
1429
+ <span><kbd>Enter</kbd> to select</span>
1430
+ <span><kbd>Esc</kbd> to close</span>
1431
+ </div>
1432
+ </div>
1433
+ </div>
1434
+ <div class="overlay"></div>
1435
+ <div class="layout">
1436
+ <aside class="sidebar">
1437
+ <nav>
1438
+ {{navigation}}
1439
+ </nav>
1440
+ </aside>
1441
+ <main class="main">
1442
+ <article class="content">
1443
+ {{content}}
1444
+ </article>
1445
+ </main>
1446
+ {{#hasToc}}
1447
+ <aside class="toc">
1448
+ <div class="toc-title">On this page</div>
1449
+ <ul class="toc-list">
1450
+ {{toc}}
1451
+ </ul>
1452
+ </aside>
1453
+ {{/hasToc}}
1454
+ </div>
1455
+ <script>
1456
+ // Menu toggle
1457
+ const toggle = document.querySelector('.menu-toggle');
1458
+ const sidebar = document.querySelector('.sidebar');
1459
+ const overlay = document.querySelector('.overlay');
1460
+ if (toggle && sidebar && overlay) {
1461
+ const close = () => { sidebar.classList.remove('open'); overlay.classList.remove('open'); };
1462
+ toggle.addEventListener('click', () => {
1463
+ sidebar.classList.toggle('open');
1464
+ overlay.classList.toggle('open');
1465
+ });
1466
+ overlay.addEventListener('click', close);
1467
+ sidebar.querySelectorAll('a').forEach(a => a.addEventListener('click', close));
1468
+ }
1469
+
1470
+ // Theme toggle
1471
+ const themeToggle = document.querySelector('.theme-toggle');
1472
+ const getPreferredTheme = () => {
1473
+ const stored = localStorage.getItem('theme');
1474
+ if (stored) return stored;
1475
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
1476
+ };
1477
+ const setTheme = (theme) => {
1478
+ document.documentElement.setAttribute('data-theme', theme);
1479
+ localStorage.setItem('theme', theme);
1480
+ };
1481
+ // Initialize theme
1482
+ setTheme(getPreferredTheme());
1483
+ if (themeToggle) {
1484
+ themeToggle.addEventListener('click', () => {
1485
+ const current = document.documentElement.getAttribute('data-theme') || getPreferredTheme();
1486
+ setTheme(current === 'dark' ? 'light' : 'dark');
1487
+ });
1488
+ }
1489
+
1490
+ // Search functionality
1491
+ const searchButton = document.querySelector('.search-button');
1492
+ const searchOverlay = document.querySelector('.search-modal-overlay');
1493
+ const searchInput = document.querySelector('.search-input');
1494
+ const searchResults = document.querySelector('.search-results');
1495
+ const searchClose = document.querySelector('.search-close');
1496
+ let searchIndex = null;
1497
+ let selectedIndex = 0;
1498
+ let results = [];
1499
+
1500
+ const openSearch = () => {
1501
+ searchOverlay.classList.add('open');
1502
+ searchInput.focus();
1503
+ };
1504
+ const closeSearch = () => {
1505
+ searchOverlay.classList.remove('open');
1506
+ searchInput.value = '';
1507
+ searchResults.innerHTML = '';
1508
+ selectedIndex = 0;
1509
+ results = [];
1510
+ };
1511
+
1512
+ // Load search index
1513
+ const loadSearchIndex = async () => {
1514
+ if (searchIndex) return;
1515
+ try {
1516
+ const res = await fetch('{{base}}search-index.json');
1517
+ searchIndex = await res.json();
1518
+ } catch (e) {
1519
+ console.warn('Failed to load search index:', e);
1520
+ }
1521
+ };
1522
+
1523
+ // Tokenize query
1524
+ const tokenize = (text) => {
1525
+ const tokens = [];
1526
+ let current = '';
1527
+ for (const char of text) {
1528
+ const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);
1529
+ if (isCjk) {
1530
+ if (current) { tokens.push(current.toLowerCase()); current = ''; }
1531
+ tokens.push(char);
1532
+ } else if (/[a-zA-Z0-9_]/.test(char)) {
1533
+ current += char;
1534
+ } else if (current) {
1535
+ tokens.push(current.toLowerCase());
1536
+ current = '';
1537
+ }
1538
+ }
1539
+ if (current) tokens.push(current.toLowerCase());
1540
+ return tokens;
1541
+ };
1542
+
1543
+ // Perform search
1544
+ const performSearch = async (query) => {
1545
+ if (!query.trim()) {
1546
+ searchResults.innerHTML = '';
1547
+ results = [];
1548
+ return;
1549
+ }
1550
+ await loadSearchIndex();
1551
+ if (!searchIndex) {
1552
+ searchResults.innerHTML = '<div class="search-empty">Search index not available</div>';
1553
+ return;
1554
+ }
1555
+
1556
+ const tokens = tokenize(query);
1557
+ if (!tokens.length) {
1558
+ searchResults.innerHTML = '';
1559
+ results = [];
1560
+ return;
1561
+ }
1562
+
1563
+ const k1 = 1.2, b = 0.75;
1564
+ const docScores = new Map();
1565
+
1566
+ for (let i = 0; i < tokens.length; i++) {
1567
+ const token = tokens[i];
1568
+ const isLast = i === tokens.length - 1;
1569
+ let matchingTerms = [];
1570
+ if (isLast && token.length >= 2) {
1571
+ matchingTerms = Object.keys(searchIndex.index).filter(t => t.startsWith(token));
1572
+ } else if (searchIndex.index[token]) {
1573
+ matchingTerms = [token];
1574
+ }
1575
+
1576
+ for (const term of matchingTerms) {
1577
+ const postings = searchIndex.index[term] || [];
1578
+ const df = searchIndex.df[term] || 1;
1579
+ const idf = Math.log((searchIndex.doc_count - df + 0.5) / (df + 0.5) + 1.0);
1580
+
1581
+ for (const posting of postings) {
1582
+ const doc = searchIndex.documents[posting.doc_idx];
1583
+ if (!doc) continue;
1584
+ const boost = posting.field === 'Title' ? 10 : posting.field === 'Heading' ? 5 : 1;
1585
+ const tf = posting.tf;
1586
+ const docLen = doc.body.length;
1587
+ const score = idf * ((tf * (k1 + 1)) / (tf + k1 * (1 - b + b * docLen / searchIndex.avg_dl))) * boost;
1588
+
1589
+ if (!docScores.has(posting.doc_idx)) {
1590
+ docScores.set(posting.doc_idx, { score: 0, matches: new Set() });
1591
+ }
1592
+ const entry = docScores.get(posting.doc_idx);
1593
+ entry.score += score;
1594
+ entry.matches.add(term);
1595
+ }
1596
+ }
1597
+ }
1598
+
1599
+ results = Array.from(docScores.entries())
1600
+ .map(([docIdx, data]) => {
1601
+ const doc = searchIndex.documents[docIdx];
1602
+ let snippet = '';
1603
+ if (doc.body) {
1604
+ const bodyLower = doc.body.toLowerCase();
1605
+ let firstPos = -1;
1606
+ for (const match of data.matches) {
1607
+ const pos = bodyLower.indexOf(match);
1608
+ if (pos !== -1 && (firstPos === -1 || pos < firstPos)) firstPos = pos;
1609
+ }
1610
+ const start = Math.max(0, firstPos - 50);
1611
+ const end = Math.min(doc.body.length, start + 150);
1612
+ snippet = doc.body.slice(start, end);
1613
+ if (start > 0) snippet = '...' + snippet;
1614
+ if (end < doc.body.length) snippet += '...';
1615
+ }
1616
+ return { ...doc, score: data.score, snippet };
1617
+ })
1618
+ .sort((a, b) => b.score - a.score)
1619
+ .slice(0, 10);
1620
+
1621
+ selectedIndex = 0;
1622
+ renderResults();
1623
+ };
1624
+
1625
+ const renderResults = () => {
1626
+ if (!results.length) {
1627
+ searchResults.innerHTML = '<div class="search-empty">No results found</div>';
1628
+ return;
1629
+ }
1630
+ searchResults.innerHTML = results.map((r, i) =>
1631
+ '<a href="' + r.url + '" class="search-result' + (i === selectedIndex ? ' selected' : '') + '">' +
1632
+ '<div class="search-result-title">' + r.title + '</div>' +
1633
+ (r.snippet ? '<div class="search-result-snippet">' + r.snippet + '</div>' : '') +
1634
+ '</a>'
1635
+ ).join('');
1636
+ };
1637
+
1638
+ // Event listeners
1639
+ if (searchButton) searchButton.addEventListener('click', openSearch);
1640
+ if (searchClose) searchClose.addEventListener('click', closeSearch);
1641
+ if (searchOverlay) searchOverlay.addEventListener('click', (e) => { if (e.target === searchOverlay) closeSearch(); });
1642
+
1643
+ let searchTimeout = null;
1644
+ if (searchInput) {
1645
+ searchInput.addEventListener('input', () => {
1646
+ if (searchTimeout) clearTimeout(searchTimeout);
1647
+ searchTimeout = setTimeout(() => performSearch(searchInput.value), 150);
1648
+ });
1649
+ searchInput.addEventListener('keydown', (e) => {
1650
+ if (e.key === 'Escape') closeSearch();
1651
+ else if (e.key === 'ArrowDown') {
1652
+ e.preventDefault();
1653
+ if (selectedIndex < results.length - 1) { selectedIndex++; renderResults(); }
1654
+ } else if (e.key === 'ArrowUp') {
1655
+ e.preventDefault();
1656
+ if (selectedIndex > 0) { selectedIndex--; renderResults(); }
1657
+ } else if (e.key === 'Enter' && results[selectedIndex]) {
1658
+ e.preventDefault();
1659
+ window.location.href = results[selectedIndex].url;
1660
+ }
1661
+ });
1662
+ }
1663
+
1664
+ // Global keyboard shortcut (/ or Cmd+K)
1665
+ document.addEventListener('keydown', (e) => {
1666
+ if ((e.key === '/' && !(e.target instanceof HTMLInputElement)) ||
1667
+ ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k')) {
1668
+ e.preventDefault();
1669
+ openSearch();
1670
+ }
1671
+ });
1672
+ </script>
1673
+ </body>
1674
+ </html>`;
1675
+ var BARE_HTML_TEMPLATE = `<!DOCTYPE html>
1676
+ <html lang="en">
1677
+ <head>
1678
+ <meta charset="UTF-8">
1679
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1680
+ <title>{{title}}</title>
1681
+ </head>
1682
+ <body>
1683
+ {{content}}
1684
+ </body>
1685
+ </html>`;
1686
+ function resolveSsgOptions(ssg) {
1687
+ if (ssg === false) {
1688
+ return {
1689
+ enabled: false,
1690
+ extension: ".html",
1691
+ clean: false,
1692
+ bare: false,
1693
+ generateOgImage: false
1694
+ };
1695
+ }
1696
+ if (ssg === true || ssg === void 0) {
1697
+ return {
1698
+ enabled: true,
1699
+ extension: ".html",
1700
+ clean: false,
1701
+ bare: false,
1702
+ generateOgImage: false
1703
+ };
1704
+ }
1705
+ return {
1706
+ enabled: ssg.enabled ?? true,
1707
+ extension: ssg.extension ?? ".html",
1708
+ clean: ssg.clean ?? false,
1709
+ bare: ssg.bare ?? false,
1710
+ siteName: ssg.siteName,
1711
+ ogImage: ssg.ogImage,
1712
+ generateOgImage: ssg.generateOgImage ?? false,
1713
+ siteUrl: ssg.siteUrl
1714
+ };
1715
+ }
1716
+ function renderTemplate(template, data) {
1717
+ let result = template;
1718
+ result = result.replace(/\{\{#(\w+)\}\}([\s\S]*?)\{\{\/\1\}\}/g, (_, key, content) => {
1719
+ return data[key] ? content : "";
1720
+ });
1721
+ result = result.replace(/\{\{(\w+)\}\}/g, (_, key) => {
1722
+ const value = data[key];
1723
+ return value !== void 0 && value !== null ? String(value) : "";
1724
+ });
1725
+ return result;
1726
+ }
1727
+ function extractTitle(content, frontmatter) {
1728
+ if (frontmatter.title && typeof frontmatter.title === "string") {
1729
+ return frontmatter.title;
1730
+ }
1731
+ const h1Match = content.match(/<h1[^>]*>([^<]+)<\/h1>/i);
1732
+ if (h1Match) {
1733
+ return h1Match[1].trim();
1734
+ }
1735
+ return "Untitled";
1736
+ }
1737
+ function generateBareHtmlPage(content, title) {
1738
+ return renderTemplate(BARE_HTML_TEMPLATE, {
1739
+ title,
1740
+ content
1741
+ });
1742
+ }
1743
+ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage) {
1744
+ const mod = await import("@ox-content/napi");
1745
+ const tocForRust = pageData.toc.map((entry) => ({
1746
+ depth: entry.depth,
1747
+ text: entry.text,
1748
+ slug: entry.slug
1749
+ }));
1750
+ const navGroupsForRust = navGroups.map((group) => ({
1751
+ title: group.title,
1752
+ items: group.items.map((item) => ({
1753
+ title: item.title,
1754
+ path: item.path,
1755
+ href: item.href
1756
+ }))
1757
+ }));
1758
+ return mod.generateSsgHtml(
1759
+ {
1760
+ title: pageData.title,
1761
+ description: pageData.description,
1762
+ content: pageData.content,
1763
+ toc: tocForRust,
1764
+ path: pageData.path
1765
+ },
1766
+ navGroupsForRust,
1767
+ {
1768
+ siteName,
1769
+ base,
1770
+ ogImage
1771
+ }
1772
+ );
1773
+ }
1774
+ function getOutputPath(inputPath, srcDir, outDir, extension) {
1775
+ const relativePath = path3.relative(srcDir, inputPath);
1776
+ const baseName = relativePath.replace(/\.(?:md|markdown)$/i, extension);
1777
+ if (baseName.endsWith(`index${extension}`)) {
1778
+ return path3.join(outDir, baseName);
1779
+ }
1780
+ const dirName = baseName.replace(new RegExp(`\\${extension}$`), "");
1781
+ return path3.join(outDir, dirName, `index${extension}`);
1782
+ }
1783
+ function getUrlPath(inputPath, srcDir) {
1784
+ const relativePath = path3.relative(srcDir, inputPath);
1785
+ const baseName = relativePath.replace(/\.(?:md|markdown)$/i, "");
1786
+ if (baseName === "index" || baseName.endsWith("/index")) {
1787
+ return baseName.replace(/\/?index$/, "") || "/";
1788
+ }
1789
+ return baseName;
1790
+ }
1791
+ function getHref(inputPath, srcDir, base, extension) {
1792
+ const urlPath = getUrlPath(inputPath, srcDir);
1793
+ if (urlPath === "/" || urlPath === "") {
1794
+ return `${base}index${extension}`;
1795
+ }
1796
+ return `${base}${urlPath}/index${extension}`;
1797
+ }
1798
+ function getOgImagePath(inputPath, srcDir, outDir) {
1799
+ const relativePath = path3.relative(srcDir, inputPath);
1800
+ const baseName = relativePath.replace(/\.(?:md|markdown)$/i, "");
1801
+ if (baseName === "index" || baseName.endsWith("/index")) {
1802
+ const dirPath = baseName.replace(/\/?index$/, "") || "";
1803
+ return path3.join(outDir, dirPath, "og-image.svg");
1804
+ }
1805
+ return path3.join(outDir, baseName, "og-image.svg");
1806
+ }
1807
+ function getOgImageUrl(inputPath, srcDir, base, siteUrl) {
1808
+ const urlPath = getUrlPath(inputPath, srcDir);
1809
+ let relativePath;
1810
+ if (urlPath === "/" || urlPath === "") {
1811
+ relativePath = `${base}og-image.svg`;
1812
+ } else {
1813
+ relativePath = `${base}${urlPath}/og-image.svg`;
1814
+ }
1815
+ if (siteUrl) {
1816
+ const cleanSiteUrl = siteUrl.replace(/\/$/, "");
1817
+ return `${cleanSiteUrl}${relativePath}`;
1818
+ }
1819
+ return relativePath;
1820
+ }
1821
+ function getDisplayTitle(filePath) {
1822
+ const fileName = path3.basename(filePath, path3.extname(filePath));
1823
+ if (fileName === "index") {
1824
+ const dirName = path3.basename(path3.dirname(filePath));
1825
+ if (dirName && dirName !== ".") {
1826
+ return formatTitle(dirName);
1827
+ }
1828
+ return "Home";
1829
+ }
1830
+ return formatTitle(fileName);
1831
+ }
1832
+ function formatTitle(name) {
1833
+ return name.replace(/[-_]([a-z])/g, (_, char) => " " + char.toUpperCase()).replace(/^[a-z]/, (char) => char.toUpperCase());
1834
+ }
1835
+ async function collectMarkdownFiles(srcDir) {
1836
+ const pattern = path3.join(srcDir, "**/*.{md,markdown}");
1837
+ const files = await (0, import_glob.glob)(pattern, {
1838
+ nodir: true,
1839
+ ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
1840
+ });
1841
+ return files.sort();
1842
+ }
1843
+ function buildNavItems(markdownFiles, srcDir, base, extension) {
1844
+ const groups = /* @__PURE__ */ new Map();
1845
+ const groupOrder = ["", "examples", "packages", "api"];
1846
+ for (const file of markdownFiles) {
1847
+ const relativePath = path3.relative(srcDir, file);
1848
+ const parts = relativePath.split(path3.sep);
1849
+ let groupKey = "";
1850
+ if (parts.length > 1) {
1851
+ groupKey = parts[0];
1852
+ }
1853
+ if (!groups.has(groupKey)) {
1854
+ groups.set(groupKey, []);
1855
+ }
1856
+ const urlPath = getUrlPath(file, srcDir);
1857
+ let title;
1858
+ if (urlPath === "/" || urlPath === "") {
1859
+ title = "Overview";
1860
+ } else {
1861
+ title = getDisplayTitle(file);
1862
+ }
1863
+ groups.get(groupKey).push({
1864
+ title,
1865
+ path: urlPath,
1866
+ href: getHref(file, srcDir, base, extension)
1867
+ });
1868
+ }
1869
+ const sortItems = (items) => {
1870
+ return items.sort((a, b) => {
1871
+ const aIsRoot = a.path === "/" || a.path === "";
1872
+ const bIsRoot = b.path === "/" || b.path === "";
1873
+ if (aIsRoot && !bIsRoot) return -1;
1874
+ if (!aIsRoot && bIsRoot) return 1;
1875
+ return a.title.localeCompare(b.title);
1876
+ });
1877
+ };
1878
+ const result = [];
1879
+ for (const key of groupOrder) {
1880
+ const items = groups.get(key);
1881
+ if (items && items.length > 0) {
1882
+ result.push({
1883
+ title: key === "" ? "Guide" : formatTitle(key),
1884
+ items: sortItems(items)
1885
+ });
1886
+ groups.delete(key);
1887
+ }
1888
+ }
1889
+ for (const [key, items] of groups) {
1890
+ if (items.length > 0) {
1891
+ result.push({
1892
+ title: formatTitle(key),
1893
+ items: sortItems(items)
1894
+ });
1895
+ }
1896
+ }
1897
+ return result;
1898
+ }
1899
+ async function buildSsg(options, root) {
1900
+ const ssgOptions = options.ssg;
1901
+ if (!ssgOptions.enabled) {
1902
+ return { files: [], errors: [] };
1903
+ }
1904
+ const srcDir = path3.resolve(root, options.srcDir);
1905
+ const outDir = path3.resolve(root, options.outDir);
1906
+ const base = options.base.endsWith("/") ? options.base : options.base + "/";
1907
+ const generatedFiles = [];
1908
+ const errors = [];
1909
+ if (ssgOptions.clean) {
1910
+ try {
1911
+ await fs2.rm(outDir, { recursive: true, force: true });
1912
+ } catch {
1913
+ }
1914
+ }
1915
+ const markdownFiles = await collectMarkdownFiles(srcDir);
1916
+ const navItems = buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension);
1917
+ let siteName = ssgOptions.siteName ?? "Documentation";
1918
+ if (!ssgOptions.siteName) {
1919
+ try {
1920
+ const pkgPath = path3.join(root, "package.json");
1921
+ const pkg = JSON.parse(await fs2.readFile(pkgPath, "utf-8"));
1922
+ if (pkg.name) {
1923
+ siteName = formatTitle(pkg.name);
1924
+ }
1925
+ } catch {
1926
+ }
1927
+ }
1928
+ for (const inputPath of markdownFiles) {
1929
+ try {
1930
+ const content = await fs2.readFile(inputPath, "utf-8");
1931
+ const result = await transformMarkdown(content, inputPath, options, {
1932
+ convertMdLinks: true,
1933
+ baseUrl: base
1934
+ });
1935
+ const title = extractTitle(result.html, result.frontmatter);
1936
+ const description = result.frontmatter.description;
1937
+ let pageOgImage = ssgOptions.ogImage;
1938
+ if (ssgOptions.generateOgImage && !ssgOptions.bare) {
1939
+ const ogImageData = {
1940
+ title,
1941
+ description,
1942
+ siteName,
1943
+ author: result.frontmatter.author
1944
+ };
1945
+ const ogImageConfig = options.ogImageOptions ? {
1946
+ width: options.ogImageOptions.width,
1947
+ height: options.ogImageOptions.height,
1948
+ backgroundColor: options.ogImageOptions.background,
1949
+ textColor: options.ogImageOptions.textColor
1950
+ } : void 0;
1951
+ const svg = await generateOgImageSvg(ogImageData, ogImageConfig);
1952
+ if (svg) {
1953
+ const ogImageOutputPath = getOgImagePath(inputPath, srcDir, outDir);
1954
+ await fs2.mkdir(path3.dirname(ogImageOutputPath), { recursive: true });
1955
+ await fs2.writeFile(ogImageOutputPath, svg, "utf-8");
1956
+ generatedFiles.push(ogImageOutputPath);
1957
+ pageOgImage = getOgImageUrl(inputPath, srcDir, base, ssgOptions.siteUrl);
1958
+ }
1959
+ }
1960
+ let html;
1961
+ if (ssgOptions.bare) {
1962
+ html = generateBareHtmlPage(result.html, title);
1963
+ } else {
1964
+ const pageData = {
1965
+ title,
1966
+ description,
1967
+ content: result.html,
1968
+ toc: result.toc,
1969
+ frontmatter: result.frontmatter,
1970
+ path: getUrlPath(inputPath, srcDir),
1971
+ href: getHref(inputPath, srcDir, base, ssgOptions.extension)
1972
+ };
1973
+ html = await generateHtmlPage(pageData, navItems, siteName, base, pageOgImage);
1974
+ }
1975
+ const outputPath = getOutputPath(
1976
+ inputPath,
1977
+ srcDir,
1978
+ outDir,
1979
+ ssgOptions.extension
1980
+ );
1981
+ await fs2.mkdir(path3.dirname(outputPath), { recursive: true });
1982
+ await fs2.writeFile(outputPath, html, "utf-8");
1983
+ generatedFiles.push(outputPath);
1984
+ } catch (err) {
1985
+ const errorMessage = err instanceof Error ? err.message : String(err);
1986
+ errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1987
+ }
1988
+ }
1989
+ return { files: generatedFiles, errors };
1990
+ }
1991
+
1992
+ // src/search.ts
1993
+ var fs3 = __toESM(require("fs/promises"), 1);
1994
+ var path4 = __toESM(require("path"), 1);
1995
+ var oxContent = null;
1996
+ async function getOxContent() {
1997
+ if (!oxContent) {
1998
+ try {
1999
+ oxContent = await import("@ox-content/napi");
2000
+ } catch {
2001
+ console.warn("[ox-content] Native bindings not available, search disabled");
2002
+ return null;
2003
+ }
2004
+ }
2005
+ return oxContent;
2006
+ }
2007
+ function resolveSearchOptions(options) {
2008
+ if (options === false) {
2009
+ return {
2010
+ enabled: false,
2011
+ limit: 10,
2012
+ prefix: true,
2013
+ placeholder: "Search documentation...",
2014
+ hotkey: "/"
2015
+ };
2016
+ }
2017
+ const opts = typeof options === "object" ? options : {};
2018
+ return {
2019
+ enabled: opts.enabled ?? true,
2020
+ limit: opts.limit ?? 10,
2021
+ prefix: opts.prefix ?? true,
2022
+ placeholder: opts.placeholder ?? "Search documentation...",
2023
+ hotkey: opts.hotkey ?? "/"
2024
+ };
2025
+ }
2026
+ async function collectMarkdownFiles2(dir) {
2027
+ const files = [];
2028
+ async function walk(currentDir) {
2029
+ try {
2030
+ const entries = await fs3.readdir(currentDir, { withFileTypes: true });
2031
+ for (const entry of entries) {
2032
+ const fullPath = path4.join(currentDir, entry.name);
2033
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
2034
+ await walk(fullPath);
2035
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
2036
+ files.push(fullPath);
2037
+ }
2038
+ }
2039
+ } catch {
2040
+ }
2041
+ }
2042
+ await walk(dir);
2043
+ return files;
2044
+ }
2045
+ async function buildSearchIndex(srcDir, base) {
2046
+ const napi = await getOxContent();
2047
+ if (!napi) {
2048
+ return JSON.stringify({ documents: [], index: {}, df: {}, avg_dl: 0, doc_count: 0 });
2049
+ }
2050
+ const files = await collectMarkdownFiles2(srcDir);
2051
+ const documents = [];
2052
+ for (const file of files) {
2053
+ try {
2054
+ const content = await fs3.readFile(file, "utf-8");
2055
+ const relativePath = path4.relative(srcDir, file);
2056
+ const url = base + relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
2057
+ const id = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
2058
+ const extractSearchContent = napi.extractSearchContent;
2059
+ if (!extractSearchContent) {
2060
+ console.warn("[ox-content] Search not available: extractSearchContent not implemented");
2061
+ return "[]";
2062
+ }
2063
+ const doc = extractSearchContent(content, id, url, { gfm: true });
2064
+ documents.push({
2065
+ id: doc.id,
2066
+ title: doc.title,
2067
+ url: doc.url,
2068
+ body: doc.body,
2069
+ headings: doc.headings,
2070
+ code: doc.code
2071
+ });
2072
+ } catch {
2073
+ }
2074
+ }
2075
+ const buildSearchIndex2 = napi.buildSearchIndex;
2076
+ if (!buildSearchIndex2) {
2077
+ console.warn("[ox-content] Search not available: buildSearchIndex not implemented");
2078
+ return JSON.stringify(documents);
2079
+ }
2080
+ return buildSearchIndex2(documents);
2081
+ }
2082
+ async function writeSearchIndex(indexJson, outDir) {
2083
+ const indexPath = path4.join(outDir, "search-index.json");
2084
+ await fs3.mkdir(outDir, { recursive: true });
2085
+ await fs3.writeFile(indexPath, indexJson, "utf-8");
2086
+ }
2087
+ function generateSearchModule(options, indexPath) {
2088
+ return `
2089
+ // Search module generated by ox-content
2090
+ const searchOptions = ${JSON.stringify(options)};
2091
+
2092
+ let searchIndex = null;
2093
+ let indexPromise = null;
2094
+
2095
+ // Tokenizer for queries
2096
+ function tokenizeQuery(text) {
2097
+ const tokens = [];
2098
+ let current = '';
2099
+
2100
+ for (const char of text) {
2101
+ const isCjk = /[\\u4E00-\\u9FFF\\u3400-\\u4DBF\\u3040-\\u309F\\u30A0-\\u30FF\\uAC00-\\uD7AF]/.test(char);
2102
+
2103
+ if (isCjk) {
2104
+ if (current) {
2105
+ tokens.push(current.toLowerCase());
2106
+ current = '';
2107
+ }
2108
+ tokens.push(char);
2109
+ } else if (/[a-zA-Z0-9_]/.test(char)) {
2110
+ current += char;
2111
+ } else if (current) {
2112
+ tokens.push(current.toLowerCase());
2113
+ current = '';
2114
+ }
2115
+ }
2116
+
2117
+ if (current) {
2118
+ tokens.push(current.toLowerCase());
2119
+ }
2120
+
2121
+ return tokens;
2122
+ }
2123
+
2124
+ // BM25 scoring
2125
+ function computeIdf(df, docCount) {
2126
+ return Math.log((docCount - df + 0.5) / (df + 0.5) + 1.0);
2127
+ }
2128
+
2129
+ function getFieldBoost(field) {
2130
+ switch (field) {
2131
+ case 'Title': return 10.0;
2132
+ case 'Heading': return 5.0;
2133
+ case 'Body': return 1.0;
2134
+ case 'Code': return 0.5;
2135
+ default: return 1.0;
2136
+ }
2137
+ }
2138
+
2139
+ // Load the index
2140
+ async function loadIndex() {
2141
+ if (searchIndex) return searchIndex;
2142
+ if (indexPromise) return indexPromise;
2143
+
2144
+ indexPromise = fetch('${indexPath}')
2145
+ .then(res => res.json())
2146
+ .then(data => {
2147
+ searchIndex = data;
2148
+ return data;
2149
+ })
2150
+ .catch(err => {
2151
+ console.error('[ox-content] Failed to load search index:', err);
2152
+ return null;
2153
+ });
2154
+
2155
+ return indexPromise;
2156
+ }
2157
+
2158
+ // Search function
2159
+ export async function search(query, options = {}) {
2160
+ const index = await loadIndex();
2161
+
2162
+ if (!index || !query.trim()) {
2163
+ return [];
2164
+ }
2165
+
2166
+ const limit = options.limit ?? searchOptions.limit;
2167
+ const prefix = options.prefix ?? searchOptions.prefix;
2168
+ const tokens = tokenizeQuery(query);
2169
+
2170
+ if (tokens.length === 0) {
2171
+ return [];
2172
+ }
2173
+
2174
+ const k1 = 1.2;
2175
+ const b = 0.75;
2176
+ const docScores = new Map();
2177
+
2178
+ for (let i = 0; i < tokens.length; i++) {
2179
+ const token = tokens[i];
2180
+ const isLast = i === tokens.length - 1;
2181
+
2182
+ // Find matching terms
2183
+ let matchingTerms = [];
2184
+ if (prefix && isLast && token.length >= 2) {
2185
+ matchingTerms = Object.keys(index.index).filter(term => term.startsWith(token));
2186
+ } else if (index.index[token]) {
2187
+ matchingTerms = [token];
2188
+ }
2189
+
2190
+ for (const term of matchingTerms) {
2191
+ const postings = index.index[term] || [];
2192
+ const df = index.df[term] || 1;
2193
+ const idf = computeIdf(df, index.doc_count);
2194
+
2195
+ for (const posting of postings) {
2196
+ const doc = index.documents[posting.doc_idx];
2197
+ if (!doc) continue;
2198
+
2199
+ const docLen = doc.body.length;
2200
+ const tf = posting.tf;
2201
+ const boost = getFieldBoost(posting.field);
2202
+
2203
+ const score = idf * ((tf * (k1 + 1.0)) / (tf + k1 * (1.0 - b + b * docLen / index.avg_dl))) * boost;
2204
+
2205
+ if (!docScores.has(posting.doc_idx)) {
2206
+ docScores.set(posting.doc_idx, { score: 0, matches: new Set() });
2207
+ }
2208
+ const entry = docScores.get(posting.doc_idx);
2209
+ entry.score += score;
2210
+ entry.matches.add(term);
2211
+ }
2212
+ }
2213
+ }
2214
+
2215
+ // Convert to results
2216
+ const results = Array.from(docScores.entries())
2217
+ .map(([docIdx, data]) => {
2218
+ const doc = index.documents[docIdx];
2219
+ const matches = Array.from(data.matches);
2220
+
2221
+ // Generate snippet
2222
+ let snippet = '';
2223
+ if (doc.body) {
2224
+ const bodyLower = doc.body.toLowerCase();
2225
+ let firstPos = -1;
2226
+ for (const match of matches) {
2227
+ const pos = bodyLower.indexOf(match);
2228
+ if (pos !== -1 && (firstPos === -1 || pos < firstPos)) {
2229
+ firstPos = pos;
2230
+ }
2231
+ }
2232
+
2233
+ const start = Math.max(0, firstPos - 50);
2234
+ const end = Math.min(doc.body.length, start + 150);
2235
+ snippet = doc.body.slice(start, end);
2236
+ if (start > 0) snippet = '...' + snippet;
2237
+ if (end < doc.body.length) snippet = snippet + '...';
2238
+ }
2239
+
2240
+ return {
2241
+ id: doc.id,
2242
+ title: doc.title,
2243
+ url: doc.url,
2244
+ score: data.score,
2245
+ matches,
2246
+ snippet,
2247
+ };
2248
+ })
2249
+ .sort((a, b) => b.score - a.score)
2250
+ .slice(0, limit);
2251
+
2252
+ return results;
2253
+ }
2254
+
2255
+ export { searchOptions };
2256
+ export default { search, searchOptions, loadIndex };
2257
+ `;
2258
+ }
2259
+
2260
+ // src/index.ts
2261
+ function oxContent2(options = {}) {
2262
+ const resolvedOptions = resolveOptions(options);
2263
+ let config;
2264
+ let _server;
2265
+ const mainPlugin = {
2266
+ name: "ox-content",
2267
+ configResolved(resolvedConfig) {
2268
+ config = resolvedConfig;
2269
+ },
2270
+ configureServer(devServer) {
2271
+ _server = devServer;
2272
+ devServer.middlewares.use(async (req, res, next) => {
2273
+ const url = req.url;
2274
+ if (!url || !url.endsWith(".md")) {
2275
+ return next();
2276
+ }
2277
+ next();
2278
+ });
2279
+ },
2280
+ resolveId(id) {
2281
+ if (id.startsWith("virtual:ox-content/")) {
2282
+ return "\0" + id;
2283
+ }
2284
+ if (id.endsWith(".md")) {
2285
+ return id;
2286
+ }
2287
+ return null;
2288
+ },
2289
+ async load(id) {
2290
+ if (id.startsWith("\0virtual:ox-content/")) {
2291
+ const path6 = id.slice("\0virtual:ox-content/".length);
2292
+ return generateVirtualModule(path6, resolvedOptions);
2293
+ }
2294
+ return null;
2295
+ },
2296
+ async transform(code, id) {
2297
+ if (!id.endsWith(".md")) {
2298
+ return null;
2299
+ }
2300
+ const result = await transformMarkdown(code, id, resolvedOptions);
2301
+ return {
2302
+ code: result.code,
2303
+ map: null
2304
+ };
2305
+ },
2306
+ // Hot Module Replacement support
2307
+ async handleHotUpdate({ file, server }) {
2308
+ if (file.endsWith(".md")) {
2309
+ server.ws.send({
2310
+ type: "custom",
2311
+ event: "ox-content:update",
2312
+ data: { file }
2313
+ });
2314
+ const modules = server.moduleGraph.getModulesByFile(file);
2315
+ return modules ? Array.from(modules) : [];
2316
+ }
2317
+ }
2318
+ };
2319
+ const environmentPlugin = {
2320
+ name: "ox-content:environment",
2321
+ config() {
2322
+ return {
2323
+ environments: {
2324
+ // Markdown processing environment
2325
+ markdown: createMarkdownEnvironment(resolvedOptions)
2326
+ }
2327
+ };
2328
+ }
2329
+ };
2330
+ const docsPlugin = {
2331
+ name: "ox-content:docs",
2332
+ async buildStart() {
2333
+ const docsOptions = resolvedOptions.docs;
2334
+ if (!docsOptions || !docsOptions.enabled) {
2335
+ return;
2336
+ }
2337
+ const root = config?.root || process.cwd();
2338
+ const srcDirs = docsOptions.src.map((src) => path5.resolve(root, src));
2339
+ const outDir = path5.resolve(root, docsOptions.out);
2340
+ try {
2341
+ const extracted = await extractDocs(srcDirs, docsOptions);
2342
+ if (extracted.length > 0) {
2343
+ const generated = generateMarkdown(extracted, docsOptions);
2344
+ await writeDocs(generated, outDir, extracted, docsOptions);
2345
+ console.log(
2346
+ `[ox-content] Generated ${Object.keys(generated).length} documentation files to ${docsOptions.out}`
2347
+ );
2348
+ }
2349
+ } catch (err) {
2350
+ console.warn("[ox-content] Failed to generate documentation:", err);
2351
+ }
2352
+ },
2353
+ configureServer(devServer) {
2354
+ const docsOptions = resolvedOptions.docs;
2355
+ if (!docsOptions || !docsOptions.enabled) {
2356
+ return;
2357
+ }
2358
+ const root = config?.root || process.cwd();
2359
+ const srcDirs = docsOptions.src.map((src) => path5.resolve(root, src));
2360
+ for (const srcDir of srcDirs) {
2361
+ devServer.watcher.add(srcDir);
2362
+ }
2363
+ devServer.watcher.on("change", async (file) => {
2364
+ const isSourceFile = srcDirs.some(
2365
+ (srcDir) => file.startsWith(srcDir) && (file.endsWith(".ts") || file.endsWith(".tsx"))
2366
+ );
2367
+ if (isSourceFile) {
2368
+ const outDir = path5.resolve(root, docsOptions.out);
2369
+ try {
2370
+ const extracted = await extractDocs(srcDirs, docsOptions);
2371
+ if (extracted.length > 0) {
2372
+ const generated = generateMarkdown(extracted, docsOptions);
2373
+ await writeDocs(generated, outDir, extracted, docsOptions);
2374
+ }
2375
+ } catch {
2376
+ }
2377
+ }
2378
+ });
2379
+ }
2380
+ };
2381
+ const ssgPlugin = {
2382
+ name: "ox-content:ssg",
2383
+ async closeBundle() {
2384
+ const ssgOptions = resolvedOptions.ssg;
2385
+ if (!ssgOptions.enabled) {
2386
+ return;
2387
+ }
2388
+ const root = config?.root || process.cwd();
2389
+ try {
2390
+ const result = await buildSsg(resolvedOptions, root);
2391
+ if (result.files.length > 0) {
2392
+ console.log(
2393
+ `[ox-content] Generated ${result.files.length} HTML files`
2394
+ );
2395
+ }
2396
+ if (result.errors.length > 0) {
2397
+ for (const error of result.errors) {
2398
+ console.warn(`[ox-content] ${error}`);
2399
+ }
2400
+ }
2401
+ } catch (err) {
2402
+ console.error("[ox-content] SSG build failed:", err);
2403
+ }
2404
+ }
2405
+ };
2406
+ let searchIndexJson = "";
2407
+ const searchPlugin = {
2408
+ name: "ox-content:search",
2409
+ resolveId(id) {
2410
+ if (id === "virtual:ox-content/search") {
2411
+ return "\0virtual:ox-content/search";
2412
+ }
2413
+ return null;
2414
+ },
2415
+ async load(id) {
2416
+ if (id === "\0virtual:ox-content/search") {
2417
+ const searchOptions = resolvedOptions.search;
2418
+ if (!searchOptions.enabled) {
2419
+ return "export const search = () => []; export const searchOptions = { enabled: false }; export default { search, searchOptions };";
2420
+ }
2421
+ const indexPath = resolvedOptions.base + "search-index.json";
2422
+ return generateSearchModule(searchOptions, indexPath);
2423
+ }
2424
+ return null;
2425
+ },
2426
+ async buildStart() {
2427
+ const searchOptions = resolvedOptions.search;
2428
+ if (!searchOptions.enabled) {
2429
+ return;
2430
+ }
2431
+ const root = config?.root || process.cwd();
2432
+ const srcDir = path5.resolve(root, resolvedOptions.srcDir);
2433
+ try {
2434
+ searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base);
2435
+ console.log("[ox-content] Search index built");
2436
+ } catch (err) {
2437
+ console.warn("[ox-content] Failed to build search index:", err);
2438
+ }
2439
+ },
2440
+ async closeBundle() {
2441
+ const searchOptions = resolvedOptions.search;
2442
+ if (!searchOptions.enabled || !searchIndexJson) {
2443
+ return;
2444
+ }
2445
+ const root = config?.root || process.cwd();
2446
+ const outDir = path5.resolve(root, resolvedOptions.outDir);
2447
+ try {
2448
+ await writeSearchIndex(searchIndexJson, outDir);
2449
+ console.log("[ox-content] Search index written to", path5.join(outDir, "search-index.json"));
2450
+ } catch (err) {
2451
+ console.warn("[ox-content] Failed to write search index:", err);
2452
+ }
2453
+ }
2454
+ };
2455
+ return [mainPlugin, environmentPlugin, docsPlugin, ssgPlugin, searchPlugin];
2456
+ }
2457
+ function resolveOptions(options) {
2458
+ return {
2459
+ srcDir: options.srcDir ?? "docs",
2460
+ outDir: options.outDir ?? "dist",
2461
+ base: options.base ?? "/",
2462
+ ssg: resolveSsgOptions(options.ssg),
2463
+ gfm: options.gfm ?? true,
2464
+ footnotes: options.footnotes ?? true,
2465
+ tables: options.tables ?? true,
2466
+ taskLists: options.taskLists ?? true,
2467
+ strikethrough: options.strikethrough ?? true,
2468
+ highlight: options.highlight ?? false,
2469
+ highlightTheme: options.highlightTheme ?? "github-dark",
2470
+ mermaid: options.mermaid ?? false,
2471
+ frontmatter: options.frontmatter ?? true,
2472
+ toc: options.toc ?? true,
2473
+ tocMaxDepth: options.tocMaxDepth ?? 3,
2474
+ ogImage: options.ogImage ?? false,
2475
+ ogImageOptions: options.ogImageOptions ?? {},
2476
+ transformers: options.transformers ?? [],
2477
+ docs: resolveDocsOptions(options.docs),
2478
+ search: resolveSearchOptions(options.search)
2479
+ };
2480
+ }
2481
+ function generateVirtualModule(path6, options) {
2482
+ if (path6 === "config") {
2483
+ return `export default ${JSON.stringify(options)};`;
2484
+ }
2485
+ if (path6 === "runtime") {
2486
+ return `
2487
+ export function useMarkdown() {
2488
+ return {
2489
+ render: (content) => {
2490
+ // Client-side rendering if needed
2491
+ return content;
2492
+ },
2493
+ };
2494
+ }
2495
+ `;
2496
+ }
2497
+ return "export default {};";
2498
+ }
2499
+ // Annotate the CommonJS export names for ESM import in node:
2500
+ 0 && (module.exports = {
2501
+ DEFAULT_HTML_TEMPLATE,
2502
+ buildSearchIndex,
2503
+ buildSsg,
2504
+ createMarkdownEnvironment,
2505
+ extractDocs,
2506
+ generateMarkdown,
2507
+ oxContent,
2508
+ resolveDocsOptions,
2509
+ resolveSearchOptions,
2510
+ resolveSsgOptions,
2511
+ transformMarkdown,
2512
+ writeDocs,
2513
+ writeSearchIndex
2514
+ });
2515
+ //# sourceMappingURL=index.cjs.map