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.
Files changed (105) hide show
  1. package/bin/boltdocs.js +7 -6
  2. package/dist/{banner-DFACg5Dy.d.cts → banner-C_xkGxzI.d.cts} +1 -1
  3. package/dist/{banner-BLTQUA2O.d.ts → banner-D2NddBfF.d.ts} +1 -1
  4. package/dist/cache-B19MLTpN.cjs +6 -0
  5. package/dist/cache-CfhOTR3Q.mjs +6 -0
  6. package/dist/chunk-Ds5LZdWN.cjs +6 -0
  7. package/dist/client/index.cjs +464 -279
  8. package/dist/client/index.d.cts +74 -8
  9. package/dist/client/index.d.ts +74 -8
  10. package/dist/client/index.js +463 -280
  11. package/dist/client/mdx.cjs +1 -1
  12. package/dist/client/mdx.js +1 -1
  13. package/dist/client/primitives.cjs +4 -4
  14. package/dist/client/primitives.d.cts +1 -1
  15. package/dist/client/primitives.d.ts +1 -1
  16. package/dist/client/primitives.js +4 -4
  17. package/dist/client/theme/neutral.css +5 -3
  18. package/dist/client/theme/reset.css +19 -0
  19. package/dist/config-BqZUYono.cjs +7 -0
  20. package/dist/config-DBKBEj3j.mjs +7 -0
  21. package/dist/config-GPVEdeon.mjs +6 -0
  22. package/dist/config-kIQOJHXC.cjs +6 -0
  23. package/dist/csp-CBysWlAf.cjs +6 -0
  24. package/dist/csp-RJyh0nHV.mjs +6 -0
  25. package/dist/{docs-layout-CVCf3xkZ.cjs → docs-layout-3TpAt6MB.cjs} +302 -238
  26. package/dist/{docs-layout-BBv6wILp.js → docs-layout-6FQ8__ok.js} +303 -239
  27. package/dist/doctor-C19DsI1e.mjs +7 -0
  28. package/dist/{doctor-DT4jh07O.cjs → doctor-D8fl3Sbg.cjs} +1 -1
  29. package/dist/doctor-DzEgMMRo.cjs +7 -0
  30. package/dist/errors-C0Hx15Ag.mjs +6 -0
  31. package/dist/errors-Dx6JHoKE.cjs +6 -0
  32. package/dist/{generator-BHCrLU6h.mjs → generator-B6lrnDZ-.mjs} +1 -1
  33. package/dist/{generator-CC2yHzhZ.cjs → generator-DtUU3Cfk.cjs} +2 -2
  34. package/dist/handler-1LRMYeI4.cjs +28 -0
  35. package/dist/handler-CyIBbZdb.mjs +28 -0
  36. package/dist/{doctor-BGPdO1Ah.mjs → handler-eNNcucnf.mjs} +1 -1
  37. package/dist/headers-BJ4jJAhU.cjs +6 -0
  38. package/dist/headers-HD_qTVVW.mjs +6 -0
  39. package/dist/{icons-dev-C8sFxeBk.cjs → icons-dev-BryEFkJu.cjs} +90 -4
  40. package/dist/{icons-dev-CFcv5Nb8.js → icons-dev-kJyGWNeg.js} +73 -5
  41. package/dist/{image-DCMxxidi.cjs → image-B9YA_oQ_.cjs} +17 -9
  42. package/dist/{image-DufDOid8.js → image-CjYu9Eml.js} +17 -9
  43. package/dist/{mdx-Bto8CvGl.js → mdx-Cy_c2zio.js} +116 -33
  44. package/dist/{mdx-BH9kHiF9.cjs → mdx-WXjAMS8Y.cjs} +121 -32
  45. package/dist/{meta-loader-Cv9O0Pzl.cjs → meta-loader-C9MKjAs0.cjs} +1 -1
  46. package/dist/{meta-loader-DzwDFtdT.mjs → meta-loader-CxAoQthW.mjs} +1 -1
  47. package/dist/node/cli-entry.cjs +8 -2
  48. package/dist/node/cli-entry.mjs +8 -2
  49. package/dist/node/index.cjs +1 -1
  50. package/dist/node/index.d.cts +362 -18
  51. package/dist/node/index.d.mts +364 -19
  52. package/dist/node/index.mjs +1 -1
  53. package/dist/node/routes/worker.cjs +1 -1
  54. package/dist/node/routes/worker.mjs +1 -1
  55. package/dist/node-B7qbjtLl.mjs +105 -0
  56. package/dist/node-Dj3-2YYP.cjs +105 -0
  57. package/dist/{package-CwNjt3Sd.cjs → package-ByXh2JHp.cjs} +1 -1
  58. package/dist/{package-DlgexgpQ.mjs → package-DQBAV8uo.mjs} +1 -1
  59. package/dist/{parser-BzB-zCkF.cjs → parser-B8naUXl2.cjs} +1 -1
  60. package/dist/parser-D4oEyuP-.cjs +6 -0
  61. package/dist/parser-IR9tfyGE.mjs +6 -0
  62. package/dist/plugin-BkquLpOI.mjs +61 -0
  63. package/dist/plugin-CL1LsQXk.cjs +61 -0
  64. package/dist/routes-DR13Y1em.mjs +6 -0
  65. package/dist/routes-DX8ryKjs.cjs +6 -0
  66. package/dist/routes-DgTQxpZi.cjs +6 -0
  67. package/dist/routes-TUSK5wD7.mjs +6 -0
  68. package/dist/{search-dialog-Cvux-qSk.cjs → search-dialog-BhXg05Ri.cjs} +1 -1
  69. package/dist/{search-dialog-CmHYOt18.cjs → search-dialog-BsAiQgnx.cjs} +2 -2
  70. package/dist/{search-dialog-jHZ21G4i.js → search-dialog-CaOO1Q4i.js} +2 -2
  71. package/dist/{search-dialog-Cr7SC8Xt.cjs → search-dialog-CzwvKoLW.cjs} +1 -1
  72. package/dist/{search-dialog-DBNaG5At.js → search-dialog-DRNJsxOv.js} +1 -1
  73. package/dist/{search-dialog-e86WPpck.js → search-dialog-DaR66wl0.js} +1 -1
  74. package/dist/shiki-adapter-BB71nVsG.cjs +6 -0
  75. package/dist/shiki-adapter-CL-IQwKI.cjs +6 -0
  76. package/dist/shiki-adapter-CxTLkjew.mjs +6 -0
  77. package/dist/shiki-adapter-rQ087Lx3.mjs +6 -0
  78. package/dist/{sidebar-kQBonN82.d.ts → sidebar-BtJyWHy4.d.cts} +42 -5
  79. package/dist/{sidebar---RgTmZD.d.cts → sidebar-C4fuRYYS.d.ts} +42 -5
  80. package/dist/types-generator-BRyf4o9F.mjs +26 -0
  81. package/dist/types-generator-BqNKCbrx.cjs +26 -0
  82. package/dist/types-generator-Cu2jQP-V.mjs +6 -0
  83. package/dist/utils-BWy_8ASj.cjs +7 -0
  84. package/dist/utils-DexBrwiP.mjs +7 -0
  85. package/dist/worker-pool-C6r6V05j.mjs +6 -0
  86. package/dist/worker-pool-m81xuM5v.cjs +6 -0
  87. package/package.json +6 -3
  88. package/src/shared/types.ts +36 -3
  89. package/dist/cache-BMUyNiiA.mjs +0 -6
  90. package/dist/cache-CKm45d2w.cjs +0 -6
  91. package/dist/chunk-CU-zTemE.cjs +0 -6
  92. package/dist/doctor-Dnv77Lfx.mjs +0 -28
  93. package/dist/doctor-VBPlQf7d.cjs +0 -28
  94. package/dist/node-JM9bx0mO.cjs +0 -160
  95. package/dist/node-q-Y2mdLX.mjs +0 -160
  96. package/dist/parser-9LqyiEnB.mjs +0 -6
  97. package/dist/parser-B7-6PyQz.cjs +0 -6
  98. package/dist/routes-3sAccArX.cjs +0 -6
  99. package/dist/routes-DZpXqq1p.mjs +0 -6
  100. package/dist/routes-DjoxNeef.mjs +0 -6
  101. package/dist/routes-DpJnb2VS.cjs +0 -6
  102. package/dist/utils-BYITg7T5.mjs +0 -7
  103. package/dist/utils-Cjmx1hhk.cjs +0 -7
  104. package/dist/worker-pool-DXLyeHJg.cjs +0 -6
  105. 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, Q as icons_exports, S as normalizePath, _ as Link$1, b as useLocalizedTo, et as useConfig, k as ChevronLeft, y as cn } from "./icons-dev-CFcv5Nb8.js";
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("h4", {
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
- return /* @__PURE__ */ jsxs("div", {
923
- className: cn("flex flex-col gap-6", className),
924
- children: [ungrouped.length > 0 && /* @__PURE__ */ jsx(SidebarGroup, { children: ungrouped.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
925
- route,
926
- activePath,
927
- activeRoute
928
- }, route.path)) }), groups.map((group) => /* @__PURE__ */ jsx(SidebarGroup, {
929
- title: group.title,
930
- icon: getIcon(group.icon),
931
- children: group.routes.map((route) => /* @__PURE__ */ jsx(SidebarItem, {
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, useItems as C, ButtonGroup as D, Popover$1 as E, Breadcrumbs$1 as O, useActiveAnchors as S, Menu as T, OnThisPage as _, SidebarContent as a, ScrollProvider as b, SidebarItem as c, SidebarMobile as d, SidebarRoot as f, AnchorProvider as g, PageNav as h, Sidebar as i, UIProvider as k, SidebarItems as l, useSidebar as m, useSearchHighlight as n, SidebarGroup as o, SidebarSubGroup as p, useLocation$1 as r, SidebarHeader as s, DocsLayout as t, SidebarLink as u, OnThisPageItems as v, Tabs as w, useActiveAnchor as x, OnThisPageTree as y };
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};
@@ -3,4 +3,4 @@
3
3
  * Copyright (c) 2026 Jesus Alcala
4
4
  * Licensed under the MIT License.
5
5
  */
6
- const e=require(`./doctor-VBPlQf7d.cjs`);exports.doctorAction=e.t;
6
+ const e=require(`./doctor-DzEgMMRo.cjs`);exports.doctorAction=e.t;