@ox-content/vite-plugin 2.16.0 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk.cjs");
3
+ const require_napi = require("./napi.cjs");
3
4
  const require_mermaid = require("./mermaid.cjs");
4
5
  const require_tabs = require("./tabs.cjs");
5
6
  const require_youtube = require("./youtube.cjs");
@@ -17,12 +18,12 @@ let shiki = require("shiki");
17
18
  let node_module = require("node:module");
18
19
  let node_path = require("node:path");
19
20
  node_path = require_chunk.__toESM(node_path, 1);
20
- let fs = require("fs");
21
- fs = require_chunk.__toESM(fs, 1);
22
21
  let fs_promises = require("fs/promises");
23
22
  fs_promises = require_chunk.__toESM(fs_promises, 1);
24
23
  let crypto = require("crypto");
25
24
  crypto = require_chunk.__toESM(crypto, 1);
25
+ let fs = require("fs");
26
+ fs = require_chunk.__toESM(fs, 1);
26
27
  let glob = require("glob");
27
28
  let node_fs_promises = require("node:fs/promises");
28
29
  node_fs_promises = require_chunk.__toESM(node_fs_promises, 1);
@@ -373,9 +374,11 @@ function protectMermaidSvgs(html) {
373
374
  * Restore protected mermaid SVG blocks from placeholders.
374
375
  */
375
376
  function restoreMermaidSvgs(html, svgs) {
376
- let result = html;
377
- for (const [placeholder, content] of svgs) result = result.replace(placeholder, content);
378
- return result;
377
+ if (svgs.size === 0) return html;
378
+ return html.replace(/<!--ox-mermaid-\d+-->/g, (placeholder) => {
379
+ const content = svgs.get(placeholder);
380
+ return content !== void 0 ? content : placeholder;
381
+ });
379
382
  }
380
383
  //#endregion
381
384
  //#region src/transform.ts
@@ -432,7 +435,7 @@ async function loadNapiBindings() {
432
435
  if (napiLoadAttempted) return napiBindings ?? null;
433
436
  napiLoadAttempted = true;
434
437
  try {
435
- const mod = await require_mermaid.importNapiModule();
438
+ const mod = await require_napi.importNapiModule();
436
439
  napiBindings = mod;
437
440
  return mod;
438
441
  } catch (error) {
@@ -545,67 +548,7 @@ if (import.meta.hot) {
545
548
  `;
546
549
  }
547
550
  //#endregion
548
- //#region src/nav-generator.ts
549
- function generateNavMetadata(docs, basePath = "/api") {
550
- return require_mermaid.importNapiModuleSync().generateDocsNavMetadata(docs.map((doc) => doc.file), basePath);
551
- }
552
- function generateNavCode(navItems, exportName = "apiNav") {
553
- return require_mermaid.importNapiModuleSync().generateDocsNavCode(navItems, exportName);
554
- }
555
- //#endregion
556
551
  //#region src/docs.ts
557
- /**
558
- * Source Documentation Extraction and Generation
559
- *
560
- * This module provides comprehensive tools for extracting JSDoc/TSDoc comments
561
- * from TypeScript/JavaScript source files and automatically generating Markdown
562
- * documentation.
563
- *
564
- * ## Features
565
- *
566
- * - **Automatic Extraction**: Parses JSDoc comments from functions, classes, interfaces, and types
567
- * - **Flexible Filtering**: Include/exclude patterns for selective documentation
568
- * - **Markdown Generation**: Converts extracted docs to organized Markdown files
569
- * - **Navigation Generation**: Auto-generates sidebar navigation metadata
570
- * - **GitHub Links**: Includes clickable links to source code on GitHub
571
- *
572
- * ## Supported JSDoc Tags
573
- *
574
- * - `@param {type} name - description` - Function parameter documentation
575
- * - `@returns {type} description` - Return value documentation
576
- * - `@example` - Code examples (multi-line blocks)
577
- * - `@private` - Mark item as private (excluded from docs if private=false)
578
- * - `@default value` - Default parameter value
579
- * - Custom tags are preserved in the `tags` field
580
- *
581
- * ## Usage Flow
582
- *
583
- * 1. Call `extractDocs()` to parse source files
584
- * 2. Call `generateMarkdown()` to create Markdown content
585
- * 3. Call `writeDocs()` to write files to output directory
586
- * 4. Generated nav.ts can be imported for sidebar navigation
587
- *
588
- * @example
589
- * ```typescript
590
- * import { extractDocs, generateMarkdown, writeDocs } from './docs';
591
- *
592
- * const docsOptions = {
593
- * enabled: true,
594
- * src: ['./src'],
595
- * out: './docs/api',
596
- * include: ['**\/*.ts'],
597
- * exclude: ['**\/*.test.ts'],
598
- * groupBy: 'file',
599
- * githubUrl: 'https://github.com/user/project',
600
- * };
601
- *
602
- * const extracted = await extractDocs(['./src'], docsOptions);
603
- * const markdown = generateMarkdown(extracted, docsOptions);
604
- * await writeDocs(markdown, './docs/api', extracted, docsOptions);
605
- * ```
606
- */
607
- const DOCS_MANIFEST_FILE = ".ox-content-docs-manifest.json";
608
- const DOCS_DATA_FILE = "docs.json";
609
552
  const DEFAULT_DOCS_INCLUDE = [
610
553
  "**/*.ts",
611
554
  "**/*.tsx",
@@ -682,7 +625,7 @@ const DEFAULT_DOCS_INCLUDE = [
682
625
  * ```
683
626
  */
684
627
  async function extractDocs(srcDirs, options) {
685
- const napi = await require_mermaid.importNapiModule();
628
+ const napi = await require_napi.importNapiModule();
686
629
  if (options.entryPoints?.length) {
687
630
  const extractDocsFromEntryPoints = napi.extractDocsFromEntryPoints;
688
631
  if (!extractDocsFromEntryPoints) throw new Error("[ox-content] extractDocsFromEntryPoints is not available from @ox-content/napi.");
@@ -695,65 +638,40 @@ async function extractDocs(srcDirs, options) {
695
638
  entries: doc.entries
696
639
  }));
697
640
  }
698
- const extractFileDocEntries = napi.extractFileDocEntries;
699
- if (!extractFileDocEntries) throw new Error("[ox-content] extractFileDocEntries is not available from @ox-content/napi.");
700
- const results = [];
701
- for (const srcDir of srcDirs) {
702
- const files = napi.collectDocsSourceFiles(srcDir, options.include, options.exclude);
703
- for (const file of files) {
704
- const entries = extractFileDocEntries(file, options.private, options.internal);
705
- if (entries.length > 0) results.push({
706
- file,
707
- entries
708
- });
709
- }
710
- }
711
- return results;
641
+ const extractDocsFromDirectories = napi.extractDocsFromDirectories;
642
+ if (!extractDocsFromDirectories) throw new Error("[ox-content] extractDocsFromDirectories is not available from @ox-content/napi.");
643
+ return extractDocsFromDirectories(srcDirs, options.include, options.exclude, options.private, options.internal).map((doc) => ({
644
+ file: doc.file,
645
+ entries: doc.entries
646
+ }));
712
647
  }
713
648
  /**
714
649
  * Generates Markdown documentation from extracted docs.
715
650
  */
716
651
  function generateMarkdown(docs, options) {
717
- const napi = require_mermaid.importNapiModuleSync();
652
+ const napi = require_napi.importNapiModuleSync();
718
653
  if (typeof napi.generateDocsMarkdown !== "function") throw new Error("[ox-content] generateDocsMarkdown is not available from @ox-content/napi. Please rebuild the NAPI package.");
719
654
  return napi.generateDocsMarkdown(toRustDocsModules(docs), {
720
655
  groupBy: options.groupBy,
721
- githubUrl: options.githubUrl
656
+ githubUrl: options.githubUrl,
657
+ linkStyle: options.linkStyle,
658
+ basePath: options.basePath,
659
+ pathStrategy: options.pathStrategy
722
660
  });
723
661
  }
724
662
  /**
725
663
  * Writes generated documentation to the output directory.
726
664
  */
727
665
  async function writeDocs(docs, outDir, extractedDocs, options) {
728
- await fs.promises.mkdir(outDir, { recursive: true });
729
- const generatedFiles = new Set(Object.keys(docs));
730
- if (extractedDocs && options?.generateNav && options.groupBy === "file") generatedFiles.add("nav.ts");
731
- if (extractedDocs) generatedFiles.add(DOCS_DATA_FILE);
732
- const manifestPath = path.join(outDir, DOCS_MANIFEST_FILE);
733
- let previousFiles = [];
734
- try {
735
- previousFiles = JSON.parse(await fs.promises.readFile(manifestPath, "utf-8"));
736
- } catch {
737
- previousFiles = [];
738
- }
739
- for (const staleFile of previousFiles) {
740
- if (generatedFiles.has(staleFile)) continue;
741
- await fs.promises.rm(path.join(outDir, staleFile), { force: true });
742
- }
743
- for (const [fileName, content] of Object.entries(docs)) {
744
- const filePath = path.join(outDir, fileName);
745
- await fs.promises.writeFile(filePath, content, "utf-8");
746
- }
747
- if (extractedDocs && options?.generateNav && options.groupBy === "file") {
748
- const navCode = generateNavCode(generateNavMetadata(extractedDocs, "/api"), "apiNav");
749
- const navFilePath = path.join(outDir, "nav.ts");
750
- await fs.promises.writeFile(navFilePath, navCode, "utf-8");
751
- }
752
- if (extractedDocs) {
753
- const napi = require_mermaid.importNapiModuleSync();
754
- await fs.promises.writeFile(path.join(outDir, DOCS_DATA_FILE), napi.generateDocsDataJson(toRustDocsModules(extractedDocs), (/* @__PURE__ */ new Date()).toISOString()), "utf-8");
755
- }
756
- await fs.promises.writeFile(manifestPath, JSON.stringify([...generatedFiles].sort(), null, 2), "utf-8");
666
+ const napi = require_napi.importNapiModuleSync();
667
+ if (typeof napi.writeGeneratedDocs !== "function") throw new Error("[ox-content] writeGeneratedDocs is not available from @ox-content/napi. Please rebuild the NAPI package.");
668
+ napi.writeGeneratedDocs(docs, outDir, extractedDocs ? toRustDocsModules(extractedDocs) : void 0, {
669
+ generateNav: options?.generateNav ?? false,
670
+ groupBy: options?.groupBy ?? "file",
671
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
672
+ basePath: options?.basePath,
673
+ pathStrategy: options?.pathStrategy
674
+ });
757
675
  }
758
676
  function toRustDocsModules(docs) {
759
677
  return docs.map((doc) => ({
@@ -778,9 +696,6 @@ function toRustDocsModules(docs) {
778
696
  }))
779
697
  }));
780
698
  }
781
- /**
782
- * Resolves docs options with defaults.
783
- */
784
699
  function resolveDocsOptions(options) {
785
700
  if (options === false) return false;
786
701
  const opts = options || {};
@@ -801,6 +716,9 @@ function resolveDocsOptions(options) {
801
716
  toc: false,
802
717
  groupBy: opts.groupBy ?? "file",
803
718
  githubUrl: opts.githubUrl,
719
+ linkStyle: opts.linkStyle ?? "markdown",
720
+ basePath: opts.basePath,
721
+ pathStrategy: opts.pathStrategy ?? "flat",
804
722
  generateNav: opts.generateNav ?? true
805
723
  };
806
724
  }
@@ -888,6 +806,8 @@ async function renderHtmlToPng(page, html, width, height, publicDir) {
888
806
  }
889
807
  //#endregion
890
808
  //#region src/og-image/browser.ts
809
+ const PLAYWRIGHT_BROWSER_INSTALL_HINT = "Install Playwright browsers with `npx playwright install chromium` to enable OG image generation.";
810
+ let chromiumUnavailableWarned = false;
891
811
  /**
892
812
  * Opens a Chromium browser and returns a session for rendering OG images.
893
813
  * Returns null if Playwright/Chromium is not available.
@@ -927,10 +847,20 @@ async function openBrowser() {
927
847
  }
928
848
  };
929
849
  } catch (err) {
930
- console.warn("[ox-content:og-image] Chromium not available, skipping OG image generation.", err instanceof Error ? err.message : err);
850
+ warnChromiumUnavailableOnce(err);
931
851
  return null;
932
852
  }
933
853
  }
854
+ function warnChromiumUnavailableOnce(err) {
855
+ if (chromiumUnavailableWarned) return;
856
+ chromiumUnavailableWarned = true;
857
+ console.warn(`[ox-content:og-image] Chromium not available, skipping OG image generation. ${formatChromiumUnavailableDetail(err)}`);
858
+ }
859
+ function formatChromiumUnavailableDetail(err) {
860
+ const message = err instanceof Error ? err.message : String(err);
861
+ if (message.includes("Executable doesn't exist") || message.includes("Please run the following command to download new browsers")) return PLAYWRIGHT_BROWSER_INSTALL_HINT;
862
+ return message.split(/\r?\n/).find((line) => line.trim())?.trim() ?? "Unknown launch error.";
863
+ }
934
864
  //#endregion
935
865
  //#region src/og-image/template.ts
936
866
  /**
@@ -1732,38 +1662,53 @@ function resolveSsgOptions(ssg) {
1732
1662
  * Extracts title from content or frontmatter.
1733
1663
  */
1734
1664
  function extractTitle$1(content, frontmatter) {
1735
- return require_mermaid.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
1665
+ return require_napi.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
1736
1666
  }
1737
1667
  /**
1738
1668
  * Generates bare HTML page (no navigation, no styles).
1739
1669
  */
1740
1670
  function generateBareHtmlPage(content, title) {
1741
- return require_mermaid.importNapiModuleSync().generateSsgBareHtml(content, title);
1671
+ return require_napi.importNapiModuleSync().generateSsgBareHtml(content, title);
1742
1672
  }
1743
1673
  /**
1744
- * Generates HTML page with navigation using Rust NAPI bindings.
1674
+ * Per-build cache for the Rust-facing nav conversion. `navGroups` is the same
1675
+ * `context.navItems` reference for every page in a build, so the deep recursive
1676
+ * copy below only needs to run once per build instead of once per page.
1745
1677
  */
1746
- async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
1747
- const mod = await require_mermaid.importNapiModule();
1748
- const toRustTocEntry = (entry) => ({
1749
- depth: entry.depth,
1750
- text: entry.text,
1751
- slug: entry.slug,
1752
- children: entry.children?.map(toRustTocEntry) ?? []
1753
- });
1754
- const tocForRust = pageData.toc.map(toRustTocEntry);
1755
- const toRustNavItem = (item) => ({
1678
+ const navGroupsForRustCache = /* @__PURE__ */ new WeakMap();
1679
+ function toRustNavItem(item) {
1680
+ return {
1756
1681
  title: item.title,
1757
1682
  path: item.path,
1758
1683
  href: item.href,
1759
1684
  children: item.children?.map(toRustNavItem),
1760
1685
  collapsed: item.collapsed
1761
- });
1762
- const navGroupsForRust = navGroups.map((group) => ({
1686
+ };
1687
+ }
1688
+ function convertNavGroupsForRust(navGroups) {
1689
+ const cached = navGroupsForRustCache.get(navGroups);
1690
+ if (cached) return cached;
1691
+ const converted = navGroups.map((group) => ({
1763
1692
  title: group.title,
1764
1693
  collapsed: group.collapsed,
1765
1694
  items: group.items.map(toRustNavItem)
1766
1695
  }));
1696
+ navGroupsForRustCache.set(navGroups, converted);
1697
+ return converted;
1698
+ }
1699
+ /**
1700
+ * Generates HTML page with navigation using Rust NAPI bindings.
1701
+ */
1702
+ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
1703
+ const mod = await require_napi.importNapiModule();
1704
+ const toRustTocEntry = (entry) => ({
1705
+ depth: entry.depth,
1706
+ text: entry.text,
1707
+ slug: entry.slug,
1708
+ children: entry.children?.map(toRustTocEntry) ?? []
1709
+ });
1710
+ const tocForRust = pageData.toc.map(toRustTocEntry);
1711
+ const navGroupsForRust = convertNavGroupsForRust(navGroups);
1767
1712
  const themeForRust = theme ? require_vitepress.themeToNapi(theme) : void 0;
1768
1713
  const entryPageForRust = pageData.entryPage ? {
1769
1714
  hero: pageData.entryPage.hero ? {
@@ -1818,7 +1763,7 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
1818
1763
  });
1819
1764
  }
1820
1765
  async function externalizeSharedPageAssets(pages, outDir, base) {
1821
- const optimized = (await require_mermaid.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
1766
+ const optimized = (await require_napi.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
1822
1767
  await Promise.all(optimized.assets.map(async (asset) => {
1823
1768
  await fs_promises.mkdir(path.dirname(asset.outputPath), { recursive: true });
1824
1769
  await fs_promises.writeFile(asset.outputPath, asset.content, "utf-8");
@@ -1832,45 +1777,45 @@ async function externalizeSharedPageAssets(pages, outDir, base) {
1832
1777
  * Converts a markdown file path to a relative URL path.
1833
1778
  */
1834
1779
  function getUrlPath$1(inputPath, srcDir) {
1835
- return require_mermaid.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
1780
+ return require_napi.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
1836
1781
  }
1837
1782
  /**
1838
1783
  * Resolves manual navigation config to the format used by the built-in SSG renderer.
1839
1784
  */
1840
1785
  function resolveNavigationGroups(navigation, base, extension) {
1841
1786
  if (!navigation) return;
1842
- return require_mermaid.importNapiModuleSync().resolveSsgNavigationGroups(navigation, base, extension);
1787
+ return require_napi.importNapiModuleSync().resolveSsgNavigationGroups(navigation, base, extension);
1843
1788
  }
1844
1789
  function getPageLocale(urlPath, i18n) {
1845
1790
  if (!i18n) return void 0;
1846
- return require_mermaid.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
1791
+ return require_napi.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
1847
1792
  }
1848
1793
  function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
1849
- return require_mermaid.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
1794
+ return require_napi.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
1850
1795
  }
1851
1796
  /**
1852
1797
  * Formats a file/dir name as a title.
1853
1798
  */
1854
1799
  function formatTitle(name) {
1855
- return require_mermaid.importNapiModuleSync().formatSsgTitle(name);
1800
+ return require_napi.importNapiModuleSync().formatSsgTitle(name);
1856
1801
  }
1857
1802
  /**
1858
1803
  * Collects all markdown files from the source directory.
1859
1804
  */
1860
1805
  async function collectMarkdownFiles(srcDir, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
1861
- return require_mermaid.importNapiModuleSync().collectSsgMarkdownFiles(srcDir, [...extensions]);
1806
+ return require_napi.importNapiModuleSync().collectSsgMarkdownFiles(srcDir, [...extensions]);
1862
1807
  }
1863
1808
  /**
1864
1809
  * Builds navigation items from markdown files, grouped by directory.
1865
1810
  */
1866
1811
  function buildNavItems(markdownFiles, srcDir, base, extension) {
1867
- return require_mermaid.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
1812
+ return require_napi.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
1868
1813
  }
1869
1814
  /**
1870
1815
  * Builds navigation items from an explicit theme sidebar tree.
1871
1816
  */
1872
1817
  function buildThemeNavItems(sidebar, base, extension) {
1873
- return require_mermaid.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
1818
+ return require_napi.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
1874
1819
  }
1875
1820
  /**
1876
1821
  * Builds all markdown files to static HTML.
@@ -1883,149 +1828,201 @@ async function buildSsg(options, root) {
1883
1828
  };
1884
1829
  const srcDir = path.resolve(root, options.srcDir);
1885
1830
  const outDir = path.resolve(root, options.outDir);
1886
- const base = options.base.endsWith("/") ? options.base : options.base + "/";
1887
1831
  const generatedFiles = [];
1888
- const generatedPages = [];
1889
1832
  const errors = [];
1890
- if (ssgOptions.clean) try {
1833
+ await cleanOutputDirectory(ssgOptions, outDir);
1834
+ const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
1835
+ const context = await createBuildSsgContext(options, root, srcDir, outDir, markdownFiles);
1836
+ const collected = await collectPageResults(context, markdownFiles);
1837
+ errors.push(...collected.errors);
1838
+ await generateOgImageAssets(context, collected, generatedFiles, errors);
1839
+ await writeGeneratedPages(await generateHtmlPages(context, collected.pageResults, collected, errors), context, generatedFiles);
1840
+ return {
1841
+ files: generatedFiles,
1842
+ errors
1843
+ };
1844
+ }
1845
+ async function cleanOutputDirectory(ssgOptions, outDir) {
1846
+ if (!ssgOptions.clean) return;
1847
+ try {
1891
1848
  await fs_promises.rm(outDir, {
1892
1849
  recursive: true,
1893
1850
  force: true
1894
1851
  });
1895
1852
  } catch {}
1896
- const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
1897
- const navItems = resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension));
1898
- let siteName = ssgOptions.siteName ?? "Documentation";
1899
- if (!ssgOptions.siteName) try {
1853
+ }
1854
+ async function createBuildSsgContext(options, root, srcDir, outDir, markdownFiles) {
1855
+ const ssgOptions = options.ssg;
1856
+ const base = options.base.endsWith("/") ? options.base : options.base + "/";
1857
+ return {
1858
+ options,
1859
+ ssgOptions,
1860
+ root,
1861
+ srcDir,
1862
+ outDir,
1863
+ base,
1864
+ navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
1865
+ siteName: await resolveSiteName$1(root, ssgOptions),
1866
+ shouldGenerateOgImages: (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare,
1867
+ napi: ssgOptions.lastUpdated ? await require_napi.importNapiModule() : void 0
1868
+ };
1869
+ }
1870
+ async function resolveSiteName$1(root, ssgOptions) {
1871
+ if (ssgOptions.siteName) return ssgOptions.siteName;
1872
+ try {
1900
1873
  const pkgPath = path.join(root, "package.json");
1901
1874
  const pkg = JSON.parse(await fs_promises.readFile(pkgPath, "utf-8"));
1902
- if (pkg.name) siteName = formatTitle(pkg.name);
1903
- } catch {}
1904
- const ogImageEntries = [];
1905
- const ogImageInputPaths = [];
1906
- const ogImageUrlMap = /* @__PURE__ */ new Map();
1907
- const shouldGenerateOgImages = (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare;
1908
- const pageResults = [];
1909
- const napi = ssgOptions.lastUpdated ? await require_mermaid.importNapiModule() : void 0;
1875
+ return pkg.name ? formatTitle(pkg.name) : "Documentation";
1876
+ } catch {
1877
+ return "Documentation";
1878
+ }
1879
+ }
1880
+ async function collectPageResults(context, markdownFiles) {
1881
+ const collected = {
1882
+ pageResults: [],
1883
+ ogImageEntries: [],
1884
+ ogImageInputPaths: [],
1885
+ ogImageUrlMap: /* @__PURE__ */ new Map(),
1886
+ errors: []
1887
+ };
1910
1888
  for (const inputPath of markdownFiles) try {
1911
- const result = await transformMarkdown(await fs_promises.readFile(inputPath, "utf-8"), inputPath, options, {
1912
- convertMdLinks: true,
1913
- baseUrl: base,
1914
- sourcePath: inputPath
1915
- });
1916
- const frontmatter = require_vitepress.normalizeVitePressFrontmatter(result.frontmatter);
1917
- let transformedHtml = result.html;
1918
- const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
1919
- transformedHtml = protectedHtml;
1920
- const pluginOptions = {
1921
- tabs: true,
1922
- youtube: true,
1923
- github: options.embeds.github,
1924
- openGraph: options.embeds.openGraph,
1925
- mermaid: true,
1926
- githubToken: process.env.GITHUB_TOKEN
1927
- };
1928
- transformedHtml = await transformAllPlugins(transformedHtml, pluginOptions);
1929
- if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
1930
- transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
1931
- const title = extractTitle$1(transformedHtml, frontmatter);
1932
- const description = frontmatter.description;
1933
- const routePaths = getRoutePaths(inputPath, srcDir, outDir, base, ssgOptions.extension, ssgOptions.siteUrl);
1934
- pageResults.push({
1935
- inputPath,
1936
- routePaths,
1937
- transformedHtml,
1938
- title,
1939
- description,
1940
- lastUpdated: napi?.getGitLastUpdated(inputPath, root) ?? void 0,
1941
- frontmatter,
1942
- toc: result.toc
1943
- });
1944
- if (shouldGenerateOgImages) {
1945
- const { layout: _layout, ...frontmatterRest } = frontmatter;
1946
- ogImageEntries.push({
1947
- props: {
1948
- ...frontmatterRest,
1949
- title,
1950
- description,
1951
- siteName
1952
- },
1953
- outputPath: routePaths.ogImagePath
1954
- });
1955
- ogImageInputPaths.push(inputPath);
1956
- ogImageUrlMap.set(inputPath, routePaths.ogImageUrl);
1957
- }
1889
+ const pageResult = await transformSsgPage(context, inputPath);
1890
+ collected.pageResults.push(pageResult);
1891
+ collectOgImageEntry(context, pageResult, collected);
1958
1892
  } catch (err) {
1959
1893
  const errorMessage = err instanceof Error ? err.message : String(err);
1960
- errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1894
+ collected.errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1961
1895
  }
1962
- if (shouldGenerateOgImages && ogImageEntries.length > 0) try {
1963
- const ogResults = await generateOgImages(ogImageEntries, options.ogImageOptions, root);
1964
- let ogSuccessCount = 0;
1965
- for (let i = 0; i < ogResults.length; i++) {
1966
- const result = ogResults[i];
1967
- if (result.error) {
1968
- errors.push(`OG image failed for ${result.outputPath}: ${result.error}`);
1969
- ogImageUrlMap.delete(ogImageInputPaths[i]);
1970
- } else {
1971
- generatedFiles.push(result.outputPath);
1972
- ogSuccessCount++;
1973
- }
1974
- }
1975
- if (ogSuccessCount > 0) {
1976
- const cachedCount = ogResults.filter((r) => r.cached && !r.error).length;
1977
- console.log(`[ox-content:og-image] Generated ${ogSuccessCount} OG images` + (cachedCount > 0 ? ` (${cachedCount} from cache)` : ""));
1978
- }
1896
+ return collected;
1897
+ }
1898
+ async function transformSsgPage(context, inputPath) {
1899
+ const result = await transformMarkdown(await fs_promises.readFile(inputPath, "utf-8"), inputPath, context.options, {
1900
+ convertMdLinks: true,
1901
+ baseUrl: context.base,
1902
+ sourcePath: inputPath
1903
+ });
1904
+ const frontmatter = require_vitepress.normalizeVitePressFrontmatter(result.frontmatter);
1905
+ const transformedHtml = await transformSsgHtml(result.html, context.options);
1906
+ const title = extractTitle$1(transformedHtml, frontmatter);
1907
+ return {
1908
+ inputPath,
1909
+ routePaths: getRoutePaths(inputPath, context.srcDir, context.outDir, context.base, context.ssgOptions.extension, context.ssgOptions.siteUrl),
1910
+ transformedHtml,
1911
+ title,
1912
+ description: frontmatter.description,
1913
+ lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
1914
+ frontmatter,
1915
+ toc: result.toc
1916
+ };
1917
+ }
1918
+ async function transformSsgHtml(html, options) {
1919
+ const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(html);
1920
+ let transformedHtml = await transformAllPlugins(protectedHtml, {
1921
+ tabs: true,
1922
+ youtube: true,
1923
+ github: options.embeds.github,
1924
+ openGraph: options.embeds.openGraph,
1925
+ mermaid: true,
1926
+ githubToken: process.env.GITHUB_TOKEN
1927
+ });
1928
+ if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
1929
+ return restoreMermaidSvgs(transformedHtml, mermaidSvgs);
1930
+ }
1931
+ function collectOgImageEntry(context, pageResult, collected) {
1932
+ if (!context.shouldGenerateOgImages) return;
1933
+ const { layout: _layout, ...frontmatterRest } = pageResult.frontmatter;
1934
+ collected.ogImageEntries.push({
1935
+ props: {
1936
+ ...frontmatterRest,
1937
+ title: pageResult.title,
1938
+ description: pageResult.description,
1939
+ siteName: context.siteName
1940
+ },
1941
+ outputPath: pageResult.routePaths.ogImagePath
1942
+ });
1943
+ collected.ogImageInputPaths.push(pageResult.inputPath);
1944
+ collected.ogImageUrlMap.set(pageResult.inputPath, pageResult.routePaths.ogImageUrl);
1945
+ }
1946
+ async function generateOgImageAssets(context, collected, generatedFiles, errors) {
1947
+ if (!context.shouldGenerateOgImages || collected.ogImageEntries.length === 0) return;
1948
+ try {
1949
+ const ogResults = await generateOgImages(collected.ogImageEntries, context.options.ogImageOptions, context.root);
1950
+ if (clearMissingBrowserOgImages(ogResults, collected)) return;
1951
+ reportOgImageResults(ogResults, collected, generatedFiles, errors);
1979
1952
  } catch (err) {
1980
1953
  const errorMessage = err instanceof Error ? err.message : String(err);
1981
1954
  console.warn(`[ox-content:og-image] Batch generation failed: ${errorMessage}`);
1982
- ogImageUrlMap.clear();
1955
+ collected.ogImageUrlMap.clear();
1983
1956
  }
1984
- for (const pageResult of pageResults) try {
1985
- const { inputPath, routePaths, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
1986
- let pageOgImage = ssgOptions.ogImage;
1987
- if (shouldGenerateOgImages && ogImageUrlMap.has(inputPath)) pageOgImage = ogImageUrlMap.get(inputPath);
1988
- let entryPage;
1989
- if (frontmatter.layout === "entry") entryPage = {
1990
- hero: frontmatter.hero,
1991
- features: frontmatter.features
1992
- };
1993
- let html;
1994
- if (ssgOptions.bare) html = generateBareHtmlPage(transformedHtml, title);
1995
- else {
1996
- const pageData = {
1997
- title,
1998
- description,
1999
- content: transformedHtml,
2000
- toc,
2001
- lastUpdated,
2002
- frontmatter,
2003
- path: routePaths.urlPath,
2004
- href: routePaths.href,
2005
- entryPage
2006
- };
2007
- html = await generateHtmlPage(pageData, navItems, siteName, base, pageOgImage, ssgOptions.theme, getPageLocale(pageData.path, options.i18n), options.i18n ? options.i18n.locales : void 0);
1957
+ }
1958
+ function clearMissingBrowserOgImages(ogResults, collected) {
1959
+ if (!(ogResults.length > 0 && ogResults.every((result) => result.error === "Chromium not available"))) return false;
1960
+ for (const inputPath of collected.ogImageInputPaths) collected.ogImageUrlMap.delete(inputPath);
1961
+ return true;
1962
+ }
1963
+ function reportOgImageResults(ogResults, collected, generatedFiles, errors) {
1964
+ let ogSuccessCount = 0;
1965
+ for (let i = 0; i < ogResults.length; i++) {
1966
+ const result = ogResults[i];
1967
+ if (result.error) {
1968
+ errors.push(`OG image failed for ${result.outputPath}: ${result.error}`);
1969
+ collected.ogImageUrlMap.delete(collected.ogImageInputPaths[i]);
1970
+ } else {
1971
+ generatedFiles.push(result.outputPath);
1972
+ ogSuccessCount++;
2008
1973
  }
1974
+ }
1975
+ if (ogSuccessCount > 0) {
1976
+ const cachedCount = ogResults.filter((result) => result.cached && !result.error).length;
1977
+ console.log(`[ox-content:og-image] Generated ${ogSuccessCount} OG images` + (cachedCount > 0 ? ` (${cachedCount} from cache)` : ""));
1978
+ }
1979
+ }
1980
+ async function generateHtmlPages(context, pageResults, collected, errors) {
1981
+ const generatedPages = [];
1982
+ for (const pageResult of pageResults) try {
2009
1983
  generatedPages.push({
2010
- inputPath,
2011
- outputPath: routePaths.outputPath,
2012
- html
1984
+ inputPath: pageResult.inputPath,
1985
+ outputPath: pageResult.routePaths.outputPath,
1986
+ html: await renderSsgPage(context, pageResult, collected.ogImageUrlMap)
2013
1987
  });
2014
1988
  } catch (err) {
2015
1989
  const errorMessage = err instanceof Error ? err.message : String(err);
2016
1990
  errors.push(`Failed to generate HTML for ${pageResult.inputPath}: ${errorMessage}`);
2017
1991
  }
2018
- const optimizedOutput = await externalizeSharedPageAssets(generatedPages, outDir, base);
1992
+ return generatedPages;
1993
+ }
1994
+ async function renderSsgPage(context, pageResult, ogImageUrlMap) {
1995
+ if (context.ssgOptions.bare) return generateBareHtmlPage(pageResult.transformedHtml, pageResult.title);
1996
+ const pageData = createSsgPageData(pageResult);
1997
+ const pageOgImage = context.shouldGenerateOgImages && ogImageUrlMap.has(pageResult.inputPath) ? ogImageUrlMap.get(pageResult.inputPath) : context.ssgOptions.ogImage;
1998
+ return generateHtmlPage(pageData, context.navItems, context.siteName, context.base, pageOgImage, context.ssgOptions.theme, getPageLocale(pageData.path, context.options.i18n), context.options.i18n ? context.options.i18n.locales : void 0);
1999
+ }
2000
+ function createSsgPageData(pageResult) {
2001
+ const { frontmatter } = pageResult;
2002
+ const entryPage = frontmatter.layout === "entry" ? {
2003
+ hero: frontmatter.hero,
2004
+ features: frontmatter.features
2005
+ } : void 0;
2006
+ return {
2007
+ title: pageResult.title,
2008
+ description: pageResult.description,
2009
+ content: pageResult.transformedHtml,
2010
+ toc: pageResult.toc,
2011
+ lastUpdated: pageResult.lastUpdated,
2012
+ frontmatter,
2013
+ path: pageResult.routePaths.urlPath,
2014
+ href: pageResult.routePaths.href,
2015
+ entryPage
2016
+ };
2017
+ }
2018
+ async function writeGeneratedPages(generatedPages, context, generatedFiles) {
2019
+ const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
2019
2020
  generatedFiles.push(...optimizedOutput.assets);
2020
2021
  for (const page of optimizedOutput.pages) {
2021
2022
  await fs_promises.mkdir(path.dirname(page.outputPath), { recursive: true });
2022
2023
  await fs_promises.writeFile(page.outputPath, page.html, "utf-8");
2023
2024
  generatedFiles.push(page.outputPath);
2024
2025
  }
2025
- return {
2026
- files: generatedFiles,
2027
- errors
2028
- };
2029
2026
  }
2030
2027
  //#endregion
2031
2028
  //#region src/search.ts
@@ -2037,7 +2034,7 @@ async function buildSsg(options, root) {
2037
2034
  let oxContent$1 = null;
2038
2035
  async function getOxContent() {
2039
2036
  if (!oxContent$1) try {
2040
- oxContent$1 = await require_mermaid.importNapiModule();
2037
+ oxContent$1 = await require_napi.importNapiModule();
2041
2038
  } catch {
2042
2039
  console.warn("[ox-content] Native bindings not available, search disabled");
2043
2040
  return null;
@@ -2091,7 +2088,7 @@ async function writeSearchIndex(indexJson, outDir) {
2091
2088
  * This is injected into the bundle as a virtual module.
2092
2089
  */
2093
2090
  function generateSearchModule(options, indexPath) {
2094
- return require_mermaid.importNapiModuleSync().generateSearchModuleFromOptions(options, indexPath);
2091
+ return require_napi.importNapiModuleSync().generateSearchModuleFromOptions(options, indexPath);
2095
2092
  }
2096
2093
  //#endregion
2097
2094
  //#region src/dev-server.ts
@@ -2705,7 +2702,7 @@ function createI18nPlugin(resolvedOptions) {
2705
2702
  return;
2706
2703
  }
2707
2704
  try {
2708
- const { checkI18nProject } = await require_mermaid.importNapiModule();
2705
+ const { checkI18nProject } = await require_napi.importNapiModule();
2709
2706
  const checkResult = checkI18nProject(dictDir, [path.resolve(root, "src"), path.resolve(root, "content")], i18nOptions.functionNames, i18nOptions.defaultLocale);
2710
2707
  if (checkResult.errorCount > 0 || checkResult.warningCount > 0) {
2711
2708
  for (const diag of checkResult.diagnostics) if (diag.severity === "error") console.error(`[ox-content:i18n] ${diag.message}`);
@@ -2859,7 +2856,10 @@ function stripMaskedDocument(result) {
2859
2856
  };
2860
2857
  }
2861
2858
  function normalizeLintOptions(options) {
2862
- const languages = options.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language)) ?? options.dictionary?.standard?.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language)) ?? [...DEFAULT_LANGUAGES];
2859
+ const standardDictionary = options.dictionary?.standard && typeof options.dictionary.standard === "object" ? options.dictionary.standard : void 0;
2860
+ const optionLanguages = options.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language));
2861
+ const standardLanguages = standardDictionary?.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language));
2862
+ const languages = optionLanguages ?? standardLanguages ?? [...DEFAULT_LANGUAGES];
2863
2863
  const standard = normalizeStandardDictionaryOptions(options.dictionary?.standard, languages);
2864
2864
  return {
2865
2865
  dictionary: {
@@ -2900,6 +2900,12 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
2900
2900
  const { spellCheckDocument } = await loadCspellLib();
2901
2901
  const locale = standard.languages.join(",");
2902
2902
  const settings = createStandardSpellcheckSettings(options, locale);
2903
+ const spellCheckOptions = {
2904
+ generateSuggestions: true,
2905
+ noConfigSearch: true,
2906
+ numSuggestions: 3,
2907
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo
2908
+ };
2903
2909
  return Promise.all(maskedDocuments.map(async (maskedDocument, index) => {
2904
2910
  if (maskedDocument.trim().length === 0) return [];
2905
2911
  return (await spellCheckDocument({
@@ -2907,12 +2913,7 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
2907
2913
  locale,
2908
2914
  text: maskedDocument,
2909
2915
  uri: `file:///ox-content-lint-${index}.md`
2910
- }, {
2911
- generateSuggestions: true,
2912
- noConfigSearch: true,
2913
- numSuggestions: 3,
2914
- resolveImportsRelativeTo: standard.resolveImportsRelativeTo
2915
- }, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages));
2916
+ }, spellCheckOptions, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages, maskedDocument));
2916
2917
  }));
2917
2918
  } catch (error) {
2918
2919
  const imports = standard.imports.join(", ");
@@ -2933,8 +2934,8 @@ async function loadCspellLib() {
2933
2934
  cspellLibPromise ??= import("cspell-lib");
2934
2935
  return cspellLibPromise;
2935
2936
  }
2936
- function mapStandardIssueToDiagnostic(issue, languages) {
2937
- const line = issue.line.position.line + 1;
2937
+ function mapStandardIssueToDiagnostic(issue, languages, documentText) {
2938
+ const line = getLineNumberAtOffset(documentText, issue.line.offset);
2938
2939
  const column = issue.offset - issue.line.offset + 1;
2939
2940
  return {
2940
2941
  column,
@@ -2948,6 +2949,11 @@ function mapStandardIssueToDiagnostic(issue, languages) {
2948
2949
  suggestions: issue.suggestions?.slice(0, 3)
2949
2950
  };
2950
2951
  }
2952
+ function getLineNumberAtOffset(text, offset) {
2953
+ let line = 1;
2954
+ for (let index = 0; index < offset && index < text.length; index++) if (text.charCodeAt(index) === 10) line++;
2955
+ return line;
2956
+ }
2951
2957
  function inferStandardIssueLanguage(word, languages) {
2952
2958
  if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
2953
2959
  if (/[\p{Script=Han}]/u.test(word)) {
@@ -3684,21 +3690,35 @@ init_page_context();
3684
3690
  function oxContent(options = {}) {
3685
3691
  const resolvedOptions = resolveOptions(options);
3686
3692
  let config;
3687
- async function regenerateDocs(root) {
3688
- const docsOptions = resolvedOptions.docs;
3689
- if (!docsOptions || !docsOptions.enabled) return 0;
3690
- const srcDirs = docsOptions.src.map((src) => path.resolve(root, src));
3691
- const outDir = path.resolve(root, docsOptions.out);
3692
- const extracted = await extractDocs(srcDirs, docsOptions);
3693
- const generated = generateMarkdown(extracted, docsOptions);
3694
- await writeDocs(generated, outDir, extracted, docsOptions);
3695
- return Object.keys(generated).length;
3696
- }
3697
- const mainPlugin = {
3698
- name: "ox-content",
3699
- configResolved(resolvedConfig) {
3693
+ const getRoot = () => config?.root || process.cwd();
3694
+ const ssgDevCache = createDevServerCache();
3695
+ const plugins = [
3696
+ createMainPlugin(resolvedOptions, (resolvedConfig) => {
3700
3697
  config = resolvedConfig;
3701
- },
3698
+ }),
3699
+ createEnvironmentPlugin(resolvedOptions),
3700
+ createDocsPlugin(resolvedOptions, getRoot),
3701
+ createSsgPlugin(resolvedOptions, getRoot, ssgDevCache),
3702
+ createSearchPlugin(resolvedOptions, getRoot)
3703
+ ];
3704
+ if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
3705
+ if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
3706
+ return plugins;
3707
+ }
3708
+ async function regenerateDocs(resolvedOptions, root) {
3709
+ const docsOptions = resolvedOptions.docs;
3710
+ if (!docsOptions || !docsOptions.enabled) return 0;
3711
+ const srcDirs = docsOptions.src.map((src) => path.resolve(root, src));
3712
+ const outDir = path.resolve(root, docsOptions.out);
3713
+ const extracted = await extractDocs(srcDirs, docsOptions);
3714
+ const generated = generateMarkdown(extracted, docsOptions);
3715
+ await writeDocs(generated, outDir, extracted, docsOptions);
3716
+ return Object.keys(generated).length;
3717
+ }
3718
+ function createMainPlugin(resolvedOptions, setConfig) {
3719
+ return {
3720
+ name: "ox-content",
3721
+ configResolved: setConfig,
3702
3722
  configureServer(devServer) {
3703
3723
  devServer.middlewares.use(async (req, res, next) => {
3704
3724
  const url = req.url;
@@ -3723,31 +3743,33 @@ function oxContent(options = {}) {
3723
3743
  };
3724
3744
  },
3725
3745
  async handleHotUpdate({ file, server }) {
3726
- if (isMarkdownFilePath(file, resolvedOptions.extensions)) {
3727
- server.ws.send({
3728
- type: "custom",
3729
- event: "ox-content:update",
3730
- data: { file }
3731
- });
3732
- const modules = server.moduleGraph.getModulesByFile(file);
3733
- return modules ? Array.from(modules) : [];
3734
- }
3746
+ if (!isMarkdownFilePath(file, resolvedOptions.extensions)) return;
3747
+ server.ws.send({
3748
+ type: "custom",
3749
+ event: "ox-content:update",
3750
+ data: { file }
3751
+ });
3752
+ const modules = server.moduleGraph.getModulesByFile(file);
3753
+ return modules ? Array.from(modules) : [];
3735
3754
  }
3736
3755
  };
3737
- const environmentPlugin = {
3756
+ }
3757
+ function createEnvironmentPlugin(resolvedOptions) {
3758
+ return {
3738
3759
  name: "ox-content:environment",
3739
3760
  config() {
3740
3761
  return { environments: { markdown: createMarkdownEnvironment(resolvedOptions) } };
3741
3762
  }
3742
3763
  };
3743
- const docsPlugin = {
3764
+ }
3765
+ function createDocsPlugin(resolvedOptions, getRoot) {
3766
+ return {
3744
3767
  name: "ox-content:docs",
3745
3768
  async buildStart() {
3746
3769
  const docsOptions = resolvedOptions.docs;
3747
3770
  if (!docsOptions || !docsOptions.enabled) return;
3748
- const root = config?.root || process.cwd();
3749
3771
  try {
3750
- const count = await regenerateDocs(root);
3772
+ const count = await regenerateDocs(resolvedOptions, getRoot());
3751
3773
  console.log(`[ox-content] Generated ${count} documentation files to ${docsOptions.out}`);
3752
3774
  } catch (err) {
3753
3775
  console.warn("[ox-content] Failed to generate documentation:", err);
@@ -3756,50 +3778,32 @@ function oxContent(options = {}) {
3756
3778
  configureServer(devServer) {
3757
3779
  const docsOptions = resolvedOptions.docs;
3758
3780
  if (!docsOptions || !docsOptions.enabled) return;
3759
- const root = config?.root || process.cwd();
3781
+ const root = getRoot();
3760
3782
  const srcDirs = docsOptions.src.map((src) => path.resolve(root, src));
3761
3783
  for (const srcDir of srcDirs) devServer.watcher.add(srcDir);
3762
3784
  devServer.watcher.on("all", async (event, file) => {
3763
3785
  if (event !== "add" && event !== "change" && event !== "unlink") return;
3764
- if (srcDirs.some((srcDir) => file.startsWith(srcDir) && (file.endsWith(".ts") || file.endsWith(".tsx")))) try {
3765
- await regenerateDocs(root);
3786
+ if (!srcDirs.some((srcDir) => file.startsWith(srcDir) && (file.endsWith(".ts") || file.endsWith(".tsx")))) return;
3787
+ try {
3788
+ await regenerateDocs(resolvedOptions, root);
3766
3789
  } catch {}
3767
3790
  });
3768
3791
  }
3769
3792
  };
3770
- const ssgDevCache = createDevServerCache();
3771
- const ssgPlugin = {
3793
+ }
3794
+ function createSsgPlugin(resolvedOptions, getRoot, ssgDevCache) {
3795
+ return {
3772
3796
  name: "ox-content:ssg",
3773
3797
  configureServer(devServer) {
3774
3798
  if (!resolvedOptions.ssg.enabled) return;
3775
- const root = config?.root || process.cwd();
3799
+ const root = getRoot();
3776
3800
  const srcDir = path.resolve(root, resolvedOptions.srcDir);
3777
3801
  devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
3778
3802
  devServer.watcher.on("add", (file) => {
3779
- if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) {
3780
- invalidateNavCache(ssgDevCache);
3781
- devServer.ws.send({
3782
- type: "custom",
3783
- event: "ox-content:update",
3784
- data: {
3785
- file,
3786
- type: "add"
3787
- }
3788
- });
3789
- }
3803
+ notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, "add");
3790
3804
  });
3791
3805
  devServer.watcher.on("unlink", (file) => {
3792
- if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) {
3793
- invalidateNavCache(ssgDevCache);
3794
- devServer.ws.send({
3795
- type: "custom",
3796
- event: "ox-content:update",
3797
- data: {
3798
- file,
3799
- type: "unlink"
3800
- }
3801
- });
3802
- }
3806
+ notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, "unlink");
3803
3807
  });
3804
3808
  devServer.watcher.on("change", (file) => {
3805
3809
  if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) invalidatePageCache(ssgDevCache, file);
@@ -3807,63 +3811,63 @@ function oxContent(options = {}) {
3807
3811
  },
3808
3812
  async closeBundle() {
3809
3813
  if (!resolvedOptions.ssg.enabled) return;
3810
- const root = config?.root || process.cwd();
3811
3814
  try {
3812
- const result = await buildSsg(resolvedOptions, root);
3815
+ const result = await buildSsg(resolvedOptions, getRoot());
3813
3816
  if (result.files.length > 0) console.log(`[ox-content] Generated ${result.files.length} output files`);
3814
- if (result.errors.length > 0) for (const error of result.errors) console.warn(`[ox-content] ${error}`);
3817
+ for (const error of result.errors) console.warn(`[ox-content] ${error}`);
3815
3818
  } catch (err) {
3816
3819
  console.error("[ox-content] SSG build failed:", err);
3817
3820
  }
3818
3821
  }
3819
3822
  };
3823
+ }
3824
+ function notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, type) {
3825
+ if (!file.startsWith(srcDir) || !isMarkdownFilePath(file, resolvedOptions.extensions)) return;
3826
+ invalidateNavCache(ssgDevCache);
3827
+ devServer.ws.send({
3828
+ type: "custom",
3829
+ event: "ox-content:update",
3830
+ data: {
3831
+ file,
3832
+ type
3833
+ }
3834
+ });
3835
+ }
3836
+ function createSearchPlugin(resolvedOptions, getRoot) {
3820
3837
  let searchIndexJson = "";
3821
- const plugins = [
3822
- mainPlugin,
3823
- environmentPlugin,
3824
- docsPlugin,
3825
- ssgPlugin,
3826
- {
3827
- name: "ox-content:search",
3828
- resolveId(id) {
3829
- if (id === "virtual:ox-content/search") return "\0virtual:ox-content/search";
3830
- return null;
3831
- },
3832
- async load(id) {
3833
- if (id === "\0virtual:ox-content/search") {
3834
- const searchOptions = resolvedOptions.search;
3835
- if (!searchOptions.enabled) return "export const search = () => []; export const searchOptions = { enabled: false }; export default { search, searchOptions };";
3836
- return generateSearchModule(searchOptions, resolvedOptions.base + "search-index.json");
3837
- }
3838
- return null;
3839
- },
3840
- async buildStart() {
3841
- if (!resolvedOptions.search.enabled) return;
3842
- const root = config?.root || process.cwd();
3843
- const srcDir = path.resolve(root, resolvedOptions.srcDir);
3844
- try {
3845
- searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base, resolvedOptions.extensions);
3846
- console.log("[ox-content] Search index built");
3847
- } catch (err) {
3848
- console.warn("[ox-content] Failed to build search index:", err);
3849
- }
3850
- },
3851
- async closeBundle() {
3852
- if (!resolvedOptions.search.enabled || !searchIndexJson) return;
3853
- const root = config?.root || process.cwd();
3854
- const outDir = path.resolve(root, resolvedOptions.outDir);
3855
- try {
3856
- await writeSearchIndex(searchIndexJson, outDir);
3857
- console.log("[ox-content] Search index written to", path.join(outDir, "search-index.json"));
3858
- } catch (err) {
3859
- console.warn("[ox-content] Failed to write search index:", err);
3860
- }
3838
+ return {
3839
+ name: "ox-content:search",
3840
+ resolveId(id) {
3841
+ if (id === "virtual:ox-content/search") return "\0virtual:ox-content/search";
3842
+ return null;
3843
+ },
3844
+ async load(id) {
3845
+ if (id !== "\0virtual:ox-content/search") return null;
3846
+ const searchOptions = resolvedOptions.search;
3847
+ if (!searchOptions.enabled) return "export const search = () => []; export const searchOptions = { enabled: false }; export default { search, searchOptions };";
3848
+ return generateSearchModule(searchOptions, resolvedOptions.base + "search-index.json");
3849
+ },
3850
+ async buildStart() {
3851
+ if (!resolvedOptions.search.enabled) return;
3852
+ const srcDir = path.resolve(getRoot(), resolvedOptions.srcDir);
3853
+ try {
3854
+ searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base, resolvedOptions.extensions);
3855
+ console.log("[ox-content] Search index built");
3856
+ } catch (err) {
3857
+ console.warn("[ox-content] Failed to build search index:", err);
3858
+ }
3859
+ },
3860
+ async closeBundle() {
3861
+ if (!resolvedOptions.search.enabled || !searchIndexJson) return;
3862
+ const outDir = path.resolve(getRoot(), resolvedOptions.outDir);
3863
+ try {
3864
+ await writeSearchIndex(searchIndexJson, outDir);
3865
+ console.log("[ox-content] Search index written to", path.join(outDir, "search-index.json"));
3866
+ } catch (err) {
3867
+ console.warn("[ox-content] Failed to write search index:", err);
3861
3868
  }
3862
3869
  }
3863
- ];
3864
- if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
3865
- if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
3866
- return plugins;
3870
+ };
3867
3871
  }
3868
3872
  /**
3869
3873
  * Resolves plugin options with defaults.