lildocs 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -3,21 +3,19 @@ import { n as renderGitHubPagesWorkflow, r as repoRelativeInputPath, t as normal
3
3
  import { createRequire } from "node:module";
4
4
  import { spawn } from "node:child_process";
5
5
  import { command, flag, option, optional, positional, run, string, subcommands } from "cmd-ts";
6
- import { createReadStream, existsSync, readFileSync, watch } from "node:fs";
6
+ import { existsSync, readFileSync } from "node:fs";
7
7
  import { access, copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
8
8
  import path from "node:path";
9
- import { fileURLToPath, pathToFileURL } from "node:url";
10
9
  import matter from "gray-matter";
11
- import { Marked, Parser, Renderer, marked } from "marked";
10
+ import { Lexer, Marked, Parser, Renderer, marked } from "marked";
11
+ import { fileURLToPath, pathToFileURL } from "node:url";
12
12
  import markedShiki from "marked-shiki";
13
13
  import { bundledThemes, codeToHtml } from "shiki";
14
+ import { decodeHTML } from "entities";
14
15
  import { renderMermaidSVG } from "beautiful-mermaid";
15
16
  import os from "node:os";
16
17
  import { generateMarkdownForModule } from "exports-md";
17
18
  import { clampGamut, converter, formatHex, parse, wcagContrast } from "culori";
18
- import { render } from "preact-render-to-string";
19
- import { jsx, jsxs } from "preact/jsx-runtime";
20
- import http from "node:http";
21
19
  //#region src/core/errors.ts
22
20
  var LildocsError = class extends Error {
23
21
  constructor(message) {
@@ -40,7 +38,7 @@ async function readDocsConfig(docsRoot) {
40
38
  try {
41
39
  parsed = JSON.parse(rawConfig);
42
40
  } catch (error) {
43
- throw new LildocsError(`Invalid docs config JSON at ${configPath}: ${errorMessage$3(error)}`);
41
+ throw new LildocsError(`Invalid docs config JSON at ${configPath}: ${errorMessage$4(error)}`);
44
42
  }
45
43
  return validateDocsConfig(parsed, configPath);
46
44
  }
@@ -180,7 +178,7 @@ function assertOptionalStringArray(value, name, configPath) {
180
178
  function isMissingFileError(error) {
181
179
  return error instanceof Error && "code" in error && error.code === "ENOENT";
182
180
  }
183
- function errorMessage$3(error) {
181
+ function errorMessage$4(error) {
184
182
  return error instanceof Error ? error.message : String(error);
185
183
  }
186
184
  //#endregion
@@ -426,6 +424,209 @@ function ensureUniqueRoutes(pages) {
426
424
  }
427
425
  }
428
426
  //#endregion
427
+ //#region src/core/frontend.ts
428
+ const sourceDir = path.dirname(fileURLToPath(import.meta.url));
429
+ const require = createRequire(import.meta.url);
430
+ const virtualOctaneCssId = "\0lildocs-octane-css";
431
+ async function readRenderAsset(name) {
432
+ return readFile(path.join(frontendSourceDir(), name), "utf8");
433
+ }
434
+ function frontendDevScriptPath() {
435
+ return `/@fs/${toVitePath(clientEntryPath())}`;
436
+ }
437
+ async function buildFrontendAssets(options) {
438
+ const vite = await loadVite();
439
+ const outDir = path.join(options.outDir, "assets", "lildocs");
440
+ await vite.build({
441
+ ...await frontendViteConfig(options.cwd, "build"),
442
+ base: "./",
443
+ build: {
444
+ outDir,
445
+ emptyOutDir: true,
446
+ manifest: true,
447
+ minify: false,
448
+ target: "esnext",
449
+ rollupOptions: { input: clientEntryPath() }
450
+ }
451
+ });
452
+ const manifestPath = path.join(outDir, ".vite", "manifest.json");
453
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
454
+ const entry = Object.values(manifest).find((chunk) => chunk.isEntry === true && typeof chunk.file === "string");
455
+ if (!entry) throw new LildocsError("Vite did not emit a frontend entry for the documentation site.");
456
+ return {
457
+ scriptPath: toPosixPath(path.join("assets", "lildocs", entry.file)),
458
+ stylePaths: (entry.css ?? []).map((file) => toPosixPath(path.join("assets", "lildocs", file)))
459
+ };
460
+ }
461
+ async function createFrontendRenderer(options) {
462
+ if (options.viteServer) return {
463
+ renderPage: renderPageExport(await options.viteServer.ssrLoadModule(serverEntryPath())),
464
+ close: async () => {}
465
+ };
466
+ const server = await (await loadVite()).createServer({
467
+ ...await frontendViteConfig(options.cwd, "ssr"),
468
+ appType: "custom",
469
+ server: {
470
+ middlewareMode: true,
471
+ fs: { allow: [frontendSourceDir(), options.cwd] }
472
+ }
473
+ });
474
+ return {
475
+ renderPage: renderPageExport(await server.ssrLoadModule(serverEntryPath())),
476
+ close: () => server.close()
477
+ };
478
+ }
479
+ async function createFrontendDevServer(options) {
480
+ const server = await (await loadVite()).createServer({
481
+ ...await frontendViteConfig(options.cwd, "dev"),
482
+ root: options.root,
483
+ appType: "mpa",
484
+ server: {
485
+ host: options.host,
486
+ port: options.port,
487
+ strictPort: false,
488
+ fs: { allow: [
489
+ frontendSourceDir(),
490
+ options.root,
491
+ options.cwd
492
+ ] }
493
+ }
494
+ });
495
+ await server.listen();
496
+ return server;
497
+ }
498
+ function frontendSourceDir() {
499
+ const nested = path.resolve(sourceDir, "render");
500
+ if (existsSync(path.join(nested, "client.tsrx"))) return nested;
501
+ return path.resolve(sourceDir, "../render");
502
+ }
503
+ function clientEntryPath() {
504
+ return path.join(frontendSourceDir(), "client.tsrx");
505
+ }
506
+ function serverEntryPath() {
507
+ return path.join(frontendSourceDir(), "renderPage.tsrx");
508
+ }
509
+ async function frontendViteConfig(cwd, mode) {
510
+ const { octane } = await loadOctaneCompiler();
511
+ return {
512
+ configFile: false,
513
+ root: frontendSourceDir(),
514
+ publicDir: false,
515
+ cacheDir: path.join(cwd, "node_modules", ".vite", `lildocs-${mode}`),
516
+ clearScreen: false,
517
+ logLevel: "warn",
518
+ plugins: [octaneRuntimeCompatibility(), octane()],
519
+ optimizeDeps: { exclude: ["octane"] },
520
+ ssr: { noExternal: [/^octane(?:$|\/)/] }
521
+ };
522
+ }
523
+ async function loadVite() {
524
+ try {
525
+ return await import("vite");
526
+ } catch (error) {
527
+ throw new LildocsError(`Lildocs needs Vite to build and preview its frontend. Install vite in the project using Lildocs.\n${errorMessage$3(error)}`);
528
+ }
529
+ }
530
+ async function loadOctaneCompiler() {
531
+ try {
532
+ return await import("octane/compiler/vite");
533
+ } catch (error) {
534
+ throw new LildocsError(`Lildocs needs Octane to compile its frontend.\n${errorMessage$3(error)}`);
535
+ }
536
+ }
537
+ function renderPageExport(mod) {
538
+ const renderPage = mod && typeof mod === "object" && "renderPage" in mod ? mod.renderPage : void 0;
539
+ if (typeof renderPage !== "function") throw new LildocsError("Lildocs frontend renderer did not export renderPage.");
540
+ return renderPage;
541
+ }
542
+ function octaneRuntimeCompatibility() {
543
+ return {
544
+ name: "lildocs-octane-runtime-compatibility",
545
+ enforce: "pre",
546
+ resolveId(source, importer, options) {
547
+ if (source === "octane/server" && options.ssr) return resolveOctaneServerRuntime();
548
+ if (source === "./css.js" && importer && isOctaneRuntimeImport(importer)) return virtualOctaneCssId;
549
+ return null;
550
+ },
551
+ load(id) {
552
+ if (id === virtualOctaneCssId) return octaneCssHelpersSource;
553
+ return null;
554
+ }
555
+ };
556
+ }
557
+ function resolveOctaneServerRuntime() {
558
+ const serverEntry = require.resolve("octane/server");
559
+ const runtimePath = [serverEntry.replace(/[/\\]server[/\\]index\.js$/, `${path.sep}runtime.server.js`), serverEntry.replace(/[/\\]server[/\\]index\.ts$/, `${path.sep}runtime.server.ts`)].find((candidate) => candidate !== serverEntry && existsSync(candidate));
560
+ if (!runtimePath) throw new LildocsError(`Unable to locate Octane server runtime from ${serverEntry}.`);
561
+ return runtimePath;
562
+ }
563
+ function isOctaneRuntimeImport(importer) {
564
+ const [filePath] = importer.split("?", 1);
565
+ const normalized = toPosixPath(filePath);
566
+ return /\/octane\/(?:src|dist)\/runtime(?:\.server)?\.(?:ts|js)$/.test(normalized);
567
+ }
568
+ const octaneCssHelpersSource = `
569
+ export function normalizeClass(value) {
570
+ if (typeof value === "string") return value;
571
+ if (typeof value !== "object") {
572
+ return typeof value === "number" && value ? "" + value : "";
573
+ }
574
+ if (value === null) return "";
575
+ let str = "";
576
+ if (Array.isArray(value)) {
577
+ for (const item of value) {
578
+ if (item) {
579
+ const inner = normalizeClass(item);
580
+ if (inner) str = str ? str + " " + inner : inner;
581
+ }
582
+ }
583
+ } else {
584
+ for (const key in value) {
585
+ if (value[key]) str = str ? str + " " + key : key;
586
+ }
587
+ }
588
+ return str;
589
+ }
590
+
591
+ const styleNameCache = new Map();
592
+
593
+ export function styleName(name) {
594
+ const cached = styleNameCache.get(name);
595
+ if (cached !== undefined) return cached;
596
+ const result = hyphenateStyleName(name);
597
+ styleNameCache.set(name, result);
598
+ return result;
599
+ }
600
+
601
+ function hyphenateStyleName(name) {
602
+ if (name.charCodeAt(0) === 45) return name;
603
+ let hasUpper = false;
604
+ for (let index = 0; index < name.length; index++) {
605
+ const code = name.charCodeAt(index);
606
+ if (code >= 65 && code <= 90) {
607
+ hasUpper = true;
608
+ break;
609
+ }
610
+ }
611
+ if (!hasUpper) return name;
612
+ let out = "";
613
+ for (let index = 0; index < name.length; index++) {
614
+ const code = name.charCodeAt(index);
615
+ out += code >= 65 && code <= 90 ? "-" + String.fromCharCode(code + 32) : name[index];
616
+ }
617
+ if (out.charCodeAt(0) === 109 && out.charCodeAt(1) === 115 && out.charCodeAt(2) === 45) {
618
+ out = "-" + out;
619
+ }
620
+ return out;
621
+ }
622
+ `;
623
+ function toVitePath(filePath) {
624
+ return path.resolve(filePath).split(path.sep).join("/");
625
+ }
626
+ function errorMessage$3(error) {
627
+ return error instanceof Error ? error.message : String(error);
628
+ }
629
+ //#endregion
429
630
  //#region src/vendor/marked-alert/index.ts
430
631
  const defaultAlertVariants = [
431
632
  {
@@ -527,12 +728,19 @@ function buildSearchIndex(pages) {
527
728
  pageTitle: page.title,
528
729
  route: page.route,
529
730
  headings: page.headings.map((heading) => heading.text),
530
- text: normalizeSearchText(page.searchText ?? page.markdown)
731
+ text: normalizeSearchText(page.searchText ?? markdownToPlainText(page.markdown)),
732
+ excerpt: markdownToExcerpt(page.markdown)
531
733
  }, ...buildSectionEntries(page)]);
532
734
  }
533
735
  function normalizeSearchText(value) {
534
736
  return value.replace(/<[^>]+>/g, " ").replace(/[^\p{L}\p{N}]+/gu, " ").replace(/\s+/g, " ").trim();
535
737
  }
738
+ function markdownToPlainText(markdown) {
739
+ return normalizeSearchText(markdownToExcerpt(markdown));
740
+ }
741
+ function markdownToExcerpt(markdown) {
742
+ return decodeHTML(Parser.parse(Lexer.lex(markdown, { gfm: true })).replace(/<br\s*\/?>/gi, "\n").replace(/<\/(?:p|h[1-6]|li|blockquote|pre|tr|div|section|article)>/gi, "\n\n").replace(/<[^>]+>/g, "")).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
743
+ }
536
744
  function buildSectionEntries(page) {
537
745
  const entries = [];
538
746
  const headingQueue = [...page.headings];
@@ -546,7 +754,8 @@ function buildSectionEntries(page) {
546
754
  pageTitle: page.title,
547
755
  route: `${page.route}#${activeSection.heading.id}`,
548
756
  headings: activeSection.path.map((heading) => heading.text),
549
- text: normalizeSearchText(activeSection.lines.join("\n")),
757
+ text: markdownToPlainText(activeSection.lines.join("\n")),
758
+ excerpt: markdownToExcerpt(activeSection.lines.join("\n")),
550
759
  depth: activeSection.heading.depth
551
760
  });
552
761
  };
@@ -621,7 +830,7 @@ async function renderMarkdownPage(model, page, outDir, options = {}) {
621
830
  return highlightCode(code, lang, props, options.shikiTheme);
622
831
  } })).parse(page.markdown),
623
832
  assets,
624
- text: normalizeSearchText(page.markdown)
833
+ text: markdownToPlainText(page.markdown)
625
834
  };
626
835
  }
627
836
  async function highlightCode(code, lang, props, theme = {}) {
@@ -769,7 +978,7 @@ function nestNavItems(items) {
769
978
  let group = byDir.get(currentPath);
770
979
  if (!group) {
771
980
  group = {
772
- title: titleFromDir$1(part),
981
+ title: titleFromDir(part),
773
982
  route: `${currentPath}/index.html`,
774
983
  active: false,
775
984
  hasPage: false,
@@ -790,7 +999,7 @@ function nestNavItems(items) {
790
999
  }
791
1000
  return topLevel;
792
1001
  }
793
- function titleFromDir$1(dir) {
1002
+ function titleFromDir(dir) {
794
1003
  return dir.replace(/[-_]+/g, " ").replace(/\S+/g, (word) => `${word[0]?.toLocaleUpperCase() ?? ""}${word.slice(1)}`);
795
1004
  }
796
1005
  //#endregion
@@ -956,6 +1165,11 @@ const BACKGROUND_LIGHTNESS_STEP = .015;
956
1165
  const MAX_BACKGROUND_CHROMA = .03;
957
1166
  const toOklch = converter("oklch");
958
1167
  const clampToRgb = clampGamut("rgb");
1168
+ const defaultThemeFontSet = {
1169
+ heading: "\"Baloo 2\", sans-serif",
1170
+ body: "Inter, sans-serif",
1171
+ code: "\"JetBrains Mono\", monospace"
1172
+ };
959
1173
  const themes = {
960
1174
  default: {
961
1175
  color: {
@@ -968,11 +1182,7 @@ const themes = {
968
1182
  codeForeground: "#24292e",
969
1183
  sidebarBackground: "#fafafa"
970
1184
  },
971
- font: {
972
- heading: "system-ui, sans-serif",
973
- body: "system-ui, sans-serif",
974
- code: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
975
- },
1185
+ font: { ...defaultThemeFontSet },
976
1186
  shiki: { theme: "github-light" }
977
1187
  },
978
1188
  minimal: {
@@ -998,8 +1208,9 @@ async function resolveTheme(options) {
998
1208
  if (options.requestedTheme) {
999
1209
  if (typeof options.requestedTheme === "string") return { light: await resolveThemeName(options.requestedTheme) };
1000
1210
  return {
1001
- light: await resolveThemeName(options.requestedTheme.light ?? "default"),
1002
- dark: await resolveThemeName(options.requestedTheme.dark ?? "github-dark")
1211
+ light: await resolveThemeName(options.requestedTheme.light ?? "vitesse-light"),
1212
+ dark: await resolveThemeName(options.requestedTheme.dark ?? "vitesse-dark"),
1213
+ useDefaultFontSet: options.requestedTheme.light === void 0
1003
1214
  };
1004
1215
  }
1005
1216
  const localThemePath = path.join(options.docsRoot, "theme.ts");
@@ -1010,8 +1221,9 @@ async function resolveTheme(options) {
1010
1221
  return { light: validateTheme((await import(localThemeUrl.href)).default, localThemePath) };
1011
1222
  }
1012
1223
  return {
1013
- light: themes.default,
1014
- dark: await resolveThemeName("github-dark")
1224
+ light: await resolveThemeName("vitesse-light"),
1225
+ dark: await resolveThemeName("vitesse-dark"),
1226
+ useDefaultFontSet: true
1015
1227
  };
1016
1228
  }
1017
1229
  async function resolveThemeName(themeName) {
@@ -1067,11 +1279,24 @@ async function resolveFontOverrides(options) {
1067
1279
  themeFonts
1068
1280
  };
1069
1281
  }
1282
+ function resolveThemeFontImports(theme, overrides = {}) {
1283
+ if (!theme.useDefaultFontSet && theme.light !== themes.default) return "";
1284
+ const imports = [
1285
+ !overrides.heading && googleFontsCssUrl("Baloo 2", "600;700"),
1286
+ !overrides.body && googleFontsCssUrl("Inter", "400;500"),
1287
+ !overrides.code && googleFontsCssUrl("JetBrains Mono", "400")
1288
+ ].filter((url) => Boolean(url));
1289
+ return imports.length > 0 ? `${imports.map((url) => `@import url("${url}");`).join("\n")}\n` : "";
1290
+ }
1070
1291
  function themeToCssVariables(theme, fontOverrides = {}, linkOptions = {}, navigationOptions = {}) {
1071
1292
  const linkDecoration = linkTextDecoration(linkOptions.underline);
1072
1293
  const navigationDuration = navigationOptions.duration ?? 180;
1073
- const rootVariables = themeToCssVariableBlock(theme.light, fontOverrides);
1074
- const darkVariables = theme.dark ? themeToCssVariableBlock(theme.dark, fontOverrides) : void 0;
1294
+ const effectiveFontOverrides = theme.useDefaultFontSet ? {
1295
+ ...defaultThemeFontSet,
1296
+ ...fontOverrides
1297
+ } : fontOverrides;
1298
+ const rootVariables = themeToCssVariableBlock(theme.light, effectiveFontOverrides);
1299
+ const darkVariables = theme.dark ? themeToCssVariableBlock(theme.dark, theme.useDefaultFontSet || theme.light === themes.default ? resolveThemeFonts(theme.light, effectiveFontOverrides) : effectiveFontOverrides) : void 0;
1075
1300
  const rootBackgroundVariables = themeToBackgroundVariableBlock(theme.light);
1076
1301
  const darkBackgroundVariables = theme.dark ? themeToBackgroundVariableBlock(theme.dark) : void 0;
1077
1302
  return `:root {
@@ -1390,233 +1615,7 @@ async function exists(filePath) {
1390
1615
  }
1391
1616
  }
1392
1617
  //#endregion
1393
- //#region src/render/Layout.tsx
1394
- function Layout({ page, nav, pageNavigation, css, searchIndexJson, logo, favicon, repositoryUrl, projectName, navigation, dev }) {
1395
- const transition = navigation?.transition ?? "fade";
1396
- const documentTitle = projectName ? `${page.title} • ${projectName}` : page.title;
1397
- const repoIconUrl = rootRelativeUrl(page.route, "assets/github-icon.svg");
1398
- const issueUrl = issueUrlForRepository(repositoryUrl);
1399
- return /* @__PURE__ */ jsxs("html", {
1400
- lang: "en",
1401
- children: [/* @__PURE__ */ jsxs("head", { children: [
1402
- /* @__PURE__ */ jsx("meta", { charSet: "utf-8" }),
1403
- /* @__PURE__ */ jsx("meta", {
1404
- name: "viewport",
1405
- content: "width=device-width, initial-scale=1"
1406
- }),
1407
- /* @__PURE__ */ jsx("title", { children: documentTitle }),
1408
- favicon ? /* @__PURE__ */ jsx("link", {
1409
- rel: "icon",
1410
- href: assetSrc(page.route, favicon)
1411
- }) : null,
1412
- /* @__PURE__ */ jsx("link", {
1413
- rel: "stylesheet",
1414
- href: rootRelativeUrl(page.route, "assets/tabler-icons.css")
1415
- }),
1416
- /* @__PURE__ */ jsx("link", {
1417
- rel: "stylesheet",
1418
- href: rootRelativeUrl(page.route, "assets/lildocs.css")
1419
- })
1420
- ] }), /* @__PURE__ */ jsxs("body", { children: [
1421
- /* @__PURE__ */ jsxs("div", {
1422
- className: "pageShell",
1423
- children: [/* @__PURE__ */ jsxs("header", {
1424
- className: "siteHeader",
1425
- children: [/* @__PURE__ */ jsxs("a", {
1426
- className: "brand",
1427
- href: relativeUrl(page.route, "index.html"),
1428
- children: [logo.image ? /* @__PURE__ */ jsx("img", {
1429
- className: "brandLogo",
1430
- src: assetSrc(page.route, logo.image),
1431
- alt: ""
1432
- }) : null, logo.text ? /* @__PURE__ */ jsx("span", { children: logo.text }) : null]
1433
- }), /* @__PURE__ */ jsxs("div", {
1434
- className: "headerActions",
1435
- children: [repositoryUrl ? /* @__PURE__ */ jsx("a", {
1436
- className: "repoButton",
1437
- href: repositoryUrl,
1438
- "aria-label": "View repository on GitHub",
1439
- title: "View repository on GitHub",
1440
- children: /* @__PURE__ */ jsx("span", {
1441
- className: "repoIcon",
1442
- "aria-hidden": "true",
1443
- style: `--ld-repo-icon: url(${repoIconUrl})`
1444
- })
1445
- }) : null, /* @__PURE__ */ jsxs("div", {
1446
- id: "lildocs-search-root",
1447
- className: "searchBox",
1448
- children: [/* @__PURE__ */ jsx("span", {
1449
- className: "searchIcon ti ti-search",
1450
- "aria-hidden": "true"
1451
- }), /* @__PURE__ */ jsx("input", {
1452
- id: "lildocs-search-input",
1453
- type: "search",
1454
- placeholder: "Search docs",
1455
- "aria-label": "Search docs"
1456
- })]
1457
- })]
1458
- })]
1459
- }), /* @__PURE__ */ jsxs("div", {
1460
- id: "swup",
1461
- className: "contentGrid",
1462
- children: [
1463
- /* @__PURE__ */ jsx("aside", {
1464
- className: "sidebar",
1465
- children: /* @__PURE__ */ jsx("nav", {
1466
- "aria-label": "Documentation navigation",
1467
- children: /* @__PURE__ */ jsx(NavList, {
1468
- items: nav,
1469
- currentRoute: page.route,
1470
- pageRoute: page.route
1471
- })
1472
- })
1473
- }),
1474
- /* @__PURE__ */ jsxs("main", {
1475
- className: `content transition-${transition}`,
1476
- children: [/* @__PURE__ */ jsxs("article", { children: [/* @__PURE__ */ jsx(GroupBreadcrumbs, { route: page.route }), /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: page.html ?? "" } })] }), /* @__PURE__ */ jsx(PageNav, { pageNavigation })]
1477
- }),
1478
- /* @__PURE__ */ jsx("aside", {
1479
- className: `toc transition-${transition}`,
1480
- children: /* @__PURE__ */ jsx(Toc, { headings: page.headings })
1481
- })
1482
- ]
1483
- })]
1484
- }),
1485
- /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `window.lildocsSearchUrl = ${JSON.stringify(rootRelativeUrl(page.route, "search-index.json"))};${issueUrl ? `window.lildocsIssueUrl = ${JSON.stringify(issueUrl)};` : ""}` } }),
1486
- /* @__PURE__ */ jsx("script", {
1487
- type: "application/json",
1488
- id: "lildocs-search-index",
1489
- dangerouslySetInnerHTML: { __html: searchIndexJson }
1490
- }),
1491
- /* @__PURE__ */ jsx("div", { id: "lildocs-overlay-root" }),
1492
- /* @__PURE__ */ jsx("script", {
1493
- type: "module",
1494
- src: rootRelativeUrl(page.route, "assets/search.js")
1495
- }),
1496
- /* @__PURE__ */ jsx("script", { src: rootRelativeUrl(page.route, "assets/copy-code.js") }),
1497
- /* @__PURE__ */ jsx("script", { src: rootRelativeUrl(page.route, "assets/swup.umd.js") }),
1498
- /* @__PURE__ */ jsx("script", { src: rootRelativeUrl(page.route, "assets/navigation.js") }),
1499
- dev ? /* @__PURE__ */ jsx("script", {
1500
- type: "module",
1501
- src: dev.clientScriptPath
1502
- }) : null,
1503
- /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: css } })
1504
- ] })]
1505
- });
1506
- }
1507
- function issueUrlForRepository(repositoryUrl) {
1508
- if (!repositoryUrl) return;
1509
- let url;
1510
- try {
1511
- url = new URL(repositoryUrl);
1512
- } catch {
1513
- return;
1514
- }
1515
- if (url.hostname !== "github.com") return;
1516
- const [owner, repo] = url.pathname.split("/").filter(Boolean);
1517
- if (!owner || !repo) return;
1518
- return `https://github.com/${owner}/${repo}/issues/new`;
1519
- }
1520
- function assetSrc(pageRoute, image) {
1521
- if (/^(?:[a-z]+:)?\/\//i.test(image) || image.startsWith("data:") || image.startsWith("/")) return image;
1522
- return rootRelativeUrl(pageRoute, image);
1523
- }
1524
- function GroupBreadcrumbs({ route }) {
1525
- const dir = route.split("/").slice(0, -1);
1526
- if (dir.length === 0) return null;
1527
- return /* @__PURE__ */ jsx("p", {
1528
- className: "groupBreadcrumbs",
1529
- children: dir.map(titleFromDir).join(" / ")
1530
- });
1531
- }
1532
- function titleFromDir(dir) {
1533
- return dir.replace(/[-_]+/g, " ").replace(/\S+/g, (word) => `${word[0]?.toLocaleUpperCase() ?? ""}${word.slice(1)}`);
1534
- }
1535
- function PageNav({ pageNavigation }) {
1536
- if (!pageNavigation?.next) return null;
1537
- return /* @__PURE__ */ jsx("nav", {
1538
- className: "pageNav",
1539
- "aria-label": "Page navigation",
1540
- children: /* @__PURE__ */ jsxs("p", { children: [
1541
- "Next:",
1542
- " ",
1543
- /* @__PURE__ */ jsx("a", {
1544
- rel: "next",
1545
- href: pageNavigation.next.href,
1546
- children: pageNavigation.next.title
1547
- })
1548
- ] })
1549
- });
1550
- }
1551
- function NavList({ items, currentRoute, pageRoute }) {
1552
- return /* @__PURE__ */ jsx("ul", {
1553
- className: "navList",
1554
- children: items.map((item) => /* @__PURE__ */ jsxs("li", {
1555
- className: item.children.length > 0 ? "navGroup" : void 0,
1556
- children: [item.children.length > 0 && item.hasPage ? /* @__PURE__ */ jsxs("details", {
1557
- className: "navDisclosure",
1558
- open: isActiveBranch(item, currentRoute),
1559
- children: [/* @__PURE__ */ jsx("summary", {
1560
- className: item.route === currentRoute ? "active" : void 0,
1561
- children: item.title
1562
- }), /* @__PURE__ */ jsx(NavList, {
1563
- items: item.children,
1564
- currentRoute,
1565
- pageRoute
1566
- })]
1567
- }) : item.children.length > 0 ? /* @__PURE__ */ jsx("span", {
1568
- className: "navFolder",
1569
- children: item.title
1570
- }) : /* @__PURE__ */ jsx("a", {
1571
- className: item.route === currentRoute ? "active" : void 0,
1572
- href: relativeUrl(pageRoute, item.route),
1573
- children: item.title
1574
- }), item.children.length > 0 && !item.hasPage ? /* @__PURE__ */ jsx(NavList, {
1575
- items: item.children,
1576
- currentRoute,
1577
- pageRoute
1578
- }) : null]
1579
- }))
1580
- });
1581
- }
1582
- function isActiveBranch(item, currentRoute) {
1583
- return item.route === currentRoute || item.children.some((child) => isActiveBranch(child, currentRoute));
1584
- }
1585
- function Toc({ headings }) {
1586
- const tocHeadings = headings.filter((heading) => heading.depth > 1 && heading.depth < 4);
1587
- if (tocHeadings.length === 0) return null;
1588
- return /* @__PURE__ */ jsxs("nav", {
1589
- "aria-label": "Table of contents",
1590
- children: [/* @__PURE__ */ jsx("p", { children: "On this page" }), /* @__PURE__ */ jsx("ul", { children: tocHeadings.map((heading) => /* @__PURE__ */ jsx("li", {
1591
- className: `tocDepth${heading.depth}`,
1592
- children: /* @__PURE__ */ jsx("a", {
1593
- href: `#${heading.id}`,
1594
- children: heading.text
1595
- })
1596
- })) })]
1597
- });
1598
- }
1599
- //#endregion
1600
- //#region src/render/renderPage.tsx
1601
- function renderPage(page, nav, pageNavigation, css, searchIndexJson, logo, favicon, repositoryUrl, projectName, navigation, dev) {
1602
- return `<!doctype html>${render(/* @__PURE__ */ jsx(Layout, {
1603
- page,
1604
- nav,
1605
- pageNavigation,
1606
- css,
1607
- searchIndexJson,
1608
- logo,
1609
- favicon,
1610
- repositoryUrl,
1611
- projectName,
1612
- navigation,
1613
- dev
1614
- }))}`;
1615
- }
1616
- //#endregion
1617
1618
  //#region src/core/build.ts
1618
- const sourceDir = path.dirname(fileURLToPath(import.meta.url));
1619
- const require = createRequire(import.meta.url);
1620
1619
  async function buildSite(options) {
1621
1620
  const input = await resolveInput(options.input, options.cwd, { homePagePreference: options.homePagePreference });
1622
1621
  const configOptions = mergeConfigOptions({
@@ -1654,11 +1653,7 @@ async function buildSite(options) {
1654
1653
  });
1655
1654
  const baseCss = await readRenderAsset("styles.css");
1656
1655
  const tablerIconsCss = await readRenderAsset("tabler-icons.css");
1657
- const searchScript = await readRenderAsset("search.js");
1658
- const copyCodeScript = await readRenderAsset("copy-code.js");
1659
- const navigationScript = await readRenderAsset("navigation.js");
1660
1656
  const githubIcon = await readRenderAsset("github-icon.svg");
1661
- const swupScript = await readSwupAsset();
1662
1657
  const backgroundResolution = resolveBackgroundOptions({
1663
1658
  cwd: options.cwd,
1664
1659
  docsRoot: input.docsRoot,
@@ -1673,19 +1668,20 @@ async function buildSite(options) {
1673
1668
  defaultText: packageName,
1674
1669
  favicon: configOptions.favicon
1675
1670
  });
1676
- const css = `${fontResolution.css}${themeToCssVariables(theme, fontResolution.themeFonts, configOptions.link, configOptions.navigation)}\n${backgroundResolution.css}${logoResolution.css}${baseCss}`;
1671
+ const css = `${resolveThemeFontImports(theme, configOptions.fonts)}${fontResolution.css}${themeToCssVariables(theme, fontResolution.themeFonts, configOptions.link, configOptions.navigation)}\n${backgroundResolution.css}${logoResolution.css}${baseCss}`;
1677
1672
  const assets = [
1678
1673
  ...fontResolution.assets,
1679
1674
  ...backgroundResolution.assets,
1680
1675
  ...logoResolution.assets
1681
1676
  ];
1682
1677
  const mermaid = await createMermaidRenderer({ themeConfig: themeToMermaidConfig(theme, fontResolution.themeFonts) });
1683
- await rm(outDir, {
1684
- recursive: true,
1685
- force: true
1686
- });
1687
- await mkdir(path.join(outDir, "assets"), { recursive: true });
1678
+ let frontendRenderer;
1688
1679
  try {
1680
+ await rm(outDir, {
1681
+ recursive: true,
1682
+ force: true
1683
+ });
1684
+ await mkdir(path.join(outDir, "assets"), { recursive: true });
1689
1685
  const renderedPages = await Promise.all(model.pages.map((page) => renderMarkdownPage(model, page, outDir, {
1690
1686
  mermaid,
1691
1687
  shikiTheme: {
@@ -1702,22 +1698,45 @@ async function buildSite(options) {
1702
1698
  }
1703
1699
  const searchIndexJson = JSON.stringify(buildSearchIndex(model.pages), null, 2);
1704
1700
  const pageNavigation = buildPageNavigation(model.pages);
1701
+ const frontendAssets = options.dev ? {
1702
+ scriptPath: options.dev.clientScriptPath,
1703
+ stylePaths: []
1704
+ } : await buildFrontendAssets({
1705
+ cwd: options.cwd,
1706
+ outDir
1707
+ });
1708
+ const renderer = await createFrontendRenderer({
1709
+ cwd: options.cwd,
1710
+ viteServer: options.dev?.viteServer
1711
+ });
1712
+ frontendRenderer = renderer;
1705
1713
  await Promise.all(model.pages.map(async (page) => {
1706
- const html = renderPage(page, buildNavigation(model, page), pageNavigation.get(page.route), css, searchIndexJson, logoResolution.logo, logoResolution.favicon, repositoryUrl, projectName, configOptions.navigation, options.dev);
1714
+ const nav = buildNavigation(model, page);
1715
+ const html = renderer.renderPage({
1716
+ page,
1717
+ nav,
1718
+ pageNavigation: pageNavigation.get(page.route),
1719
+ css,
1720
+ searchIndexJson,
1721
+ logo: logoResolution.logo,
1722
+ favicon: logoResolution.favicon,
1723
+ repositoryUrl,
1724
+ projectName,
1725
+ navigation: configOptions.navigation,
1726
+ clientScriptPath: frontendAssets.scriptPath,
1727
+ clientStylePaths: frontendAssets.stylePaths
1728
+ });
1707
1729
  const outputPath = path.join(outDir, page.outputPath);
1708
1730
  await mkdir(path.dirname(outputPath), { recursive: true });
1709
1731
  await writeFile(outputPath, html);
1710
1732
  }));
1711
1733
  await writeFile(path.join(outDir, "assets", "lildocs.css"), css);
1712
1734
  await writeFile(path.join(outDir, "assets", "tabler-icons.css"), tablerIconsCss);
1713
- await writeFile(path.join(outDir, "assets", "search.js"), searchScript);
1714
- await writeFile(path.join(outDir, "assets", "copy-code.js"), copyCodeScript);
1715
- await writeFile(path.join(outDir, "assets", "swup.umd.js"), swupScript);
1716
- await writeFile(path.join(outDir, "assets", "navigation.js"), navigationScript);
1717
1735
  await writeFile(path.join(outDir, "assets", "github-icon.svg"), githubIcon);
1718
1736
  await writeFile(path.join(outDir, "search-index.json"), searchIndexJson);
1719
1737
  await copyAssets(assets);
1720
1738
  } finally {
1739
+ await frontendRenderer?.close();
1721
1740
  await mermaid.close();
1722
1741
  }
1723
1742
  return {
@@ -1768,17 +1787,6 @@ function githubRepositoryName(repositoryUrl) {
1768
1787
  if (!repositoryUrl) return;
1769
1788
  return /^https:\/\/github\.com\/([^/]+\/[^/]+)$/.exec(repositoryUrl)?.[1];
1770
1789
  }
1771
- async function readRenderAsset(name) {
1772
- const bundledPath = path.resolve(sourceDir, "render", name);
1773
- try {
1774
- return await readFile(bundledPath, "utf8");
1775
- } catch {
1776
- return readFile(path.resolve(sourceDir, "../render", name), "utf8");
1777
- }
1778
- }
1779
- async function readSwupAsset() {
1780
- return readFile(path.join(path.dirname(require.resolve("swup")), "Swup.umd.js"), "utf8");
1781
- }
1782
1790
  function buildPageNavigation(pages) {
1783
1791
  const navigation = /* @__PURE__ */ new Map();
1784
1792
  if (pages.length < 2) return navigation;
@@ -1799,95 +1807,18 @@ function buildPageNavigation(pages) {
1799
1807
  return navigation;
1800
1808
  }
1801
1809
  //#endregion
1802
- //#region src/core/server.ts
1803
- const MIME_TYPES = new Map([
1804
- [".html", "text/html; charset=utf-8"],
1805
- [".css", "text/css; charset=utf-8"],
1806
- [".js", "text/javascript; charset=utf-8"],
1807
- [".json", "application/json; charset=utf-8"],
1808
- [".svg", "image/svg+xml"],
1809
- [".png", "image/png"],
1810
- [".jpg", "image/jpeg"],
1811
- [".jpeg", "image/jpeg"],
1812
- [".gif", "image/gif"],
1813
- [".webp", "image/webp"],
1814
- [".avif", "image/avif"],
1815
- [".ico", "image/x-icon"],
1816
- [".txt", "text/plain; charset=utf-8"],
1817
- [".woff", "font/woff"],
1818
- [".woff2", "font/woff2"],
1819
- [".ttf", "font/ttf"],
1820
- [".otf", "font/otf"],
1821
- [".eot", "application/vnd.ms-fontobject"]
1822
- ]);
1823
- async function serveStaticFile(req, res, outDir) {
1824
- if (req.method !== "GET" && req.method !== "HEAD") {
1825
- res.writeHead(405, { allow: "GET, HEAD" });
1826
- res.end("Method Not Allowed");
1827
- return;
1828
- }
1829
- const pathname = requestPathname$1(req);
1830
- if (!pathname) {
1831
- res.writeHead(400);
1832
- res.end("Bad Request");
1833
- return;
1834
- }
1835
- const relativePath = pathname === "/" ? "index.html" : pathname.slice(1);
1836
- const filePath = path.resolve(outDir, relativePath);
1837
- if (!isInside$1(outDir, filePath)) {
1838
- res.writeHead(404);
1839
- res.end("Not Found");
1840
- return;
1841
- }
1842
- let fileStat;
1843
- try {
1844
- fileStat = await stat(filePath);
1845
- } catch {
1846
- res.writeHead(404);
1847
- res.end("Not Found");
1848
- return;
1849
- }
1850
- if (!fileStat.isFile()) {
1851
- res.writeHead(404);
1852
- res.end("Not Found");
1853
- return;
1854
- }
1855
- res.writeHead(200, {
1856
- "content-length": fileStat.size,
1857
- "content-type": MIME_TYPES.get(path.extname(filePath).toLowerCase()) ?? "application/octet-stream"
1858
- });
1859
- if (req.method === "HEAD") {
1860
- res.end();
1861
- return;
1862
- }
1863
- createReadStream(filePath).pipe(res);
1864
- }
1865
- function requestPathname$1(req) {
1866
- try {
1867
- return decodeURIComponent(new URL(req.url ?? "/", "http://lildocs.local").pathname);
1868
- } catch {
1869
- return;
1870
- }
1871
- }
1872
- function isInside$1(root, candidate) {
1873
- const relative = path.relative(root, candidate);
1874
- return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
1875
- }
1876
- //#endregion
1877
1810
  //#region src/core/dev.ts
1878
- const DEV_CLIENT_PATH = "/__lildocs/client.js";
1879
- const DEV_EVENTS_PATH = "/__lildocs/events";
1880
- const DEV_CLIENT_SCRIPT = `
1881
- const events = new EventSource("${DEV_EVENTS_PATH}");
1882
- events.addEventListener("reload", () => location.reload());
1883
- events.onerror = () => console.debug("[lildocs] live reload disconnected");
1884
- `;
1885
1811
  async function startDevServer(options) {
1886
1812
  const input = await resolveInput(options.input, options.cwd, { homePagePreference: "readme-first" });
1887
1813
  const outDir = path.resolve(options.cwd, options.outDir);
1888
1814
  validateDevOutDir(options.cwd, input.docsRoot, outDir, options.outDir);
1889
- const clients = /* @__PURE__ */ new Set();
1890
- let watchers = [];
1815
+ await mkdir(outDir, { recursive: true });
1816
+ const vite = await createFrontendDevServer({
1817
+ cwd: options.cwd,
1818
+ root: outDir,
1819
+ host: options.host,
1820
+ port: options.port
1821
+ });
1891
1822
  let rebuildTimer;
1892
1823
  let rebuilding = false;
1893
1824
  let pending = false;
@@ -1909,12 +1840,14 @@ async function startDevServer(options) {
1909
1840
  background: options.background,
1910
1841
  link: options.link,
1911
1842
  homePagePreference: "readme-first",
1912
- dev: { clientScriptPath: DEV_CLIENT_PATH }
1843
+ dev: {
1844
+ clientScriptPath: frontendDevScriptPath(),
1845
+ viteServer: vite
1846
+ }
1913
1847
  });
1914
- await refreshWatchers();
1915
1848
  const elapsed = Math.round(performance.now() - started);
1916
1849
  console.log(`Rebuilt ${lastSuccessfulBuild.pages.length} page${lastSuccessfulBuild.pages.length === 1 ? "" : "s"} in ${elapsed}ms`);
1917
- if (emitReload) sendReload(clients);
1850
+ if (emitReload) vite.ws.send({ type: "full-reload" });
1918
1851
  } catch (error) {
1919
1852
  if (throwOnFailure) throw error;
1920
1853
  const message = error instanceof Error ? error.message : String(error);
@@ -1933,37 +1866,14 @@ async function startDevServer(options) {
1933
1866
  rebuild(true);
1934
1867
  }, 150);
1935
1868
  }
1936
- async function refreshWatchers() {
1937
- for (const watcher of watchers) watcher.close();
1938
- watchers = [];
1939
- const directories = await collectWatchDirectories(input.docsRoot, outDir);
1940
- for (const directory of directories) watchers.push(watch(directory, { persistent: true }, (_event, filename) => {
1941
- if (filename && shouldIgnore(path.join(directory, filename.toString()), input.docsRoot, outDir)) return;
1942
- scheduleRebuild();
1943
- }));
1944
- }
1945
- await rebuild(false, true);
1946
- const server = http.createServer((req, res) => {
1947
- const pathname = requestPathname(req.url);
1948
- if (pathname === DEV_CLIENT_PATH) {
1949
- res.writeHead(200, { "content-type": "text/javascript; charset=utf-8" });
1950
- res.end(DEV_CLIENT_SCRIPT);
1951
- return;
1952
- }
1953
- if (pathname === DEV_EVENTS_PATH) {
1954
- connectEvents(res, clients);
1955
- return;
1956
- }
1957
- serveStaticFile(req, res, outDir);
1869
+ vite.watcher.add(input.docsRoot);
1870
+ vite.watcher.on("all", (_event, file) => {
1871
+ const changedPath = path.resolve(file);
1872
+ if (!isInside(input.docsRoot, changedPath) || shouldIgnore(changedPath, input.docsRoot, outDir)) return;
1873
+ scheduleRebuild();
1958
1874
  });
1959
- await new Promise((resolve, reject) => {
1960
- server.once("error", reject);
1961
- server.listen(options.port, options.host, () => {
1962
- server.off("error", reject);
1963
- resolve();
1964
- });
1965
- });
1966
- const address = server.address();
1875
+ await rebuild(false, true);
1876
+ const address = vite.httpServer?.address();
1967
1877
  const actualPort = typeof address === "object" && address ? address.port : options.port;
1968
1878
  const url = `http://${options.host}:${actualPort}/`;
1969
1879
  console.log(`lildocs dev server listening at ${url}`);
@@ -1971,11 +1881,7 @@ async function startDevServer(options) {
1971
1881
  url,
1972
1882
  close: async () => {
1973
1883
  if (rebuildTimer) clearTimeout(rebuildTimer);
1974
- for (const watcher of watchers) watcher.close();
1975
- for (const client of clients) client.end();
1976
- await new Promise((resolve, reject) => {
1977
- server.close((error) => error ? reject(error) : resolve());
1978
- });
1884
+ await vite.close();
1979
1885
  }
1980
1886
  };
1981
1887
  }
@@ -1986,46 +1892,12 @@ function validateDevOutDir(cwd, docsRoot, outDir, requestedOutDir) {
1986
1892
  if (outDir === docsRoot) throw new LildocsError("Dev output directory cannot be the docs root.");
1987
1893
  if (isAncestor(outDir, docsRoot)) throw new LildocsError("Dev output directory cannot contain the docs root.");
1988
1894
  }
1989
- async function collectWatchDirectories(docsRoot, outDir) {
1990
- const directories = /* @__PURE__ */ new Set();
1991
- async function walk(directory) {
1992
- if (shouldIgnore(directory, docsRoot, outDir)) return;
1993
- directories.add(directory);
1994
- const entries = await readdir(directory, { withFileTypes: true });
1995
- await Promise.all(entries.map(async (entry) => {
1996
- if (!entry.isDirectory()) return;
1997
- await walk(path.join(directory, entry.name));
1998
- }));
1999
- }
2000
- await walk(docsRoot);
2001
- return [...directories];
2002
- }
2003
1895
  function shouldIgnore(candidate, docsRoot, outDir) {
2004
1896
  const resolved = path.resolve(candidate);
2005
1897
  if (resolved === outDir || isAncestor(outDir, resolved)) return true;
2006
1898
  const relative = path.relative(docsRoot, resolved);
2007
1899
  return relative === "dist" || relative.startsWith(`dist${path.sep}`) || relative === "node_modules" || relative.startsWith(`node_modules${path.sep}`) || isHiddenOrSystemPath(relative);
2008
1900
  }
2009
- function connectEvents(res, clients) {
2010
- res.writeHead(200, {
2011
- "cache-control": "no-cache",
2012
- connection: "keep-alive",
2013
- "content-type": "text/event-stream"
2014
- });
2015
- res.write(": connected\n\n");
2016
- clients.add(res);
2017
- res.on("close", () => clients.delete(res));
2018
- }
2019
- function sendReload(clients) {
2020
- for (const client of clients) client.write("event: reload\ndata: {}\n\n");
2021
- }
2022
- function requestPathname(url) {
2023
- try {
2024
- return new URL(url ?? "/", "http://lildocs.local").pathname;
2025
- } catch {
2026
- return;
2027
- }
2028
- }
2029
1901
  function isAncestor(parent, child) {
2030
1902
  const relative = path.relative(parent, child);
2031
1903
  return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);