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.
- package/dist/index.d.ts +2 -5
- package/dist/index.js +0 -42
- package/dist/typedoc/index.js +259 -10
- package/dist/typedoc/index.js.map +1 -1
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1314 -16
- package/dist/vite/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7VP5YEX2.js +0 -1336
- package/dist/chunk-7VP5YEX2.js.map +0 -1
- package/dist/chunk-Y46KCHBO.js +0 -274
- package/dist/chunk-Y46KCHBO.js.map +0 -1
package/dist/vite/index.js
CHANGED
|
@@ -1,19 +1,1317 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import "
|
|
16
|
-
|
|
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(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/g, '"').replace(/'/g, "'");
|
|
212
|
+
}
|
|
213
|
+
function escapeHtml(text) {
|
|
214
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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,
|