ardo 3.0.2 → 3.0.4

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.
@@ -1,19 +1,1317 @@
1
1
  import {
2
- ardoPlugin,
3
- ardoRoutesPlugin,
4
- createShikiHighlighter,
5
- detectGitHubBasename,
6
- generateSidebar,
7
- getPageDataForRoute,
8
- getSlugFromPath,
9
- highlightCode,
10
- loadAllDocs,
11
- loadDoc,
12
- transformMarkdown,
13
- transformMarkdownToReact
14
- } from "../chunk-7VP5YEX2.js";
15
- import "../chunk-NBRHGTR2.js";
16
- import "../chunk-PGHUPTGL.js";
2
+ defaultMarkdownConfig,
3
+ resolveConfig
4
+ } from "../chunk-NBRHGTR2.js";
5
+ import {
6
+ generateApiDocs
7
+ } from "../chunk-PGHUPTGL.js";
8
+
9
+ // src/vite/plugin.ts
10
+ import { reactRouter } from "@react-router/dev/vite";
11
+ import mdx from "@mdx-js/rollup";
12
+ import remarkFrontmatter from "remark-frontmatter";
13
+ import remarkMdxFrontmatter from "remark-mdx-frontmatter";
14
+ import remarkGfm from "remark-gfm";
15
+ import rehypeShiki from "@shikijs/rehype";
16
+
17
+ // src/markdown/shiki.ts
18
+ import {
19
+ createHighlighter
20
+ } from "shiki";
21
+ import { visit } from "unist-util-visit";
22
+ var DEFAULT_THEMES = {
23
+ light: "github-light-default",
24
+ dark: "github-dark-default"
25
+ };
26
+ var cachedHighlighter;
27
+ async function highlightCode(code, language, options) {
28
+ const themeConfig = options?.theme ?? DEFAULT_THEMES;
29
+ if (!cachedHighlighter) {
30
+ cachedHighlighter = await createShikiHighlighter({
31
+ theme: themeConfig,
32
+ lineNumbers: false,
33
+ anchor: false,
34
+ toc: { level: [2, 3] }
35
+ });
36
+ }
37
+ if (typeof themeConfig === "string") {
38
+ return cachedHighlighter.codeToHtml(code, { lang: language, theme: themeConfig });
39
+ }
40
+ return cachedHighlighter.codeToHtml(code, {
41
+ lang: language,
42
+ themes: { light: themeConfig.light, dark: themeConfig.dark },
43
+ defaultColor: false
44
+ });
45
+ }
46
+ async function createShikiHighlighter(config) {
47
+ const themeConfig = config.theme;
48
+ const themes = typeof themeConfig === "string" ? [themeConfig] : [themeConfig.light, themeConfig.dark];
49
+ const highlighter = await createHighlighter({
50
+ themes,
51
+ langs: [
52
+ // Web fundamentals
53
+ "javascript",
54
+ "typescript",
55
+ "jsx",
56
+ "tsx",
57
+ "html",
58
+ "css",
59
+ "scss",
60
+ // Data & config formats
61
+ "json",
62
+ "jsonc",
63
+ "yaml",
64
+ "toml",
65
+ "xml",
66
+ "graphql",
67
+ // Markdown & docs
68
+ "markdown",
69
+ "mdx",
70
+ // Shell & DevOps
71
+ "bash",
72
+ "shell",
73
+ "dockerfile",
74
+ // General purpose
75
+ "python",
76
+ "rust",
77
+ "go",
78
+ "sql",
79
+ "diff"
80
+ ]
81
+ });
82
+ return highlighter;
83
+ }
84
+ function rehypeShikiFromHighlighter(options) {
85
+ const { highlighter, config } = options;
86
+ const themeConfig = config.theme;
87
+ return function(tree) {
88
+ visit(tree, "element", (node, index, parent) => {
89
+ if (node.tagName !== "pre" || !node.children[0] || node.children[0].tagName !== "code") {
90
+ return;
91
+ }
92
+ const codeNode = node.children[0];
93
+ const className = codeNode.properties?.className || [];
94
+ const langClass = className.find((c) => c.startsWith("language-"));
95
+ const lang = langClass ? langClass.replace("language-", "") : "text";
96
+ const codeContent = getTextContent(codeNode);
97
+ if (!codeContent.trim()) {
98
+ return;
99
+ }
100
+ try {
101
+ let html;
102
+ if (typeof themeConfig === "string") {
103
+ html = highlighter.codeToHtml(codeContent, {
104
+ lang,
105
+ theme: themeConfig
106
+ });
107
+ } else {
108
+ html = highlighter.codeToHtml(codeContent, {
109
+ lang,
110
+ themes: {
111
+ light: themeConfig.light,
112
+ dark: themeConfig.dark
113
+ },
114
+ defaultColor: false
115
+ });
116
+ }
117
+ const metaString = codeNode.properties?.metastring || "";
118
+ const lineNumbers = config.lineNumbers || metaString.includes("showLineNumbers");
119
+ const highlightLines = parseHighlightLines(metaString);
120
+ const title = parseTitle(metaString);
121
+ const wrapperHtml = buildCodeBlockHtml(html, {
122
+ lang,
123
+ lineNumbers,
124
+ highlightLines,
125
+ title
126
+ });
127
+ if (parent && typeof index === "number") {
128
+ const newNode = {
129
+ type: "element",
130
+ tagName: "div",
131
+ properties: {
132
+ className: ["ardo-code-block"],
133
+ "data-lang": lang
134
+ },
135
+ children: [
136
+ {
137
+ type: "raw",
138
+ value: wrapperHtml
139
+ }
140
+ ]
141
+ };
142
+ parent.children[index] = newNode;
143
+ }
144
+ } catch {
145
+ }
146
+ });
147
+ };
148
+ }
149
+ function getTextContent(node) {
150
+ if (node.type === "text") {
151
+ return node.value;
152
+ }
153
+ if ("children" in node) {
154
+ return node.children.map((child) => getTextContent(child)).join("");
155
+ }
156
+ return "";
157
+ }
158
+ function parseHighlightLines(meta) {
159
+ const match = meta.match(/\{([\d,-]+)\}/);
160
+ if (!match) return [];
161
+ const ranges = match[1].split(",");
162
+ const lines = [];
163
+ for (const range of ranges) {
164
+ if (range.includes("-")) {
165
+ const [start, end] = range.split("-").map(Number);
166
+ for (let i = start; i <= end; i++) {
167
+ lines.push(i);
168
+ }
169
+ } else {
170
+ lines.push(Number(range));
171
+ }
172
+ }
173
+ return lines;
174
+ }
175
+ function parseTitle(meta) {
176
+ const match = meta.match(/title="([^"]+)"/);
177
+ return match ? match[1] : void 0;
178
+ }
179
+ function buildCodeBlockHtml(shikiHtml, options) {
180
+ const { lang, lineNumbers, highlightLines, title } = options;
181
+ let html = "";
182
+ if (title) {
183
+ html += `<div class="ardo-code-title">${escapeHtml(title)}</div>`;
184
+ }
185
+ html += `<div class="ardo-code-wrapper" data-lang="${lang}">`;
186
+ if (lineNumbers || highlightLines.length > 0) {
187
+ const lines = shikiHtml.split("\n");
188
+ const processedHtml = lines.map((line, i) => {
189
+ const lineNum = i + 1;
190
+ const isHighlighted = highlightLines.includes(lineNum);
191
+ const classes = ["ardo-code-line"];
192
+ if (isHighlighted) classes.push("highlighted");
193
+ let prefix = "";
194
+ if (lineNumbers) {
195
+ prefix = `<span class="ardo-line-number">${lineNum}</span>`;
196
+ }
197
+ return `<span class="${classes.join(" ")}">${prefix}${line}</span>`;
198
+ }).join("\n");
199
+ html += processedHtml;
200
+ } else {
201
+ html += shikiHtml;
202
+ }
203
+ html += `<button class="ardo-copy-button" data-code="${encodeURIComponent(extractCodeFromHtml(shikiHtml))}">
204
+ <span class="ardo-copy-icon">Copy</span>
205
+ <span class="ardo-copied-icon" style="display:none">Copied!</span>
206
+ </button>`;
207
+ html += "</div>";
208
+ return html;
209
+ }
210
+ function extractCodeFromHtml(html) {
211
+ return html.replace(/<[^>]+>/g, "").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
212
+ }
213
+ function escapeHtml(text) {
214
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
215
+ }
216
+ function remarkCodeMeta() {
217
+ return function(tree) {
218
+ visit(tree, "code", (node) => {
219
+ if (!node.meta) return;
220
+ const meta = node.meta;
221
+ const data = node.data || (node.data = {});
222
+ const hProperties = data.hProperties || {};
223
+ hProperties.metastring = meta;
224
+ data.hProperties = hProperties;
225
+ node.meta = null;
226
+ });
227
+ };
228
+ }
229
+ function ardoLineTransformer(options = {}) {
230
+ let highlightLines = [];
231
+ let showLineNumbers = false;
232
+ let metaRaw = "";
233
+ return {
234
+ name: "ardo:lines",
235
+ // preprocess runs BEFORE line() hooks, so state is ready for line()
236
+ preprocess(_code, shikiOptions) {
237
+ metaRaw = shikiOptions.meta?.__raw || "";
238
+ highlightLines = parseHighlightLines(metaRaw);
239
+ showLineNumbers = options.globalLineNumbers || metaRaw.includes("showLineNumbers");
240
+ },
241
+ // pre runs AFTER line() — used only for node property modifications
242
+ pre(node) {
243
+ node.properties = node.properties || {};
244
+ const title = parseTitle(metaRaw);
245
+ if (title) {
246
+ node.properties["data-title"] = title;
247
+ }
248
+ const labelMatch = metaRaw.match(/\[([^\]]+)\]/);
249
+ if (labelMatch) {
250
+ node.properties["data-label"] = labelMatch[1];
251
+ }
252
+ },
253
+ line(node, line) {
254
+ const currentClass = node.properties?.class || "";
255
+ const classes = currentClass ? currentClass.split(" ") : [];
256
+ classes.push("ardo-code-line");
257
+ if (highlightLines.includes(line)) {
258
+ classes.push("highlighted");
259
+ }
260
+ node.properties = node.properties || {};
261
+ node.properties.class = classes.join(" ");
262
+ if (showLineNumbers) {
263
+ node.children.unshift({
264
+ type: "element",
265
+ tagName: "span",
266
+ properties: { class: "ardo-line-number" },
267
+ children: [{ type: "text", value: String(line) }]
268
+ });
269
+ }
270
+ }
271
+ };
272
+ }
273
+
274
+ // src/vite/plugin.ts
275
+ import fs2 from "fs/promises";
276
+ import fsSync2 from "fs";
277
+ import path2 from "path";
278
+ import { execSync } from "child_process";
279
+ import matter from "gray-matter";
280
+
281
+ // src/vite/routes-plugin.ts
282
+ import fs from "fs/promises";
283
+ import fsSync from "fs";
284
+ import path from "path";
285
+ function ardoRoutesPlugin(options = {}) {
286
+ let routesDir;
287
+ let appDir;
288
+ let routesFilePath;
289
+ function scanRoutesSync(dir, rootDir) {
290
+ const routes = [];
291
+ try {
292
+ const entries = fsSync.readdirSync(dir, { withFileTypes: true });
293
+ for (const entry of entries) {
294
+ const fullPath = path.join(dir, entry.name);
295
+ if (entry.isDirectory()) {
296
+ const children = scanRoutesSync(fullPath, rootDir);
297
+ routes.push(...children);
298
+ } else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md") || entry.name.endsWith(".tsx")) {
299
+ if (entry.name === "root.tsx" || entry.name.startsWith("_")) {
300
+ continue;
301
+ }
302
+ const relativePath = path.relative(rootDir, fullPath);
303
+ const ext = entry.name.endsWith(".mdx") ? ".mdx" : entry.name.endsWith(".md") ? ".md" : ".tsx";
304
+ const baseName = entry.name.replace(ext, "");
305
+ let urlPath;
306
+ if (baseName === "index" || baseName === "home") {
307
+ const parentDir = path.dirname(relativePath);
308
+ urlPath = parentDir === "." ? "/" : "/" + parentDir.replace(/\\/g, "/");
309
+ } else {
310
+ urlPath = "/" + relativePath.replace(ext, "").replace(/\\/g, "/");
311
+ }
312
+ urlPath = urlPath.replace(/\$(\w+)/g, ":$1");
313
+ routes.push({
314
+ path: urlPath,
315
+ file: "routes/" + relativePath.replace(/\\/g, "/"),
316
+ isIndex: baseName === "index" || baseName === "home"
317
+ });
318
+ }
319
+ }
320
+ } catch {
321
+ }
322
+ return routes;
323
+ }
324
+ function generateRoutesFile(routes) {
325
+ const sortedRoutes = [...routes].sort((a, b) => {
326
+ if (a.path === "/" && b.path !== "/") return -1;
327
+ if (b.path === "/" && a.path !== "/") return 1;
328
+ if (a.isIndex && !b.isIndex) return -1;
329
+ if (b.isIndex && !a.isIndex) return 1;
330
+ return a.path.localeCompare(b.path);
331
+ });
332
+ const entries = sortedRoutes.map((r) => {
333
+ if (r.path === "/") {
334
+ return ` index("${r.file}"),`;
335
+ }
336
+ const routePath = r.path.substring(1);
337
+ return ` route("${routePath}", "${r.file}"),`;
338
+ });
339
+ return `// AUTO-GENERATED by Ardo - Do not edit manually
340
+
341
+ import { type RouteConfig, route, index } from "@react-router/dev/routes"
342
+
343
+ export default [
344
+ ${entries.join("\n")}
345
+ ] satisfies RouteConfig
346
+ `;
347
+ }
348
+ function writeRoutesFileSync() {
349
+ const routes = scanRoutesSync(routesDir, routesDir);
350
+ if (routes.length === 0) {
351
+ return;
352
+ }
353
+ const content = generateRoutesFile(routes);
354
+ try {
355
+ const existing = fsSync.readFileSync(routesFilePath, "utf-8");
356
+ if (existing === content) {
357
+ return;
358
+ }
359
+ } catch {
360
+ }
361
+ fsSync.mkdirSync(appDir, { recursive: true });
362
+ fsSync.writeFileSync(routesFilePath, content, "utf-8");
363
+ console.log(`[ardo] Generated routes.ts with ${routes.length} routes`);
364
+ }
365
+ async function writeRoutesFile() {
366
+ const routes = scanRoutesSync(routesDir, routesDir);
367
+ if (routes.length === 0) {
368
+ return;
369
+ }
370
+ const content = generateRoutesFile(routes);
371
+ try {
372
+ const existing = await fs.readFile(routesFilePath, "utf-8");
373
+ if (existing === content) {
374
+ return;
375
+ }
376
+ } catch {
377
+ }
378
+ await fs.mkdir(appDir, { recursive: true });
379
+ await fs.writeFile(routesFilePath, content, "utf-8");
380
+ }
381
+ return {
382
+ name: "ardo:routes",
383
+ enforce: "pre",
384
+ config(userConfig) {
385
+ const root = userConfig.root || process.cwd();
386
+ appDir = path.join(root, "app");
387
+ routesDir = options.routesDir || path.join(appDir, "routes");
388
+ routesFilePath = path.join(appDir, "routes.ts");
389
+ try {
390
+ writeRoutesFileSync();
391
+ } catch (err) {
392
+ console.warn("[ardo] Could not generate routes.ts in config phase:", err);
393
+ }
394
+ },
395
+ configResolved(config) {
396
+ if (!appDir) {
397
+ appDir = path.join(config.root, "app");
398
+ routesDir = options.routesDir || path.join(appDir, "routes");
399
+ routesFilePath = path.join(appDir, "routes.ts");
400
+ }
401
+ },
402
+ async buildStart() {
403
+ await writeRoutesFile();
404
+ },
405
+ configureServer(server) {
406
+ server.watcher.add(routesDir);
407
+ const handleChange = async (changedPath) => {
408
+ if (changedPath.startsWith(routesDir) && (changedPath.endsWith(".mdx") || changedPath.endsWith(".md") || changedPath.endsWith(".tsx"))) {
409
+ await writeRoutesFile();
410
+ }
411
+ };
412
+ server.watcher.on("add", handleChange);
413
+ server.watcher.on("unlink", handleChange);
414
+ }
415
+ };
416
+ }
417
+
418
+ // src/vite/codeblock-plugin.ts
419
+ function outdent(text) {
420
+ const trimmed = text.replace(/^\n+/, "").replace(/\n\s*$/, "");
421
+ const lines = trimmed.split("\n");
422
+ const indent = lines.reduce((min, line) => {
423
+ if (line.trim().length === 0) return min;
424
+ const match = line.match(/^(\s*)/);
425
+ return match ? Math.min(min, match[1].length) : min;
426
+ }, Infinity);
427
+ if (indent === 0 || indent === Infinity) return trimmed;
428
+ return lines.map((line) => line.slice(indent)).join("\n");
429
+ }
430
+ function findSelfClosingCodeBlocks(source) {
431
+ const results = [];
432
+ const tag = "<CodeBlock";
433
+ let searchFrom = 0;
434
+ while (true) {
435
+ const start = source.indexOf(tag, searchFrom);
436
+ if (start === -1) break;
437
+ const afterTag = start + tag.length;
438
+ if (afterTag >= source.length || !/\s/.test(source[afterTag])) {
439
+ searchFrom = afterTag;
440
+ continue;
441
+ }
442
+ let i = afterTag;
443
+ let depth = 0;
444
+ let inSingle = false;
445
+ let inDouble = false;
446
+ let inTemplate = false;
447
+ let found = false;
448
+ while (i < source.length) {
449
+ const ch = source[i];
450
+ if ((inSingle || inDouble || inTemplate) && ch === "\\") {
451
+ i += 2;
452
+ continue;
453
+ }
454
+ if (inSingle) {
455
+ if (ch === "'") inSingle = false;
456
+ i++;
457
+ continue;
458
+ }
459
+ if (inDouble) {
460
+ if (ch === '"') inDouble = false;
461
+ i++;
462
+ continue;
463
+ }
464
+ if (inTemplate) {
465
+ if (ch === "`") inTemplate = false;
466
+ i++;
467
+ continue;
468
+ }
469
+ if (ch === "'") {
470
+ inSingle = true;
471
+ i++;
472
+ continue;
473
+ }
474
+ if (ch === '"') {
475
+ inDouble = true;
476
+ i++;
477
+ continue;
478
+ }
479
+ if (ch === "`") {
480
+ inTemplate = true;
481
+ i++;
482
+ continue;
483
+ }
484
+ if (ch === "{") {
485
+ depth++;
486
+ i++;
487
+ continue;
488
+ }
489
+ if (ch === "}") {
490
+ depth--;
491
+ i++;
492
+ continue;
493
+ }
494
+ if (depth === 0 && ch === "/" && i + 1 < source.length && source[i + 1] === ">") {
495
+ const fullMatch = source.substring(start, i + 2);
496
+ const propsStr = source.substring(afterTag, i).trim();
497
+ results.push({ fullMatch, propsStr, index: start });
498
+ found = true;
499
+ searchFrom = i + 2;
500
+ break;
501
+ }
502
+ if (depth === 0 && ch === ">") {
503
+ searchFrom = i + 1;
504
+ found = true;
505
+ break;
506
+ }
507
+ i++;
508
+ }
509
+ if (!found) break;
510
+ }
511
+ return results;
512
+ }
513
+ function ardoCodeBlockPlugin(markdownConfig) {
514
+ return {
515
+ name: "ardo:codeblock-highlight",
516
+ enforce: "pre",
517
+ async transform(code, id) {
518
+ if (!/\.[jt]sx$/.test(id)) return;
519
+ if (!code.includes("CodeBlock")) return;
520
+ if (id.includes("node_modules")) return;
521
+ let result = code;
522
+ let offset = 0;
523
+ const propMatches = findSelfClosingCodeBlocks(code);
524
+ for (const match of propMatches) {
525
+ const { fullMatch, propsStr } = match;
526
+ const codeMatch = propsStr.match(/\bcode="((?:[^"\\]|\\.)*)"/s) || propsStr.match(/\bcode=\{\s*"((?:[^"\\]|\\.)*)"\s*\}/s) || propsStr.match(/\bcode=\{\s*'((?:[^'\\]|\\.)*)'\s*\}/s);
527
+ if (!codeMatch) continue;
528
+ const langMatch = propsStr.match(/\blanguage="([^"]*)"/) || propsStr.match(/\blanguage=\{"([^"]*)"\}/) || propsStr.match(/\blanguage=\{'([^']*)'\}/);
529
+ if (!langMatch) continue;
530
+ if (propsStr.includes("__html")) continue;
531
+ const codeContent = codeMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
532
+ const language = langMatch[1];
533
+ try {
534
+ const html = await highlightCode(codeContent, language, {
535
+ theme: markdownConfig?.theme
536
+ });
537
+ const escapedHtml = JSON.stringify(html);
538
+ const newPropsStr = `__html={${escapedHtml}} ` + propsStr;
539
+ const newFullMatch = fullMatch.replace(propsStr, newPropsStr);
540
+ result = result.slice(0, match.index + offset) + newFullMatch + result.slice(match.index + offset + fullMatch.length);
541
+ offset += newFullMatch.length - fullMatch.length;
542
+ } catch {
543
+ }
544
+ }
545
+ const childrenRegex = /<CodeBlock\s+([^>]*?)>([\s\S]*?)<\/CodeBlock>/g;
546
+ offset = result.length - code.length;
547
+ let regexMatch;
548
+ while ((regexMatch = childrenRegex.exec(code)) !== null) {
549
+ const fullMatch = regexMatch[0];
550
+ const propsStr = regexMatch[1];
551
+ let rawChildren = regexMatch[2];
552
+ const langMatch = propsStr.match(/\blanguage="([^"]*)"/) || propsStr.match(/\blanguage=\{"([^"]*)"\}/) || propsStr.match(/\blanguage=\{'([^']*)'\}/);
553
+ if (!langMatch) continue;
554
+ if (propsStr.includes("__html")) continue;
555
+ const templateMatch = rawChildren.match(/^\s*\{`([\s\S]*)`\}\s*$/);
556
+ if (templateMatch) {
557
+ rawChildren = templateMatch[1];
558
+ }
559
+ const codeContent = outdent(rawChildren);
560
+ const language = langMatch[1];
561
+ try {
562
+ const html = await highlightCode(codeContent, language, {
563
+ theme: markdownConfig?.theme
564
+ });
565
+ const escapedHtml = JSON.stringify(html);
566
+ const escapedCode = JSON.stringify(codeContent);
567
+ const newTag = `<CodeBlock __html={${escapedHtml}} code={${escapedCode}} ${propsStr} />`;
568
+ result = result.slice(0, regexMatch.index + offset) + newTag + result.slice(regexMatch.index + offset + fullMatch.length);
569
+ offset += newTag.length - fullMatch.length;
570
+ } catch {
571
+ }
572
+ }
573
+ if (result !== code) {
574
+ return { code: result, map: null };
575
+ }
576
+ }
577
+ };
578
+ }
579
+
580
+ // src/vite/plugin.ts
581
+ function findPackageRoot(cwd) {
582
+ let dir = path2.resolve(cwd);
583
+ const root = path2.parse(dir).root;
584
+ while (dir !== root) {
585
+ const parentDir = path2.dirname(dir);
586
+ const packageJsonPath = path2.join(parentDir, "package.json");
587
+ if (fsSync2.existsSync(packageJsonPath)) {
588
+ return path2.relative(cwd, parentDir) || ".";
589
+ }
590
+ dir = parentDir;
591
+ }
592
+ return void 0;
593
+ }
594
+ function detectGitHubRepoName(cwd) {
595
+ try {
596
+ const remoteUrl = execSync("git remote get-url origin", {
597
+ cwd,
598
+ encoding: "utf-8",
599
+ stdio: ["pipe", "pipe", "pipe"]
600
+ }).trim();
601
+ const match = remoteUrl.match(/github\.com[/:][\w-]+\/([\w.-]+?)(?:\.git)?$/);
602
+ return match?.[1];
603
+ } catch {
604
+ return void 0;
605
+ }
606
+ }
607
+ function detectGitHash(cwd) {
608
+ try {
609
+ return execSync("git rev-parse --short HEAD", {
610
+ cwd,
611
+ encoding: "utf-8",
612
+ stdio: ["pipe", "pipe", "pipe"]
613
+ }).trim();
614
+ } catch {
615
+ return void 0;
616
+ }
617
+ }
618
+ function readProjectMeta(root) {
619
+ const pkgPath = path2.join(root, "package.json");
620
+ try {
621
+ const raw = fsSync2.readFileSync(pkgPath, "utf-8");
622
+ const pkg = JSON.parse(raw);
623
+ let repository;
624
+ if (typeof pkg.repository === "string") {
625
+ repository = pkg.repository;
626
+ } else if (pkg.repository?.url) {
627
+ repository = pkg.repository.url.replace(/^git\+/, "").replace(/^git:\/\//, "https://").replace(/\.git$/, "");
628
+ }
629
+ let author;
630
+ if (typeof pkg.author === "string") {
631
+ author = pkg.author;
632
+ } else if (pkg.author?.name) {
633
+ author = pkg.author.name;
634
+ }
635
+ return {
636
+ name: pkg.name,
637
+ homepage: pkg.homepage,
638
+ repository,
639
+ version: pkg.version,
640
+ author,
641
+ license: pkg.license
642
+ };
643
+ } catch {
644
+ return {};
645
+ }
646
+ }
647
+ function copyRecursive(src, dest) {
648
+ const stat = fsSync2.statSync(src);
649
+ if (stat.isDirectory()) {
650
+ if (!fsSync2.existsSync(dest)) {
651
+ fsSync2.mkdirSync(dest, { recursive: true });
652
+ }
653
+ for (const item of fsSync2.readdirSync(src)) {
654
+ copyRecursive(path2.join(src, item), path2.join(dest, item));
655
+ }
656
+ } else {
657
+ fsSync2.copyFileSync(src, dest);
658
+ }
659
+ }
660
+ function detectGitHubBasename(cwd) {
661
+ if (process.env.NODE_ENV !== "production") {
662
+ return "/";
663
+ }
664
+ const repoName = detectGitHubRepoName(cwd || process.cwd());
665
+ return repoName ? `/${repoName}/` : "/";
666
+ }
667
+ var VIRTUAL_MODULE_ID = "virtual:ardo/config";
668
+ var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
669
+ var VIRTUAL_SIDEBAR_ID = "virtual:ardo/sidebar";
670
+ var RESOLVED_VIRTUAL_SIDEBAR_ID = "\0" + VIRTUAL_SIDEBAR_ID;
671
+ var VIRTUAL_SEARCH_ID = "virtual:ardo/search-index";
672
+ var RESOLVED_VIRTUAL_SEARCH_ID = "\0" + VIRTUAL_SEARCH_ID;
673
+ var typedocGenerated = false;
674
+ var flattenExecuted = false;
675
+ function ardoPlugin(options = {}) {
676
+ let resolvedConfig;
677
+ let routesDir;
678
+ const {
679
+ routes,
680
+ typedoc,
681
+ githubPages = true,
682
+ routesDir: routesDirOption,
683
+ ...pressConfig
684
+ } = options;
685
+ const mainPlugin = {
686
+ name: "ardo",
687
+ enforce: "pre",
688
+ config(userConfig, env) {
689
+ const root = userConfig.root || process.cwd();
690
+ routesDir = routesDirOption || path2.join(root, "app", "routes");
691
+ const result = {
692
+ define: {
693
+ __BUILD_TIME__: JSON.stringify((/* @__PURE__ */ new Date()).toISOString())
694
+ },
695
+ optimizeDeps: {
696
+ exclude: ["ardo/ui/styles.css"]
697
+ },
698
+ ssr: {
699
+ noExternal: ["ardo"]
700
+ }
701
+ };
702
+ if (githubPages && env.command === "build" && !userConfig.base) {
703
+ const repoName = detectGitHubRepoName(root);
704
+ if (repoName) {
705
+ result.base = `/${repoName}/`;
706
+ console.log(`[ardo] GitHub Pages detected, using base: ${result.base}`);
707
+ }
708
+ }
709
+ return result;
710
+ },
711
+ async configResolved(config) {
712
+ const root = config.root;
713
+ routesDir = routesDirOption || path2.join(root, "app", "routes");
714
+ const detectedProject = readProjectMeta(root);
715
+ const project = { ...detectedProject, ...pressConfig.project };
716
+ const defaultConfig = {
717
+ title: pressConfig.title ?? "Ardo",
718
+ description: pressConfig.description ?? "Documentation powered by Ardo"
719
+ };
720
+ const configWithRoutes = {
721
+ ...defaultConfig,
722
+ ...pressConfig,
723
+ project,
724
+ srcDir: routesDir
725
+ };
726
+ resolvedConfig = resolveConfig(configWithRoutes, root);
727
+ },
728
+ resolveId(id) {
729
+ if (id === VIRTUAL_MODULE_ID) {
730
+ return RESOLVED_VIRTUAL_MODULE_ID;
731
+ }
732
+ if (id === VIRTUAL_SIDEBAR_ID) {
733
+ return RESOLVED_VIRTUAL_SIDEBAR_ID;
734
+ }
735
+ if (id === VIRTUAL_SEARCH_ID) {
736
+ return RESOLVED_VIRTUAL_SEARCH_ID;
737
+ }
738
+ },
739
+ async load(id) {
740
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
741
+ const clientConfig = {
742
+ title: resolvedConfig.title,
743
+ description: resolvedConfig.description,
744
+ base: resolvedConfig.base,
745
+ lang: resolvedConfig.lang,
746
+ themeConfig: resolvedConfig.themeConfig,
747
+ project: resolvedConfig.project,
748
+ buildTime: (/* @__PURE__ */ new Date()).toISOString(),
749
+ buildHash: detectGitHash(resolvedConfig.root)
750
+ };
751
+ return `export default ${JSON.stringify(clientConfig)}`;
752
+ }
753
+ if (id === RESOLVED_VIRTUAL_SIDEBAR_ID) {
754
+ const sidebar = await generateSidebar(resolvedConfig, routesDir);
755
+ return `export default ${JSON.stringify(sidebar)}`;
756
+ }
757
+ if (id === RESOLVED_VIRTUAL_SEARCH_ID) {
758
+ const searchIndex = await generateSearchIndex(routesDir);
759
+ return `export default ${JSON.stringify(searchIndex)}`;
760
+ }
761
+ },
762
+ transform(code, id) {
763
+ if (!/\.(mdx|md)$/.test(id)) return;
764
+ if (!id.startsWith(routesDir)) return;
765
+ if (/export\s+(const|function)\s+meta\b/.test(code)) return;
766
+ const titleMatch = code.match(
767
+ /export\s+const\s+frontmatter\s*=\s*\{[^}]*title\s*:\s*"([^"]*)"/
768
+ );
769
+ const descMatch = code.match(
770
+ /export\s+const\s+frontmatter\s*=\s*\{[^}]*description\s*:\s*"([^"]*)"/
771
+ );
772
+ const pageTitle = titleMatch?.[1];
773
+ if (!pageTitle) return;
774
+ const siteTitle = resolvedConfig.title;
775
+ const separator = resolvedConfig.titleSeparator;
776
+ const fullTitle = `${pageTitle}${separator}${siteTitle}`;
777
+ const description = descMatch?.[1];
778
+ const metaEntries = [`{ title: ${JSON.stringify(fullTitle)} }`];
779
+ if (description) {
780
+ metaEntries.push(`{ name: "description", content: ${JSON.stringify(description)} }`);
781
+ }
782
+ return {
783
+ code: `${code}
784
+ export const meta = () => [${metaEntries.join(", ")}];
785
+ `,
786
+ map: null
787
+ };
788
+ }
789
+ };
790
+ const plugins = [mainPlugin];
791
+ if (routes !== false) {
792
+ plugins.push(
793
+ ardoRoutesPlugin({
794
+ routesDir: routesDirOption,
795
+ ...routes
796
+ })
797
+ );
798
+ }
799
+ if (typedoc) {
800
+ const packageRoot = findPackageRoot(process.cwd());
801
+ const defaultEntryPoint = packageRoot ? `${packageRoot}/src/index.ts` : "./src/index.ts";
802
+ const defaultTsconfig = packageRoot ? `${packageRoot}/tsconfig.json` : "./tsconfig.json";
803
+ const defaultTypedocConfig = {
804
+ enabled: true,
805
+ entryPoints: [defaultEntryPoint],
806
+ tsconfig: defaultTsconfig,
807
+ out: "api-reference",
808
+ excludePrivate: true,
809
+ excludeInternal: true
810
+ };
811
+ const typedocConfig = typedoc === true ? defaultTypedocConfig : { ...defaultTypedocConfig, ...typedoc };
812
+ const typedocPlugin = {
813
+ name: "ardo:typedoc",
814
+ async buildStart() {
815
+ if (typedocGenerated || !typedocConfig.enabled) {
816
+ return;
817
+ }
818
+ console.log("[ardo] Generating API documentation with TypeDoc...");
819
+ const startTime = Date.now();
820
+ try {
821
+ const outputDir = routesDirOption || "./app/routes";
822
+ const docs = await generateApiDocs(typedocConfig, outputDir);
823
+ const duration = Date.now() - startTime;
824
+ console.log(`[ardo] Generated ${docs.length} API documentation pages in ${duration}ms`);
825
+ } catch (error) {
826
+ console.warn("[ardo] TypeDoc generation failed. API documentation will not be available.");
827
+ console.warn("[ardo] Check your typedoc.entryPoints configuration.");
828
+ if (error instanceof Error) {
829
+ console.warn(`[ardo] Error: ${error.message}`);
830
+ }
831
+ }
832
+ typedocGenerated = true;
833
+ }
834
+ };
835
+ plugins.unshift(typedocPlugin);
836
+ }
837
+ plugins.push(ardoCodeBlockPlugin(pressConfig.markdown));
838
+ const themeConfig = pressConfig.markdown?.theme ?? defaultMarkdownConfig.theme;
839
+ const hasThemeObject = themeConfig && typeof themeConfig === "object" && "light" in themeConfig;
840
+ const lineNumbers = pressConfig.markdown?.lineNumbers || false;
841
+ const shikiOptions = hasThemeObject ? {
842
+ themes: {
843
+ light: themeConfig.light,
844
+ dark: themeConfig.dark
845
+ },
846
+ defaultColor: false,
847
+ transformers: [ardoLineTransformer({ globalLineNumbers: lineNumbers })]
848
+ } : {
849
+ theme: themeConfig,
850
+ transformers: [ardoLineTransformer({ globalLineNumbers: lineNumbers })]
851
+ };
852
+ const mdxPlugin = mdx({
853
+ include: /\.(md|mdx)$/,
854
+ remarkPlugins: [
855
+ remarkFrontmatter,
856
+ [remarkMdxFrontmatter, { name: "frontmatter" }],
857
+ remarkGfm,
858
+ remarkCodeMeta
859
+ ],
860
+ rehypePlugins: [[rehypeShiki, shikiOptions]],
861
+ providerImportSource: "ardo/mdx-provider"
862
+ });
863
+ plugins.push(mdxPlugin);
864
+ const reactRouterPlugin = reactRouter();
865
+ const reactRouterPlugins = (Array.isArray(reactRouterPlugin) ? reactRouterPlugin : [reactRouterPlugin]).filter((p) => p != null);
866
+ plugins.push(...reactRouterPlugins);
867
+ if (githubPages) {
868
+ let detectedBase;
869
+ const flattenPlugin = {
870
+ name: "ardo:flatten-github-pages",
871
+ enforce: "post",
872
+ configResolved(config) {
873
+ if (config.base && config.base !== "/") {
874
+ detectedBase = config.base;
875
+ }
876
+ },
877
+ closeBundle() {
878
+ if (flattenExecuted || !detectedBase) {
879
+ return;
880
+ }
881
+ const baseName = detectedBase.replace(/^\/|\/$/g, "");
882
+ if (!baseName) return;
883
+ const buildDir = path2.join(process.cwd(), "build", "client");
884
+ const nestedDir = path2.join(buildDir, baseName);
885
+ if (!fsSync2.existsSync(nestedDir)) {
886
+ return;
887
+ }
888
+ console.log(`[ardo] Flattening build/client/${baseName}/ to build/client/ for GitHub Pages`);
889
+ copyRecursive(nestedDir, buildDir);
890
+ fsSync2.rmSync(nestedDir, { recursive: true, force: true });
891
+ console.log("[ardo] Build output flattened successfully.");
892
+ flattenExecuted = true;
893
+ }
894
+ };
895
+ plugins.push(flattenPlugin);
896
+ }
897
+ return plugins;
898
+ }
899
+ async function generateSidebar(config, routesDir) {
900
+ const { themeConfig } = config;
901
+ if (themeConfig.sidebar && !Array.isArray(themeConfig.sidebar)) {
902
+ return themeConfig.sidebar;
903
+ }
904
+ if (themeConfig.sidebar && Array.isArray(themeConfig.sidebar) && themeConfig.sidebar.length > 0) {
905
+ return themeConfig.sidebar;
906
+ }
907
+ try {
908
+ const sidebar = await scanDirectory(routesDir, routesDir);
909
+ return sidebar;
910
+ } catch {
911
+ return [];
912
+ }
913
+ }
914
+ async function scanDirectory(dir, rootDir) {
915
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
916
+ const items = [];
917
+ for (const entry of entries) {
918
+ const fullPath = path2.join(dir, entry.name);
919
+ const relativePath = path2.relative(rootDir, fullPath);
920
+ if (entry.isDirectory()) {
921
+ const children = await scanDirectory(fullPath, rootDir);
922
+ if (children.length > 0) {
923
+ const indexPath = path2.join(fullPath, "index.mdx");
924
+ let link;
925
+ try {
926
+ await fs2.access(indexPath);
927
+ link = "/" + relativePath.replace(/\\/g, "/");
928
+ } catch {
929
+ }
930
+ items.push({
931
+ text: formatTitle(entry.name),
932
+ link,
933
+ items: children
934
+ });
935
+ }
936
+ } else if ((entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) && entry.name !== "index.mdx" && entry.name !== "index.md") {
937
+ const fileContent = await fs2.readFile(fullPath, "utf-8");
938
+ const { data: frontmatter } = matter(fileContent);
939
+ const ext = entry.name.endsWith(".mdx") ? ".mdx" : ".md";
940
+ const title = frontmatter.title || formatTitle(entry.name.replace(ext, ""));
941
+ const order = typeof frontmatter.order === "number" ? frontmatter.order : void 0;
942
+ const link = "/" + relativePath.replace(ext, "").replace(/\\/g, "/");
943
+ items.push({
944
+ text: title,
945
+ link,
946
+ order
947
+ });
948
+ }
949
+ }
950
+ items.sort((a, b) => {
951
+ if (a.order !== void 0 && b.order !== void 0) {
952
+ return a.order - b.order;
953
+ }
954
+ if (a.order !== void 0) return -1;
955
+ if (b.order !== void 0) return 1;
956
+ return a.text.localeCompare(b.text);
957
+ });
958
+ return items.map(({ order: _order, ...item }) => item);
959
+ }
960
+ function formatTitle(name) {
961
+ return name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
962
+ }
963
+ async function generateSearchIndex(routesDir) {
964
+ const docs = [];
965
+ async function scanForSearch(dir, section) {
966
+ try {
967
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
968
+ for (const entry of entries) {
969
+ const fullPath = path2.join(dir, entry.name);
970
+ if (entry.isDirectory()) {
971
+ const newSection = section ? `${section} > ${formatTitle(entry.name)}` : formatTitle(entry.name);
972
+ await scanForSearch(fullPath, newSection);
973
+ } else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
974
+ const relativePath = path2.relative(routesDir, fullPath);
975
+ const fileContent = await fs2.readFile(fullPath, "utf-8");
976
+ const { data: frontmatter, content: rawContent } = matter(fileContent);
977
+ const ext = entry.name.endsWith(".mdx") ? ".mdx" : ".md";
978
+ const title = frontmatter.title || formatTitle(entry.name.replace(ext, ""));
979
+ let content = rawContent;
980
+ content = content.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/import\s+.*?from\s+['"].*?['"]/g, "").replace(/<[^>]+>/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[#*_~>]/g, "").replace(/\n+/g, " ").replace(/\s+/g, " ").trim().slice(0, 2e3);
981
+ const routePath = entry.name === "index.mdx" || entry.name === "index.md" ? "/" + path2.dirname(relativePath).replace(/\\/g, "/") : "/" + relativePath.replace(ext, "").replace(/\\/g, "/");
982
+ const finalPath = routePath === "/." ? "/" : routePath;
983
+ docs.push({
984
+ id: relativePath,
985
+ title,
986
+ content,
987
+ path: finalPath,
988
+ section
989
+ });
990
+ }
991
+ }
992
+ } catch (error) {
993
+ console.warn(
994
+ "[ardo] Failed to scan for search index:",
995
+ error instanceof Error ? error.message : error
996
+ );
997
+ }
998
+ }
999
+ await scanForSearch(routesDir);
1000
+ return docs;
1001
+ }
1002
+
1003
+ // src/runtime/loader.ts
1004
+ import fs3 from "fs/promises";
1005
+ import path3 from "path";
1006
+
1007
+ // src/markdown/pipeline.ts
1008
+ import { unified } from "unified";
1009
+ import remarkParse from "remark-parse";
1010
+ import remarkGfm2 from "remark-gfm";
1011
+ import remarkFrontmatter2 from "remark-frontmatter";
1012
+ import remarkRehype from "remark-rehype";
1013
+ import rehypeStringify from "rehype-stringify";
1014
+ import matter2 from "gray-matter";
1015
+
1016
+ // src/markdown/toc.ts
1017
+ import { visit as visit2 } from "unist-util-visit";
1018
+ function remarkExtractToc(options) {
1019
+ const { tocExtraction, levels } = options;
1020
+ const [minLevel, maxLevel] = levels;
1021
+ return function(tree) {
1022
+ const headings = [];
1023
+ let headingIndex = 0;
1024
+ visit2(tree, "heading", (node) => {
1025
+ if (node.depth < minLevel || node.depth > maxLevel) {
1026
+ return;
1027
+ }
1028
+ const text = getHeadingText(node);
1029
+ const slug = slugify(text);
1030
+ const id = slug || `heading-${headingIndex}`;
1031
+ headingIndex++;
1032
+ headings.push({
1033
+ text,
1034
+ level: node.depth,
1035
+ id
1036
+ });
1037
+ const data = node.data || (node.data = {});
1038
+ const hProperties = data.hProperties || (data.hProperties = {});
1039
+ hProperties.id = id;
1040
+ });
1041
+ tocExtraction.toc = buildTocTree(headings, minLevel);
1042
+ };
1043
+ }
1044
+ function getHeadingText(node) {
1045
+ const textParts = [];
1046
+ function extractText(child) {
1047
+ if (!child || typeof child !== "object") return;
1048
+ const typedChild = child;
1049
+ if (typedChild.type === "text") {
1050
+ textParts.push(typedChild.value || "");
1051
+ } else if (typedChild.type === "inlineCode") {
1052
+ textParts.push(typedChild.value || "");
1053
+ } else if (Array.isArray(typedChild.children)) {
1054
+ typedChild.children.forEach(extractText);
1055
+ }
1056
+ }
1057
+ node.children.forEach(extractText);
1058
+ return textParts.join("");
1059
+ }
1060
+ function slugify(text) {
1061
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
1062
+ }
1063
+ function buildTocTree(headings, _minLevel) {
1064
+ const result = [];
1065
+ const stack = [];
1066
+ for (const heading of headings) {
1067
+ const item = {
1068
+ id: heading.id,
1069
+ text: heading.text,
1070
+ level: heading.level
1071
+ };
1072
+ while (stack.length > 0 && stack[stack.length - 1].level >= heading.level) {
1073
+ stack.pop();
1074
+ }
1075
+ if (stack.length === 0) {
1076
+ result.push(item);
1077
+ } else {
1078
+ const parent = stack[stack.length - 1].item;
1079
+ if (!parent.children) {
1080
+ parent.children = [];
1081
+ }
1082
+ parent.children.push(item);
1083
+ }
1084
+ stack.push({ item, level: heading.level });
1085
+ }
1086
+ return result;
1087
+ }
1088
+
1089
+ // src/markdown/links.ts
1090
+ import { visit as visit3 } from "unist-util-visit";
1091
+ function rehypeLinks(options) {
1092
+ const { basePath } = options;
1093
+ const normalizedBase = basePath === "/" ? "" : basePath.replace(/\/$/, "");
1094
+ return (tree) => {
1095
+ if (!normalizedBase) {
1096
+ return;
1097
+ }
1098
+ visit3(tree, "element", (node) => {
1099
+ if (node.tagName === "a") {
1100
+ const href = node.properties?.href;
1101
+ if (typeof href === "string") {
1102
+ if (href.startsWith("/") && !href.startsWith("//") && !href.startsWith(normalizedBase)) {
1103
+ node.properties = node.properties || {};
1104
+ node.properties.href = normalizedBase + href;
1105
+ }
1106
+ }
1107
+ }
1108
+ });
1109
+ };
1110
+ }
1111
+
1112
+ // src/markdown/pipeline.ts
1113
+ async function transformMarkdown(content, config, options = {}) {
1114
+ const { data: frontmatter, content: markdownContent } = matter2(content);
1115
+ const { basePath = "/", highlighter: providedHighlighter } = options;
1116
+ const tocExtraction = { toc: [] };
1117
+ const highlighter = providedHighlighter ?? await createShikiHighlighter(config);
1118
+ const processor = unified().use(remarkParse).use(remarkFrontmatter2, ["yaml"]).use(remarkGfm2).use(remarkExtractToc, { tocExtraction, levels: config.toc?.level ?? [2, 3] }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShikiFromHighlighter, { highlighter, config }).use(rehypeLinks, { basePath }).use(rehypeStringify, { allowDangerousHtml: true });
1119
+ if (config.remarkPlugins) {
1120
+ for (const plugin of config.remarkPlugins) {
1121
+ processor.use(plugin);
1122
+ }
1123
+ }
1124
+ if (config.rehypePlugins) {
1125
+ for (const plugin of config.rehypePlugins) {
1126
+ processor.use(plugin);
1127
+ }
1128
+ }
1129
+ const result = await processor.process(markdownContent);
1130
+ return {
1131
+ html: String(result),
1132
+ frontmatter,
1133
+ toc: tocExtraction.toc
1134
+ };
1135
+ }
1136
+ async function transformMarkdownToReact(content, config) {
1137
+ return transformMarkdown(content, config);
1138
+ }
1139
+
1140
+ // src/runtime/loader.ts
1141
+ async function loadDoc(options) {
1142
+ const { slug, contentDir, config } = options;
1143
+ const possiblePaths = [
1144
+ path3.join(contentDir, `${slug}.md`),
1145
+ path3.join(contentDir, slug, "index.md")
1146
+ ];
1147
+ let filePath = null;
1148
+ let fileContent = null;
1149
+ for (const tryPath of possiblePaths) {
1150
+ try {
1151
+ fileContent = await fs3.readFile(tryPath, "utf-8");
1152
+ filePath = tryPath;
1153
+ break;
1154
+ } catch {
1155
+ continue;
1156
+ }
1157
+ }
1158
+ if (!filePath || !fileContent) {
1159
+ return null;
1160
+ }
1161
+ const result = await transformMarkdown(fileContent, config.markdown);
1162
+ const relativePath = path3.relative(contentDir, filePath);
1163
+ let lastUpdated;
1164
+ try {
1165
+ const stat = await fs3.stat(filePath);
1166
+ lastUpdated = stat.mtimeMs;
1167
+ } catch {
1168
+ }
1169
+ return {
1170
+ content: result.html,
1171
+ frontmatter: result.frontmatter,
1172
+ toc: result.toc,
1173
+ filePath,
1174
+ relativePath,
1175
+ lastUpdated
1176
+ };
1177
+ }
1178
+ async function loadAllDocs(contentDir, config) {
1179
+ const docs = [];
1180
+ async function scanDir(dir) {
1181
+ const entries = await fs3.readdir(dir, { withFileTypes: true });
1182
+ for (const entry of entries) {
1183
+ const fullPath = path3.join(dir, entry.name);
1184
+ if (entry.isDirectory()) {
1185
+ await scanDir(fullPath);
1186
+ } else if (entry.name.endsWith(".md")) {
1187
+ const fileContent = await fs3.readFile(fullPath, "utf-8");
1188
+ const result = await transformMarkdown(fileContent, config.markdown);
1189
+ const relativePath = path3.relative(contentDir, fullPath);
1190
+ let lastUpdated;
1191
+ try {
1192
+ const stat = await fs3.stat(fullPath);
1193
+ lastUpdated = stat.mtimeMs;
1194
+ } catch {
1195
+ }
1196
+ docs.push({
1197
+ title: result.frontmatter.title || formatTitle2(entry.name.replace(/\.md$/, "")),
1198
+ description: result.frontmatter.description,
1199
+ frontmatter: result.frontmatter,
1200
+ content: result.html,
1201
+ toc: result.toc,
1202
+ filePath: fullPath,
1203
+ relativePath,
1204
+ lastUpdated
1205
+ });
1206
+ }
1207
+ }
1208
+ }
1209
+ await scanDir(contentDir);
1210
+ return docs;
1211
+ }
1212
+ function formatTitle2(name) {
1213
+ return name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1214
+ }
1215
+ function getSlugFromPath(relativePath) {
1216
+ return relativePath.replace(/\.md$/, "").replace(/\/index$/, "").replace(/\\/g, "/");
1217
+ }
1218
+ function getPageDataForRoute(docs, slug) {
1219
+ return docs.find((doc) => {
1220
+ const docSlug = getSlugFromPath(doc.relativePath);
1221
+ return docSlug === slug || docSlug === `${slug}/index`;
1222
+ });
1223
+ }
1224
+
1225
+ // src/runtime/sidebar.ts
1226
+ import fs4 from "fs/promises";
1227
+ import path4 from "path";
1228
+ import matter3 from "gray-matter";
1229
+ async function generateSidebar2(options) {
1230
+ const { contentDir, basePath, config } = options;
1231
+ const configSidebar = config.themeConfig.sidebar;
1232
+ if (configSidebar) {
1233
+ if (Array.isArray(configSidebar) && configSidebar.length > 0) {
1234
+ return configSidebar;
1235
+ }
1236
+ if (!Array.isArray(configSidebar)) {
1237
+ return [];
1238
+ }
1239
+ }
1240
+ return await scanDirectoryForSidebar(contentDir, contentDir, basePath);
1241
+ }
1242
+ async function scanDirectoryForSidebar(dir, rootDir, _basePath) {
1243
+ let entries;
1244
+ try {
1245
+ entries = await fs4.readdir(dir, { withFileTypes: true });
1246
+ } catch {
1247
+ return [];
1248
+ }
1249
+ const items = [];
1250
+ for (const entry of entries) {
1251
+ const fullPath = path4.join(dir, entry.name);
1252
+ const relativePath = path4.relative(rootDir, fullPath);
1253
+ if (entry.name.startsWith(".") || entry.name.startsWith("_")) {
1254
+ continue;
1255
+ }
1256
+ if (entry.isDirectory()) {
1257
+ const children = await scanDirectoryForSidebar(fullPath, rootDir, _basePath);
1258
+ if (children.length > 0) {
1259
+ const indexPath = path4.join(fullPath, "index.md");
1260
+ let link;
1261
+ let title = formatTitle3(entry.name);
1262
+ let order;
1263
+ try {
1264
+ const indexContent = await fs4.readFile(indexPath, "utf-8");
1265
+ const { data: frontmatter } = matter3(indexContent);
1266
+ if (frontmatter.title) {
1267
+ title = frontmatter.title;
1268
+ }
1269
+ if (typeof frontmatter.order === "number") {
1270
+ order = frontmatter.order;
1271
+ }
1272
+ link = normalizePath(relativePath);
1273
+ } catch {
1274
+ }
1275
+ items.push({
1276
+ text: title,
1277
+ link,
1278
+ collapsed: false,
1279
+ items: children,
1280
+ order
1281
+ });
1282
+ }
1283
+ } else if (entry.name.endsWith(".md") && entry.name !== "index.md") {
1284
+ const fileContent = await fs4.readFile(fullPath, "utf-8");
1285
+ const { data: frontmatter } = matter3(fileContent);
1286
+ if (frontmatter.sidebar === false) {
1287
+ continue;
1288
+ }
1289
+ const title = frontmatter.title || formatTitle3(entry.name.replace(/\.md$/, ""));
1290
+ const order = typeof frontmatter.order === "number" ? frontmatter.order : void 0;
1291
+ const link = normalizePath(relativePath.replace(/\.md$/, ""));
1292
+ items.push({
1293
+ text: title,
1294
+ link,
1295
+ order
1296
+ });
1297
+ }
1298
+ }
1299
+ items.sort((a, b) => {
1300
+ if (a.order !== void 0 && b.order !== void 0) {
1301
+ return a.order - b.order;
1302
+ }
1303
+ if (a.order !== void 0) return -1;
1304
+ if (b.order !== void 0) return 1;
1305
+ return a.text.localeCompare(b.text);
1306
+ });
1307
+ return items.map(({ order: _order, ...item }) => item);
1308
+ }
1309
+ function formatTitle3(name) {
1310
+ return name.replace(/^\d+-/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1311
+ }
1312
+ function normalizePath(p) {
1313
+ return "/" + p.replace(/\\/g, "/").replace(/^\/+/, "");
1314
+ }
17
1315
  export {
18
1316
  ardoPlugin as ardo,
19
1317
  ardoPlugin,
@@ -21,7 +1319,7 @@ export {
21
1319
  createShikiHighlighter,
22
1320
  ardoPlugin as default,
23
1321
  detectGitHubBasename,
24
- generateSidebar,
1322
+ generateSidebar2 as generateSidebar,
25
1323
  getPageDataForRoute,
26
1324
  getSlugFromPath,
27
1325
  highlightCode,