boltdocs 2.8.4 → 2.9.1
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/bin/boltdocs.js +7 -6
- package/dist/{banner-DFACg5Dy.d.cts → banner-C_xkGxzI.d.cts} +1 -1
- package/dist/{banner-BLTQUA2O.d.ts → banner-D2NddBfF.d.ts} +1 -1
- package/dist/cache-B19MLTpN.cjs +6 -0
- package/dist/cache-CfhOTR3Q.mjs +6 -0
- package/dist/chunk-Ds5LZdWN.cjs +6 -0
- package/dist/client/index.cjs +464 -279
- package/dist/client/index.d.cts +74 -8
- package/dist/client/index.d.ts +74 -8
- package/dist/client/index.js +463 -280
- package/dist/client/mdx.cjs +1 -1
- package/dist/client/mdx.js +1 -1
- package/dist/client/primitives.cjs +4 -4
- package/dist/client/primitives.d.cts +1 -1
- package/dist/client/primitives.d.ts +1 -1
- package/dist/client/primitives.js +4 -4
- package/dist/client/theme/neutral.css +5 -3
- package/dist/client/theme/reset.css +19 -0
- package/dist/config-BqZUYono.cjs +7 -0
- package/dist/config-DBKBEj3j.mjs +7 -0
- package/dist/config-GPVEdeon.mjs +6 -0
- package/dist/config-kIQOJHXC.cjs +6 -0
- package/dist/csp-CBysWlAf.cjs +6 -0
- package/dist/csp-RJyh0nHV.mjs +6 -0
- package/dist/{docs-layout-CVCf3xkZ.cjs → docs-layout-3TpAt6MB.cjs} +302 -238
- package/dist/{docs-layout-BBv6wILp.js → docs-layout-6FQ8__ok.js} +303 -239
- package/dist/doctor-C19DsI1e.mjs +7 -0
- package/dist/{doctor-DT4jh07O.cjs → doctor-D8fl3Sbg.cjs} +1 -1
- package/dist/doctor-DzEgMMRo.cjs +7 -0
- package/dist/errors-C0Hx15Ag.mjs +6 -0
- package/dist/errors-Dx6JHoKE.cjs +6 -0
- package/dist/{generator-BHCrLU6h.mjs → generator-B6lrnDZ-.mjs} +1 -1
- package/dist/{generator-CC2yHzhZ.cjs → generator-DtUU3Cfk.cjs} +2 -2
- package/dist/handler-1LRMYeI4.cjs +28 -0
- package/dist/handler-CyIBbZdb.mjs +28 -0
- package/dist/{doctor-BGPdO1Ah.mjs → handler-eNNcucnf.mjs} +1 -1
- package/dist/headers-BJ4jJAhU.cjs +6 -0
- package/dist/headers-HD_qTVVW.mjs +6 -0
- package/dist/{icons-dev-C8sFxeBk.cjs → icons-dev-BryEFkJu.cjs} +90 -4
- package/dist/{icons-dev-CFcv5Nb8.js → icons-dev-kJyGWNeg.js} +73 -5
- package/dist/{image-DCMxxidi.cjs → image-B9YA_oQ_.cjs} +17 -9
- package/dist/{image-DufDOid8.js → image-CjYu9Eml.js} +17 -9
- package/dist/{mdx-Bto8CvGl.js → mdx-Cy_c2zio.js} +116 -33
- package/dist/{mdx-BH9kHiF9.cjs → mdx-WXjAMS8Y.cjs} +121 -32
- package/dist/{meta-loader-Cv9O0Pzl.cjs → meta-loader-C9MKjAs0.cjs} +1 -1
- package/dist/{meta-loader-DzwDFtdT.mjs → meta-loader-CxAoQthW.mjs} +1 -1
- package/dist/node/cli-entry.cjs +8 -2
- package/dist/node/cli-entry.mjs +8 -2
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +362 -18
- package/dist/node/index.d.mts +364 -19
- package/dist/node/index.mjs +1 -1
- package/dist/node/routes/worker.cjs +1 -1
- package/dist/node/routes/worker.mjs +1 -1
- package/dist/node-B7qbjtLl.mjs +105 -0
- package/dist/node-Dj3-2YYP.cjs +105 -0
- package/dist/{package-CwNjt3Sd.cjs → package-ByXh2JHp.cjs} +1 -1
- package/dist/{package-DlgexgpQ.mjs → package-DQBAV8uo.mjs} +1 -1
- package/dist/{parser-BzB-zCkF.cjs → parser-B8naUXl2.cjs} +1 -1
- package/dist/parser-D4oEyuP-.cjs +6 -0
- package/dist/parser-IR9tfyGE.mjs +6 -0
- package/dist/plugin-BkquLpOI.mjs +61 -0
- package/dist/plugin-CL1LsQXk.cjs +61 -0
- package/dist/routes-DR13Y1em.mjs +6 -0
- package/dist/routes-DX8ryKjs.cjs +6 -0
- package/dist/routes-DgTQxpZi.cjs +6 -0
- package/dist/routes-TUSK5wD7.mjs +6 -0
- package/dist/{search-dialog-Cvux-qSk.cjs → search-dialog-BhXg05Ri.cjs} +1 -1
- package/dist/{search-dialog-CmHYOt18.cjs → search-dialog-BsAiQgnx.cjs} +2 -2
- package/dist/{search-dialog-jHZ21G4i.js → search-dialog-CaOO1Q4i.js} +2 -2
- package/dist/{search-dialog-Cr7SC8Xt.cjs → search-dialog-CzwvKoLW.cjs} +1 -1
- package/dist/{search-dialog-DBNaG5At.js → search-dialog-DRNJsxOv.js} +1 -1
- package/dist/{search-dialog-e86WPpck.js → search-dialog-DaR66wl0.js} +1 -1
- package/dist/shiki-adapter-BB71nVsG.cjs +6 -0
- package/dist/shiki-adapter-CL-IQwKI.cjs +6 -0
- package/dist/shiki-adapter-CxTLkjew.mjs +6 -0
- package/dist/shiki-adapter-rQ087Lx3.mjs +6 -0
- package/dist/{sidebar-kQBonN82.d.ts → sidebar-BtJyWHy4.d.cts} +42 -5
- package/dist/{sidebar---RgTmZD.d.cts → sidebar-C4fuRYYS.d.ts} +42 -5
- package/dist/types-generator-BRyf4o9F.mjs +26 -0
- package/dist/types-generator-BqNKCbrx.cjs +26 -0
- package/dist/types-generator-Cu2jQP-V.mjs +6 -0
- package/dist/utils-BWy_8ASj.cjs +7 -0
- package/dist/utils-DexBrwiP.mjs +7 -0
- package/dist/worker-pool-C6r6V05j.mjs +6 -0
- package/dist/worker-pool-m81xuM5v.cjs +6 -0
- package/package.json +6 -3
- package/src/shared/types.ts +36 -3
- package/dist/cache-BMUyNiiA.mjs +0 -6
- package/dist/cache-CKm45d2w.cjs +0 -6
- package/dist/chunk-CU-zTemE.cjs +0 -6
- package/dist/doctor-Dnv77Lfx.mjs +0 -28
- package/dist/doctor-VBPlQf7d.cjs +0 -28
- package/dist/node-JM9bx0mO.cjs +0 -160
- package/dist/node-q-Y2mdLX.mjs +0 -160
- package/dist/parser-9LqyiEnB.mjs +0 -6
- package/dist/parser-B7-6PyQz.cjs +0 -6
- package/dist/routes-3sAccArX.cjs +0 -6
- package/dist/routes-DZpXqq1p.mjs +0 -6
- package/dist/routes-DjoxNeef.mjs +0 -6
- package/dist/routes-DpJnb2VS.cjs +0 -6
- package/dist/utils-BYITg7T5.mjs +0 -7
- package/dist/utils-Cjmx1hhk.cjs +0 -7
- package/dist/worker-pool-DXLyeHJg.cjs +0 -6
- package/dist/worker-pool-Kn4F9gDq.mjs +0 -6
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copyright (c) 2026 Jesus Alcala
|
|
4
4
|
* Licensed under the MIT License.
|
|
5
5
|
*/
|
|
6
|
-
import { A as ChevronRight, D as Check,
|
|
6
|
+
import { A as ChevronRight, D as Check, S as normalizePath, _ as Link$1, b as useLocalizedTo, k as ChevronLeft, rt as useConfig, tt as icons_exports, y as cn } from "./icons-dev-kJyGWNeg.js";
|
|
7
7
|
import { Children, createContext, use, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { useLocation } from "react-router-dom";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -242,6 +242,235 @@ Tabs.Item = TabsItem;
|
|
|
242
242
|
Tabs.Content = TabsContent;
|
|
243
243
|
Tabs.Indicator = TabsIndicator;
|
|
244
244
|
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/client/hooks/use-sidebar.ts
|
|
247
|
+
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
|
|
248
|
+
const getCleanDirectoryMeta = (directoryMeta) => {
|
|
249
|
+
const meta = {};
|
|
250
|
+
if (!directoryMeta) return meta;
|
|
251
|
+
for (const [key, value] of Object.entries(directoryMeta)) {
|
|
252
|
+
const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
|
|
253
|
+
meta[cleanKey === "" ? "." : cleanKey] = value;
|
|
254
|
+
}
|
|
255
|
+
return meta;
|
|
256
|
+
};
|
|
257
|
+
const getOrCreateNode = (parts, rootMap, directoryMeta) => {
|
|
258
|
+
let currentMap = rootMap;
|
|
259
|
+
let parentPath = "";
|
|
260
|
+
let lastNode;
|
|
261
|
+
for (const segment of parts) {
|
|
262
|
+
const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
|
|
263
|
+
if (!currentMap.has(segment)) {
|
|
264
|
+
const meta = directoryMeta[currentRelPath] || {};
|
|
265
|
+
const newNode = {
|
|
266
|
+
path: "#",
|
|
267
|
+
title: meta.title || capitalize(segment),
|
|
268
|
+
componentPath: "",
|
|
269
|
+
filePath: "",
|
|
270
|
+
icon: meta.icon,
|
|
271
|
+
groupPosition: typeof meta.order === "number" ? meta.order : 999,
|
|
272
|
+
collapsible: meta.collapsible,
|
|
273
|
+
collapsed: meta.collapsed,
|
|
274
|
+
hasCustomTitle: meta.title !== void 0,
|
|
275
|
+
subRoutes: [],
|
|
276
|
+
childrenMap: /* @__PURE__ */ new Map()
|
|
277
|
+
};
|
|
278
|
+
currentMap.set(segment, newNode);
|
|
279
|
+
}
|
|
280
|
+
lastNode = currentMap.get(segment);
|
|
281
|
+
currentMap = lastNode.childrenMap;
|
|
282
|
+
parentPath = currentRelPath;
|
|
283
|
+
}
|
|
284
|
+
return lastNode;
|
|
285
|
+
};
|
|
286
|
+
const getRoutePosition = (r) => r.sidebarPosition ?? r.order ?? 999;
|
|
287
|
+
const getNodePosition = (n) => n.sidebarPosition ?? n.groupPosition ?? 999;
|
|
288
|
+
const finalizeTree = (nodes) => {
|
|
289
|
+
return nodes.map((node) => {
|
|
290
|
+
if (node.childrenMap && node.childrenMap.size > 0) {
|
|
291
|
+
const childDirs = Array.from(node.childrenMap.values());
|
|
292
|
+
node.subRoutes = [...node.subRoutes || [], ...childDirs];
|
|
293
|
+
}
|
|
294
|
+
const { childrenMap, ...restNode } = node;
|
|
295
|
+
if (restNode.subRoutes && restNode.subRoutes.length > 0) restNode.subRoutes = finalizeTree(restNode.subRoutes);
|
|
296
|
+
return restNode;
|
|
297
|
+
}).sort((a, b) => {
|
|
298
|
+
const posA = getNodePosition(a);
|
|
299
|
+
const posB = getNodePosition(b);
|
|
300
|
+
return posA !== posB ? posA - posB : a.title.localeCompare(b.title);
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
function useSidebar(routes) {
|
|
304
|
+
const config = useConfig();
|
|
305
|
+
const { pathname } = useLocation();
|
|
306
|
+
return useMemo(() => {
|
|
307
|
+
const currentPath = normalizePath(pathname);
|
|
308
|
+
const activeRoute = routes.find((r) => normalizePath(r.path) === currentPath);
|
|
309
|
+
const activeTabId = activeRoute?.tab?.toLowerCase();
|
|
310
|
+
const filteredRoutes = routes.filter((r) => !r.collection && !r.sidebarHidden && !r.fallback).filter((r) => !activeTabId || r.tab?.toLowerCase() === activeTabId).sort((a, b) => getRoutePosition(a) - getRoutePosition(b));
|
|
311
|
+
const directoryMeta = getCleanDirectoryMeta(config.directoryMeta);
|
|
312
|
+
const rootNodesMap = /* @__PURE__ */ new Map();
|
|
313
|
+
const ungrouped = [];
|
|
314
|
+
for (const route of filteredRoutes) {
|
|
315
|
+
const parts = route.slugParts || [];
|
|
316
|
+
const isIndex = /^index\.mdx?$/.test(route.filePath.split("/").pop() || "");
|
|
317
|
+
if (parts.length === 0) {
|
|
318
|
+
if (route.filePath) ungrouped.push(route);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const containerNode = getOrCreateNode(parts, rootNodesMap, directoryMeta);
|
|
322
|
+
if (isIndex) Object.assign(containerNode, {
|
|
323
|
+
path: route.path,
|
|
324
|
+
title: containerNode.hasCustomTitle ? containerNode.title : route.title || containerNode.title,
|
|
325
|
+
icon: route.icon || containerNode.icon,
|
|
326
|
+
badge: route.badge,
|
|
327
|
+
sidebarPosition: route.sidebarPosition,
|
|
328
|
+
frontmatter: route.frontmatter,
|
|
329
|
+
filePath: route.filePath
|
|
330
|
+
});
|
|
331
|
+
else containerNode.subRoutes.push(route);
|
|
332
|
+
}
|
|
333
|
+
const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
|
|
334
|
+
const groups = [];
|
|
335
|
+
for (const node of finalizedTopNodes) if (node.subRoutes && node.subRoutes.length > 0) groups.push({
|
|
336
|
+
slug: node.title.toLowerCase().replace(/\s+/g, "-"),
|
|
337
|
+
title: node.title,
|
|
338
|
+
icon: node.icon,
|
|
339
|
+
path: node.path,
|
|
340
|
+
filePath: node.filePath,
|
|
341
|
+
routes: node.subRoutes,
|
|
342
|
+
sidebarPosition: node.sidebarPosition ?? node.groupPosition ?? 999,
|
|
343
|
+
collapsible: node.collapsible,
|
|
344
|
+
collapsed: node.collapsed
|
|
345
|
+
});
|
|
346
|
+
else ungrouped.push(node);
|
|
347
|
+
return {
|
|
348
|
+
groups,
|
|
349
|
+
ungrouped: finalizeTree(ungrouped),
|
|
350
|
+
activeRoute,
|
|
351
|
+
activePath: currentPath,
|
|
352
|
+
config
|
|
353
|
+
};
|
|
354
|
+
}, [
|
|
355
|
+
routes,
|
|
356
|
+
config,
|
|
357
|
+
pathname
|
|
358
|
+
]);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/client/hooks/use-location.ts
|
|
363
|
+
const useLocation$1 = () => {
|
|
364
|
+
return useLocation();
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/client/hooks/use-search-highlight.ts
|
|
369
|
+
/**
|
|
370
|
+
* Hook to highlight search terms based on the 'hl' query parameter.
|
|
371
|
+
*/
|
|
372
|
+
function useSearchHighlight(containerSelector = ".boltdocs-page") {
|
|
373
|
+
const { search } = useLocation$1();
|
|
374
|
+
const query = new URLSearchParams(search).get("hl");
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (!query) {
|
|
377
|
+
clearHighlights(containerSelector);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const container = document.querySelector(containerSelector);
|
|
381
|
+
if (!container) return;
|
|
382
|
+
let rafId;
|
|
383
|
+
const observer = new MutationObserver((mutations) => {
|
|
384
|
+
if (mutations.some((m) => {
|
|
385
|
+
const addedNodes = Array.from(m.addedNodes);
|
|
386
|
+
const removedNodes = Array.from(m.removedNodes);
|
|
387
|
+
return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
|
|
388
|
+
})) run();
|
|
389
|
+
});
|
|
390
|
+
function run() {
|
|
391
|
+
cancelAnimationFrame(rafId);
|
|
392
|
+
rafId = requestAnimationFrame(() => {
|
|
393
|
+
observer.disconnect();
|
|
394
|
+
clearHighlights(containerSelector);
|
|
395
|
+
const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
|
|
396
|
+
if (terms.length > 0) highlightTerms(container, terms);
|
|
397
|
+
observer.observe(container, {
|
|
398
|
+
childList: true,
|
|
399
|
+
subtree: true
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
run();
|
|
404
|
+
return () => {
|
|
405
|
+
cancelAnimationFrame(rafId);
|
|
406
|
+
observer.disconnect();
|
|
407
|
+
clearHighlights(containerSelector);
|
|
408
|
+
};
|
|
409
|
+
}, [
|
|
410
|
+
query,
|
|
411
|
+
search,
|
|
412
|
+
containerSelector
|
|
413
|
+
]);
|
|
414
|
+
}
|
|
415
|
+
function clearHighlights(selector) {
|
|
416
|
+
document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
|
|
417
|
+
try {
|
|
418
|
+
const parent = mark.parentNode;
|
|
419
|
+
if (parent && parent.contains(mark)) {
|
|
420
|
+
const text = mark.textContent || "";
|
|
421
|
+
parent.replaceChild(document.createTextNode(text), mark);
|
|
422
|
+
}
|
|
423
|
+
} catch (e) {}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
function highlightTerms(container, terms) {
|
|
427
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
|
|
428
|
+
const parent = node.parentElement;
|
|
429
|
+
if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
|
|
430
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
431
|
+
} });
|
|
432
|
+
const nodes = [];
|
|
433
|
+
let node;
|
|
434
|
+
while (node = walker.nextNode()) nodes.push(node);
|
|
435
|
+
const accentMap = {
|
|
436
|
+
a: "[aáàäâã]",
|
|
437
|
+
e: "[eéèëê]",
|
|
438
|
+
i: "[iíìïî]",
|
|
439
|
+
o: "[oóòöôõ]",
|
|
440
|
+
u: "[uúùüû]",
|
|
441
|
+
n: "[nñ]",
|
|
442
|
+
c: "[cç]"
|
|
443
|
+
};
|
|
444
|
+
const prepareRegex = (term) => {
|
|
445
|
+
let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
446
|
+
pattern = pattern.split("").map((char) => {
|
|
447
|
+
return accentMap[char.toLowerCase()] || char;
|
|
448
|
+
}).join("");
|
|
449
|
+
return pattern;
|
|
450
|
+
};
|
|
451
|
+
const combinedPattern = terms.map(prepareRegex).join("|");
|
|
452
|
+
const regex = new RegExp(`(${combinedPattern})`, "gi");
|
|
453
|
+
const matchRegexes = terms.map((term) => {
|
|
454
|
+
const p = prepareRegex(term);
|
|
455
|
+
return new RegExp(`^${p}$`, "i");
|
|
456
|
+
});
|
|
457
|
+
nodes.forEach((textNode) => {
|
|
458
|
+
const text = textNode.textContent;
|
|
459
|
+
if (text && regex.test(text)) {
|
|
460
|
+
const fragment = document.createDocumentFragment();
|
|
461
|
+
text.split(regex).forEach((part) => {
|
|
462
|
+
if (matchRegexes.some((rx) => rx.test(part))) {
|
|
463
|
+
const mark = document.createElement("mark");
|
|
464
|
+
mark.textContent = part;
|
|
465
|
+
mark.setAttribute("data-search-highlight", "true");
|
|
466
|
+
fragment.appendChild(mark);
|
|
467
|
+
} else if (part) fragment.appendChild(document.createTextNode(part));
|
|
468
|
+
});
|
|
469
|
+
if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
245
474
|
//#endregion
|
|
246
475
|
//#region src/client/components/primitives/helpers/observer.ts
|
|
247
476
|
function getItemId(url) {
|
|
@@ -617,116 +846,6 @@ PageNav.Title = PageNavTitle;
|
|
|
617
846
|
PageNav.Description = PageNavDescription;
|
|
618
847
|
PageNav.Icon = PageNavIcon;
|
|
619
848
|
|
|
620
|
-
//#endregion
|
|
621
|
-
//#region src/client/hooks/use-sidebar.ts
|
|
622
|
-
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
|
|
623
|
-
const getCleanDirectoryMeta = (directoryMeta) => {
|
|
624
|
-
const meta = {};
|
|
625
|
-
if (!directoryMeta) return meta;
|
|
626
|
-
for (const [key, value] of Object.entries(directoryMeta)) {
|
|
627
|
-
const cleanKey = key.split("/").filter((part) => !part.startsWith("(") || !part.endsWith(")")).map((part) => part.replace(/^\d+-/, "")).join("/");
|
|
628
|
-
meta[cleanKey === "" ? "." : cleanKey] = value;
|
|
629
|
-
}
|
|
630
|
-
return meta;
|
|
631
|
-
};
|
|
632
|
-
const getOrCreateNode = (parts, rootMap, directoryMeta) => {
|
|
633
|
-
let currentMap = rootMap;
|
|
634
|
-
let parentPath = "";
|
|
635
|
-
let lastNode;
|
|
636
|
-
for (const segment of parts) {
|
|
637
|
-
const currentRelPath = parentPath ? `${parentPath}/${segment}` : segment;
|
|
638
|
-
if (!currentMap.has(segment)) {
|
|
639
|
-
const meta = directoryMeta[currentRelPath] || {};
|
|
640
|
-
const newNode = {
|
|
641
|
-
path: "#",
|
|
642
|
-
title: meta.title || capitalize(segment),
|
|
643
|
-
componentPath: "",
|
|
644
|
-
filePath: "",
|
|
645
|
-
icon: meta.icon,
|
|
646
|
-
groupPosition: typeof meta.order === "number" ? meta.order : 999,
|
|
647
|
-
subRoutes: [],
|
|
648
|
-
childrenMap: /* @__PURE__ */ new Map()
|
|
649
|
-
};
|
|
650
|
-
currentMap.set(segment, newNode);
|
|
651
|
-
}
|
|
652
|
-
lastNode = currentMap.get(segment);
|
|
653
|
-
currentMap = lastNode.childrenMap;
|
|
654
|
-
parentPath = currentRelPath;
|
|
655
|
-
}
|
|
656
|
-
return lastNode;
|
|
657
|
-
};
|
|
658
|
-
const getRoutePosition = (r) => r.sidebarPosition ?? r.order ?? 999;
|
|
659
|
-
const getNodePosition = (n) => n.sidebarPosition ?? n.groupPosition ?? 999;
|
|
660
|
-
const finalizeTree = (nodes) => {
|
|
661
|
-
return nodes.map((node) => {
|
|
662
|
-
if (node.childrenMap && node.childrenMap.size > 0) {
|
|
663
|
-
const childDirs = Array.from(node.childrenMap.values());
|
|
664
|
-
node.subRoutes = [...node.subRoutes || [], ...childDirs];
|
|
665
|
-
}
|
|
666
|
-
const { childrenMap, ...restNode } = node;
|
|
667
|
-
if (restNode.subRoutes && restNode.subRoutes.length > 0) restNode.subRoutes = finalizeTree(restNode.subRoutes);
|
|
668
|
-
return restNode;
|
|
669
|
-
}).sort((a, b) => {
|
|
670
|
-
const posA = getNodePosition(a);
|
|
671
|
-
const posB = getNodePosition(b);
|
|
672
|
-
return posA !== posB ? posA - posB : a.title.localeCompare(b.title);
|
|
673
|
-
});
|
|
674
|
-
};
|
|
675
|
-
function useSidebar(routes) {
|
|
676
|
-
const config = useConfig();
|
|
677
|
-
const { pathname } = useLocation();
|
|
678
|
-
return useMemo(() => {
|
|
679
|
-
const currentPath = normalizePath(pathname);
|
|
680
|
-
const activeRoute = routes.find((r) => normalizePath(r.path) === currentPath);
|
|
681
|
-
const activeTabId = activeRoute?.tab?.toLowerCase();
|
|
682
|
-
const filteredRoutes = routes.filter((r) => !r.collection && !r.sidebarHidden && !r.fallback).filter((r) => !activeTabId || r.tab?.toLowerCase() === activeTabId).sort((a, b) => getRoutePosition(a) - getRoutePosition(b));
|
|
683
|
-
const directoryMeta = getCleanDirectoryMeta(config.directoryMeta);
|
|
684
|
-
const rootNodesMap = /* @__PURE__ */ new Map();
|
|
685
|
-
const ungrouped = [];
|
|
686
|
-
for (const route of filteredRoutes) {
|
|
687
|
-
const parts = route.slugParts || [];
|
|
688
|
-
const isIndex = /^index\.mdx?$/.test(route.filePath.split("/").pop() || "");
|
|
689
|
-
if (parts.length === 0) {
|
|
690
|
-
if (route.filePath) ungrouped.push(route);
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
const containerNode = getOrCreateNode(parts, rootNodesMap, directoryMeta);
|
|
694
|
-
if (isIndex) Object.assign(containerNode, {
|
|
695
|
-
path: route.path,
|
|
696
|
-
title: route.title || containerNode.title,
|
|
697
|
-
icon: route.icon || containerNode.icon,
|
|
698
|
-
badge: route.badge,
|
|
699
|
-
sidebarPosition: route.sidebarPosition,
|
|
700
|
-
frontmatter: route.frontmatter,
|
|
701
|
-
filePath: route.filePath
|
|
702
|
-
});
|
|
703
|
-
else containerNode.subRoutes.push(route);
|
|
704
|
-
}
|
|
705
|
-
const finalizedTopNodes = finalizeTree(Array.from(rootNodesMap.values()));
|
|
706
|
-
const groups = [];
|
|
707
|
-
for (const node of finalizedTopNodes) if (node.subRoutes && node.subRoutes.length > 0) groups.push({
|
|
708
|
-
slug: node.title.toLowerCase().replace(/\s+/g, "-"),
|
|
709
|
-
title: node.title,
|
|
710
|
-
icon: node.icon,
|
|
711
|
-
path: node.path,
|
|
712
|
-
filePath: node.filePath,
|
|
713
|
-
routes: node.subRoutes
|
|
714
|
-
});
|
|
715
|
-
else ungrouped.push(node);
|
|
716
|
-
return {
|
|
717
|
-
groups,
|
|
718
|
-
ungrouped: finalizeTree(ungrouped),
|
|
719
|
-
activeRoute,
|
|
720
|
-
activePath: currentPath,
|
|
721
|
-
config
|
|
722
|
-
};
|
|
723
|
-
}, [
|
|
724
|
-
routes,
|
|
725
|
-
config,
|
|
726
|
-
pathname
|
|
727
|
-
]);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
849
|
//#endregion
|
|
731
850
|
//#region src/client/components/primitives/sidebar.tsx
|
|
732
851
|
let sidebarScrollPos = 0;
|
|
@@ -820,13 +939,29 @@ function SidebarContent({ children, className }) {
|
|
|
820
939
|
/**
|
|
821
940
|
* Navigation Group
|
|
822
941
|
*/
|
|
823
|
-
function SidebarGroup({ title, icon: Icon, children, className }) {
|
|
942
|
+
function SidebarGroup({ title, icon: Icon, children, className, collapsible = false, collapsed = false, active = false }) {
|
|
943
|
+
const [isOpen, setIsOpen] = useState(() => active || !collapsed);
|
|
944
|
+
const [prevActive, setPrevActive] = useState(active);
|
|
945
|
+
if (active !== prevActive) {
|
|
946
|
+
setPrevActive(active);
|
|
947
|
+
if (active) setIsOpen(true);
|
|
948
|
+
}
|
|
824
949
|
return /* @__PURE__ */ jsxs("div", {
|
|
825
950
|
className,
|
|
826
|
-
children: [title && /* @__PURE__ */ jsxs("
|
|
951
|
+
children: [title && (collapsible ? /* @__PURE__ */ jsxs("button", {
|
|
952
|
+
onClick: () => setIsOpen(!isOpen),
|
|
953
|
+
className: "w-full text-left px-2 mb-2 flex items-center justify-between text-xs font-regular tracking-widest text-muted/50 hover:text-body transition-colors outline-none cursor-pointer group",
|
|
954
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
955
|
+
className: "flex items-center gap-2",
|
|
956
|
+
children: [Icon && /* @__PURE__ */ jsx(Icon, { size: 12 }), title]
|
|
957
|
+
}), /* @__PURE__ */ jsx(ChevronRight, {
|
|
958
|
+
size: 12,
|
|
959
|
+
className: cn("transition-transform duration-200 text-muted/40 group-hover:text-body", isOpen && "rotate-90")
|
|
960
|
+
})]
|
|
961
|
+
}) : /* @__PURE__ */ jsxs("h4", {
|
|
827
962
|
className: "px-2 mb-2 flex items-center gap-2 text-[11px] font-bold uppercase tracking-widest text-muted/50",
|
|
828
963
|
children: [Icon && /* @__PURE__ */ jsx(Icon, { size: 12 }), title]
|
|
829
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
964
|
+
})), (!collapsible || isOpen) && /* @__PURE__ */ jsx("div", {
|
|
830
965
|
className: "flex flex-col gap-0.5",
|
|
831
966
|
children
|
|
832
967
|
})]
|
|
@@ -917,23 +1052,65 @@ function SidebarItem({ route, activePath, activeRoute, className }) {
|
|
|
917
1052
|
className
|
|
918
1053
|
});
|
|
919
1054
|
}
|
|
1055
|
+
const isRouteActive = (route, activePath, activeRoute) => {
|
|
1056
|
+
const normalizedPath = route.path.endsWith("/") ? route.path.slice(0, -1) : route.path;
|
|
1057
|
+
if ((activePath.endsWith("/") ? activePath.slice(0, -1) : activePath) === normalizedPath) return true;
|
|
1058
|
+
if (activeRoute?.filePath && route.filePath && activeRoute.filePath === route.filePath) return true;
|
|
1059
|
+
if (route.routes && route.routes.some((r) => isRouteActive(r, activePath, activeRoute))) return true;
|
|
1060
|
+
if (route.subRoutes && route.subRoutes.some((r) => isRouteActive(r, activePath, activeRoute))) return true;
|
|
1061
|
+
return false;
|
|
1062
|
+
};
|
|
920
1063
|
function SidebarItems({ routes, className }) {
|
|
921
1064
|
const { groups, ungrouped, activePath, activeRoute } = useSidebar(routes);
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1065
|
+
const mergedItems = [...ungrouped.map((route) => ({
|
|
1066
|
+
type: "link",
|
|
1067
|
+
position: route.sidebarPosition ?? 999,
|
|
1068
|
+
title: route.title,
|
|
1069
|
+
route
|
|
1070
|
+
})), ...groups.map((group) => ({
|
|
1071
|
+
type: "group",
|
|
1072
|
+
position: group.sidebarPosition ?? 999,
|
|
1073
|
+
title: group.title,
|
|
1074
|
+
group
|
|
1075
|
+
}))].sort((a, b) => {
|
|
1076
|
+
if (a.position !== b.position) return a.position - b.position;
|
|
1077
|
+
if (a.type !== b.type) return a.type === "link" ? -1 : 1;
|
|
1078
|
+
return a.title.localeCompare(b.title);
|
|
1079
|
+
});
|
|
1080
|
+
const renderedElements = [];
|
|
1081
|
+
let currentUngrouped = [];
|
|
1082
|
+
const pushUngrouped = () => {
|
|
1083
|
+
if (currentUngrouped.length > 0) {
|
|
1084
|
+
const routesToRender = [...currentUngrouped];
|
|
1085
|
+
renderedElements.push(/* @__PURE__ */ jsx(SidebarGroup, { children: routesToRender.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
|
|
1086
|
+
route,
|
|
1087
|
+
activePath,
|
|
1088
|
+
activeRoute
|
|
1089
|
+
}, route.path)) }, `ungrouped-${routesToRender[0].path}`));
|
|
1090
|
+
currentUngrouped = [];
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
for (const item of mergedItems) if (item.type === "link") currentUngrouped.push(item.route);
|
|
1094
|
+
else {
|
|
1095
|
+
pushUngrouped();
|
|
1096
|
+
const isGroupActive = item.group.routes.some((route) => isRouteActive(route, activePath, activeRoute));
|
|
1097
|
+
renderedElements.push(/* @__PURE__ */ jsx(SidebarGroup, {
|
|
1098
|
+
title: item.group.title,
|
|
1099
|
+
icon: getIcon(item.group.icon),
|
|
1100
|
+
collapsible: item.group.collapsible,
|
|
1101
|
+
collapsed: item.group.collapsed,
|
|
1102
|
+
active: isGroupActive,
|
|
1103
|
+
children: item.group.routes.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
|
|
932
1104
|
route,
|
|
933
1105
|
activePath,
|
|
934
1106
|
activeRoute
|
|
935
1107
|
}, route.path))
|
|
936
|
-
}, group.title))
|
|
1108
|
+
}, item.group.title));
|
|
1109
|
+
}
|
|
1110
|
+
pushUngrouped();
|
|
1111
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1112
|
+
className: cn("flex flex-col gap-6", className),
|
|
1113
|
+
children: renderedElements
|
|
937
1114
|
});
|
|
938
1115
|
}
|
|
939
1116
|
/**
|
|
@@ -951,119 +1128,6 @@ const Sidebar = Object.assign(SidebarRoot, {
|
|
|
951
1128
|
Items: SidebarItems
|
|
952
1129
|
});
|
|
953
1130
|
|
|
954
|
-
//#endregion
|
|
955
|
-
//#region src/client/hooks/use-location.ts
|
|
956
|
-
const useLocation$1 = () => {
|
|
957
|
-
return useLocation();
|
|
958
|
-
};
|
|
959
|
-
|
|
960
|
-
//#endregion
|
|
961
|
-
//#region src/client/hooks/use-search-highlight.ts
|
|
962
|
-
/**
|
|
963
|
-
* Hook to highlight search terms based on the 'hl' query parameter.
|
|
964
|
-
*/
|
|
965
|
-
function useSearchHighlight(containerSelector = ".boltdocs-page") {
|
|
966
|
-
const { search } = useLocation$1();
|
|
967
|
-
const query = new URLSearchParams(search).get("hl");
|
|
968
|
-
useEffect(() => {
|
|
969
|
-
if (!query) {
|
|
970
|
-
clearHighlights(containerSelector);
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
const container = document.querySelector(containerSelector);
|
|
974
|
-
if (!container) return;
|
|
975
|
-
let rafId;
|
|
976
|
-
const observer = new MutationObserver((mutations) => {
|
|
977
|
-
if (mutations.some((m) => {
|
|
978
|
-
const addedNodes = Array.from(m.addedNodes);
|
|
979
|
-
const removedNodes = Array.from(m.removedNodes);
|
|
980
|
-
return addedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight"))) || removedNodes.some((n) => !(n instanceof HTMLElement && n.hasAttribute("data-search-highlight")));
|
|
981
|
-
})) run();
|
|
982
|
-
});
|
|
983
|
-
function run() {
|
|
984
|
-
cancelAnimationFrame(rafId);
|
|
985
|
-
rafId = requestAnimationFrame(() => {
|
|
986
|
-
observer.disconnect();
|
|
987
|
-
clearHighlights(containerSelector);
|
|
988
|
-
const terms = query.split(/\s+/).map((t) => t.trim()).filter((t) => t.length >= 2);
|
|
989
|
-
if (terms.length > 0) highlightTerms(container, terms);
|
|
990
|
-
observer.observe(container, {
|
|
991
|
-
childList: true,
|
|
992
|
-
subtree: true
|
|
993
|
-
});
|
|
994
|
-
});
|
|
995
|
-
}
|
|
996
|
-
run();
|
|
997
|
-
return () => {
|
|
998
|
-
cancelAnimationFrame(rafId);
|
|
999
|
-
observer.disconnect();
|
|
1000
|
-
clearHighlights(containerSelector);
|
|
1001
|
-
};
|
|
1002
|
-
}, [
|
|
1003
|
-
query,
|
|
1004
|
-
search,
|
|
1005
|
-
containerSelector
|
|
1006
|
-
]);
|
|
1007
|
-
}
|
|
1008
|
-
function clearHighlights(selector) {
|
|
1009
|
-
document.querySelectorAll(`${selector} mark[data-search-highlight]`).forEach((mark) => {
|
|
1010
|
-
try {
|
|
1011
|
-
const parent = mark.parentNode;
|
|
1012
|
-
if (parent && parent.contains(mark)) {
|
|
1013
|
-
const text = mark.textContent || "";
|
|
1014
|
-
parent.replaceChild(document.createTextNode(text), mark);
|
|
1015
|
-
}
|
|
1016
|
-
} catch (e) {}
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
function highlightTerms(container, terms) {
|
|
1020
|
-
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => {
|
|
1021
|
-
const parent = node.parentElement;
|
|
1022
|
-
if (parent && (parent.tagName === "SCRIPT" || parent.tagName === "STYLE" || parent.tagName === "MARK" || parent.closest("pre") || parent.closest("code"))) return NodeFilter.FILTER_REJECT;
|
|
1023
|
-
return NodeFilter.FILTER_ACCEPT;
|
|
1024
|
-
} });
|
|
1025
|
-
const nodes = [];
|
|
1026
|
-
let node;
|
|
1027
|
-
while (node = walker.nextNode()) nodes.push(node);
|
|
1028
|
-
const accentMap = {
|
|
1029
|
-
a: "[aáàäâã]",
|
|
1030
|
-
e: "[eéèëê]",
|
|
1031
|
-
i: "[iíìïî]",
|
|
1032
|
-
o: "[oóòöôõ]",
|
|
1033
|
-
u: "[uúùüû]",
|
|
1034
|
-
n: "[nñ]",
|
|
1035
|
-
c: "[cç]"
|
|
1036
|
-
};
|
|
1037
|
-
const prepareRegex = (term) => {
|
|
1038
|
-
let pattern = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1039
|
-
pattern = pattern.split("").map((char) => {
|
|
1040
|
-
return accentMap[char.toLowerCase()] || char;
|
|
1041
|
-
}).join("");
|
|
1042
|
-
return pattern;
|
|
1043
|
-
};
|
|
1044
|
-
const combinedPattern = terms.map(prepareRegex).join("|");
|
|
1045
|
-
const regex = new RegExp(`(${combinedPattern})`, "gi");
|
|
1046
|
-
const matchRegexes = terms.map((term) => {
|
|
1047
|
-
const p = prepareRegex(term);
|
|
1048
|
-
return new RegExp(`^${p}$`, "i");
|
|
1049
|
-
});
|
|
1050
|
-
nodes.forEach((textNode) => {
|
|
1051
|
-
const text = textNode.textContent;
|
|
1052
|
-
if (text && regex.test(text)) {
|
|
1053
|
-
const fragment = document.createDocumentFragment();
|
|
1054
|
-
text.split(regex).forEach((part) => {
|
|
1055
|
-
if (matchRegexes.some((rx) => rx.test(part))) {
|
|
1056
|
-
const mark = document.createElement("mark");
|
|
1057
|
-
mark.textContent = part;
|
|
1058
|
-
mark.setAttribute("data-search-highlight", "true");
|
|
1059
|
-
fragment.appendChild(mark);
|
|
1060
|
-
} else if (part) fragment.appendChild(document.createTextNode(part));
|
|
1061
|
-
});
|
|
1062
|
-
if (textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode);
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
1131
|
//#endregion
|
|
1068
1132
|
//#region src/client/components/ui-base/search-highlight.tsx
|
|
1069
1133
|
/**
|
|
@@ -1154,4 +1218,4 @@ const DocsLayout = Object.assign(DocsLayoutRoot, {
|
|
|
1154
1218
|
});
|
|
1155
1219
|
|
|
1156
1220
|
//#endregion
|
|
1157
|
-
export { useUI as A,
|
|
1221
|
+
export { useUI as A, useSidebar as C, ButtonGroup as D, Popover$1 as E, Breadcrumbs$1 as O, useLocation$1 as S, Menu as T, ScrollProvider as _, SidebarHeader as a, useItems as b, SidebarLink as c, SidebarSubGroup as d, PageNav as f, OnThisPageTree as g, OnThisPageItems as h, SidebarGroup as i, UIProvider as k, SidebarMobile as l, OnThisPage as m, Sidebar as n, SidebarItem as o, AnchorProvider as p, SidebarContent as r, SidebarItems as s, DocsLayout as t, SidebarRoot as u, useActiveAnchor as v, Tabs as w, useSearchHighlight as x, useActiveAnchors as y };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boltdocs - https://boltdocs.vercel.app
|
|
3
|
+
* Copyright (c) 2026 Jesus Alcala
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
import{P as e}from"./node-B7qbjtLl.mjs";import{a as t,d as n,g as r,u as i}from"./utils-DexBrwiP.mjs";import{n as a}from"./config-DBKBEj3j.mjs";import{t as o}from"./node/cli-entry.mjs";import s from"node:path";import c from"node:fs";import{bullet as l,colors as u,confirm as d,dividerLog as f,double as p,error as m,info as h,round as g,single as _,success as v,tasks as y,warn as b}from"@bdocs/dui";import{fdir as x}from"fdir";import S from"picomatch";import{distance as C}from"fastest-levenshtein";const w={$schema:`https://boltdocs.vercel.app/schemas/doctor-config.schema.json`,checks:{metadata:{enabled:!0,titleMin:10,titleMax:60,descriptionMin:50,required:[`title`,`description`],optional:[],validateDates:!1},links:{internal:!0,external:!1,timeout:1e4,concurrency:10,ignore:[]},i18n:{enabled:!0},performance:{enabled:!0,budgets:{maxJSBundleSize:`200kb`,maxCSSBundleSize:`30kb`,maxPageHTMLSize:`80kb`,maxImagesKB:500,maxBuildTime:3e4,maxFontCount:3}}},fix:{confirmChanges:!1,backupFiles:!1,backupPath:`.boltdocs/backups`},reporting:{format:`pretty`,outputFile:`.boltdocs/reports/doctor.json`,failOnError:!1,maxWarnings:-1},severity:{missingTranslation:`warning`,brokenLink:`high`,brokenAnchor:`warning`,largeFile:`warning`,orphanedPage:`low`,duplicateTitle:`low`,shortMetadata:`low`,missingMetadata:`warning`,malformedFrontmatter:`high`,invalidFrontmatter:`high`,budgetExceeded:`warning`},exclude:[]};function T(e,t){if(e==null)return t;if(typeof e==`number`)return e;let n=e.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);if(!n)return t;let r=Number.parseFloat(n[1]),i=n[2]||`b`;return Math.round(r*({b:1,kb:1024,mb:1024*1024,gb:1024*1024*1024}[i]||1))}function E(e,t,n){return e.doctorConfig.severity[t]||n}async function D(e,t){c.existsSync(t)||c.mkdirSync(t,{recursive:!0});let n=s.basename(e),r=new Date().toISOString().replace(/[:.]/g,`-`),i=s.join(t,`${n}.${r}.bak`);c.copyFileSync(e,i)}const O=new Map;function k(e){let t=O.get(e);if(t)return t;let r=(async()=>{let t=await n(e,!1);return{raw:t.raw,data:t.data,content:t.content}})();return O.set(e,r),r}const A=new Map;function j(e){if(A.has(e))return A.get(e);let t=!1;try{t=c.existsSync(e)&&c.statSync(e).isFile()}catch{t=!1}return A.set(e,t),t}async function M(e){let t=s.resolve(e,`doctor.json`);if(c.existsSync(t))try{let e=JSON.parse(c.readFileSync(t,`utf-8`));return{...w,...e,checks:{...w.checks,...e.checks,metadata:{...w.checks.metadata,...e.checks?.metadata},links:{...w.checks.links,...e.checks?.links},i18n:{...w.checks.i18n,...e.checks?.i18n},performance:{...w.checks.performance,...e.checks?.performance,budgets:{...w.checks.performance?.budgets,...e.checks?.performance?.budgets}}},fix:{...w.fix,...e.fix},reporting:{...w.reporting,...e.reporting},severity:{...w.severity,...e.severity},exclude:[...w.exclude,...e.exclude||[]]}}catch(e){b(`Failed to parse doctor.json: ${e}`)}return w}async function N(e,n=process.cwd(),r,i){let a=s.resolve(n,`.boltdocs`,`generated`);c.existsSync(a)||c.mkdirSync(a,{recursive:!0});let o=i;o||=await new x().withFullPaths().filter(e=>e.endsWith(`.md`)||e.endsWith(`.mdx`)).crawl(e).withPromise();let l=r?.base||`/docs`,u=[];for(let n=0;n<o.length;n+=100){let r=o.slice(n,n+100),i=await Promise.all(r.map(async n=>{let r=s.isAbsolute(n)?n:s.resolve(e,n),i=s.relative(e,r),{data:a}=await k(r),o;return o=a.permalink?a.permalink.startsWith(`/`)?a.permalink:`/${a.permalink}`:t(i),l===`/`?o:(l.endsWith(`/`)?l:l+`/`)+(o.startsWith(`/`)?o.substring(1):o)}));u.push(...i)}u.includes(l)||u.push(l);let d={routes:Array.from(new Set(u)).sort(),timestamp:Date.now()};return c.writeFileSync(s.resolve(a,`link-tree.json`),JSON.stringify(d,null,2)),d}function P(e,t){if(e===t)return 1;if(!e||!t)return 0;let n=e.split(`/`).filter(Boolean),r=t.split(`/`).filter(Boolean),i=n[n.length-1]||``,a=r[r.length-1]||``;if(i===a){let e=0;for(let t of n)r.includes(t)&&e++;return e>=2?.99:.9}if(i.length>3&&a.length>3&&(a.startsWith(i)||i.startsWith(a))&&Math.min(i.length,a.length)/Math.max(i.length,a.length)>.5)return .88;let o=1-C(i,a)/Math.max(i.length,a.length);return o>.8?o*.95:1-C(e,t)/Math.max(e.length,t.length)}const F=new Map;function I(e,t){if(F.has(e))return F.get(e);let n=e.length,r=``,i=0;for(let a of t){if(a===e)continue;let t=a.length,o=Math.max(n,t);if(o===0)continue;let s=1-Math.abs(n-t)/o;if(s<=.8&&s<=i)continue;let c=P(e,a);c>i&&(i=c,r=a)}let a={bestMatch:r,similarity:i};return F.set(e,a),a}async function L(e){let t=[];if(!e.doctorConfig.checks.metadata.enabled)return t;let{titleMin:n,titleMax:a,descriptionMin:o}=e.doctorConfig.checks.metadata,c=new Map;for(let l of e.files){let u=i(s.relative(e.docsDir,l));try{let{raw:i,data:s}=await k(l);if(i.trim().startsWith(`---`)&&i.split(`---`).length>=3&&Object.keys(s).length===0){let n=E(e,`malformedFrontmatter`,`high`);n!==`off`&&t.push({file:u,level:n,message:`Malformed frontmatter (YAML parsing failed).`,suggestion:`Check your YAML syntax for indentation or unquoted special characters.`})}let d=r.safeParse(s);if(!d.success){let n=E(e,`invalidFrontmatter`,`high`);if(n!==`off`)for(let e of d.error.issues)t.push({file:u,level:n,message:`Invalid frontmatter field "${e.path.join(`.`)}": ${e.message}`,suggestion:`Ensure the field follows the correct type.`})}let f=Array.from(new Set([`title`,...e.doctorConfig.checks.metadata.required]));for(let n of f)if(s[n]===void 0){let r=E(e,`missingMetadata`,`warning`);r!==`off`&&t.push({file:u,level:r,message:`Missing required frontmatter field: "${n}".`,suggestion:`Add the "${n}" field to your frontmatter.`})}if(e.doctorConfig.checks.metadata.validateDates){let n=[`date`,`lastUpdated`,...e.doctorConfig.checks.metadata.optional.filter(e=>e.toLowerCase().includes(`date`))];for(let r of n)if(s[r]&&Number.isNaN(Date.parse(String(s[r])))){let n=E(e,`invalidFrontmatter`,`high`);n!==`off`&&t.push({file:u,level:n,message:`Invalid date format in field "${r}": "${s[r]}".`,suggestion:`Use a valid ISO date format (e.g., YYYY-MM-DD).`})}}if(s.title){let r=String(s.title);if(r.length<n){let i=E(e,`shortMetadata`,`low`);i!==`off`&&t.push({file:u,level:i,message:`Title is too short (${r.length} chars).`,suggestion:`Titles should be at least ${n} characters for better SEO.`})}else if(r.length>a){let n=E(e,`shortMetadata`,`low`);n!==`off`&&t.push({file:u,level:n,message:`Title is too long (${r.length} chars).`,suggestion:`Titles should be under ${a} characters.`})}let i=c.get(r)||[];i.push(u),c.set(r,i)}if(s.description&&String(s.description).length<o){let n=E(e,`shortMetadata`,`low`);n!==`off`&&t.push({file:u,level:n,message:`Description is very short.`,suggestion:`Descriptions should ideally be at least ${o} characters.`})}}catch(n){if(n instanceof Error){let r=E(e,`malformedFrontmatter`,`high`);r!==`off`&&t.push({file:u,level:r,message:`Malformed frontmatter (YAML error): ${n.message}`,suggestion:`Check your YAML syntax for indentation or unquoted special characters.`})}}}for(let[n,r]of c.entries())if(r.length>1){let i=E(e,`duplicateTitle`,`low`);if(i!==`off`)for(let e of r)t.push({file:e,level:i,message:`Duplicate title found: "${n}"`,suggestion:`Ensure each page has a unique title. Also used in: ${r.filter(t=>t!==e).join(`, `)}`})}return t}async function R(e){let t=[],{internal:n,external:r,ignore:a,timeout:o,concurrency:l}=e.doctorConfig.checks.links;if(!n&&!r&&!e.options.checkExternal)return t;let d=/(?:\[.*?\]\((.*?)\))|(?:href=["']([^"']+)["'])/g,f=new Set,p=5e5;for(let o of e.files){let l=i(s.relative(e.docsDir,o)),{content:u}=await k(o),m=[...(u.length>p?u.slice(0,p):u).replace(/```[\s\S]*?```/g,``).replace(/`[^`\n]*`/g,``).matchAll(d)];for(let i of m){let u=i[1]||i[2],d=!!i[1];if(!u||a.some(e=>u.includes(e)))continue;if(/^https?:\/\//i.test(u)){(r||e.options.checkExternal)&&f.add({url:u,file:o});continue}if(!n||/^(mailto|tel|#)/i.test(u))continue;let p;try{p=decodeURIComponent(u.split(`#`)[0].split(`?`)[0])}catch{p=u.split(`#`)[0].split(`?`)[0]}if(!p)continue;let m=!1,h=``;if(p.startsWith(`/`))if(e.routeIndex.has(p)||e.routeIndexWithSlash.has(p)||e.routeIndexWithoutSlash.has(p))m=!0;else{let t=e.basePrefix+(p.startsWith(`/`)?p:`/`+p);if(e.routeIndex.has(t)||e.routeIndexWithSlash.has(t))m=!1,h=t;else{let t=e.config.base!==`/`&&p.startsWith(e.config.base||`/`)?p.substring((e.config.base||`/`).length):p,n=t.startsWith(`/`)?t.substring(1):t;h=s.join(e.docsDir,n),m=[``,`.md`,`.mdx`,`/index.md`,`/index.mdx`].some(e=>j(h+e))}}else h=s.resolve(s.dirname(o),p),m=[``,`.md`,`.mdx`,`/index.md`,`/index.mdx`].some(e=>j(h+e));if(!m){let{bestMatch:n,similarity:r}=I(p,e.linkTree.routes),i=!1,a=e.basePrefix+(p.startsWith(`/`)?p:`/`+p);(e.routeIndex.has(a)||e.routeIndexWithSlash.has(a))&&(n=a,r=1,i=!0);let s=r>.6||i,f=r>.75&&n!==p||i,m=E(e,`brokenLink`,`high`);m!==`off`&&t.push({file:l,level:m,message:`Broken internal link: "${u}"`,suggestion:s?`Did you mean "${n}"?`:`Ensure the target exists or check for typos.`,fix:f?async()=>{let e=u.includes(`#`)?`#`+u.split(`#`)[1]:``,t=d?`(${u})`:`href="${u}"`,r=d?`(${n}${e})`:`href="${n}${e}"`,i=c.readFileSync(o,`utf-8`).replace(t,r);c.writeFileSync(o,i),O.delete(o)}:void 0})}}}if(f.size>0){h(u.gray(`Verifying ${f.size} external links...`));let n=new Map;for(let e of f)n.has(e.url)||n.set(e.url,[]),n.get(e.url).push(e.file);let r=async e=>{try{let t=new AbortController,n=setTimeout(()=>t.abort(),o),r=await fetch(e,{method:`HEAD`,signal:t.signal,headers:{"User-Agent":`boltdocs-doctor/1.0`}});return clearTimeout(n),!r.ok&&r.status!==404?{url:e,ok:(await fetch(e,{method:`GET`,signal:t.signal,headers:{"User-Agent":`boltdocs-doctor/1.0`}})).ok}:{url:e,ok:r.ok}}catch(t){return{url:e,ok:!1,error:t.message}}},a=Array.from(n.keys()),c=[];for(let e=0;e<a.length;e+=l){let t=a.slice(e,e+l),n=await Promise.allSettled(t.map(r));c.push(...n.map(e=>e.status===`fulfilled`?e.value:{url:`unknown`,ok:!1,error:e.reason}))}for(let r of c)if(!r.ok){let a=E(e,`brokenLink`,`warning`);if(a!==`off`){let o=n.get(r.url)||[];for(let n of o)t.push({file:i(s.relative(e.docsDir,n)),level:a,message:`Broken external link: "${r.url}"`,suggestion:`Verify the URL or update it if it's permanently down. Error: ${r.error||`Status >= 400`}`})}}}return t}async function z(e){let t=[];if(!e.doctorConfig.checks.i18n.enabled||!e.config.i18n)return t;let{defaultLocale:n,locales:r}=e.config.i18n,a=Object.keys(r),o=a.filter(e=>e!==n);for(let r of e.files){let l=i(s.relative(e.docsDir,r)),u=l.split(`/`),d=u[0];if(d===n){let n=u.slice(1).join(`/`);for(let i of o){let a=s.join(e.docsDir,i,n);if(!j(a)){let o=E(e,`missingTranslation`,`warning`);o!==`off`&&t.push({file:l,level:o,message:`Missing translation for locale "${i}"`,suggestion:`Create a version at "${i}/${n}".`,fix:async()=>{let e=s.dirname(a);c.existsSync(e)||c.mkdirSync(e,{recursive:!0}),c.copyFileSync(r,a)}})}}}else if(a.includes(d)){let i=u.slice(1).join(`/`);if(!j(s.join(e.docsDir,n,i))){let a=E(e,`missingTranslation`,`low`);a!==`off`&&t.push({file:l,level:a,message:`Orphaned translation (source missing in "${n}")`,suggestion:`Remove this file or create the source at "${n}/${i}".`,fix:async()=>{c.unlinkSync(r)}})}}}return t}async function B(e){let t=[];if(!e.config.theme.sidebar)return t;let n=new Set,r=e.config.theme.sidebar;for(let[i,a]of Object.entries(r))for(let r of a){if(!r.text){let n=E(e,`invalidFrontmatter`,`warning`);n!==`off`&&t.push({file:`boltdocs.config.ts`,level:n,message:`Sidebar item in group "${i}" is missing a label.`,suggestion:`Add a "text" property to the sidebar item.`})}if(r.link&&(n.add(r.link),!e.routeIndex.has(r.link))){let{bestMatch:n,similarity:i}=I(r.link,e.linkTree.routes),a=i>.6,o=E(e,`brokenLink`,`high`);o!==`off`&&t.push({file:`boltdocs.config.ts`,level:o,message:`Broken sidebar link: "${r.link}"`,suggestion:a?`Did you mean "${n}"?`:`Ensure the route exists and is correctly formatted.`})}}for(let r of e.linkTree.routes)if(!(r===`/`||r===``)&&!n.has(r)){let n=E(e,`orphanedPage`,`low`);n!==`off`&&t.push({file:`Sidebar`,level:n,message:`Orphaned page found: "${r}" is not linked in the sidebar.`,suggestion:`Consider adding it to the sidebar for better discoverability.`})}return t}async function V(e){let t=[],n=e.doctorConfig.checks.performance;if(!n?.enabled)return t;let r=s.resolve(e.root,`.boltdocs`,`reports`,`performance.json`);if(!c.existsSync(r))return t.push({file:`(build)`,level:E(e,`budgetExceeded`,`warning`),message:"Performance metrics not found. Run `boltdocs build` first."}),t;let i;try{i=JSON.parse(c.readFileSync(r,`utf-8`))}catch{return t.push({file:`(build)`,level:E(e,`budgetExceeded`,`warning`),message:`Failed to parse performance metrics file.`}),t}let a=n.budgets,o=E(e,`budgetExceeded`,`warning`),l=T(a?.maxJSBundleSize,1/0),u=T(a?.maxCSSBundleSize,1/0),d=T(a?.maxPageHTMLSize,1/0),f=a?.maxImagesKB??1/0,p=a?.maxBuildTime??1/0,m=a?.maxFontCount??1/0;if(l!==1/0&&i.totalJSBundleSize>l){let e=(i.totalJSBundleSize/1024).toFixed(0),n=(l/1024).toFixed(0);t.push({file:`(build)`,level:o,message:`JS bundle size exceeds budget: ${e}kb > ${n}kb`,suggestion:`Code-split large dependencies or lazy-load route components.`})}if(u!==1/0&&i.totalCSSBundleSize>u){let e=(i.totalCSSBundleSize/1024).toFixed(0),n=(u/1024).toFixed(0);t.push({file:`(build)`,level:o,message:`CSS bundle size exceeds budget: ${e}kb > ${n}kb`,suggestion:`Remove unused styles or split CSS by route.`})}if(d!==1/0){for(let e of i.pages)if(e.htmlSize>d){let n=(e.htmlSize/1024).toFixed(0),r=(d/1024).toFixed(0);t.push({file:e.route,level:o,message:`Page HTML size exceeds budget: ${n}kb > ${r}kb`,suggestion:`Reduce the amount of inline content or split into sub-pages.`})}}let h=f*1024;if(f!==1/0&&i.totalImagesSize>h){let e=(i.totalImagesSize/1024).toFixed(0);t.push({file:`(build)`,level:o,message:`Image assets exceed budget: ${e}kb > ${f}kb`,suggestion:`Optimize images with lossy compression or use next-gen formats (webp/avif).`})}if(p!==1/0&&i.buildTime>p){let e=(i.buildTime/1e3).toFixed(1),n=(p/1e3).toFixed(1);t.push({file:`(build)`,level:o,message:`Build time exceeds budget: ${e}s > ${n}s`,suggestion:"Check for large unoptimized assets or increase `concurrency` in SSG options."})}return m!==1/0&&i.fontCount>m&&t.push({file:`(build)`,level:o,message:`Font files exceed budget: ${i.fontCount} > ${m}`,suggestion:`Reduce the number of font families or use variable fonts.`}),t}var H=e({DEFAULT_DOCTOR_CONFIG:()=>w,checkI18n:()=>z,checkLinks:()=>R,checkMetadata:()=>L,checkPerformance:()=>V,checkSidebar:()=>B,doctorAction:()=>W,doctorInit:()=>U,generateLinkTree:()=>N,loadDoctorConfig:()=>M});async function U(e){let t=s.resolve(e,`doctor.json`);if(c.existsSync(t)){b(`"doctor.json" already exists at ${e}.`);return}try{c.writeFileSync(t,JSON.stringify(w,null,2)),v(`Created "doctor.json" with default configuration.`)}catch(e){m(`Failed to create "doctor.json": ${e}`)}}async function W(e=process.cwd(),t={}){if(t.init){await U(e);return}o();try{let n=await M(e),{format:r}=n.reporting,i=performance.now(),o=await a(`docs`,e),b=s.resolve(e,`docs`);c.existsSync(b)||(r===`pretty`&&m(`Docs dir not found at ${b}`),process.exit(1)),r===`pretty`&&console.log(p([` ${u.dim(`Docs dir:`)} ${b}`,` ${u.dim(`Reports:`)} ${e}/.boltdocs/reports/`],{title:`✦ DOCTOR — Documentation Health Check`})),r===`pretty`&&h(u.dim(`🔍 Discovering files and routes...`));let C=S(n.exclude||[]),w=await new x().withFullPaths().filter(e=>{if(!(e.endsWith(`.md`)||e.endsWith(`.mdx`)))return!1;let t=s.relative(b,e).replace(/\\/g,`/`),n=t.split(`/`).some(e=>e.startsWith(`_`)&&e!==`_index.md`&&e!==`_index.mdx`);return!C(t)&&!n}).crawl(b).withPromise();for(let e of w)A.set(e,!0);let T=await N(b,e,o,w),E=o.base||`/`,O=E===`/`?``:E.endsWith(`/`)?E.slice(0,-1):E,k={root:e,docsDir:b,config:o,doctorConfig:n,linkTree:T,files:w,options:t,routeIndex:new Set(T.routes),routeIndexWithSlash:new Set(T.routes.map(e=>e.endsWith(`/`)?e:e+`/`)),routeIndexWithoutSlash:new Set(T.routes.map(e=>e.endsWith(`/`)?e.slice(0,-1):e)),basePrefix:O};r===`pretty`&&h(u.dim(`🧪 Running diagnostic checks in parallel...`));let j=[L(k),R(k),z(k),B(k)];t.budget&&j.push(V(k));let[P,F,I,H,...U]=await Promise.all(j),W=t.budget?U[0]:[],G=[...P,...F,...I,...H,...W];if(r===`pretty`){let e=[{label:`Metadata checks ${P.length>0?`— ${P.length} issue${P.length===1?``:`s`}`:`— OK`}`,done:P.length===0},{label:`Link checks ${F.length>0?`— ${F.length} issue${F.length===1?``:`s`}`:`— OK`}`,done:F.length===0},{label:`i18n checks ${I.length>0?`— ${I.length} issue${I.length===1?``:`s`}`:`— OK`}`,done:I.length===0},{label:`Sidebar checks ${H.length>0?`— ${H.length} issue${H.length===1?``:`s`}`:`— OK`}`,done:H.length===0}];t.budget&&e.push({label:`Performance budget ${W.length>0?`— ${W.length} issue${W.length===1?``:`s`}`:`— OK`}`,done:W.length===0}),console.log(`\n${y(e)}`)}let K=0;if(t.fix){for(let e of G)if(e.fix){if(k.doctorConfig.fix.confirmChanges&&!await d(`Fix issue in "${e.file}": ${e.message}?`))continue;if(k.doctorConfig.fix.backupFiles){let t=s.resolve(k.docsDir,e.file);c.existsSync(t)&&await D(t,s.resolve(k.root,k.doctorConfig.fix.backupPath))}await e.fix(),K++}}let q=((performance.now()-i)/1e3).toFixed(2),J=G.filter(e=>e.level===`high`).length,Y=G.filter(e=>e.level===`warning`).length,X=G.filter(e=>e.level===`low`).length,Z={summary:{total:G.length,high:J,warning:Y,low:X,fixed:K,duration:q},issues:G.map(e=>({...e,fix:void 0}))};if(n.reporting.outputFile){let t=s.resolve(e,n.reporting.outputFile);c.existsSync(s.dirname(t))||c.mkdirSync(s.dirname(t),{recursive:!0}),c.writeFileSync(t,JSON.stringify(Z,null,2))}if(r===`json`)console.log(JSON.stringify(Z,null,2));else if(r===`pretty`){let e=G.reduce((e,t)=>(e[t.file]||(e[t.file]=[]),e[t.file].push(t),e),{});if(G.length>0){f();for(let[n,r]of Object.entries(e)){let e=[];for(let n of r){let r=n.level===`high`?`❌`:n.level===`warning`?`⚠️`:`ℹ️`,i=n.level===`high`?u.red:n.level===`warning`?u.yellow:u.blue;e.push(`${r} ${i(n.level.toUpperCase())}: ${n.message}`),n.suggestion&&e.push(` ${u.dim(`💡 ${n.suggestion}`)}`),t.fix&&n.fix&&e.push(` ${u.green(`✅ Fixed automatically`)}`)}console.log(`\n${_(e,{title:`📄 ${n}`})}`)}f()}if(G.length===0)console.log(g([` Everything looks perfect!`,` Your documentation is in great shape.`,``,` ${u.dim(`Scanned ${w.length} file${w.length===1?``:`s`} in ${q}s`)}`],{title:`✨ Documentation Health Check`}));else{let e=[];J>0&&e.push(u.red(`${J} Critical Error${J===1?``:`s`}`)),Y>0&&e.push(u.yellow(`${Y} Warning${Y===1?``:`s`}`)),X>0&&e.push(u.blue(`${X} Improvement${X===1?``:`s`}`));let t=[...l(e).split(`
|
|
7
|
+
`).map(e=>e.trimStart()),``,u.dim(`Scanned ${w.length} file${w.length===1?``:`s`} in ${q}s`)];console.log(`\n${p(t,{title:`Diagnosis Results`})}\n`),K>0&&v(`Successfully fixed ${K} issues automatically!`),J>0?m(`Please fix the critical errors before building for production.`):v(`No critical issues found. You are ready to go!`)}}n.reporting.failOnError&&J>0&&process.exit(1),n.reporting.maxWarnings!==-1&&Y>n.reporting.maxWarnings&&(r===`pretty`&&m(`Failed: Too many warnings (${Y} > ${n.reporting.maxWarnings})`),process.exit(1))}catch(e){m(`Doctor failed: ${e}`),process.exit(1)}}export{N as n,H as t};
|