@ox-content/vite-plugin 2.16.0 → 2.25.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);
@@ -432,7 +433,7 @@ async function loadNapiBindings() {
432
433
  if (napiLoadAttempted) return napiBindings ?? null;
433
434
  napiLoadAttempted = true;
434
435
  try {
435
- const mod = await require_mermaid.importNapiModule();
436
+ const mod = await require_napi.importNapiModule();
436
437
  napiBindings = mod;
437
438
  return mod;
438
439
  } catch (error) {
@@ -545,67 +546,7 @@ if (import.meta.hot) {
545
546
  `;
546
547
  }
547
548
  //#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
549
  //#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
550
  const DEFAULT_DOCS_INCLUDE = [
610
551
  "**/*.ts",
611
552
  "**/*.tsx",
@@ -682,7 +623,7 @@ const DEFAULT_DOCS_INCLUDE = [
682
623
  * ```
683
624
  */
684
625
  async function extractDocs(srcDirs, options) {
685
- const napi = await require_mermaid.importNapiModule();
626
+ const napi = await require_napi.importNapiModule();
686
627
  if (options.entryPoints?.length) {
687
628
  const extractDocsFromEntryPoints = napi.extractDocsFromEntryPoints;
688
629
  if (!extractDocsFromEntryPoints) throw new Error("[ox-content] extractDocsFromEntryPoints is not available from @ox-content/napi.");
@@ -695,65 +636,40 @@ async function extractDocs(srcDirs, options) {
695
636
  entries: doc.entries
696
637
  }));
697
638
  }
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;
639
+ const extractDocsFromDirectories = napi.extractDocsFromDirectories;
640
+ if (!extractDocsFromDirectories) throw new Error("[ox-content] extractDocsFromDirectories is not available from @ox-content/napi.");
641
+ return extractDocsFromDirectories(srcDirs, options.include, options.exclude, options.private, options.internal).map((doc) => ({
642
+ file: doc.file,
643
+ entries: doc.entries
644
+ }));
712
645
  }
713
646
  /**
714
647
  * Generates Markdown documentation from extracted docs.
715
648
  */
716
649
  function generateMarkdown(docs, options) {
717
- const napi = require_mermaid.importNapiModuleSync();
650
+ const napi = require_napi.importNapiModuleSync();
718
651
  if (typeof napi.generateDocsMarkdown !== "function") throw new Error("[ox-content] generateDocsMarkdown is not available from @ox-content/napi. Please rebuild the NAPI package.");
719
652
  return napi.generateDocsMarkdown(toRustDocsModules(docs), {
720
653
  groupBy: options.groupBy,
721
- githubUrl: options.githubUrl
654
+ githubUrl: options.githubUrl,
655
+ linkStyle: options.linkStyle,
656
+ basePath: options.basePath,
657
+ pathStrategy: options.pathStrategy
722
658
  });
723
659
  }
724
660
  /**
725
661
  * Writes generated documentation to the output directory.
726
662
  */
727
663
  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");
664
+ const napi = require_napi.importNapiModuleSync();
665
+ if (typeof napi.writeGeneratedDocs !== "function") throw new Error("[ox-content] writeGeneratedDocs is not available from @ox-content/napi. Please rebuild the NAPI package.");
666
+ napi.writeGeneratedDocs(docs, outDir, extractedDocs ? toRustDocsModules(extractedDocs) : void 0, {
667
+ generateNav: options?.generateNav ?? false,
668
+ groupBy: options?.groupBy ?? "file",
669
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
670
+ basePath: options?.basePath,
671
+ pathStrategy: options?.pathStrategy
672
+ });
757
673
  }
758
674
  function toRustDocsModules(docs) {
759
675
  return docs.map((doc) => ({
@@ -778,9 +694,6 @@ function toRustDocsModules(docs) {
778
694
  }))
779
695
  }));
780
696
  }
781
- /**
782
- * Resolves docs options with defaults.
783
- */
784
697
  function resolveDocsOptions(options) {
785
698
  if (options === false) return false;
786
699
  const opts = options || {};
@@ -801,6 +714,9 @@ function resolveDocsOptions(options) {
801
714
  toc: false,
802
715
  groupBy: opts.groupBy ?? "file",
803
716
  githubUrl: opts.githubUrl,
717
+ linkStyle: opts.linkStyle ?? "markdown",
718
+ basePath: opts.basePath,
719
+ pathStrategy: opts.pathStrategy ?? "flat",
804
720
  generateNav: opts.generateNav ?? true
805
721
  };
806
722
  }
@@ -888,6 +804,8 @@ async function renderHtmlToPng(page, html, width, height, publicDir) {
888
804
  }
889
805
  //#endregion
890
806
  //#region src/og-image/browser.ts
807
+ const PLAYWRIGHT_BROWSER_INSTALL_HINT = "Install Playwright browsers with `npx playwright install chromium` to enable OG image generation.";
808
+ let chromiumUnavailableWarned = false;
891
809
  /**
892
810
  * Opens a Chromium browser and returns a session for rendering OG images.
893
811
  * Returns null if Playwright/Chromium is not available.
@@ -927,10 +845,20 @@ async function openBrowser() {
927
845
  }
928
846
  };
929
847
  } catch (err) {
930
- console.warn("[ox-content:og-image] Chromium not available, skipping OG image generation.", err instanceof Error ? err.message : err);
848
+ warnChromiumUnavailableOnce(err);
931
849
  return null;
932
850
  }
933
851
  }
852
+ function warnChromiumUnavailableOnce(err) {
853
+ if (chromiumUnavailableWarned) return;
854
+ chromiumUnavailableWarned = true;
855
+ console.warn(`[ox-content:og-image] Chromium not available, skipping OG image generation. ${formatChromiumUnavailableDetail(err)}`);
856
+ }
857
+ function formatChromiumUnavailableDetail(err) {
858
+ const message = err instanceof Error ? err.message : String(err);
859
+ if (message.includes("Executable doesn't exist") || message.includes("Please run the following command to download new browsers")) return PLAYWRIGHT_BROWSER_INSTALL_HINT;
860
+ return message.split(/\r?\n/).find((line) => line.trim())?.trim() ?? "Unknown launch error.";
861
+ }
934
862
  //#endregion
935
863
  //#region src/og-image/template.ts
936
864
  /**
@@ -1732,19 +1660,19 @@ function resolveSsgOptions(ssg) {
1732
1660
  * Extracts title from content or frontmatter.
1733
1661
  */
1734
1662
  function extractTitle$1(content, frontmatter) {
1735
- return require_mermaid.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
1663
+ return require_napi.importNapiModuleSync().extractSsgTitle(content, typeof frontmatter.title === "string" ? frontmatter.title : void 0);
1736
1664
  }
1737
1665
  /**
1738
1666
  * Generates bare HTML page (no navigation, no styles).
1739
1667
  */
1740
1668
  function generateBareHtmlPage(content, title) {
1741
- return require_mermaid.importNapiModuleSync().generateSsgBareHtml(content, title);
1669
+ return require_napi.importNapiModuleSync().generateSsgBareHtml(content, title);
1742
1670
  }
1743
1671
  /**
1744
1672
  * Generates HTML page with navigation using Rust NAPI bindings.
1745
1673
  */
1746
1674
  async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
1747
- const mod = await require_mermaid.importNapiModule();
1675
+ const mod = await require_napi.importNapiModule();
1748
1676
  const toRustTocEntry = (entry) => ({
1749
1677
  depth: entry.depth,
1750
1678
  text: entry.text,
@@ -1818,7 +1746,7 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
1818
1746
  });
1819
1747
  }
1820
1748
  async function externalizeSharedPageAssets(pages, outDir, base) {
1821
- const optimized = (await require_mermaid.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
1749
+ const optimized = (await require_napi.importNapiModule()).externalizeSsgAssets(pages, outDir, base);
1822
1750
  await Promise.all(optimized.assets.map(async (asset) => {
1823
1751
  await fs_promises.mkdir(path.dirname(asset.outputPath), { recursive: true });
1824
1752
  await fs_promises.writeFile(asset.outputPath, asset.content, "utf-8");
@@ -1832,45 +1760,45 @@ async function externalizeSharedPageAssets(pages, outDir, base) {
1832
1760
  * Converts a markdown file path to a relative URL path.
1833
1761
  */
1834
1762
  function getUrlPath$1(inputPath, srcDir) {
1835
- return require_mermaid.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
1763
+ return require_napi.importNapiModuleSync().getSsgUrlPath(inputPath, srcDir);
1836
1764
  }
1837
1765
  /**
1838
1766
  * Resolves manual navigation config to the format used by the built-in SSG renderer.
1839
1767
  */
1840
1768
  function resolveNavigationGroups(navigation, base, extension) {
1841
1769
  if (!navigation) return;
1842
- return require_mermaid.importNapiModuleSync().resolveSsgNavigationGroups(navigation, base, extension);
1770
+ return require_napi.importNapiModuleSync().resolveSsgNavigationGroups(navigation, base, extension);
1843
1771
  }
1844
1772
  function getPageLocale(urlPath, i18n) {
1845
1773
  if (!i18n) return void 0;
1846
- return require_mermaid.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
1774
+ return require_napi.importNapiModuleSync().getSsgPageLocale(urlPath, i18n.defaultLocale, i18n.locales.map((locale) => locale.code)) ?? void 0;
1847
1775
  }
1848
1776
  function getRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl) {
1849
- return require_mermaid.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
1777
+ return require_napi.importNapiModuleSync().resolveSsgRoutePaths(inputPath, srcDir, outDir, base, extension, siteUrl);
1850
1778
  }
1851
1779
  /**
1852
1780
  * Formats a file/dir name as a title.
1853
1781
  */
1854
1782
  function formatTitle(name) {
1855
- return require_mermaid.importNapiModuleSync().formatSsgTitle(name);
1783
+ return require_napi.importNapiModuleSync().formatSsgTitle(name);
1856
1784
  }
1857
1785
  /**
1858
1786
  * Collects all markdown files from the source directory.
1859
1787
  */
1860
1788
  async function collectMarkdownFiles(srcDir, extensions = DEFAULT_MARKDOWN_EXTENSIONS) {
1861
- return require_mermaid.importNapiModuleSync().collectSsgMarkdownFiles(srcDir, [...extensions]);
1789
+ return require_napi.importNapiModuleSync().collectSsgMarkdownFiles(srcDir, [...extensions]);
1862
1790
  }
1863
1791
  /**
1864
1792
  * Builds navigation items from markdown files, grouped by directory.
1865
1793
  */
1866
1794
  function buildNavItems(markdownFiles, srcDir, base, extension) {
1867
- return require_mermaid.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
1795
+ return require_napi.importNapiModuleSync().buildSsgNavItems(markdownFiles, srcDir, base, extension);
1868
1796
  }
1869
1797
  /**
1870
1798
  * Builds navigation items from an explicit theme sidebar tree.
1871
1799
  */
1872
1800
  function buildThemeNavItems(sidebar, base, extension) {
1873
- return require_mermaid.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
1801
+ return require_napi.importNapiModuleSync().buildSsgThemeNavItems(sidebar, base, extension);
1874
1802
  }
1875
1803
  /**
1876
1804
  * Builds all markdown files to static HTML.
@@ -1883,149 +1811,201 @@ async function buildSsg(options, root) {
1883
1811
  };
1884
1812
  const srcDir = path.resolve(root, options.srcDir);
1885
1813
  const outDir = path.resolve(root, options.outDir);
1886
- const base = options.base.endsWith("/") ? options.base : options.base + "/";
1887
1814
  const generatedFiles = [];
1888
- const generatedPages = [];
1889
1815
  const errors = [];
1890
- if (ssgOptions.clean) try {
1816
+ await cleanOutputDirectory(ssgOptions, outDir);
1817
+ const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
1818
+ const context = await createBuildSsgContext(options, root, srcDir, outDir, markdownFiles);
1819
+ const collected = await collectPageResults(context, markdownFiles);
1820
+ errors.push(...collected.errors);
1821
+ await generateOgImageAssets(context, collected, generatedFiles, errors);
1822
+ await writeGeneratedPages(await generateHtmlPages(context, collected.pageResults, collected, errors), context, generatedFiles);
1823
+ return {
1824
+ files: generatedFiles,
1825
+ errors
1826
+ };
1827
+ }
1828
+ async function cleanOutputDirectory(ssgOptions, outDir) {
1829
+ if (!ssgOptions.clean) return;
1830
+ try {
1891
1831
  await fs_promises.rm(outDir, {
1892
1832
  recursive: true,
1893
1833
  force: true
1894
1834
  });
1895
1835
  } 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 {
1836
+ }
1837
+ async function createBuildSsgContext(options, root, srcDir, outDir, markdownFiles) {
1838
+ const ssgOptions = options.ssg;
1839
+ const base = options.base.endsWith("/") ? options.base : options.base + "/";
1840
+ return {
1841
+ options,
1842
+ ssgOptions,
1843
+ root,
1844
+ srcDir,
1845
+ outDir,
1846
+ base,
1847
+ navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
1848
+ siteName: await resolveSiteName$1(root, ssgOptions),
1849
+ shouldGenerateOgImages: (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare,
1850
+ napi: ssgOptions.lastUpdated ? await require_napi.importNapiModule() : void 0
1851
+ };
1852
+ }
1853
+ async function resolveSiteName$1(root, ssgOptions) {
1854
+ if (ssgOptions.siteName) return ssgOptions.siteName;
1855
+ try {
1900
1856
  const pkgPath = path.join(root, "package.json");
1901
1857
  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;
1858
+ return pkg.name ? formatTitle(pkg.name) : "Documentation";
1859
+ } catch {
1860
+ return "Documentation";
1861
+ }
1862
+ }
1863
+ async function collectPageResults(context, markdownFiles) {
1864
+ const collected = {
1865
+ pageResults: [],
1866
+ ogImageEntries: [],
1867
+ ogImageInputPaths: [],
1868
+ ogImageUrlMap: /* @__PURE__ */ new Map(),
1869
+ errors: []
1870
+ };
1910
1871
  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
- }
1872
+ const pageResult = await transformSsgPage(context, inputPath);
1873
+ collected.pageResults.push(pageResult);
1874
+ collectOgImageEntry(context, pageResult, collected);
1958
1875
  } catch (err) {
1959
1876
  const errorMessage = err instanceof Error ? err.message : String(err);
1960
- errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1877
+ collected.errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1961
1878
  }
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
- }
1879
+ return collected;
1880
+ }
1881
+ async function transformSsgPage(context, inputPath) {
1882
+ const result = await transformMarkdown(await fs_promises.readFile(inputPath, "utf-8"), inputPath, context.options, {
1883
+ convertMdLinks: true,
1884
+ baseUrl: context.base,
1885
+ sourcePath: inputPath
1886
+ });
1887
+ const frontmatter = require_vitepress.normalizeVitePressFrontmatter(result.frontmatter);
1888
+ const transformedHtml = await transformSsgHtml(result.html, context.options);
1889
+ const title = extractTitle$1(transformedHtml, frontmatter);
1890
+ return {
1891
+ inputPath,
1892
+ routePaths: getRoutePaths(inputPath, context.srcDir, context.outDir, context.base, context.ssgOptions.extension, context.ssgOptions.siteUrl),
1893
+ transformedHtml,
1894
+ title,
1895
+ description: frontmatter.description,
1896
+ lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
1897
+ frontmatter,
1898
+ toc: result.toc
1899
+ };
1900
+ }
1901
+ async function transformSsgHtml(html, options) {
1902
+ const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(html);
1903
+ let transformedHtml = await transformAllPlugins(protectedHtml, {
1904
+ tabs: true,
1905
+ youtube: true,
1906
+ github: options.embeds.github,
1907
+ openGraph: options.embeds.openGraph,
1908
+ mermaid: true,
1909
+ githubToken: process.env.GITHUB_TOKEN
1910
+ });
1911
+ if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
1912
+ return restoreMermaidSvgs(transformedHtml, mermaidSvgs);
1913
+ }
1914
+ function collectOgImageEntry(context, pageResult, collected) {
1915
+ if (!context.shouldGenerateOgImages) return;
1916
+ const { layout: _layout, ...frontmatterRest } = pageResult.frontmatter;
1917
+ collected.ogImageEntries.push({
1918
+ props: {
1919
+ ...frontmatterRest,
1920
+ title: pageResult.title,
1921
+ description: pageResult.description,
1922
+ siteName: context.siteName
1923
+ },
1924
+ outputPath: pageResult.routePaths.ogImagePath
1925
+ });
1926
+ collected.ogImageInputPaths.push(pageResult.inputPath);
1927
+ collected.ogImageUrlMap.set(pageResult.inputPath, pageResult.routePaths.ogImageUrl);
1928
+ }
1929
+ async function generateOgImageAssets(context, collected, generatedFiles, errors) {
1930
+ if (!context.shouldGenerateOgImages || collected.ogImageEntries.length === 0) return;
1931
+ try {
1932
+ const ogResults = await generateOgImages(collected.ogImageEntries, context.options.ogImageOptions, context.root);
1933
+ if (clearMissingBrowserOgImages(ogResults, collected)) return;
1934
+ reportOgImageResults(ogResults, collected, generatedFiles, errors);
1979
1935
  } catch (err) {
1980
1936
  const errorMessage = err instanceof Error ? err.message : String(err);
1981
1937
  console.warn(`[ox-content:og-image] Batch generation failed: ${errorMessage}`);
1982
- ogImageUrlMap.clear();
1938
+ collected.ogImageUrlMap.clear();
1983
1939
  }
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);
1940
+ }
1941
+ function clearMissingBrowserOgImages(ogResults, collected) {
1942
+ if (!(ogResults.length > 0 && ogResults.every((result) => result.error === "Chromium not available"))) return false;
1943
+ for (const inputPath of collected.ogImageInputPaths) collected.ogImageUrlMap.delete(inputPath);
1944
+ return true;
1945
+ }
1946
+ function reportOgImageResults(ogResults, collected, generatedFiles, errors) {
1947
+ let ogSuccessCount = 0;
1948
+ for (let i = 0; i < ogResults.length; i++) {
1949
+ const result = ogResults[i];
1950
+ if (result.error) {
1951
+ errors.push(`OG image failed for ${result.outputPath}: ${result.error}`);
1952
+ collected.ogImageUrlMap.delete(collected.ogImageInputPaths[i]);
1953
+ } else {
1954
+ generatedFiles.push(result.outputPath);
1955
+ ogSuccessCount++;
2008
1956
  }
1957
+ }
1958
+ if (ogSuccessCount > 0) {
1959
+ const cachedCount = ogResults.filter((result) => result.cached && !result.error).length;
1960
+ console.log(`[ox-content:og-image] Generated ${ogSuccessCount} OG images` + (cachedCount > 0 ? ` (${cachedCount} from cache)` : ""));
1961
+ }
1962
+ }
1963
+ async function generateHtmlPages(context, pageResults, collected, errors) {
1964
+ const generatedPages = [];
1965
+ for (const pageResult of pageResults) try {
2009
1966
  generatedPages.push({
2010
- inputPath,
2011
- outputPath: routePaths.outputPath,
2012
- html
1967
+ inputPath: pageResult.inputPath,
1968
+ outputPath: pageResult.routePaths.outputPath,
1969
+ html: await renderSsgPage(context, pageResult, collected.ogImageUrlMap)
2013
1970
  });
2014
1971
  } catch (err) {
2015
1972
  const errorMessage = err instanceof Error ? err.message : String(err);
2016
1973
  errors.push(`Failed to generate HTML for ${pageResult.inputPath}: ${errorMessage}`);
2017
1974
  }
2018
- const optimizedOutput = await externalizeSharedPageAssets(generatedPages, outDir, base);
1975
+ return generatedPages;
1976
+ }
1977
+ async function renderSsgPage(context, pageResult, ogImageUrlMap) {
1978
+ if (context.ssgOptions.bare) return generateBareHtmlPage(pageResult.transformedHtml, pageResult.title);
1979
+ const pageData = createSsgPageData(pageResult);
1980
+ const pageOgImage = context.shouldGenerateOgImages && ogImageUrlMap.has(pageResult.inputPath) ? ogImageUrlMap.get(pageResult.inputPath) : context.ssgOptions.ogImage;
1981
+ 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);
1982
+ }
1983
+ function createSsgPageData(pageResult) {
1984
+ const { frontmatter } = pageResult;
1985
+ const entryPage = frontmatter.layout === "entry" ? {
1986
+ hero: frontmatter.hero,
1987
+ features: frontmatter.features
1988
+ } : void 0;
1989
+ return {
1990
+ title: pageResult.title,
1991
+ description: pageResult.description,
1992
+ content: pageResult.transformedHtml,
1993
+ toc: pageResult.toc,
1994
+ lastUpdated: pageResult.lastUpdated,
1995
+ frontmatter,
1996
+ path: pageResult.routePaths.urlPath,
1997
+ href: pageResult.routePaths.href,
1998
+ entryPage
1999
+ };
2000
+ }
2001
+ async function writeGeneratedPages(generatedPages, context, generatedFiles) {
2002
+ const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
2019
2003
  generatedFiles.push(...optimizedOutput.assets);
2020
2004
  for (const page of optimizedOutput.pages) {
2021
2005
  await fs_promises.mkdir(path.dirname(page.outputPath), { recursive: true });
2022
2006
  await fs_promises.writeFile(page.outputPath, page.html, "utf-8");
2023
2007
  generatedFiles.push(page.outputPath);
2024
2008
  }
2025
- return {
2026
- files: generatedFiles,
2027
- errors
2028
- };
2029
2009
  }
2030
2010
  //#endregion
2031
2011
  //#region src/search.ts
@@ -2037,7 +2017,7 @@ async function buildSsg(options, root) {
2037
2017
  let oxContent$1 = null;
2038
2018
  async function getOxContent() {
2039
2019
  if (!oxContent$1) try {
2040
- oxContent$1 = await require_mermaid.importNapiModule();
2020
+ oxContent$1 = await require_napi.importNapiModule();
2041
2021
  } catch {
2042
2022
  console.warn("[ox-content] Native bindings not available, search disabled");
2043
2023
  return null;
@@ -2091,7 +2071,7 @@ async function writeSearchIndex(indexJson, outDir) {
2091
2071
  * This is injected into the bundle as a virtual module.
2092
2072
  */
2093
2073
  function generateSearchModule(options, indexPath) {
2094
- return require_mermaid.importNapiModuleSync().generateSearchModuleFromOptions(options, indexPath);
2074
+ return require_napi.importNapiModuleSync().generateSearchModuleFromOptions(options, indexPath);
2095
2075
  }
2096
2076
  //#endregion
2097
2077
  //#region src/dev-server.ts
@@ -2705,7 +2685,7 @@ function createI18nPlugin(resolvedOptions) {
2705
2685
  return;
2706
2686
  }
2707
2687
  try {
2708
- const { checkI18nProject } = await require_mermaid.importNapiModule();
2688
+ const { checkI18nProject } = await require_napi.importNapiModule();
2709
2689
  const checkResult = checkI18nProject(dictDir, [path.resolve(root, "src"), path.resolve(root, "content")], i18nOptions.functionNames, i18nOptions.defaultLocale);
2710
2690
  if (checkResult.errorCount > 0 || checkResult.warningCount > 0) {
2711
2691
  for (const diag of checkResult.diagnostics) if (diag.severity === "error") console.error(`[ox-content:i18n] ${diag.message}`);
@@ -2859,7 +2839,10 @@ function stripMaskedDocument(result) {
2859
2839
  };
2860
2840
  }
2861
2841
  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];
2842
+ const standardDictionary = options.dictionary?.standard && typeof options.dictionary.standard === "object" ? options.dictionary.standard : void 0;
2843
+ const optionLanguages = options.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language));
2844
+ const standardLanguages = standardDictionary?.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language));
2845
+ const languages = optionLanguages ?? standardLanguages ?? [...DEFAULT_LANGUAGES];
2863
2846
  const standard = normalizeStandardDictionaryOptions(options.dictionary?.standard, languages);
2864
2847
  return {
2865
2848
  dictionary: {
@@ -2900,6 +2883,12 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
2900
2883
  const { spellCheckDocument } = await loadCspellLib();
2901
2884
  const locale = standard.languages.join(",");
2902
2885
  const settings = createStandardSpellcheckSettings(options, locale);
2886
+ const spellCheckOptions = {
2887
+ generateSuggestions: true,
2888
+ noConfigSearch: true,
2889
+ numSuggestions: 3,
2890
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo
2891
+ };
2903
2892
  return Promise.all(maskedDocuments.map(async (maskedDocument, index) => {
2904
2893
  if (maskedDocument.trim().length === 0) return [];
2905
2894
  return (await spellCheckDocument({
@@ -2907,12 +2896,7 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
2907
2896
  locale,
2908
2897
  text: maskedDocument,
2909
2898
  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));
2899
+ }, spellCheckOptions, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages, maskedDocument));
2916
2900
  }));
2917
2901
  } catch (error) {
2918
2902
  const imports = standard.imports.join(", ");
@@ -2933,8 +2917,8 @@ async function loadCspellLib() {
2933
2917
  cspellLibPromise ??= import("cspell-lib");
2934
2918
  return cspellLibPromise;
2935
2919
  }
2936
- function mapStandardIssueToDiagnostic(issue, languages) {
2937
- const line = issue.line.position.line + 1;
2920
+ function mapStandardIssueToDiagnostic(issue, languages, documentText) {
2921
+ const line = getLineNumberAtOffset(documentText, issue.line.offset);
2938
2922
  const column = issue.offset - issue.line.offset + 1;
2939
2923
  return {
2940
2924
  column,
@@ -2948,6 +2932,11 @@ function mapStandardIssueToDiagnostic(issue, languages) {
2948
2932
  suggestions: issue.suggestions?.slice(0, 3)
2949
2933
  };
2950
2934
  }
2935
+ function getLineNumberAtOffset(text, offset) {
2936
+ let line = 1;
2937
+ for (let index = 0; index < offset && index < text.length; index++) if (text.charCodeAt(index) === 10) line++;
2938
+ return line;
2939
+ }
2951
2940
  function inferStandardIssueLanguage(word, languages) {
2952
2941
  if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
2953
2942
  if (/[\p{Script=Han}]/u.test(word)) {
@@ -3684,21 +3673,35 @@ init_page_context();
3684
3673
  function oxContent(options = {}) {
3685
3674
  const resolvedOptions = resolveOptions(options);
3686
3675
  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) {
3676
+ const getRoot = () => config?.root || process.cwd();
3677
+ const ssgDevCache = createDevServerCache();
3678
+ const plugins = [
3679
+ createMainPlugin(resolvedOptions, (resolvedConfig) => {
3700
3680
  config = resolvedConfig;
3701
- },
3681
+ }),
3682
+ createEnvironmentPlugin(resolvedOptions),
3683
+ createDocsPlugin(resolvedOptions, getRoot),
3684
+ createSsgPlugin(resolvedOptions, getRoot, ssgDevCache),
3685
+ createSearchPlugin(resolvedOptions, getRoot)
3686
+ ];
3687
+ if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
3688
+ if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
3689
+ return plugins;
3690
+ }
3691
+ async function regenerateDocs(resolvedOptions, root) {
3692
+ const docsOptions = resolvedOptions.docs;
3693
+ if (!docsOptions || !docsOptions.enabled) return 0;
3694
+ const srcDirs = docsOptions.src.map((src) => path.resolve(root, src));
3695
+ const outDir = path.resolve(root, docsOptions.out);
3696
+ const extracted = await extractDocs(srcDirs, docsOptions);
3697
+ const generated = generateMarkdown(extracted, docsOptions);
3698
+ await writeDocs(generated, outDir, extracted, docsOptions);
3699
+ return Object.keys(generated).length;
3700
+ }
3701
+ function createMainPlugin(resolvedOptions, setConfig) {
3702
+ return {
3703
+ name: "ox-content",
3704
+ configResolved: setConfig,
3702
3705
  configureServer(devServer) {
3703
3706
  devServer.middlewares.use(async (req, res, next) => {
3704
3707
  const url = req.url;
@@ -3723,31 +3726,33 @@ function oxContent(options = {}) {
3723
3726
  };
3724
3727
  },
3725
3728
  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
- }
3729
+ if (!isMarkdownFilePath(file, resolvedOptions.extensions)) return;
3730
+ server.ws.send({
3731
+ type: "custom",
3732
+ event: "ox-content:update",
3733
+ data: { file }
3734
+ });
3735
+ const modules = server.moduleGraph.getModulesByFile(file);
3736
+ return modules ? Array.from(modules) : [];
3735
3737
  }
3736
3738
  };
3737
- const environmentPlugin = {
3739
+ }
3740
+ function createEnvironmentPlugin(resolvedOptions) {
3741
+ return {
3738
3742
  name: "ox-content:environment",
3739
3743
  config() {
3740
3744
  return { environments: { markdown: createMarkdownEnvironment(resolvedOptions) } };
3741
3745
  }
3742
3746
  };
3743
- const docsPlugin = {
3747
+ }
3748
+ function createDocsPlugin(resolvedOptions, getRoot) {
3749
+ return {
3744
3750
  name: "ox-content:docs",
3745
3751
  async buildStart() {
3746
3752
  const docsOptions = resolvedOptions.docs;
3747
3753
  if (!docsOptions || !docsOptions.enabled) return;
3748
- const root = config?.root || process.cwd();
3749
3754
  try {
3750
- const count = await regenerateDocs(root);
3755
+ const count = await regenerateDocs(resolvedOptions, getRoot());
3751
3756
  console.log(`[ox-content] Generated ${count} documentation files to ${docsOptions.out}`);
3752
3757
  } catch (err) {
3753
3758
  console.warn("[ox-content] Failed to generate documentation:", err);
@@ -3756,50 +3761,32 @@ function oxContent(options = {}) {
3756
3761
  configureServer(devServer) {
3757
3762
  const docsOptions = resolvedOptions.docs;
3758
3763
  if (!docsOptions || !docsOptions.enabled) return;
3759
- const root = config?.root || process.cwd();
3764
+ const root = getRoot();
3760
3765
  const srcDirs = docsOptions.src.map((src) => path.resolve(root, src));
3761
3766
  for (const srcDir of srcDirs) devServer.watcher.add(srcDir);
3762
3767
  devServer.watcher.on("all", async (event, file) => {
3763
3768
  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);
3769
+ if (!srcDirs.some((srcDir) => file.startsWith(srcDir) && (file.endsWith(".ts") || file.endsWith(".tsx")))) return;
3770
+ try {
3771
+ await regenerateDocs(resolvedOptions, root);
3766
3772
  } catch {}
3767
3773
  });
3768
3774
  }
3769
3775
  };
3770
- const ssgDevCache = createDevServerCache();
3771
- const ssgPlugin = {
3776
+ }
3777
+ function createSsgPlugin(resolvedOptions, getRoot, ssgDevCache) {
3778
+ return {
3772
3779
  name: "ox-content:ssg",
3773
3780
  configureServer(devServer) {
3774
3781
  if (!resolvedOptions.ssg.enabled) return;
3775
- const root = config?.root || process.cwd();
3782
+ const root = getRoot();
3776
3783
  const srcDir = path.resolve(root, resolvedOptions.srcDir);
3777
3784
  devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
3778
3785
  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
- }
3786
+ notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, "add");
3790
3787
  });
3791
3788
  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
- }
3789
+ notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, "unlink");
3803
3790
  });
3804
3791
  devServer.watcher.on("change", (file) => {
3805
3792
  if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) invalidatePageCache(ssgDevCache, file);
@@ -3807,63 +3794,63 @@ function oxContent(options = {}) {
3807
3794
  },
3808
3795
  async closeBundle() {
3809
3796
  if (!resolvedOptions.ssg.enabled) return;
3810
- const root = config?.root || process.cwd();
3811
3797
  try {
3812
- const result = await buildSsg(resolvedOptions, root);
3798
+ const result = await buildSsg(resolvedOptions, getRoot());
3813
3799
  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}`);
3800
+ for (const error of result.errors) console.warn(`[ox-content] ${error}`);
3815
3801
  } catch (err) {
3816
3802
  console.error("[ox-content] SSG build failed:", err);
3817
3803
  }
3818
3804
  }
3819
3805
  };
3806
+ }
3807
+ function notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, type) {
3808
+ if (!file.startsWith(srcDir) || !isMarkdownFilePath(file, resolvedOptions.extensions)) return;
3809
+ invalidateNavCache(ssgDevCache);
3810
+ devServer.ws.send({
3811
+ type: "custom",
3812
+ event: "ox-content:update",
3813
+ data: {
3814
+ file,
3815
+ type
3816
+ }
3817
+ });
3818
+ }
3819
+ function createSearchPlugin(resolvedOptions, getRoot) {
3820
3820
  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
- }
3821
+ return {
3822
+ name: "ox-content:search",
3823
+ resolveId(id) {
3824
+ if (id === "virtual:ox-content/search") return "\0virtual:ox-content/search";
3825
+ return null;
3826
+ },
3827
+ async load(id) {
3828
+ if (id !== "\0virtual:ox-content/search") return null;
3829
+ const searchOptions = resolvedOptions.search;
3830
+ if (!searchOptions.enabled) return "export const search = () => []; export const searchOptions = { enabled: false }; export default { search, searchOptions };";
3831
+ return generateSearchModule(searchOptions, resolvedOptions.base + "search-index.json");
3832
+ },
3833
+ async buildStart() {
3834
+ if (!resolvedOptions.search.enabled) return;
3835
+ const srcDir = path.resolve(getRoot(), resolvedOptions.srcDir);
3836
+ try {
3837
+ searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base, resolvedOptions.extensions);
3838
+ console.log("[ox-content] Search index built");
3839
+ } catch (err) {
3840
+ console.warn("[ox-content] Failed to build search index:", err);
3841
+ }
3842
+ },
3843
+ async closeBundle() {
3844
+ if (!resolvedOptions.search.enabled || !searchIndexJson) return;
3845
+ const outDir = path.resolve(getRoot(), resolvedOptions.outDir);
3846
+ try {
3847
+ await writeSearchIndex(searchIndexJson, outDir);
3848
+ console.log("[ox-content] Search index written to", path.join(outDir, "search-index.json"));
3849
+ } catch (err) {
3850
+ console.warn("[ox-content] Failed to write search index:", err);
3861
3851
  }
3862
3852
  }
3863
- ];
3864
- if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
3865
- if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
3866
- return plugins;
3853
+ };
3867
3854
  }
3868
3855
  /**
3869
3856
  * Resolves plugin options with defaults.