@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.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { i as importNapiModuleSync, n as transformMermaidStatic, r as importNapiModule, t as mermaidClientScript } from "./mermaid.mjs";
1
+ import { n as importNapiModuleSync, t as importNapiModule } from "./napi.mjs";
2
+ import { n as transformMermaidStatic, t as mermaidClientScript } from "./mermaid2.mjs";
2
3
  import { n as resetTabGroupCounter, r as transformTabs, t as generateTabsCSS } from "./tabs2.mjs";
3
4
  import { n as transformYouTube, t as extractVideoId } from "./youtube2.mjs";
4
5
  import { a as fetchRepoData, c as parseGitHubPermalink, d as transformGitHub, i as fetchGitHubSource, l as prefetchGitHubRepos, n as collectGitHubSources, s as parseGitHubLineRange, t as collectGitHubRepos, u as prefetchGitHubSources } from "./github2.mjs";
@@ -12,9 +13,9 @@ import rehypeStringify from "rehype-stringify";
12
13
  import { createHighlighter } from "shiki";
13
14
  import * as path from "node:path";
14
15
  import { dirname, join } from "node:path";
15
- import * as fs$2 from "fs";
16
- import * as fs$1 from "fs/promises";
16
+ import * as fs$2 from "fs/promises";
17
17
  import * as crypto from "crypto";
18
+ import * as fs$1 from "fs";
18
19
  import { glob } from "glob";
19
20
  import * as fs from "node:fs/promises";
20
21
  import { mkdir, writeFile } from "node:fs/promises";
@@ -322,7 +323,7 @@ async function transformAllPlugins(html, options = {}) {
322
323
  result = await transformOgp(result, void 0, typeof ogpOptions === "object" ? ogpOptions : {});
323
324
  }
324
325
  if (mermaid) {
325
- const { transformMermaidStatic } = await import("./mermaid2.mjs");
326
+ const { transformMermaidStatic } = await import("./mermaid.mjs");
326
327
  result = await transformMermaidStatic(result);
327
328
  }
328
329
  return result;
@@ -393,9 +394,11 @@ function protectMermaidSvgs(html) {
393
394
  * Restore protected mermaid SVG blocks from placeholders.
394
395
  */
395
396
  function restoreMermaidSvgs(html, svgs) {
396
- let result = html;
397
- for (const [placeholder, content] of svgs) result = result.replace(placeholder, content);
398
- return result;
397
+ if (svgs.size === 0) return html;
398
+ return html.replace(/<!--ox-mermaid-\d+-->/g, (placeholder) => {
399
+ const content = svgs.get(placeholder);
400
+ return content !== void 0 ? content : placeholder;
401
+ });
399
402
  }
400
403
  //#endregion
401
404
  //#region src/transform.ts
@@ -565,67 +568,7 @@ if (import.meta.hot) {
565
568
  `;
566
569
  }
567
570
  //#endregion
568
- //#region src/nav-generator.ts
569
- function generateNavMetadata(docs, basePath = "/api") {
570
- return importNapiModuleSync().generateDocsNavMetadata(docs.map((doc) => doc.file), basePath);
571
- }
572
- function generateNavCode(navItems, exportName = "apiNav") {
573
- return importNapiModuleSync().generateDocsNavCode(navItems, exportName);
574
- }
575
- //#endregion
576
571
  //#region src/docs.ts
577
- /**
578
- * Source Documentation Extraction and Generation
579
- *
580
- * This module provides comprehensive tools for extracting JSDoc/TSDoc comments
581
- * from TypeScript/JavaScript source files and automatically generating Markdown
582
- * documentation.
583
- *
584
- * ## Features
585
- *
586
- * - **Automatic Extraction**: Parses JSDoc comments from functions, classes, interfaces, and types
587
- * - **Flexible Filtering**: Include/exclude patterns for selective documentation
588
- * - **Markdown Generation**: Converts extracted docs to organized Markdown files
589
- * - **Navigation Generation**: Auto-generates sidebar navigation metadata
590
- * - **GitHub Links**: Includes clickable links to source code on GitHub
591
- *
592
- * ## Supported JSDoc Tags
593
- *
594
- * - `@param {type} name - description` - Function parameter documentation
595
- * - `@returns {type} description` - Return value documentation
596
- * - `@example` - Code examples (multi-line blocks)
597
- * - `@private` - Mark item as private (excluded from docs if private=false)
598
- * - `@default value` - Default parameter value
599
- * - Custom tags are preserved in the `tags` field
600
- *
601
- * ## Usage Flow
602
- *
603
- * 1. Call `extractDocs()` to parse source files
604
- * 2. Call `generateMarkdown()` to create Markdown content
605
- * 3. Call `writeDocs()` to write files to output directory
606
- * 4. Generated nav.ts can be imported for sidebar navigation
607
- *
608
- * @example
609
- * ```typescript
610
- * import { extractDocs, generateMarkdown, writeDocs } from './docs';
611
- *
612
- * const docsOptions = {
613
- * enabled: true,
614
- * src: ['./src'],
615
- * out: './docs/api',
616
- * include: ['**\/*.ts'],
617
- * exclude: ['**\/*.test.ts'],
618
- * groupBy: 'file',
619
- * githubUrl: 'https://github.com/user/project',
620
- * };
621
- *
622
- * const extracted = await extractDocs(['./src'], docsOptions);
623
- * const markdown = generateMarkdown(extracted, docsOptions);
624
- * await writeDocs(markdown, './docs/api', extracted, docsOptions);
625
- * ```
626
- */
627
- const DOCS_MANIFEST_FILE = ".ox-content-docs-manifest.json";
628
- const DOCS_DATA_FILE = "docs.json";
629
572
  const DEFAULT_DOCS_INCLUDE = [
630
573
  "**/*.ts",
631
574
  "**/*.tsx",
@@ -715,20 +658,12 @@ async function extractDocs(srcDirs, options) {
715
658
  entries: doc.entries
716
659
  }));
717
660
  }
718
- const extractFileDocEntries = napi.extractFileDocEntries;
719
- if (!extractFileDocEntries) throw new Error("[ox-content] extractFileDocEntries is not available from @ox-content/napi.");
720
- const results = [];
721
- for (const srcDir of srcDirs) {
722
- const files = napi.collectDocsSourceFiles(srcDir, options.include, options.exclude);
723
- for (const file of files) {
724
- const entries = extractFileDocEntries(file, options.private, options.internal);
725
- if (entries.length > 0) results.push({
726
- file,
727
- entries
728
- });
729
- }
730
- }
731
- return results;
661
+ const extractDocsFromDirectories = napi.extractDocsFromDirectories;
662
+ if (!extractDocsFromDirectories) throw new Error("[ox-content] extractDocsFromDirectories is not available from @ox-content/napi.");
663
+ return extractDocsFromDirectories(srcDirs, options.include, options.exclude, options.private, options.internal).map((doc) => ({
664
+ file: doc.file,
665
+ entries: doc.entries
666
+ }));
732
667
  }
733
668
  /**
734
669
  * Generates Markdown documentation from extracted docs.
@@ -738,42 +673,25 @@ function generateMarkdown(docs, options) {
738
673
  if (typeof napi.generateDocsMarkdown !== "function") throw new Error("[ox-content] generateDocsMarkdown is not available from @ox-content/napi. Please rebuild the NAPI package.");
739
674
  return napi.generateDocsMarkdown(toRustDocsModules(docs), {
740
675
  groupBy: options.groupBy,
741
- githubUrl: options.githubUrl
676
+ githubUrl: options.githubUrl,
677
+ linkStyle: options.linkStyle,
678
+ basePath: options.basePath,
679
+ pathStrategy: options.pathStrategy
742
680
  });
743
681
  }
744
682
  /**
745
683
  * Writes generated documentation to the output directory.
746
684
  */
747
685
  async function writeDocs(docs, outDir, extractedDocs, options) {
748
- await fs$2.promises.mkdir(outDir, { recursive: true });
749
- const generatedFiles = new Set(Object.keys(docs));
750
- if (extractedDocs && options?.generateNav && options.groupBy === "file") generatedFiles.add("nav.ts");
751
- if (extractedDocs) generatedFiles.add(DOCS_DATA_FILE);
752
- const manifestPath = path$1.join(outDir, DOCS_MANIFEST_FILE);
753
- let previousFiles = [];
754
- try {
755
- previousFiles = JSON.parse(await fs$2.promises.readFile(manifestPath, "utf-8"));
756
- } catch {
757
- previousFiles = [];
758
- }
759
- for (const staleFile of previousFiles) {
760
- if (generatedFiles.has(staleFile)) continue;
761
- await fs$2.promises.rm(path$1.join(outDir, staleFile), { force: true });
762
- }
763
- for (const [fileName, content] of Object.entries(docs)) {
764
- const filePath = path$1.join(outDir, fileName);
765
- await fs$2.promises.writeFile(filePath, content, "utf-8");
766
- }
767
- if (extractedDocs && options?.generateNav && options.groupBy === "file") {
768
- const navCode = generateNavCode(generateNavMetadata(extractedDocs, "/api"), "apiNav");
769
- const navFilePath = path$1.join(outDir, "nav.ts");
770
- await fs$2.promises.writeFile(navFilePath, navCode, "utf-8");
771
- }
772
- if (extractedDocs) {
773
- const napi = importNapiModuleSync();
774
- await fs$2.promises.writeFile(path$1.join(outDir, DOCS_DATA_FILE), napi.generateDocsDataJson(toRustDocsModules(extractedDocs), (/* @__PURE__ */ new Date()).toISOString()), "utf-8");
775
- }
776
- await fs$2.promises.writeFile(manifestPath, JSON.stringify([...generatedFiles].sort(), null, 2), "utf-8");
686
+ const napi = importNapiModuleSync();
687
+ if (typeof napi.writeGeneratedDocs !== "function") throw new Error("[ox-content] writeGeneratedDocs is not available from @ox-content/napi. Please rebuild the NAPI package.");
688
+ napi.writeGeneratedDocs(docs, outDir, extractedDocs ? toRustDocsModules(extractedDocs) : void 0, {
689
+ generateNav: options?.generateNav ?? false,
690
+ groupBy: options?.groupBy ?? "file",
691
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
692
+ basePath: options?.basePath,
693
+ pathStrategy: options?.pathStrategy
694
+ });
777
695
  }
778
696
  function toRustDocsModules(docs) {
779
697
  return docs.map((doc) => ({
@@ -798,9 +716,6 @@ function toRustDocsModules(docs) {
798
716
  }))
799
717
  }));
800
718
  }
801
- /**
802
- * Resolves docs options with defaults.
803
- */
804
719
  function resolveDocsOptions(options) {
805
720
  if (options === false) return false;
806
721
  const opts = options || {};
@@ -821,6 +736,9 @@ function resolveDocsOptions(options) {
821
736
  toc: false,
822
737
  groupBy: opts.groupBy ?? "file",
823
738
  githubUrl: opts.githubUrl,
739
+ linkStyle: opts.linkStyle ?? "markdown",
740
+ basePath: opts.basePath,
741
+ pathStrategy: opts.pathStrategy ?? "flat",
824
742
  generateNav: opts.generateNav ?? true
825
743
  };
826
744
  }
@@ -908,6 +826,8 @@ async function renderHtmlToPng(page, html, width, height, publicDir) {
908
826
  }
909
827
  //#endregion
910
828
  //#region src/og-image/browser.ts
829
+ const PLAYWRIGHT_BROWSER_INSTALL_HINT = "Install Playwright browsers with `npx playwright install chromium` to enable OG image generation.";
830
+ let chromiumUnavailableWarned = false;
911
831
  /**
912
832
  * Opens a Chromium browser and returns a session for rendering OG images.
913
833
  * Returns null if Playwright/Chromium is not available.
@@ -947,10 +867,20 @@ async function openBrowser() {
947
867
  }
948
868
  };
949
869
  } catch (err) {
950
- console.warn("[ox-content:og-image] Chromium not available, skipping OG image generation.", err instanceof Error ? err.message : err);
870
+ warnChromiumUnavailableOnce(err);
951
871
  return null;
952
872
  }
953
873
  }
874
+ function warnChromiumUnavailableOnce(err) {
875
+ if (chromiumUnavailableWarned) return;
876
+ chromiumUnavailableWarned = true;
877
+ console.warn(`[ox-content:og-image] Chromium not available, skipping OG image generation. ${formatChromiumUnavailableDetail(err)}`);
878
+ }
879
+ function formatChromiumUnavailableDetail(err) {
880
+ const message = err instanceof Error ? err.message : String(err);
881
+ if (message.includes("Executable doesn't exist") || message.includes("Please run the following command to download new browsers")) return PLAYWRIGHT_BROWSER_INSTALL_HINT;
882
+ return message.split(/\r?\n/).find((line) => line.trim())?.trim() ?? "Unknown launch error.";
883
+ }
954
884
  //#endregion
955
885
  //#region src/og-image/template.ts
956
886
  /**
@@ -1058,7 +988,7 @@ function computeCacheKey(templateSource, props, width, height) {
1058
988
  async function getCached(cacheDir, key) {
1059
989
  const filePath = path$1.join(cacheDir, `${key}.png`);
1060
990
  try {
1061
- return await fs$1.readFile(filePath);
991
+ return await fs$2.readFile(filePath);
1062
992
  } catch {
1063
993
  return null;
1064
994
  }
@@ -1067,9 +997,9 @@ async function getCached(cacheDir, key) {
1067
997
  * Writes a PNG buffer to the cache.
1068
998
  */
1069
999
  async function writeCache(cacheDir, key, png) {
1070
- await fs$1.mkdir(cacheDir, { recursive: true });
1000
+ await fs$2.mkdir(cacheDir, { recursive: true });
1071
1001
  const filePath = path$1.join(cacheDir, `${key}.png`);
1072
- await fs$1.writeFile(filePath, png);
1002
+ await fs$2.writeFile(filePath, png);
1073
1003
  }
1074
1004
  //#endregion
1075
1005
  //#region \0@oxc-project+runtime@0.129.0/helpers/usingCtx.js
@@ -1761,6 +1691,32 @@ function generateBareHtmlPage(content, title) {
1761
1691
  return importNapiModuleSync().generateSsgBareHtml(content, title);
1762
1692
  }
1763
1693
  /**
1694
+ * Per-build cache for the Rust-facing nav conversion. `navGroups` is the same
1695
+ * `context.navItems` reference for every page in a build, so the deep recursive
1696
+ * copy below only needs to run once per build instead of once per page.
1697
+ */
1698
+ const navGroupsForRustCache = /* @__PURE__ */ new WeakMap();
1699
+ function toRustNavItem(item) {
1700
+ return {
1701
+ title: item.title,
1702
+ path: item.path,
1703
+ href: item.href,
1704
+ children: item.children?.map(toRustNavItem),
1705
+ collapsed: item.collapsed
1706
+ };
1707
+ }
1708
+ function convertNavGroupsForRust(navGroups) {
1709
+ const cached = navGroupsForRustCache.get(navGroups);
1710
+ if (cached) return cached;
1711
+ const converted = navGroups.map((group) => ({
1712
+ title: group.title,
1713
+ collapsed: group.collapsed,
1714
+ items: group.items.map(toRustNavItem)
1715
+ }));
1716
+ navGroupsForRustCache.set(navGroups, converted);
1717
+ return converted;
1718
+ }
1719
+ /**
1764
1720
  * Generates HTML page with navigation using Rust NAPI bindings.
1765
1721
  */
1766
1722
  async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, theme, locale, availableLocales) {
@@ -1772,18 +1728,7 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
1772
1728
  children: entry.children?.map(toRustTocEntry) ?? []
1773
1729
  });
1774
1730
  const tocForRust = pageData.toc.map(toRustTocEntry);
1775
- const toRustNavItem = (item) => ({
1776
- title: item.title,
1777
- path: item.path,
1778
- href: item.href,
1779
- children: item.children?.map(toRustNavItem),
1780
- collapsed: item.collapsed
1781
- });
1782
- const navGroupsForRust = navGroups.map((group) => ({
1783
- title: group.title,
1784
- collapsed: group.collapsed,
1785
- items: group.items.map(toRustNavItem)
1786
- }));
1731
+ const navGroupsForRust = convertNavGroupsForRust(navGroups);
1787
1732
  const themeForRust = theme ? themeToNapi(theme) : void 0;
1788
1733
  const entryPageForRust = pageData.entryPage ? {
1789
1734
  hero: pageData.entryPage.hero ? {
@@ -1840,8 +1785,8 @@ async function generateHtmlPage(pageData, navGroups, siteName, base, ogImage, th
1840
1785
  async function externalizeSharedPageAssets(pages, outDir, base) {
1841
1786
  const optimized = (await importNapiModule()).externalizeSsgAssets(pages, outDir, base);
1842
1787
  await Promise.all(optimized.assets.map(async (asset) => {
1843
- await fs$1.mkdir(path$1.dirname(asset.outputPath), { recursive: true });
1844
- await fs$1.writeFile(asset.outputPath, asset.content, "utf-8");
1788
+ await fs$2.mkdir(path$1.dirname(asset.outputPath), { recursive: true });
1789
+ await fs$2.writeFile(asset.outputPath, asset.content, "utf-8");
1845
1790
  }));
1846
1791
  return {
1847
1792
  pages: optimized.pages,
@@ -1903,149 +1848,201 @@ async function buildSsg(options, root) {
1903
1848
  };
1904
1849
  const srcDir = path$1.resolve(root, options.srcDir);
1905
1850
  const outDir = path$1.resolve(root, options.outDir);
1906
- const base = options.base.endsWith("/") ? options.base : options.base + "/";
1907
1851
  const generatedFiles = [];
1908
- const generatedPages = [];
1909
1852
  const errors = [];
1910
- if (ssgOptions.clean) try {
1911
- await fs$1.rm(outDir, {
1853
+ await cleanOutputDirectory(ssgOptions, outDir);
1854
+ const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
1855
+ const context = await createBuildSsgContext(options, root, srcDir, outDir, markdownFiles);
1856
+ const collected = await collectPageResults(context, markdownFiles);
1857
+ errors.push(...collected.errors);
1858
+ await generateOgImageAssets(context, collected, generatedFiles, errors);
1859
+ await writeGeneratedPages(await generateHtmlPages(context, collected.pageResults, collected, errors), context, generatedFiles);
1860
+ return {
1861
+ files: generatedFiles,
1862
+ errors
1863
+ };
1864
+ }
1865
+ async function cleanOutputDirectory(ssgOptions, outDir) {
1866
+ if (!ssgOptions.clean) return;
1867
+ try {
1868
+ await fs$2.rm(outDir, {
1912
1869
  recursive: true,
1913
1870
  force: true
1914
1871
  });
1915
1872
  } catch {}
1916
- const markdownFiles = await collectMarkdownFiles(srcDir, options.extensions);
1917
- 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));
1918
- let siteName = ssgOptions.siteName ?? "Documentation";
1919
- if (!ssgOptions.siteName) try {
1873
+ }
1874
+ async function createBuildSsgContext(options, root, srcDir, outDir, markdownFiles) {
1875
+ const ssgOptions = options.ssg;
1876
+ const base = options.base.endsWith("/") ? options.base : options.base + "/";
1877
+ return {
1878
+ options,
1879
+ ssgOptions,
1880
+ root,
1881
+ srcDir,
1882
+ outDir,
1883
+ base,
1884
+ navItems: resolveNavigationGroups(ssgOptions.navigation, base, ssgOptions.extension) ?? (ssgOptions.theme?.sidebar.length ? buildThemeNavItems(ssgOptions.theme.sidebar, base, ssgOptions.extension) : buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension)),
1885
+ siteName: await resolveSiteName$1(root, ssgOptions),
1886
+ shouldGenerateOgImages: (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare,
1887
+ napi: ssgOptions.lastUpdated ? await importNapiModule() : void 0
1888
+ };
1889
+ }
1890
+ async function resolveSiteName$1(root, ssgOptions) {
1891
+ if (ssgOptions.siteName) return ssgOptions.siteName;
1892
+ try {
1920
1893
  const pkgPath = path$1.join(root, "package.json");
1921
- const pkg = JSON.parse(await fs$1.readFile(pkgPath, "utf-8"));
1922
- if (pkg.name) siteName = formatTitle(pkg.name);
1923
- } catch {}
1924
- const ogImageEntries = [];
1925
- const ogImageInputPaths = [];
1926
- const ogImageUrlMap = /* @__PURE__ */ new Map();
1927
- const shouldGenerateOgImages = (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare;
1928
- const pageResults = [];
1929
- const napi = ssgOptions.lastUpdated ? await importNapiModule() : void 0;
1894
+ const pkg = JSON.parse(await fs$2.readFile(pkgPath, "utf-8"));
1895
+ return pkg.name ? formatTitle(pkg.name) : "Documentation";
1896
+ } catch {
1897
+ return "Documentation";
1898
+ }
1899
+ }
1900
+ async function collectPageResults(context, markdownFiles) {
1901
+ const collected = {
1902
+ pageResults: [],
1903
+ ogImageEntries: [],
1904
+ ogImageInputPaths: [],
1905
+ ogImageUrlMap: /* @__PURE__ */ new Map(),
1906
+ errors: []
1907
+ };
1930
1908
  for (const inputPath of markdownFiles) try {
1931
- const result = await transformMarkdown(await fs$1.readFile(inputPath, "utf-8"), inputPath, options, {
1932
- convertMdLinks: true,
1933
- baseUrl: base,
1934
- sourcePath: inputPath
1935
- });
1936
- const frontmatter = normalizeVitePressFrontmatter(result.frontmatter);
1937
- let transformedHtml = result.html;
1938
- const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(transformedHtml);
1939
- transformedHtml = protectedHtml;
1940
- const pluginOptions = {
1941
- tabs: true,
1942
- youtube: true,
1943
- github: options.embeds.github,
1944
- openGraph: options.embeds.openGraph,
1945
- mermaid: true,
1946
- githubToken: process.env.GITHUB_TOKEN
1947
- };
1948
- transformedHtml = await transformAllPlugins(transformedHtml, pluginOptions);
1949
- if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
1950
- transformedHtml = restoreMermaidSvgs(transformedHtml, mermaidSvgs);
1951
- const title = extractTitle$1(transformedHtml, frontmatter);
1952
- const description = frontmatter.description;
1953
- const routePaths = getRoutePaths(inputPath, srcDir, outDir, base, ssgOptions.extension, ssgOptions.siteUrl);
1954
- pageResults.push({
1955
- inputPath,
1956
- routePaths,
1957
- transformedHtml,
1958
- title,
1959
- description,
1960
- lastUpdated: napi?.getGitLastUpdated(inputPath, root) ?? void 0,
1961
- frontmatter,
1962
- toc: result.toc
1963
- });
1964
- if (shouldGenerateOgImages) {
1965
- const { layout: _layout, ...frontmatterRest } = frontmatter;
1966
- ogImageEntries.push({
1967
- props: {
1968
- ...frontmatterRest,
1969
- title,
1970
- description,
1971
- siteName
1972
- },
1973
- outputPath: routePaths.ogImagePath
1974
- });
1975
- ogImageInputPaths.push(inputPath);
1976
- ogImageUrlMap.set(inputPath, routePaths.ogImageUrl);
1977
- }
1909
+ const pageResult = await transformSsgPage(context, inputPath);
1910
+ collected.pageResults.push(pageResult);
1911
+ collectOgImageEntry(context, pageResult, collected);
1978
1912
  } catch (err) {
1979
1913
  const errorMessage = err instanceof Error ? err.message : String(err);
1980
- errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1914
+ collected.errors.push(`Failed to process ${inputPath}: ${errorMessage}`);
1981
1915
  }
1982
- if (shouldGenerateOgImages && ogImageEntries.length > 0) try {
1983
- const ogResults = await generateOgImages(ogImageEntries, options.ogImageOptions, root);
1984
- let ogSuccessCount = 0;
1985
- for (let i = 0; i < ogResults.length; i++) {
1986
- const result = ogResults[i];
1987
- if (result.error) {
1988
- errors.push(`OG image failed for ${result.outputPath}: ${result.error}`);
1989
- ogImageUrlMap.delete(ogImageInputPaths[i]);
1990
- } else {
1991
- generatedFiles.push(result.outputPath);
1992
- ogSuccessCount++;
1993
- }
1994
- }
1995
- if (ogSuccessCount > 0) {
1996
- const cachedCount = ogResults.filter((r) => r.cached && !r.error).length;
1997
- console.log(`[ox-content:og-image] Generated ${ogSuccessCount} OG images` + (cachedCount > 0 ? ` (${cachedCount} from cache)` : ""));
1998
- }
1916
+ return collected;
1917
+ }
1918
+ async function transformSsgPage(context, inputPath) {
1919
+ const result = await transformMarkdown(await fs$2.readFile(inputPath, "utf-8"), inputPath, context.options, {
1920
+ convertMdLinks: true,
1921
+ baseUrl: context.base,
1922
+ sourcePath: inputPath
1923
+ });
1924
+ const frontmatter = normalizeVitePressFrontmatter(result.frontmatter);
1925
+ const transformedHtml = await transformSsgHtml(result.html, context.options);
1926
+ const title = extractTitle$1(transformedHtml, frontmatter);
1927
+ return {
1928
+ inputPath,
1929
+ routePaths: getRoutePaths(inputPath, context.srcDir, context.outDir, context.base, context.ssgOptions.extension, context.ssgOptions.siteUrl),
1930
+ transformedHtml,
1931
+ title,
1932
+ description: frontmatter.description,
1933
+ lastUpdated: context.napi?.getGitLastUpdated(inputPath, context.root) ?? void 0,
1934
+ frontmatter,
1935
+ toc: result.toc
1936
+ };
1937
+ }
1938
+ async function transformSsgHtml(html, options) {
1939
+ const { html: protectedHtml, svgs: mermaidSvgs } = protectMermaidSvgs(html);
1940
+ let transformedHtml = await transformAllPlugins(protectedHtml, {
1941
+ tabs: true,
1942
+ youtube: true,
1943
+ github: options.embeds.github,
1944
+ openGraph: options.embeds.openGraph,
1945
+ mermaid: true,
1946
+ githubToken: process.env.GITHUB_TOKEN
1947
+ });
1948
+ if (hasIslands(transformedHtml)) transformedHtml = (await transformIslands(transformedHtml)).html;
1949
+ return restoreMermaidSvgs(transformedHtml, mermaidSvgs);
1950
+ }
1951
+ function collectOgImageEntry(context, pageResult, collected) {
1952
+ if (!context.shouldGenerateOgImages) return;
1953
+ const { layout: _layout, ...frontmatterRest } = pageResult.frontmatter;
1954
+ collected.ogImageEntries.push({
1955
+ props: {
1956
+ ...frontmatterRest,
1957
+ title: pageResult.title,
1958
+ description: pageResult.description,
1959
+ siteName: context.siteName
1960
+ },
1961
+ outputPath: pageResult.routePaths.ogImagePath
1962
+ });
1963
+ collected.ogImageInputPaths.push(pageResult.inputPath);
1964
+ collected.ogImageUrlMap.set(pageResult.inputPath, pageResult.routePaths.ogImageUrl);
1965
+ }
1966
+ async function generateOgImageAssets(context, collected, generatedFiles, errors) {
1967
+ if (!context.shouldGenerateOgImages || collected.ogImageEntries.length === 0) return;
1968
+ try {
1969
+ const ogResults = await generateOgImages(collected.ogImageEntries, context.options.ogImageOptions, context.root);
1970
+ if (clearMissingBrowserOgImages(ogResults, collected)) return;
1971
+ reportOgImageResults(ogResults, collected, generatedFiles, errors);
1999
1972
  } catch (err) {
2000
1973
  const errorMessage = err instanceof Error ? err.message : String(err);
2001
1974
  console.warn(`[ox-content:og-image] Batch generation failed: ${errorMessage}`);
2002
- ogImageUrlMap.clear();
1975
+ collected.ogImageUrlMap.clear();
2003
1976
  }
2004
- for (const pageResult of pageResults) try {
2005
- const { inputPath, routePaths, transformedHtml, title, description, lastUpdated, frontmatter, toc } = pageResult;
2006
- let pageOgImage = ssgOptions.ogImage;
2007
- if (shouldGenerateOgImages && ogImageUrlMap.has(inputPath)) pageOgImage = ogImageUrlMap.get(inputPath);
2008
- let entryPage;
2009
- if (frontmatter.layout === "entry") entryPage = {
2010
- hero: frontmatter.hero,
2011
- features: frontmatter.features
2012
- };
2013
- let html;
2014
- if (ssgOptions.bare) html = generateBareHtmlPage(transformedHtml, title);
2015
- else {
2016
- const pageData = {
2017
- title,
2018
- description,
2019
- content: transformedHtml,
2020
- toc,
2021
- lastUpdated,
2022
- frontmatter,
2023
- path: routePaths.urlPath,
2024
- href: routePaths.href,
2025
- entryPage
2026
- };
2027
- html = await generateHtmlPage(pageData, navItems, siteName, base, pageOgImage, ssgOptions.theme, getPageLocale(pageData.path, options.i18n), options.i18n ? options.i18n.locales : void 0);
1977
+ }
1978
+ function clearMissingBrowserOgImages(ogResults, collected) {
1979
+ if (!(ogResults.length > 0 && ogResults.every((result) => result.error === "Chromium not available"))) return false;
1980
+ for (const inputPath of collected.ogImageInputPaths) collected.ogImageUrlMap.delete(inputPath);
1981
+ return true;
1982
+ }
1983
+ function reportOgImageResults(ogResults, collected, generatedFiles, errors) {
1984
+ let ogSuccessCount = 0;
1985
+ for (let i = 0; i < ogResults.length; i++) {
1986
+ const result = ogResults[i];
1987
+ if (result.error) {
1988
+ errors.push(`OG image failed for ${result.outputPath}: ${result.error}`);
1989
+ collected.ogImageUrlMap.delete(collected.ogImageInputPaths[i]);
1990
+ } else {
1991
+ generatedFiles.push(result.outputPath);
1992
+ ogSuccessCount++;
2028
1993
  }
1994
+ }
1995
+ if (ogSuccessCount > 0) {
1996
+ const cachedCount = ogResults.filter((result) => result.cached && !result.error).length;
1997
+ console.log(`[ox-content:og-image] Generated ${ogSuccessCount} OG images` + (cachedCount > 0 ? ` (${cachedCount} from cache)` : ""));
1998
+ }
1999
+ }
2000
+ async function generateHtmlPages(context, pageResults, collected, errors) {
2001
+ const generatedPages = [];
2002
+ for (const pageResult of pageResults) try {
2029
2003
  generatedPages.push({
2030
- inputPath,
2031
- outputPath: routePaths.outputPath,
2032
- html
2004
+ inputPath: pageResult.inputPath,
2005
+ outputPath: pageResult.routePaths.outputPath,
2006
+ html: await renderSsgPage(context, pageResult, collected.ogImageUrlMap)
2033
2007
  });
2034
2008
  } catch (err) {
2035
2009
  const errorMessage = err instanceof Error ? err.message : String(err);
2036
2010
  errors.push(`Failed to generate HTML for ${pageResult.inputPath}: ${errorMessage}`);
2037
2011
  }
2038
- const optimizedOutput = await externalizeSharedPageAssets(generatedPages, outDir, base);
2012
+ return generatedPages;
2013
+ }
2014
+ async function renderSsgPage(context, pageResult, ogImageUrlMap) {
2015
+ if (context.ssgOptions.bare) return generateBareHtmlPage(pageResult.transformedHtml, pageResult.title);
2016
+ const pageData = createSsgPageData(pageResult);
2017
+ const pageOgImage = context.shouldGenerateOgImages && ogImageUrlMap.has(pageResult.inputPath) ? ogImageUrlMap.get(pageResult.inputPath) : context.ssgOptions.ogImage;
2018
+ 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);
2019
+ }
2020
+ function createSsgPageData(pageResult) {
2021
+ const { frontmatter } = pageResult;
2022
+ const entryPage = frontmatter.layout === "entry" ? {
2023
+ hero: frontmatter.hero,
2024
+ features: frontmatter.features
2025
+ } : void 0;
2026
+ return {
2027
+ title: pageResult.title,
2028
+ description: pageResult.description,
2029
+ content: pageResult.transformedHtml,
2030
+ toc: pageResult.toc,
2031
+ lastUpdated: pageResult.lastUpdated,
2032
+ frontmatter,
2033
+ path: pageResult.routePaths.urlPath,
2034
+ href: pageResult.routePaths.href,
2035
+ entryPage
2036
+ };
2037
+ }
2038
+ async function writeGeneratedPages(generatedPages, context, generatedFiles) {
2039
+ const optimizedOutput = await externalizeSharedPageAssets(generatedPages, context.outDir, context.base);
2039
2040
  generatedFiles.push(...optimizedOutput.assets);
2040
2041
  for (const page of optimizedOutput.pages) {
2041
- await fs$1.mkdir(path$1.dirname(page.outputPath), { recursive: true });
2042
- await fs$1.writeFile(page.outputPath, page.html, "utf-8");
2042
+ await fs$2.mkdir(path$1.dirname(page.outputPath), { recursive: true });
2043
+ await fs$2.writeFile(page.outputPath, page.html, "utf-8");
2043
2044
  generatedFiles.push(page.outputPath);
2044
2045
  }
2045
- return {
2046
- files: generatedFiles,
2047
- errors
2048
- };
2049
2046
  }
2050
2047
  //#endregion
2051
2048
  //#region src/search.ts
@@ -2179,14 +2176,14 @@ async function resolveMarkdownFile(url, srcDir, extensions) {
2179
2176
  for (const relativePath of directCandidates) {
2180
2177
  const filePath = path$1.join(srcDir, relativePath);
2181
2178
  try {
2182
- await fs$1.access(filePath);
2179
+ await fs$2.access(filePath);
2183
2180
  return filePath;
2184
2181
  } catch {}
2185
2182
  }
2186
2183
  for (const extension of extensions) {
2187
2184
  const indexPath = path$1.join(srcDir, routePath, `index${extension}`);
2188
2185
  try {
2189
- await fs$1.access(indexPath);
2186
+ await fs$2.access(indexPath);
2190
2187
  return indexPath;
2191
2188
  } catch {}
2192
2189
  }
@@ -2228,7 +2225,7 @@ async function resolveSiteName(options, root) {
2228
2225
  if (options.ssg.siteName) return options.ssg.siteName;
2229
2226
  try {
2230
2227
  const pkgPath = path$1.join(root, "package.json");
2231
- const pkg = JSON.parse(await fs$1.readFile(pkgPath, "utf-8"));
2228
+ const pkg = JSON.parse(await fs$2.readFile(pkgPath, "utf-8"));
2232
2229
  if (pkg.name) return formatTitle(pkg.name);
2233
2230
  } catch {}
2234
2231
  return "Documentation";
@@ -2240,7 +2237,7 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root)
2240
2237
  const srcDir = path$1.resolve(root, options.srcDir);
2241
2238
  resetTabGroupCounter();
2242
2239
  resetIslandCounter();
2243
- const result = await transformMarkdown(await fs$1.readFile(filePath, "utf-8"), filePath, options, {
2240
+ const result = await transformMarkdown(await fs$2.readFile(filePath, "utf-8"), filePath, options, {
2244
2241
  convertMdLinks: true,
2245
2242
  baseUrl: base,
2246
2243
  sourcePath: filePath
@@ -2396,7 +2393,7 @@ async function collectPages(options, root) {
2396
2393
  const pages = [];
2397
2394
  const generateOgImage = options.ogImage || options.ssg.generateOgImage;
2398
2395
  for (const file of files.sort()) {
2399
- const content = fs$2.readFileSync(file, "utf-8");
2396
+ const content = fs$1.readFileSync(file, "utf-8");
2400
2397
  const frontmatter = normalizeVitePressFrontmatter(parseFrontmatter(content));
2401
2398
  if (frontmatter.layout === "entry") continue;
2402
2399
  const title = extractTitle(content, frontmatter);
@@ -2720,7 +2717,7 @@ function createI18nPlugin(resolvedOptions) {
2720
2717
  async buildStart() {
2721
2718
  if (!i18nOptions || !i18nOptions.check) return;
2722
2719
  const dictDir = path$1.resolve(root, i18nOptions.dir);
2723
- if (!fs$2.existsSync(dictDir)) {
2720
+ if (!fs$1.existsSync(dictDir)) {
2724
2721
  console.warn(`[ox-content:i18n] Dictionary directory not found: ${dictDir}`);
2725
2722
  return;
2726
2723
  }
@@ -2736,7 +2733,7 @@ function createI18nPlugin(resolvedOptions) {
2736
2733
  configureServer(server) {
2737
2734
  if (!i18nOptions) return;
2738
2735
  const dictDir = path$1.resolve(root, i18nOptions.dir);
2739
- if (fs$2.existsSync(dictDir)) {
2736
+ if (fs$1.existsSync(dictDir)) {
2740
2737
  server.watcher.add(dictDir);
2741
2738
  server.watcher.on("change", (filePath) => {
2742
2739
  if (!filePath.startsWith(dictDir)) return;
@@ -2879,7 +2876,10 @@ function stripMaskedDocument(result) {
2879
2876
  };
2880
2877
  }
2881
2878
  function normalizeLintOptions(options) {
2882
- 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];
2879
+ const standardDictionary = options.dictionary?.standard && typeof options.dictionary.standard === "object" ? options.dictionary.standard : void 0;
2880
+ const optionLanguages = options.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language));
2881
+ const standardLanguages = standardDictionary?.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language));
2882
+ const languages = optionLanguages ?? standardLanguages ?? [...DEFAULT_LANGUAGES];
2883
2883
  const standard = normalizeStandardDictionaryOptions(options.dictionary?.standard, languages);
2884
2884
  return {
2885
2885
  dictionary: {
@@ -2920,6 +2920,12 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
2920
2920
  const { spellCheckDocument } = await loadCspellLib();
2921
2921
  const locale = standard.languages.join(",");
2922
2922
  const settings = createStandardSpellcheckSettings(options, locale);
2923
+ const spellCheckOptions = {
2924
+ generateSuggestions: true,
2925
+ noConfigSearch: true,
2926
+ numSuggestions: 3,
2927
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo
2928
+ };
2923
2929
  return Promise.all(maskedDocuments.map(async (maskedDocument, index) => {
2924
2930
  if (maskedDocument.trim().length === 0) return [];
2925
2931
  return (await spellCheckDocument({
@@ -2927,12 +2933,7 @@ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
2927
2933
  locale,
2928
2934
  text: maskedDocument,
2929
2935
  uri: `file:///ox-content-lint-${index}.md`
2930
- }, {
2931
- generateSuggestions: true,
2932
- noConfigSearch: true,
2933
- numSuggestions: 3,
2934
- resolveImportsRelativeTo: standard.resolveImportsRelativeTo
2935
- }, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages));
2936
+ }, spellCheckOptions, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages, maskedDocument));
2936
2937
  }));
2937
2938
  } catch (error) {
2938
2939
  const imports = standard.imports.join(", ");
@@ -2953,8 +2954,8 @@ async function loadCspellLib() {
2953
2954
  cspellLibPromise ??= import("cspell-lib");
2954
2955
  return cspellLibPromise;
2955
2956
  }
2956
- function mapStandardIssueToDiagnostic(issue, languages) {
2957
- const line = issue.line.position.line + 1;
2957
+ function mapStandardIssueToDiagnostic(issue, languages, documentText) {
2958
+ const line = getLineNumberAtOffset(documentText, issue.line.offset);
2958
2959
  const column = issue.offset - issue.line.offset + 1;
2959
2960
  return {
2960
2961
  column,
@@ -2968,6 +2969,11 @@ function mapStandardIssueToDiagnostic(issue, languages) {
2968
2969
  suggestions: issue.suggestions?.slice(0, 3)
2969
2970
  };
2970
2971
  }
2972
+ function getLineNumberAtOffset(text, offset) {
2973
+ let line = 1;
2974
+ for (let index = 0; index < offset && index < text.length; index++) if (text.charCodeAt(index) === 10) line++;
2975
+ return line;
2976
+ }
2971
2977
  function inferStandardIssueLanguage(word, languages) {
2972
2978
  if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
2973
2979
  if (/[\p{Script=Han}]/u.test(word)) {
@@ -3704,21 +3710,35 @@ init_page_context();
3704
3710
  function oxContent(options = {}) {
3705
3711
  const resolvedOptions = resolveOptions(options);
3706
3712
  let config;
3707
- async function regenerateDocs(root) {
3708
- const docsOptions = resolvedOptions.docs;
3709
- if (!docsOptions || !docsOptions.enabled) return 0;
3710
- const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
3711
- const outDir = path$1.resolve(root, docsOptions.out);
3712
- const extracted = await extractDocs(srcDirs, docsOptions);
3713
- const generated = generateMarkdown(extracted, docsOptions);
3714
- await writeDocs(generated, outDir, extracted, docsOptions);
3715
- return Object.keys(generated).length;
3716
- }
3717
- const mainPlugin = {
3718
- name: "ox-content",
3719
- configResolved(resolvedConfig) {
3713
+ const getRoot = () => config?.root || process.cwd();
3714
+ const ssgDevCache = createDevServerCache();
3715
+ const plugins = [
3716
+ createMainPlugin(resolvedOptions, (resolvedConfig) => {
3720
3717
  config = resolvedConfig;
3721
- },
3718
+ }),
3719
+ createEnvironmentPlugin(resolvedOptions),
3720
+ createDocsPlugin(resolvedOptions, getRoot),
3721
+ createSsgPlugin(resolvedOptions, getRoot, ssgDevCache),
3722
+ createSearchPlugin(resolvedOptions, getRoot)
3723
+ ];
3724
+ if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
3725
+ if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
3726
+ return plugins;
3727
+ }
3728
+ async function regenerateDocs(resolvedOptions, root) {
3729
+ const docsOptions = resolvedOptions.docs;
3730
+ if (!docsOptions || !docsOptions.enabled) return 0;
3731
+ const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
3732
+ const outDir = path$1.resolve(root, docsOptions.out);
3733
+ const extracted = await extractDocs(srcDirs, docsOptions);
3734
+ const generated = generateMarkdown(extracted, docsOptions);
3735
+ await writeDocs(generated, outDir, extracted, docsOptions);
3736
+ return Object.keys(generated).length;
3737
+ }
3738
+ function createMainPlugin(resolvedOptions, setConfig) {
3739
+ return {
3740
+ name: "ox-content",
3741
+ configResolved: setConfig,
3722
3742
  configureServer(devServer) {
3723
3743
  devServer.middlewares.use(async (req, res, next) => {
3724
3744
  const url = req.url;
@@ -3743,31 +3763,33 @@ function oxContent(options = {}) {
3743
3763
  };
3744
3764
  },
3745
3765
  async handleHotUpdate({ file, server }) {
3746
- if (isMarkdownFilePath(file, resolvedOptions.extensions)) {
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) : [];
3754
- }
3766
+ if (!isMarkdownFilePath(file, resolvedOptions.extensions)) return;
3767
+ server.ws.send({
3768
+ type: "custom",
3769
+ event: "ox-content:update",
3770
+ data: { file }
3771
+ });
3772
+ const modules = server.moduleGraph.getModulesByFile(file);
3773
+ return modules ? Array.from(modules) : [];
3755
3774
  }
3756
3775
  };
3757
- const environmentPlugin = {
3776
+ }
3777
+ function createEnvironmentPlugin(resolvedOptions) {
3778
+ return {
3758
3779
  name: "ox-content:environment",
3759
3780
  config() {
3760
3781
  return { environments: { markdown: createMarkdownEnvironment(resolvedOptions) } };
3761
3782
  }
3762
3783
  };
3763
- const docsPlugin = {
3784
+ }
3785
+ function createDocsPlugin(resolvedOptions, getRoot) {
3786
+ return {
3764
3787
  name: "ox-content:docs",
3765
3788
  async buildStart() {
3766
3789
  const docsOptions = resolvedOptions.docs;
3767
3790
  if (!docsOptions || !docsOptions.enabled) return;
3768
- const root = config?.root || process.cwd();
3769
3791
  try {
3770
- const count = await regenerateDocs(root);
3792
+ const count = await regenerateDocs(resolvedOptions, getRoot());
3771
3793
  console.log(`[ox-content] Generated ${count} documentation files to ${docsOptions.out}`);
3772
3794
  } catch (err) {
3773
3795
  console.warn("[ox-content] Failed to generate documentation:", err);
@@ -3776,50 +3798,32 @@ function oxContent(options = {}) {
3776
3798
  configureServer(devServer) {
3777
3799
  const docsOptions = resolvedOptions.docs;
3778
3800
  if (!docsOptions || !docsOptions.enabled) return;
3779
- const root = config?.root || process.cwd();
3801
+ const root = getRoot();
3780
3802
  const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
3781
3803
  for (const srcDir of srcDirs) devServer.watcher.add(srcDir);
3782
3804
  devServer.watcher.on("all", async (event, file) => {
3783
3805
  if (event !== "add" && event !== "change" && event !== "unlink") return;
3784
- if (srcDirs.some((srcDir) => file.startsWith(srcDir) && (file.endsWith(".ts") || file.endsWith(".tsx")))) try {
3785
- await regenerateDocs(root);
3806
+ if (!srcDirs.some((srcDir) => file.startsWith(srcDir) && (file.endsWith(".ts") || file.endsWith(".tsx")))) return;
3807
+ try {
3808
+ await regenerateDocs(resolvedOptions, root);
3786
3809
  } catch {}
3787
3810
  });
3788
3811
  }
3789
3812
  };
3790
- const ssgDevCache = createDevServerCache();
3791
- const ssgPlugin = {
3813
+ }
3814
+ function createSsgPlugin(resolvedOptions, getRoot, ssgDevCache) {
3815
+ return {
3792
3816
  name: "ox-content:ssg",
3793
3817
  configureServer(devServer) {
3794
3818
  if (!resolvedOptions.ssg.enabled) return;
3795
- const root = config?.root || process.cwd();
3819
+ const root = getRoot();
3796
3820
  const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
3797
3821
  devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
3798
3822
  devServer.watcher.on("add", (file) => {
3799
- if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) {
3800
- invalidateNavCache(ssgDevCache);
3801
- devServer.ws.send({
3802
- type: "custom",
3803
- event: "ox-content:update",
3804
- data: {
3805
- file,
3806
- type: "add"
3807
- }
3808
- });
3809
- }
3823
+ notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, "add");
3810
3824
  });
3811
3825
  devServer.watcher.on("unlink", (file) => {
3812
- if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) {
3813
- invalidateNavCache(ssgDevCache);
3814
- devServer.ws.send({
3815
- type: "custom",
3816
- event: "ox-content:update",
3817
- data: {
3818
- file,
3819
- type: "unlink"
3820
- }
3821
- });
3822
- }
3826
+ notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, "unlink");
3823
3827
  });
3824
3828
  devServer.watcher.on("change", (file) => {
3825
3829
  if (file.startsWith(srcDir) && isMarkdownFilePath(file, resolvedOptions.extensions)) invalidatePageCache(ssgDevCache, file);
@@ -3827,63 +3831,63 @@ function oxContent(options = {}) {
3827
3831
  },
3828
3832
  async closeBundle() {
3829
3833
  if (!resolvedOptions.ssg.enabled) return;
3830
- const root = config?.root || process.cwd();
3831
3834
  try {
3832
- const result = await buildSsg(resolvedOptions, root);
3835
+ const result = await buildSsg(resolvedOptions, getRoot());
3833
3836
  if (result.files.length > 0) console.log(`[ox-content] Generated ${result.files.length} output files`);
3834
- if (result.errors.length > 0) for (const error of result.errors) console.warn(`[ox-content] ${error}`);
3837
+ for (const error of result.errors) console.warn(`[ox-content] ${error}`);
3835
3838
  } catch (err) {
3836
3839
  console.error("[ox-content] SSG build failed:", err);
3837
3840
  }
3838
3841
  }
3839
3842
  };
3843
+ }
3844
+ function notifySsgFileAddedOrRemoved(devServer, resolvedOptions, ssgDevCache, srcDir, file, type) {
3845
+ if (!file.startsWith(srcDir) || !isMarkdownFilePath(file, resolvedOptions.extensions)) return;
3846
+ invalidateNavCache(ssgDevCache);
3847
+ devServer.ws.send({
3848
+ type: "custom",
3849
+ event: "ox-content:update",
3850
+ data: {
3851
+ file,
3852
+ type
3853
+ }
3854
+ });
3855
+ }
3856
+ function createSearchPlugin(resolvedOptions, getRoot) {
3840
3857
  let searchIndexJson = "";
3841
- const plugins = [
3842
- mainPlugin,
3843
- environmentPlugin,
3844
- docsPlugin,
3845
- ssgPlugin,
3846
- {
3847
- name: "ox-content:search",
3848
- resolveId(id) {
3849
- if (id === "virtual:ox-content/search") return "\0virtual:ox-content/search";
3850
- return null;
3851
- },
3852
- async load(id) {
3853
- if (id === "\0virtual:ox-content/search") {
3854
- const searchOptions = resolvedOptions.search;
3855
- if (!searchOptions.enabled) return "export const search = () => []; export const searchOptions = { enabled: false }; export default { search, searchOptions };";
3856
- return generateSearchModule(searchOptions, resolvedOptions.base + "search-index.json");
3857
- }
3858
- return null;
3859
- },
3860
- async buildStart() {
3861
- if (!resolvedOptions.search.enabled) return;
3862
- const root = config?.root || process.cwd();
3863
- const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
3864
- try {
3865
- searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base, resolvedOptions.extensions);
3866
- console.log("[ox-content] Search index built");
3867
- } catch (err) {
3868
- console.warn("[ox-content] Failed to build search index:", err);
3869
- }
3870
- },
3871
- async closeBundle() {
3872
- if (!resolvedOptions.search.enabled || !searchIndexJson) return;
3873
- const root = config?.root || process.cwd();
3874
- const outDir = path$1.resolve(root, resolvedOptions.outDir);
3875
- try {
3876
- await writeSearchIndex(searchIndexJson, outDir);
3877
- console.log("[ox-content] Search index written to", path$1.join(outDir, "search-index.json"));
3878
- } catch (err) {
3879
- console.warn("[ox-content] Failed to write search index:", err);
3880
- }
3858
+ return {
3859
+ name: "ox-content:search",
3860
+ resolveId(id) {
3861
+ if (id === "virtual:ox-content/search") return "\0virtual:ox-content/search";
3862
+ return null;
3863
+ },
3864
+ async load(id) {
3865
+ if (id !== "\0virtual:ox-content/search") return null;
3866
+ const searchOptions = resolvedOptions.search;
3867
+ if (!searchOptions.enabled) return "export const search = () => []; export const searchOptions = { enabled: false }; export default { search, searchOptions };";
3868
+ return generateSearchModule(searchOptions, resolvedOptions.base + "search-index.json");
3869
+ },
3870
+ async buildStart() {
3871
+ if (!resolvedOptions.search.enabled) return;
3872
+ const srcDir = path$1.resolve(getRoot(), resolvedOptions.srcDir);
3873
+ try {
3874
+ searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base, resolvedOptions.extensions);
3875
+ console.log("[ox-content] Search index built");
3876
+ } catch (err) {
3877
+ console.warn("[ox-content] Failed to build search index:", err);
3878
+ }
3879
+ },
3880
+ async closeBundle() {
3881
+ if (!resolvedOptions.search.enabled || !searchIndexJson) return;
3882
+ const outDir = path$1.resolve(getRoot(), resolvedOptions.outDir);
3883
+ try {
3884
+ await writeSearchIndex(searchIndexJson, outDir);
3885
+ console.log("[ox-content] Search index written to", path$1.join(outDir, "search-index.json"));
3886
+ } catch (err) {
3887
+ console.warn("[ox-content] Failed to write search index:", err);
3881
3888
  }
3882
3889
  }
3883
- ];
3884
- if (resolvedOptions.i18n) plugins.push(createI18nPlugin(resolvedOptions));
3885
- if (resolvedOptions.ogViewer) plugins.push(createOgViewerPlugin(resolvedOptions));
3886
- return plugins;
3890
+ };
3887
3891
  }
3888
3892
  /**
3889
3893
  * Resolves plugin options with defaults.