@ox-content/vite-plugin 2.2.0 → 2.4.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
@@ -4,18 +4,20 @@ import { n as transformYouTube, t as extractVideoId } from "./youtube2.mjs";
4
4
  import { i as transformGitHub, n as fetchRepoData, r as prefetchGitHubRepos, t as collectGitHubRepos } from "./github2.mjs";
5
5
  import { i as transformOgp, n as fetchOgpData, r as prefetchOgpData, t as collectOgpUrls } from "./ogp2.mjs";
6
6
  import { createRequire } from "node:module";
7
- import * as path$1 from "path";
7
+ import * as path$2 from "path";
8
8
  import path from "path";
9
9
  import { unified } from "unified";
10
10
  import rehypeParse from "rehype-parse";
11
11
  import rehypeStringify from "rehype-stringify";
12
12
  import { createHighlighter } from "shiki";
13
+ import * as path$1 from "node:path";
13
14
  import { dirname, join } from "node:path";
14
- import * as fs$1 from "fs";
15
+ import * as fs$2 from "fs";
15
16
  import { createHash } from "node:crypto";
16
- import * as fs from "fs/promises";
17
+ import * as fs$1 from "fs/promises";
17
18
  import { glob } from "glob";
18
19
  import * as crypto from "crypto";
20
+ import * as fs from "node:fs/promises";
19
21
  import { mkdir, writeFile } from "node:fs/promises";
20
22
  //#region \0rolldown/runtime.js
21
23
  var __create = Object.create;
@@ -7546,11 +7548,22 @@ function renderDetailsControlsHtml(targetSelector) {
7546
7548
  <button type="button" class="ox-api-controls__button" data-ox-api-toggle="collapse">Close all</button>
7547
7549
  </div>`;
7548
7550
  }
7551
+ function normalizeDocFilePath(filePath) {
7552
+ const normalized = filePath.replace(/\\/g, "/");
7553
+ return normalized.match(/(?:^|\/)((?:npm|packages|crates|src)\/.+)$/)?.[1] ?? normalized.replace(/^\/+/, "");
7554
+ }
7549
7555
  function buildDocsData(docs) {
7550
7556
  return {
7551
7557
  version: 1,
7552
7558
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7553
- modules: docs
7559
+ modules: docs.map((doc) => ({
7560
+ ...doc,
7561
+ file: normalizeDocFilePath(doc.file),
7562
+ entries: doc.entries.map((entry) => ({
7563
+ ...entry,
7564
+ file: normalizeDocFilePath(entry.file)
7565
+ }))
7566
+ }))
7554
7567
  };
7555
7568
  }
7556
7569
  /**
@@ -7644,12 +7657,12 @@ async function findFiles(dir, options) {
7644
7657
  async function walk(currentDir) {
7645
7658
  let entries;
7646
7659
  try {
7647
- entries = await fs$1.promises.readdir(currentDir, { withFileTypes: true });
7660
+ entries = await fs$2.promises.readdir(currentDir, { withFileTypes: true });
7648
7661
  } catch {
7649
7662
  return;
7650
7663
  }
7651
7664
  for (const entry of entries) {
7652
- const fullPath = path$1.join(currentDir, entry.name);
7665
+ const fullPath = path$2.join(currentDir, entry.name);
7653
7666
  if (entry.isDirectory()) {
7654
7667
  if (!isExcluded(fullPath, options.exclude)) await walk(fullPath);
7655
7668
  } else if (entry.isFile()) {
@@ -7806,7 +7819,7 @@ function generateMarkdown(docs, options) {
7806
7819
  if (options.groupBy === "file") {
7807
7820
  const docToFile = /* @__PURE__ */ new Map();
7808
7821
  for (const doc of sortedDocs) {
7809
- let fileName = path$1.basename(doc.file, path$1.extname(doc.file));
7822
+ let fileName = path$2.basename(doc.file, path$2.extname(doc.file));
7810
7823
  if (fileName === "index") fileName = "index-module";
7811
7824
  docToFile.set(doc, fileName);
7812
7825
  const markdown = generateFileMarkdown(doc, options, fileName, symbolMap);
@@ -7839,10 +7852,10 @@ function sortExtractedDocs(docs) {
7839
7852
  return [...docs].map((doc) => ({
7840
7853
  ...doc,
7841
7854
  entries: [...doc.entries].sort(compareEntriesByName)
7842
- })).sort((a, b) => compareStrings(path$1.basename(a.file), path$1.basename(b.file)));
7855
+ })).sort((a, b) => compareStrings(path$2.basename(a.file), path$2.basename(b.file)));
7843
7856
  }
7844
7857
  function generateFileMarkdown(doc, options, currentFileName, symbolMap) {
7845
- let md = `# ${path$1.basename(doc.file)}\n\n`;
7858
+ let md = `# ${path$2.basename(doc.file)}\n\n`;
7846
7859
  if (options.githubUrl) {
7847
7860
  const sourceLink = generateSourceLink(doc.file, options.githubUrl);
7848
7861
  if (sourceLink) md += sourceLink + "\n\n";
@@ -7945,7 +7958,7 @@ function generateIndex(docs, docToFile) {
7945
7958
  md += "## Modules\n\n";
7946
7959
  if (docs.length > 1) md += renderDetailsControlsHtml(".ox-api-module") + "\n\n";
7947
7960
  for (const doc of docs) {
7948
- const displayName = path$1.basename(doc.file, path$1.extname(doc.file));
7961
+ const displayName = path$2.basename(doc.file, path$2.extname(doc.file));
7949
7962
  let fileName = displayName;
7950
7963
  if (docToFile && docToFile.has(doc)) fileName = docToFile.get(doc);
7951
7964
  else if (fileName === "index") fileName = "index-module";
@@ -8026,7 +8039,7 @@ function convertSymbolLinks(text, currentFileName, symbolMap) {
8026
8039
  function buildSymbolMap(docs) {
8027
8040
  const map = /* @__PURE__ */ new Map();
8028
8041
  for (const doc of docs) {
8029
- let fileName = path$1.basename(doc.file, path$1.extname(doc.file));
8042
+ let fileName = path$2.basename(doc.file, path$2.extname(doc.file));
8030
8043
  if (fileName === "index") fileName = "index-module";
8031
8044
  for (const entry of doc.entries) map.set(entry.name, {
8032
8045
  name: entry.name,
@@ -8040,32 +8053,32 @@ function buildSymbolMap(docs) {
8040
8053
  * Writes generated documentation to the output directory.
8041
8054
  */
8042
8055
  async function writeDocs(docs, outDir, extractedDocs, options) {
8043
- await fs$1.promises.mkdir(outDir, { recursive: true });
8056
+ await fs$2.promises.mkdir(outDir, { recursive: true });
8044
8057
  const generatedFiles = new Set(Object.keys(docs));
8045
8058
  if (extractedDocs && options?.generateNav && options.groupBy === "file") generatedFiles.add("nav.ts");
8046
8059
  if (extractedDocs) generatedFiles.add(DOCS_DATA_FILE);
8047
- const manifestPath = path$1.join(outDir, DOCS_MANIFEST_FILE);
8060
+ const manifestPath = path$2.join(outDir, DOCS_MANIFEST_FILE);
8048
8061
  let previousFiles = [];
8049
8062
  try {
8050
- previousFiles = JSON.parse(await fs$1.promises.readFile(manifestPath, "utf-8"));
8063
+ previousFiles = JSON.parse(await fs$2.promises.readFile(manifestPath, "utf-8"));
8051
8064
  } catch {
8052
8065
  previousFiles = [];
8053
8066
  }
8054
8067
  for (const staleFile of previousFiles) {
8055
8068
  if (generatedFiles.has(staleFile)) continue;
8056
- await fs$1.promises.rm(path$1.join(outDir, staleFile), { force: true });
8069
+ await fs$2.promises.rm(path$2.join(outDir, staleFile), { force: true });
8057
8070
  }
8058
8071
  for (const [fileName, content] of Object.entries(docs)) {
8059
- const filePath = path$1.join(outDir, fileName);
8060
- await fs$1.promises.writeFile(filePath, content, "utf-8");
8072
+ const filePath = path$2.join(outDir, fileName);
8073
+ await fs$2.promises.writeFile(filePath, content, "utf-8");
8061
8074
  }
8062
8075
  if (extractedDocs && options?.generateNav && options.groupBy === "file") {
8063
8076
  const navCode = generateNavCode(generateNavMetadata(extractedDocs, "/api"), "apiNav");
8064
- const navFilePath = path$1.join(outDir, "nav.ts");
8065
- await fs$1.promises.writeFile(navFilePath, navCode, "utf-8");
8077
+ const navFilePath = path$2.join(outDir, "nav.ts");
8078
+ await fs$2.promises.writeFile(navFilePath, navCode, "utf-8");
8066
8079
  }
8067
- if (extractedDocs) await fs$1.promises.writeFile(path$1.join(outDir, DOCS_DATA_FILE), JSON.stringify(buildDocsData(extractedDocs), null, 2), "utf-8");
8068
- await fs$1.promises.writeFile(manifestPath, JSON.stringify([...generatedFiles].sort(), null, 2), "utf-8");
8080
+ if (extractedDocs) await fs$2.promises.writeFile(path$2.join(outDir, DOCS_DATA_FILE), JSON.stringify(buildDocsData(extractedDocs), null, 2), "utf-8");
8081
+ await fs$2.promises.writeFile(manifestPath, JSON.stringify([...generatedFiles].sort(), null, 2), "utf-8");
8069
8082
  }
8070
8083
  /**
8071
8084
  * Resolves docs options with defaults.
@@ -8080,7 +8093,7 @@ async function writeDocs(docs, outDir, extractedDocs, options) {
8080
8093
  * @returns Absolute GitHub URL to source code
8081
8094
  */
8082
8095
  function generateSourceHref(filePath, githubUrl, lineNumber, endLineNumber) {
8083
- return `${githubUrl}/blob/main/${filePath.replace(/^.*?\/(npm|packages|crates|src)\//, "$1/")}${lineNumber ? endLineNumber && endLineNumber > lineNumber ? `#L${lineNumber}-L${endLineNumber}` : `#L${lineNumber}` : ""}`;
8096
+ return `${githubUrl}/blob/main/${normalizeDocFilePath(filePath)}${lineNumber ? endLineNumber && endLineNumber > lineNumber ? `#L${lineNumber}-L${endLineNumber}` : `#L${lineNumber}` : ""}`;
8084
8097
  }
8085
8098
  function generateSourceLink(filePath, githubUrl, lineNumber, endLineNumber) {
8086
8099
  return `**[Source](${generateSourceHref(filePath, githubUrl, lineNumber, endLineNumber)})**`;
@@ -8150,10 +8163,10 @@ async function renderHtmlToPng(page, html, width, height, publicDir) {
8150
8163
  await route.continue();
8151
8164
  return;
8152
8165
  }
8153
- const filePath = path$1.join(publicDir, url.pathname);
8166
+ const filePath = path$2.join(publicDir, url.pathname);
8154
8167
  try {
8155
8168
  const body = await fs.readFile(filePath);
8156
- const ext = path$1.extname(filePath).toLowerCase();
8169
+ const ext = path$2.extname(filePath).toLowerCase();
8157
8170
  await route.fulfill({
8158
8171
  body,
8159
8172
  contentType: {
@@ -8338,9 +8351,9 @@ function computeCacheKey(templateSource, props, width, height) {
8338
8351
  * Returns the cached file path if found, null otherwise.
8339
8352
  */
8340
8353
  async function getCached(cacheDir, key) {
8341
- const filePath = path$1.join(cacheDir, `${key}.png`);
8354
+ const filePath = path$2.join(cacheDir, `${key}.png`);
8342
8355
  try {
8343
- return await fs.readFile(filePath);
8356
+ return await fs$1.readFile(filePath);
8344
8357
  } catch {
8345
8358
  return null;
8346
8359
  }
@@ -8349,9 +8362,9 @@ async function getCached(cacheDir, key) {
8349
8362
  * Writes a PNG buffer to the cache.
8350
8363
  */
8351
8364
  async function writeCache(cacheDir, key, png) {
8352
- await fs.mkdir(cacheDir, { recursive: true });
8353
- const filePath = path$1.join(cacheDir, `${key}.png`);
8354
- await fs.writeFile(filePath, png);
8365
+ await fs$1.mkdir(cacheDir, { recursive: true });
8366
+ const filePath = path$2.join(cacheDir, `${key}.png`);
8367
+ await fs$1.writeFile(filePath, png);
8355
8368
  }
8356
8369
  //#endregion
8357
8370
  //#region \0@oxc-project+runtime@0.115.0/helpers/usingCtx.js
@@ -8441,14 +8454,14 @@ function resolveOgImageOptions(options) {
8441
8454
  */
8442
8455
  async function resolveTemplate(options, root) {
8443
8456
  if (!options.template) return getDefaultTemplate();
8444
- const templatePath = path$1.resolve(root, options.template);
8457
+ const templatePath = path$2.resolve(root, options.template);
8445
8458
  const fs = await import("fs/promises");
8446
8459
  try {
8447
8460
  await fs.access(templatePath);
8448
8461
  } catch {
8449
8462
  throw new Error(`[ox-content:og-image] Template file not found: ${templatePath}`);
8450
8463
  }
8451
- switch (path$1.extname(templatePath).toLowerCase()) {
8464
+ switch (path$2.extname(templatePath).toLowerCase()) {
8452
8465
  case ".vue": return resolveVueTemplate(templatePath, options, root);
8453
8466
  case ".svelte": return resolveSvelteTemplate(templatePath, root);
8454
8467
  case ".tsx":
@@ -8462,9 +8475,9 @@ async function resolveTemplate(options, root) {
8462
8475
  async function resolveTsTemplate(templatePath, options, root) {
8463
8476
  const fs = await import("fs/promises");
8464
8477
  const { rolldown } = await import("rolldown");
8465
- const cacheDir = path$1.join(root, ".cache", "og-images");
8478
+ const cacheDir = path$2.join(root, ".cache", "og-images");
8466
8479
  await fs.mkdir(cacheDir, { recursive: true });
8467
- const outfile = path$1.join(cacheDir, "_template.mjs");
8480
+ const outfile = path$2.join(cacheDir, "_template.mjs");
8468
8481
  const bundle = await rolldown({
8469
8482
  input: templatePath,
8470
8483
  platform: "node"
@@ -8487,9 +8500,9 @@ async function resolveTsTemplate(templatePath, options, root) {
8487
8500
  async function resolveVueTemplate(templatePath, options, root) {
8488
8501
  const fs = await import("fs/promises");
8489
8502
  const { rolldown } = await import("rolldown");
8490
- const cacheDir = path$1.join(root, ".cache", "og-images");
8503
+ const cacheDir = path$2.join(root, ".cache", "og-images");
8491
8504
  await fs.mkdir(cacheDir, { recursive: true });
8492
- const outfile = path$1.join(cacheDir, "_template_vue.mjs");
8505
+ const outfile = path$2.join(cacheDir, "_template_vue.mjs");
8493
8506
  const bundle = await rolldown({
8494
8507
  input: templatePath,
8495
8508
  platform: "node",
@@ -8585,9 +8598,9 @@ async function getVizejsPlugin() {
8585
8598
  async function resolveSvelteTemplate(templatePath, root) {
8586
8599
  const fs = await import("fs/promises");
8587
8600
  const { rolldown } = await import("rolldown");
8588
- const cacheDir = path$1.join(root, ".cache", "og-images");
8601
+ const cacheDir = path$2.join(root, ".cache", "og-images");
8589
8602
  await fs.mkdir(cacheDir, { recursive: true });
8590
- const outfile = path$1.join(cacheDir, "_template_svelte.mjs");
8603
+ const outfile = path$2.join(cacheDir, "_template_svelte.mjs");
8591
8604
  const bundle = await rolldown({
8592
8605
  input: templatePath,
8593
8606
  platform: "node",
@@ -8643,9 +8656,9 @@ function createSvelteCompilerPlugin() {
8643
8656
  async function resolveReactTemplate(templatePath, root) {
8644
8657
  const fs = await import("fs/promises");
8645
8658
  const { rolldown } = await import("rolldown");
8646
- const cacheDir = path$1.join(root, ".cache", "og-images");
8659
+ const cacheDir = path$2.join(root, ".cache", "og-images");
8647
8660
  await fs.mkdir(cacheDir, { recursive: true });
8648
- const outfile = path$1.join(cacheDir, "_template_react.mjs");
8661
+ const outfile = path$2.join(cacheDir, "_template_react.mjs");
8649
8662
  const bundle = await rolldown({
8650
8663
  input: templatePath,
8651
8664
  platform: "node",
@@ -8695,7 +8708,7 @@ async function resolveReactTemplate(templatePath, root) {
8695
8708
  async function computeTemplateSource(options, root) {
8696
8709
  if (!options.template) return "__default__";
8697
8710
  const fs = await import("fs/promises");
8698
- const templatePath = path$1.resolve(root, options.template);
8711
+ const templatePath = path$2.resolve(root, options.template);
8699
8712
  const content = await fs.readFile(templatePath, "utf-8");
8700
8713
  return crypto.createHash("sha256").update(content).digest("hex");
8701
8714
  }
@@ -8713,7 +8726,7 @@ async function generateOgImages(pages, options, root) {
8713
8726
  if (pages.length === 0) return [];
8714
8727
  const templateFn = await resolveTemplate(options, root);
8715
8728
  const templateSource = await computeTemplateSource(options, root);
8716
- const cacheDir = path$1.join(root, ".cache", "og-images");
8729
+ const cacheDir = path$2.join(root, ".cache", "og-images");
8717
8730
  if (options.cache) {
8718
8731
  const allCached = await tryServeAllFromCache(pages, templateSource, options, cacheDir);
8719
8732
  if (allCached) return allCached;
@@ -8725,7 +8738,7 @@ async function generateOgImages(pages, options, root) {
8725
8738
  error: "Chromium not available"
8726
8739
  }));
8727
8740
  const results = [];
8728
- const publicDir = path$1.join(root, "public");
8741
+ const publicDir = path$2.join(root, "public");
8729
8742
  const concurrency = Math.max(1, options.concurrency);
8730
8743
  for (let i = 0; i < pages.length; i += concurrency) {
8731
8744
  const batch = pages.slice(i, i + concurrency);
@@ -8749,7 +8762,7 @@ async function tryServeAllFromCache(pages, templateSource, options, cacheDir) {
8749
8762
  for (const entry of pages) {
8750
8763
  const cached = await getCached(cacheDir, computeCacheKey(templateSource, entry.props, options.width, options.height));
8751
8764
  if (!cached) return null;
8752
- await fs.mkdir(path$1.dirname(entry.outputPath), { recursive: true });
8765
+ await fs.mkdir(path$2.dirname(entry.outputPath), { recursive: true });
8753
8766
  await fs.writeFile(entry.outputPath, cached);
8754
8767
  results.push({
8755
8768
  outputPath: entry.outputPath,
@@ -8767,7 +8780,7 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
8767
8780
  if (options.cache) {
8768
8781
  const cached = await getCached(cacheDir, computeCacheKey(templateSource, entry.props, options.width, options.height));
8769
8782
  if (cached) {
8770
- await fs.mkdir(path$1.dirname(entry.outputPath), { recursive: true });
8783
+ await fs.mkdir(path$2.dirname(entry.outputPath), { recursive: true });
8771
8784
  await fs.writeFile(entry.outputPath, cached);
8772
8785
  return {
8773
8786
  outputPath: entry.outputPath,
@@ -8777,7 +8790,7 @@ async function renderSinglePage(entry, templateFn, templateSource, options, cach
8777
8790
  }
8778
8791
  const html = await templateFn(entry.props);
8779
8792
  const png = await session.renderPage(html, options.width, options.height, publicDir);
8780
- await fs.mkdir(path$1.dirname(entry.outputPath), { recursive: true });
8793
+ await fs.mkdir(path$2.dirname(entry.outputPath), { recursive: true });
8781
8794
  await fs.writeFile(entry.outputPath, png);
8782
8795
  if (options.cache) await writeCache(cacheDir, computeCacheKey(templateSource, entry.props, options.width, options.height), png);
8783
8796
  return {
@@ -10657,7 +10670,7 @@ function createSharedAssetChunk(type, label, content, outDir, base) {
10657
10670
  const hash = createContentHash(content);
10658
10671
  const fileName = `ox-content-${sanitizeChunkLabel(label)}-${hash}.${type}`;
10659
10672
  return {
10660
- outputPath: path$1.join(outDir, "assets", fileName),
10673
+ outputPath: path$2.join(outDir, "assets", fileName),
10661
10674
  publicPath: toPublicAssetPath(base, fileName),
10662
10675
  content
10663
10676
  };
@@ -10747,8 +10760,8 @@ async function externalizeSharedPageAssets(pages, outDir, base) {
10747
10760
  });
10748
10761
  const chunks = [...cssChunks.values(), ...jsChunks.values()];
10749
10762
  await Promise.all(chunks.map(async (chunk) => {
10750
- await fs.mkdir(path$1.dirname(chunk.outputPath), { recursive: true });
10751
- await fs.writeFile(chunk.outputPath, chunk.content, "utf-8");
10763
+ await fs$1.mkdir(path$2.dirname(chunk.outputPath), { recursive: true });
10764
+ await fs$1.writeFile(chunk.outputPath, chunk.content, "utf-8");
10752
10765
  }));
10753
10766
  return {
10754
10767
  pages: optimizedPages,
@@ -10759,16 +10772,16 @@ async function externalizeSharedPageAssets(pages, outDir, base) {
10759
10772
  * Converts a markdown file path to its corresponding HTML output path.
10760
10773
  */
10761
10774
  function getOutputPath(inputPath, srcDir, outDir, extension) {
10762
- const baseName = path$1.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, extension);
10763
- if (baseName.endsWith(`index${extension}`)) return path$1.join(outDir, baseName);
10775
+ const baseName = path$2.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, extension);
10776
+ if (baseName.endsWith(`index${extension}`)) return path$2.join(outDir, baseName);
10764
10777
  const dirName = baseName.replace(new RegExp(`\\${extension}$`), "");
10765
- return path$1.join(outDir, dirName, `index${extension}`);
10778
+ return path$2.join(outDir, dirName, `index${extension}`);
10766
10779
  }
10767
10780
  /**
10768
10781
  * Converts a markdown file path to a relative URL path.
10769
10782
  */
10770
10783
  function getUrlPath$1(inputPath, srcDir) {
10771
- const baseName = path$1.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
10784
+ const baseName = path$2.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
10772
10785
  if (baseName === "index" || baseName.endsWith("/index")) return baseName.replace(/\/?index$/, "") || "/";
10773
10786
  return baseName;
10774
10787
  }
@@ -10784,12 +10797,12 @@ function getHref(inputPath, srcDir, base, extension) {
10784
10797
  * Gets the OG image output path for a given markdown file.
10785
10798
  */
10786
10799
  function getOgImagePath(inputPath, srcDir, outDir) {
10787
- const baseName = path$1.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
10800
+ const baseName = path$2.relative(srcDir, inputPath).replace(/\.(?:md|markdown)$/i, "");
10788
10801
  if (baseName === "index" || baseName.endsWith("/index")) {
10789
10802
  const dirPath = baseName.replace(/\/?index$/, "") || "";
10790
- return path$1.join(outDir, dirPath, "og-image.png");
10803
+ return path$2.join(outDir, dirPath, "og-image.png");
10791
10804
  }
10792
- return path$1.join(outDir, baseName, "og-image.png");
10805
+ return path$2.join(outDir, baseName, "og-image.png");
10793
10806
  }
10794
10807
  /**
10795
10808
  * Gets the OG image URL for use in meta tags.
@@ -10807,9 +10820,9 @@ function getOgImageUrl(inputPath, srcDir, base, siteUrl) {
10807
10820
  * Gets display title from file path.
10808
10821
  */
10809
10822
  function getDisplayTitle(filePath) {
10810
- const fileName = path$1.basename(filePath, path$1.extname(filePath));
10823
+ const fileName = path$2.basename(filePath, path$2.extname(filePath));
10811
10824
  if (fileName === "index") {
10812
- const dirName = path$1.basename(path$1.dirname(filePath));
10825
+ const dirName = path$2.basename(path$2.dirname(filePath));
10813
10826
  if (dirName && dirName !== ".") return formatTitle(dirName);
10814
10827
  return "Home";
10815
10828
  }
@@ -10825,7 +10838,7 @@ function formatTitle(name) {
10825
10838
  * Collects all markdown files from the source directory.
10826
10839
  */
10827
10840
  async function collectMarkdownFiles$1(srcDir) {
10828
- return (await glob(path$1.join(srcDir, "**/*.{md,markdown}"), {
10841
+ return (await glob(path$2.join(srcDir, "**/*.{md,markdown}"), {
10829
10842
  nodir: true,
10830
10843
  ignore: [
10831
10844
  "**/node_modules/**",
@@ -10846,7 +10859,7 @@ function buildNavItems(markdownFiles, srcDir, base, extension) {
10846
10859
  "api"
10847
10860
  ];
10848
10861
  for (const file of markdownFiles) {
10849
- const parts = path$1.relative(srcDir, file).split(path$1.sep);
10862
+ const parts = path$2.relative(srcDir, file).split(path$2.sep);
10850
10863
  let groupKey = "";
10851
10864
  if (parts.length > 1) groupKey = parts[0];
10852
10865
  if (!groups.has(groupKey)) groups.set(groupKey, []);
@@ -10895,14 +10908,14 @@ async function buildSsg(options, root) {
10895
10908
  files: [],
10896
10909
  errors: []
10897
10910
  };
10898
- const srcDir = path$1.resolve(root, options.srcDir);
10899
- const outDir = path$1.resolve(root, options.outDir);
10911
+ const srcDir = path$2.resolve(root, options.srcDir);
10912
+ const outDir = path$2.resolve(root, options.outDir);
10900
10913
  const base = options.base.endsWith("/") ? options.base : options.base + "/";
10901
10914
  const generatedFiles = [];
10902
10915
  const generatedPages = [];
10903
10916
  const errors = [];
10904
10917
  if (ssgOptions.clean) try {
10905
- await fs.rm(outDir, {
10918
+ await fs$1.rm(outDir, {
10906
10919
  recursive: true,
10907
10920
  force: true
10908
10921
  });
@@ -10911,8 +10924,8 @@ async function buildSsg(options, root) {
10911
10924
  const navItems = buildNavItems(markdownFiles, srcDir, base, ssgOptions.extension);
10912
10925
  let siteName = ssgOptions.siteName ?? "Documentation";
10913
10926
  if (!ssgOptions.siteName) try {
10914
- const pkgPath = path$1.join(root, "package.json");
10915
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
10927
+ const pkgPath = path$2.join(root, "package.json");
10928
+ const pkg = JSON.parse(await fs$1.readFile(pkgPath, "utf-8"));
10916
10929
  if (pkg.name) siteName = formatTitle(pkg.name);
10917
10930
  } catch {}
10918
10931
  const ogImageEntries = [];
@@ -10921,7 +10934,7 @@ async function buildSsg(options, root) {
10921
10934
  const shouldGenerateOgImages = (options.ogImage || ssgOptions.generateOgImage) && !ssgOptions.bare;
10922
10935
  const pageResults = [];
10923
10936
  for (const inputPath of markdownFiles) try {
10924
- const result = await transformMarkdown(await fs.readFile(inputPath, "utf-8"), inputPath, options, {
10937
+ const result = await transformMarkdown(await fs$1.readFile(inputPath, "utf-8"), inputPath, options, {
10925
10938
  convertMdLinks: true,
10926
10939
  baseUrl: base,
10927
10940
  sourcePath: inputPath
@@ -11025,8 +11038,8 @@ async function buildSsg(options, root) {
11025
11038
  const optimizedOutput = await externalizeSharedPageAssets(generatedPages, outDir, base);
11026
11039
  generatedFiles.push(...optimizedOutput.assets);
11027
11040
  for (const page of optimizedOutput.pages) {
11028
- await fs.mkdir(path$1.dirname(page.outputPath), { recursive: true });
11029
- await fs.writeFile(page.outputPath, page.html, "utf-8");
11041
+ await fs$1.mkdir(path$2.dirname(page.outputPath), { recursive: true });
11042
+ await fs$1.writeFile(page.outputPath, page.html, "utf-8");
11030
11043
  generatedFiles.push(page.outputPath);
11031
11044
  }
11032
11045
  return {
@@ -11078,9 +11091,9 @@ async function collectMarkdownFiles(dir) {
11078
11091
  const files = [];
11079
11092
  async function walk(currentDir) {
11080
11093
  try {
11081
- const entries = await fs.readdir(currentDir, { withFileTypes: true });
11094
+ const entries = await fs$1.readdir(currentDir, { withFileTypes: true });
11082
11095
  for (const entry of entries) {
11083
- const fullPath = path$1.join(currentDir, entry.name);
11096
+ const fullPath = path$2.join(currentDir, entry.name);
11084
11097
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") await walk(fullPath);
11085
11098
  else if (entry.isFile() && entry.name.endsWith(".md")) files.push(fullPath);
11086
11099
  }
@@ -11104,8 +11117,8 @@ async function buildSearchIndex(srcDir, base) {
11104
11117
  const files = await collectMarkdownFiles(srcDir);
11105
11118
  const documents = [];
11106
11119
  for (const file of files) try {
11107
- const content = await fs.readFile(file, "utf-8");
11108
- const relativePath = path$1.relative(srcDir, file);
11120
+ const content = await fs$1.readFile(file, "utf-8");
11121
+ const relativePath = path$2.relative(srcDir, file);
11109
11122
  const url = base + relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
11110
11123
  const id = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
11111
11124
  const extractSearchContent = napi.extractSearchContent;
@@ -11134,9 +11147,9 @@ async function buildSearchIndex(srcDir, base) {
11134
11147
  * Writes the search index to a file.
11135
11148
  */
11136
11149
  async function writeSearchIndex(indexJson, outDir) {
11137
- const indexPath = path$1.join(outDir, "search-index.json");
11138
- await fs.mkdir(outDir, { recursive: true });
11139
- await fs.writeFile(indexPath, indexJson, "utf-8");
11150
+ const indexPath = path$2.join(outDir, "search-index.json");
11151
+ await fs$1.mkdir(outDir, { recursive: true });
11152
+ await fs$1.writeFile(indexPath, indexJson, "utf-8");
11140
11153
  }
11141
11154
  /**
11142
11155
  * Client-side search module code.
@@ -11436,14 +11449,14 @@ async function resolveMarkdownFile(url, srcDir) {
11436
11449
  let relativePath;
11437
11450
  if (pathname === "/") relativePath = "index.md";
11438
11451
  else relativePath = pathname.slice(1) + ".md";
11439
- const filePath = path$1.join(srcDir, relativePath);
11452
+ const filePath = path$2.join(srcDir, relativePath);
11440
11453
  try {
11441
- await fs.access(filePath);
11454
+ await fs$1.access(filePath);
11442
11455
  return filePath;
11443
11456
  } catch {
11444
- const indexPath = path$1.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
11457
+ const indexPath = path$2.join(srcDir, pathname === "/" ? "" : pathname.slice(1), "index.md");
11445
11458
  try {
11446
- await fs.access(indexPath);
11459
+ await fs$1.access(indexPath);
11447
11460
  return indexPath;
11448
11461
  } catch {
11449
11462
  return null;
@@ -11485,8 +11498,8 @@ function invalidatePageCache(cache, filePath) {
11485
11498
  async function resolveSiteName(options, root) {
11486
11499
  if (options.ssg.siteName) return options.ssg.siteName;
11487
11500
  try {
11488
- const pkgPath = path$1.join(root, "package.json");
11489
- const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
11501
+ const pkgPath = path$2.join(root, "package.json");
11502
+ const pkg = JSON.parse(await fs$1.readFile(pkgPath, "utf-8"));
11490
11503
  if (pkg.name) return formatTitle(pkg.name);
11491
11504
  } catch {}
11492
11505
  return "Documentation";
@@ -11495,10 +11508,10 @@ async function resolveSiteName(options, root) {
11495
11508
  * Render a single markdown page to full HTML.
11496
11509
  */
11497
11510
  async function renderPage$1(filePath, options, navGroups, siteName, base, root) {
11498
- const srcDir = path$1.resolve(root, options.srcDir);
11511
+ const srcDir = path$2.resolve(root, options.srcDir);
11499
11512
  resetTabGroupCounter();
11500
11513
  resetIslandCounter();
11501
- const result = await transformMarkdown(await fs.readFile(filePath, "utf-8"), filePath, options, {
11514
+ const result = await transformMarkdown(await fs$1.readFile(filePath, "utf-8"), filePath, options, {
11502
11515
  convertMdLinks: true,
11503
11516
  baseUrl: base,
11504
11517
  sourcePath: filePath
@@ -11540,7 +11553,7 @@ async function renderPage$1(filePath, options, navGroups, siteName, base, root)
11540
11553
  * Create the dev server middleware for SSG page serving.
11541
11554
  */
11542
11555
  function createDevServerMiddleware(options, root, cache) {
11543
- const srcDir = path$1.resolve(root, options.srcDir);
11556
+ const srcDir = path$2.resolve(root, options.srcDir);
11544
11557
  const base = options.base.endsWith("/") ? options.base : options.base + "/";
11545
11558
  return async (req, res, next) => {
11546
11559
  const url = req.url;
@@ -11605,7 +11618,7 @@ function extractTitle(content, frontmatter) {
11605
11618
  return match ? match[1].trim() : "";
11606
11619
  }
11607
11620
  function getUrlPath(filePath, srcDir) {
11608
- let rel = path$1.relative(srcDir, filePath).replace(/\\/g, "/");
11621
+ let rel = path$2.relative(srcDir, filePath).replace(/\\/g, "/");
11609
11622
  rel = rel.replace(/\.md$/, "");
11610
11623
  if (rel === "index") return "/";
11611
11624
  if (rel.endsWith("/index")) rel = rel.slice(0, -6);
@@ -11645,7 +11658,7 @@ function validatePage(page, options) {
11645
11658
  return warnings;
11646
11659
  }
11647
11660
  async function collectPages(options, root) {
11648
- const srcDir = path$1.resolve(root, options.srcDir);
11661
+ const srcDir = path$2.resolve(root, options.srcDir);
11649
11662
  const files = await glob("**/*.md", {
11650
11663
  cwd: srcDir,
11651
11664
  absolute: true
@@ -11653,7 +11666,7 @@ async function collectPages(options, root) {
11653
11666
  const pages = [];
11654
11667
  const generateOgImage = options.ogImage || options.ssg.generateOgImage;
11655
11668
  for (const file of files.sort()) {
11656
- const content = fs$1.readFileSync(file, "utf-8");
11669
+ const content = fs$2.readFileSync(file, "utf-8");
11657
11670
  const frontmatter = parseFrontmatter(content);
11658
11671
  if (frontmatter.layout === "entry") continue;
11659
11672
  const title = extractTitle(content, frontmatter);
@@ -11663,7 +11676,7 @@ async function collectPages(options, root) {
11663
11676
  const urlPath = getUrlPath(file, srcDir);
11664
11677
  const ogImageUrl = computeOgImageUrl(urlPath, options.base, options.ssg.siteUrl, generateOgImage, options.ssg.ogImage);
11665
11678
  const page = {
11666
- path: path$1.relative(srcDir, file),
11679
+ path: path$2.relative(srcDir, file),
11667
11680
  urlPath,
11668
11681
  title,
11669
11682
  description,
@@ -11976,8 +11989,8 @@ function createI18nPlugin(resolvedOptions) {
11976
11989
  },
11977
11990
  async buildStart() {
11978
11991
  if (!i18nOptions || !i18nOptions.check) return;
11979
- const dictDir = path$1.resolve(root, i18nOptions.dir);
11980
- if (!fs$1.existsSync(dictDir)) {
11992
+ const dictDir = path$2.resolve(root, i18nOptions.dir);
11993
+ if (!fs$2.existsSync(dictDir)) {
11981
11994
  console.warn(`[ox-content:i18n] Dictionary directory not found: ${dictDir}`);
11982
11995
  return;
11983
11996
  }
@@ -11998,8 +12011,8 @@ function createI18nPlugin(resolvedOptions) {
11998
12011
  },
11999
12012
  configureServer(server) {
12000
12013
  if (!i18nOptions) return;
12001
- const dictDir = path$1.resolve(root, i18nOptions.dir);
12002
- if (fs$1.existsSync(dictDir)) {
12014
+ const dictDir = path$2.resolve(root, i18nOptions.dir);
12015
+ if (fs$2.existsSync(dictDir)) {
12003
12016
  server.watcher.add(dictDir);
12004
12017
  server.watcher.on("change", (filePath) => {
12005
12018
  if (!filePath.startsWith(dictDir)) return;
@@ -12025,7 +12038,7 @@ function createI18nPlugin(resolvedOptions) {
12025
12038
  * Generates the virtual module for i18n configuration.
12026
12039
  */
12027
12040
  function generateI18nModule(options, root) {
12028
- const dictDir = path$1.resolve(root, options.dir);
12041
+ const dictDir = path$2.resolve(root, options.dir);
12029
12042
  const localesJson = JSON.stringify(options.locales);
12030
12043
  const defaultLocale = JSON.stringify(options.defaultLocale);
12031
12044
  let dictionariesCode = "{}";
@@ -12108,15 +12121,15 @@ function flattenObject(obj, prefix, result) {
12108
12121
  function loadDictionariesFallback(options, dictDir) {
12109
12122
  const dictData = {};
12110
12123
  for (const locale of options.locales) {
12111
- const localeDir = path$1.join(dictDir, locale.code);
12112
- if (!fs$1.existsSync(localeDir)) continue;
12113
- const files = fs$1.readdirSync(localeDir);
12124
+ const localeDir = path$2.join(dictDir, locale.code);
12125
+ if (!fs$2.existsSync(localeDir)) continue;
12126
+ const files = fs$2.readdirSync(localeDir);
12114
12127
  const localeDict = {};
12115
12128
  for (const file of files) {
12116
12129
  if (!file.endsWith(".json")) continue;
12117
- const filePath = path$1.join(localeDir, file);
12118
- const content = fs$1.readFileSync(filePath, "utf-8");
12119
- const namespace = path$1.basename(file, ".json");
12130
+ const filePath = path$2.join(localeDir, file);
12131
+ const content = fs$2.readFileSync(filePath, "utf-8");
12132
+ const namespace = path$2.basename(file, ".json");
12120
12133
  try {
12121
12134
  flattenObject(JSON.parse(content), namespace, localeDict);
12122
12135
  } catch {}
@@ -12129,17 +12142,17 @@ function loadDictionariesFallback(options, dictDir) {
12129
12142
  * Collects translation keys from source files using NAPI extractTranslationKeys.
12130
12143
  */
12131
12144
  function collectKeysFromSource(root, extractTranslationKeys, options) {
12132
- const srcDir = path$1.resolve(root, "src");
12145
+ const srcDir = path$2.resolve(root, "src");
12133
12146
  const keys = /* @__PURE__ */ new Set();
12134
- if (fs$1.existsSync(srcDir)) walkDir(srcDir, /\.(ts|tsx|js|jsx)$/, (filePath) => {
12135
- const usages = extractTranslationKeys(fs$1.readFileSync(filePath, "utf-8"), filePath, options.functionNames);
12147
+ if (fs$2.existsSync(srcDir)) walkDir(srcDir, /\.(ts|tsx|js|jsx)$/, (filePath) => {
12148
+ const usages = extractTranslationKeys(fs$2.readFileSync(filePath, "utf-8"), filePath, options.functionNames);
12136
12149
  for (const usage of usages) keys.add(usage.key);
12137
12150
  });
12138
- const contentDir = path$1.resolve(root, "content");
12139
- if (fs$1.existsSync(contentDir)) {
12151
+ const contentDir = path$2.resolve(root, "content");
12152
+ if (fs$2.existsSync(contentDir)) {
12140
12153
  const tPattern = /\{\{t\(['"]([^'"]+)['"]\)\}\}/g;
12141
12154
  walkDir(contentDir, /\.(md|mdx)$/, (filePath) => {
12142
- const content = fs$1.readFileSync(filePath, "utf-8");
12155
+ const content = fs$2.readFileSync(filePath, "utf-8");
12143
12156
  let match;
12144
12157
  while ((match = tPattern.exec(content)) !== null) keys.add(match[1]);
12145
12158
  tPattern.lastIndex = 0;
@@ -12151,9 +12164,9 @@ function collectKeysFromSource(root, extractTranslationKeys, options) {
12151
12164
  * Recursively walks a directory, calling the callback for files matching the pattern.
12152
12165
  */
12153
12166
  function walkDir(dir, pattern, callback) {
12154
- const entries = fs$1.readdirSync(dir, { withFileTypes: true });
12167
+ const entries = fs$2.readdirSync(dir, { withFileTypes: true });
12155
12168
  for (const entry of entries) {
12156
- const fullPath = path$1.join(dir, entry.name);
12169
+ const fullPath = path$2.join(dir, entry.name);
12157
12170
  if (entry.isDirectory()) {
12158
12171
  if (entry.name === "node_modules" || entry.name === ".git") continue;
12159
12172
  walkDir(fullPath, pattern, callback);
@@ -12161,6 +12174,358 @@ function walkDir(dir, pattern, callback) {
12161
12174
  }
12162
12175
  }
12163
12176
  //#endregion
12177
+ //#region src/lint.ts
12178
+ const require$1 = createRequire(import.meta.url);
12179
+ const SUPPORTED_MARKDOWN_LINT_LANGUAGES = [
12180
+ "en",
12181
+ "ja",
12182
+ "zh",
12183
+ "fr",
12184
+ "de",
12185
+ "pl"
12186
+ ];
12187
+ const DEFAULT_LANGUAGES = ["en"];
12188
+ const DEFAULT_RULES = {
12189
+ duplicateHeadings: true,
12190
+ headingIncrement: true,
12191
+ maxConsecutiveBlankLines: 1,
12192
+ repeatedPunctuation: true,
12193
+ repeatedWords: true,
12194
+ spellcheck: true,
12195
+ trailingSpaces: true
12196
+ };
12197
+ const DEFAULT_CSPELL_IMPORTS = {
12198
+ de: "@cspell/dict-de-de/cspell-ext.json",
12199
+ en: "@cspell/dict-en_us/cspell-ext.json",
12200
+ fr: "@cspell/dict-fr-fr/cspell-ext.json",
12201
+ pl: "@cspell/dict-pl_pl/cspell-ext.json"
12202
+ };
12203
+ let napiBinding;
12204
+ let cspellLibPromise;
12205
+ /**
12206
+ * Lints Markdown prose with the Rust-backed built-in rule engine.
12207
+ */
12208
+ function lintMarkdown(source, options = {}) {
12209
+ return lintMarkdownWithNormalizedOptions(source, normalizeLintOptions(options));
12210
+ }
12211
+ /**
12212
+ * Async Markdown linter that supports opt-in standard dictionaries.
12213
+ */
12214
+ async function lintMarkdownAsync(source, options = {}) {
12215
+ const normalizedOptions = normalizeLintOptions(options);
12216
+ const [result] = await lintMarkdownDocumentsWithNormalizedOptions([source], normalizedOptions);
12217
+ return result ?? createEmptyLintResult$1();
12218
+ }
12219
+ /**
12220
+ * Internal batched Markdown linting entry point used by file-based workflows.
12221
+ */
12222
+ async function lintMarkdownDocumentsAsync(sources, options = {}) {
12223
+ return lintMarkdownDocumentsWithNormalizedOptions(sources, normalizeLintOptions(options));
12224
+ }
12225
+ function lintMarkdownWithNormalizedOptions(source, normalizedOptions) {
12226
+ if (normalizedOptions.dictionary.standard) throw new Error("[ox-content] lintMarkdownAsync is required when dictionary.standard is enabled.");
12227
+ return stripMaskedDocument(loadNapiBindingSync().lintMarkdown(source, toNapiMarkdownLintOptions(normalizedOptions)));
12228
+ }
12229
+ async function lintMarkdownDocumentsWithNormalizedOptions(sources, normalizedOptions) {
12230
+ if (sources.length === 0) return [];
12231
+ const napi = loadNapiBindingSync();
12232
+ const napiOptions = toNapiMarkdownLintOptions(normalizedOptions, Boolean(normalizedOptions.dictionary.standard));
12233
+ const builtInResults = typeof napi.lintMarkdownDocuments === "function" ? napi.lintMarkdownDocuments(sources, napiOptions) : sources.map((source) => napi.lintMarkdown(source, napiOptions));
12234
+ if (!normalizedOptions.rules.spellcheck || !normalizedOptions.dictionary.standard) return builtInResults.map(stripMaskedDocument);
12235
+ const standardDiagnostics = await runStandardSpellcheckDocuments(builtInResults.map((result) => result.maskedDocument), normalizedOptions);
12236
+ return builtInResults.map((result, index) => summarizeDiagnostics(sortDiagnostics(result.diagnostics.concat(standardDiagnostics[index] ?? []))));
12237
+ }
12238
+ function loadNapiBindingSync() {
12239
+ if (napiBinding) return napiBinding;
12240
+ if (napiBinding === null) throw new Error("[ox-content] @ox-content/napi is required for Markdown linting. Please ensure the NAPI module is built.");
12241
+ try {
12242
+ const loaded = require$1("@ox-content/napi");
12243
+ napiBinding = loaded.default && typeof loaded.default === "object" ? {
12244
+ ...loaded.default,
12245
+ ...loaded
12246
+ } : loaded;
12247
+ return napiBinding;
12248
+ } catch {
12249
+ napiBinding = null;
12250
+ throw new Error("[ox-content] @ox-content/napi is required for Markdown linting. Please ensure the NAPI module is built.");
12251
+ }
12252
+ }
12253
+ function toNapiMarkdownLintOptions(options, disableBuiltinSpellcheck = false) {
12254
+ return {
12255
+ dictionary: {
12256
+ byLanguage: Object.entries(options.dictionary.byLanguage ?? {}).map(([language, words]) => ({
12257
+ language,
12258
+ words
12259
+ })),
12260
+ ignoredWords: options.dictionary.ignoredWords,
12261
+ words: options.dictionary.words
12262
+ },
12263
+ languages: options.languages,
12264
+ rules: {
12265
+ ...options.rules,
12266
+ spellcheck: disableBuiltinSpellcheck ? false : options.rules.spellcheck
12267
+ }
12268
+ };
12269
+ }
12270
+ function stripMaskedDocument(result) {
12271
+ return {
12272
+ diagnostics: result.diagnostics,
12273
+ errorCount: result.errorCount,
12274
+ infoCount: result.infoCount,
12275
+ warningCount: result.warningCount
12276
+ };
12277
+ }
12278
+ function normalizeLintOptions(options) {
12279
+ 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];
12280
+ const standard = normalizeStandardDictionaryOptions(options.dictionary?.standard, languages);
12281
+ return {
12282
+ dictionary: {
12283
+ ...options.dictionary,
12284
+ standard
12285
+ },
12286
+ languages: [...new Set(languages)],
12287
+ rules: {
12288
+ duplicateHeadings: options.rules?.duplicateHeadings ?? DEFAULT_RULES.duplicateHeadings,
12289
+ headingIncrement: options.rules?.headingIncrement ?? DEFAULT_RULES.headingIncrement,
12290
+ maxConsecutiveBlankLines: options.rules?.maxConsecutiveBlankLines ?? DEFAULT_RULES.maxConsecutiveBlankLines,
12291
+ repeatedPunctuation: options.rules?.repeatedPunctuation ?? DEFAULT_RULES.repeatedPunctuation,
12292
+ repeatedWords: options.rules?.repeatedWords ?? DEFAULT_RULES.repeatedWords,
12293
+ spellcheck: options.rules?.spellcheck ?? DEFAULT_RULES.spellcheck,
12294
+ trailingSpaces: options.rules?.trailingSpaces ?? DEFAULT_RULES.trailingSpaces
12295
+ }
12296
+ };
12297
+ }
12298
+ function normalizeStandardDictionaryOptions(standard, fallbackLanguages) {
12299
+ if (!standard) return false;
12300
+ const languages = standard.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language)) ?? fallbackLanguages;
12301
+ const customImports = standard.imports ?? [];
12302
+ const missingPresetLanguages = languages.filter((language) => !DEFAULT_CSPELL_IMPORTS[language]);
12303
+ if (missingPresetLanguages.length > 0 && customImports.length === 0) throw new Error(`[ox-content] No bundled standard dictionary preset exists for ${missingPresetLanguages.join(", ")}. Provide dictionary.standard.imports to enable those languages.`);
12304
+ const imports = [...languages.map((language) => DEFAULT_CSPELL_IMPORTS[language]).filter((value) => Boolean(value)), ...customImports];
12305
+ if (imports.length === 0) throw new Error("[ox-content] dictionary.standard requires at least one bundled preset language or custom import.");
12306
+ return {
12307
+ imports: [...new Set(imports)],
12308
+ languages: [...new Set(languages)],
12309
+ provider: standard.provider ?? "cspell",
12310
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo ?? new URL(".", import.meta.url)
12311
+ };
12312
+ }
12313
+ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
12314
+ const standard = options.dictionary.standard;
12315
+ if (!standard || maskedDocuments.length === 0) return maskedDocuments.map(() => []);
12316
+ try {
12317
+ const { spellCheckDocument } = await loadCspellLib();
12318
+ const locale = standard.languages.join(",");
12319
+ const settings = createStandardSpellcheckSettings(options, locale);
12320
+ return Promise.all(maskedDocuments.map(async (maskedDocument, index) => {
12321
+ if (maskedDocument.trim().length === 0) return [];
12322
+ return (await spellCheckDocument({
12323
+ languageId: "plaintext",
12324
+ locale,
12325
+ text: maskedDocument,
12326
+ uri: `file:///ox-content-lint-${index}.md`
12327
+ }, {
12328
+ generateSuggestions: true,
12329
+ noConfigSearch: true,
12330
+ numSuggestions: 3,
12331
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo
12332
+ }, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages));
12333
+ }));
12334
+ } catch (error) {
12335
+ const imports = standard.imports.join(", ");
12336
+ const message = imports.length > 0 ? `[ox-content] Failed to load standard dictionaries from ${imports}. Verify the imports and install the referenced CSpell packages.` : "[ox-content] Failed to load the configured standard dictionaries.";
12337
+ throw new Error(message, { cause: error });
12338
+ }
12339
+ }
12340
+ function createStandardSpellcheckSettings(options, locale) {
12341
+ return {
12342
+ import: options.dictionary.standard ? options.dictionary.standard.imports : [],
12343
+ ignoreWords: options.dictionary.ignoredWords,
12344
+ language: locale,
12345
+ version: "0.2",
12346
+ words: [...options.dictionary.words ?? [], ...Object.values(options.dictionary.byLanguage ?? {}).flat()]
12347
+ };
12348
+ }
12349
+ async function loadCspellLib() {
12350
+ cspellLibPromise ??= import("cspell-lib");
12351
+ return cspellLibPromise;
12352
+ }
12353
+ function mapStandardIssueToDiagnostic(issue, languages) {
12354
+ const line = issue.line.position.line + 1;
12355
+ const column = issue.offset - issue.line.offset + 1;
12356
+ return {
12357
+ column,
12358
+ endColumn: column + (issue.length ?? issue.text.length),
12359
+ endLine: line,
12360
+ language: inferStandardIssueLanguage(issue.text, languages),
12361
+ line,
12362
+ message: `Unknown word "${issue.text}".`,
12363
+ ruleId: "spellcheck",
12364
+ severity: "warning",
12365
+ suggestions: issue.suggestions?.slice(0, 3)
12366
+ };
12367
+ }
12368
+ function inferStandardIssueLanguage(word, languages) {
12369
+ if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
12370
+ if (/[\p{Script=Han}]/u.test(word)) {
12371
+ if (languages.includes("zh") && !languages.includes("ja")) return "zh";
12372
+ if (languages.includes("ja") && !languages.includes("zh")) return "ja";
12373
+ }
12374
+ if (/[\p{Script=Latin}]/u.test(word)) {
12375
+ const latinLanguages = languages.filter((language) => language !== "ja" && language !== "zh");
12376
+ if (latinLanguages.length === 1) return latinLanguages[0];
12377
+ return inferLatinLanguageFromCharacters(word, latinLanguages);
12378
+ }
12379
+ }
12380
+ function inferLatinLanguageFromCharacters(word, languages) {
12381
+ if (languages.includes("pl") && /[ąćęłńóśźż]/iu.test(word)) return "pl";
12382
+ if (languages.includes("de") && /[äöüß]/iu.test(word)) return "de";
12383
+ if (languages.includes("fr") && /[àâæçéèêëîïôœùûüÿ]/iu.test(word)) return "fr";
12384
+ }
12385
+ function summarizeDiagnostics(diagnostics) {
12386
+ let errorCount = 0;
12387
+ let warningCount = 0;
12388
+ let infoCount = 0;
12389
+ for (const diagnostic of diagnostics) if (diagnostic.severity === "error") errorCount += 1;
12390
+ else if (diagnostic.severity === "warning") warningCount += 1;
12391
+ else infoCount += 1;
12392
+ return {
12393
+ diagnostics,
12394
+ errorCount,
12395
+ infoCount,
12396
+ warningCount
12397
+ };
12398
+ }
12399
+ function createEmptyLintResult$1() {
12400
+ return summarizeDiagnostics([]);
12401
+ }
12402
+ function sortDiagnostics(diagnostics) {
12403
+ return [...diagnostics].sort((left, right) => {
12404
+ if (left.line !== right.line) return left.line - right.line;
12405
+ if (left.column !== right.column) return left.column - right.column;
12406
+ return left.ruleId.localeCompare(right.ruleId);
12407
+ });
12408
+ }
12409
+ //#endregion
12410
+ //#region src/lint-files.ts
12411
+ const DEFAULT_LINT_FILE_INCLUDE = ["**/*.md", "**/*.markdown"];
12412
+ const DEFAULT_LINT_FILE_EXCLUDE = [
12413
+ "**/node_modules/**",
12414
+ "**/.git/**",
12415
+ "**/dist/**"
12416
+ ];
12417
+ /**
12418
+ * Returns true if the file path is included by the configured glob filters.
12419
+ */
12420
+ function shouldLintMarkdownFile(filePath, options = {}) {
12421
+ const resolvedOptions = resolveMarkdownLintFileOptions(options);
12422
+ return shouldLintAbsoluteFile(path$1.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
12423
+ }
12424
+ /**
12425
+ * Lints a single Markdown file using project-style include/exclude settings.
12426
+ *
12427
+ * If the file is filtered out by `include` / `exclude`, the returned result is
12428
+ * marked as `skipped` and contains no diagnostics.
12429
+ */
12430
+ async function lintMarkdownFile(filePath, options = {}) {
12431
+ const resolvedOptions = resolveMarkdownLintFileOptions(options);
12432
+ return lintMarkdownFileWithResolvedOptions(path$1.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
12433
+ }
12434
+ /**
12435
+ * Lints all Markdown files matched by the configured include/exclude patterns.
12436
+ */
12437
+ async function lintMarkdownFiles(options = {}) {
12438
+ const resolvedOptions = resolveMarkdownLintFileOptions(options);
12439
+ const matchedFiles = await collectMarkdownLintFileEntries(resolvedOptions);
12440
+ const results = await lintMarkdownDocumentsAsync(await Promise.all(matchedFiles.map((file) => fs.readFile(file.filePath, "utf-8"))), resolvedOptions.lintOptions);
12441
+ const files = matchedFiles.map((file, index) => ({
12442
+ ...results[index] ?? createEmptyLintResult(),
12443
+ filePath: file.filePath,
12444
+ relativePath: file.relativePath,
12445
+ skipped: false
12446
+ }));
12447
+ const diagnostics = files.flatMap((fileResult) => fileResult.diagnostics.map((diagnostic) => ({
12448
+ ...diagnostic,
12449
+ filePath: fileResult.filePath,
12450
+ relativePath: fileResult.relativePath
12451
+ })));
12452
+ return {
12453
+ checkedFileCount: files.length,
12454
+ diagnostics,
12455
+ errorCount: files.reduce((count, fileResult) => count + fileResult.errorCount, 0),
12456
+ files,
12457
+ infoCount: files.reduce((count, fileResult) => count + fileResult.infoCount, 0),
12458
+ warningCount: files.reduce((count, fileResult) => count + fileResult.warningCount, 0)
12459
+ };
12460
+ }
12461
+ function resolveMarkdownLintFileOptions(options) {
12462
+ return {
12463
+ cwd: path$1.resolve(options.cwd ?? process.cwd()),
12464
+ exclude: [...new Set([...options.exclude ?? DEFAULT_LINT_FILE_EXCLUDE, ...options.ignore ?? []])],
12465
+ include: [...new Set(options.include ?? DEFAULT_LINT_FILE_INCLUDE)],
12466
+ lintOptions: {
12467
+ dictionary: options.dictionary,
12468
+ languages: options.languages,
12469
+ rules: options.rules
12470
+ }
12471
+ };
12472
+ }
12473
+ async function lintMarkdownFileWithResolvedOptions(filePath, options) {
12474
+ const absoluteFilePath = path$1.resolve(filePath);
12475
+ const relativePath = normalizePath(path$1.relative(options.cwd, absoluteFilePath));
12476
+ if (!shouldLintAbsoluteFile(absoluteFilePath, options)) return {
12477
+ ...createEmptyLintResult(),
12478
+ filePath: absoluteFilePath,
12479
+ relativePath,
12480
+ skipped: true
12481
+ };
12482
+ return {
12483
+ ...await lintMarkdownAsync(await fs.readFile(absoluteFilePath, "utf-8"), options.lintOptions),
12484
+ filePath: absoluteFilePath,
12485
+ relativePath,
12486
+ skipped: false
12487
+ };
12488
+ }
12489
+ async function collectMarkdownLintFileEntries(options) {
12490
+ const files = /* @__PURE__ */ new Map();
12491
+ for (const pattern of options.include) {
12492
+ const matches = await glob(pattern, {
12493
+ absolute: true,
12494
+ cwd: options.cwd,
12495
+ ignore: options.exclude,
12496
+ nodir: true
12497
+ });
12498
+ for (const filePath of matches) {
12499
+ const absoluteFilePath = path$1.resolve(filePath);
12500
+ if (shouldLintAbsoluteFile(absoluteFilePath, options)) files.set(absoluteFilePath, {
12501
+ filePath: absoluteFilePath,
12502
+ relativePath: normalizePath(path$1.relative(options.cwd, absoluteFilePath))
12503
+ });
12504
+ }
12505
+ }
12506
+ return [...files.values()].sort((left, right) => left.filePath.localeCompare(right.filePath));
12507
+ }
12508
+ function shouldLintAbsoluteFile(filePath, options) {
12509
+ const absolutePath = normalizePath(path$1.resolve(filePath));
12510
+ const relativePath = normalizePath(path$1.relative(options.cwd, absolutePath));
12511
+ const matches = (patterns) => patterns.some((pattern) => {
12512
+ const normalizedPattern = normalizePath(pattern);
12513
+ return path$1.matchesGlob(relativePath, normalizedPattern) || path$1.matchesGlob(absolutePath, normalizedPattern);
12514
+ });
12515
+ return matches(options.include) && !matches(options.exclude);
12516
+ }
12517
+ function normalizePath(value) {
12518
+ return value.split(path$1.sep).join("/");
12519
+ }
12520
+ function createEmptyLintResult() {
12521
+ return {
12522
+ diagnostics: [],
12523
+ errorCount: 0,
12524
+ infoCount: 0,
12525
+ warningCount: 0
12526
+ };
12527
+ }
12528
+ //#endregion
12164
12529
  //#region src/jsx-runtime.ts
12165
12530
  /**
12166
12531
  * Custom JSX Runtime for Static HTML Generation
@@ -12733,8 +13098,8 @@ function oxContent(options = {}) {
12733
13098
  async function regenerateDocs(root) {
12734
13099
  const docsOptions = resolvedOptions.docs;
12735
13100
  if (!docsOptions || !docsOptions.enabled) return 0;
12736
- const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
12737
- const outDir = path$1.resolve(root, docsOptions.out);
13101
+ const srcDirs = docsOptions.src.map((src) => path$2.resolve(root, src));
13102
+ const outDir = path$2.resolve(root, docsOptions.out);
12738
13103
  const extracted = await extractDocs(srcDirs, docsOptions);
12739
13104
  const generated = generateMarkdown(extracted, docsOptions);
12740
13105
  await writeDocs(generated, outDir, extracted, docsOptions);
@@ -12803,7 +13168,7 @@ function oxContent(options = {}) {
12803
13168
  const docsOptions = resolvedOptions.docs;
12804
13169
  if (!docsOptions || !docsOptions.enabled) return;
12805
13170
  const root = config?.root || process.cwd();
12806
- const srcDirs = docsOptions.src.map((src) => path$1.resolve(root, src));
13171
+ const srcDirs = docsOptions.src.map((src) => path$2.resolve(root, src));
12807
13172
  for (const srcDir of srcDirs) devServer.watcher.add(srcDir);
12808
13173
  devServer.watcher.on("all", async (event, file) => {
12809
13174
  if (event !== "add" && event !== "change" && event !== "unlink") return;
@@ -12819,7 +13184,7 @@ function oxContent(options = {}) {
12819
13184
  configureServer(devServer) {
12820
13185
  if (!resolvedOptions.ssg.enabled) return;
12821
13186
  const root = config?.root || process.cwd();
12822
- const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
13187
+ const srcDir = path$2.resolve(root, resolvedOptions.srcDir);
12823
13188
  devServer.middlewares.use(createDevServerMiddleware(resolvedOptions, root, ssgDevCache));
12824
13189
  devServer.watcher.on("add", (file) => {
12825
13190
  if (file.startsWith(srcDir) && file.endsWith(".md")) {
@@ -12886,7 +13251,7 @@ function oxContent(options = {}) {
12886
13251
  async buildStart() {
12887
13252
  if (!resolvedOptions.search.enabled) return;
12888
13253
  const root = config?.root || process.cwd();
12889
- const srcDir = path$1.resolve(root, resolvedOptions.srcDir);
13254
+ const srcDir = path$2.resolve(root, resolvedOptions.srcDir);
12890
13255
  try {
12891
13256
  searchIndexJson = await buildSearchIndex(srcDir, resolvedOptions.base);
12892
13257
  console.log("[ox-content] Search index built");
@@ -12897,10 +13262,10 @@ function oxContent(options = {}) {
12897
13262
  async closeBundle() {
12898
13263
  if (!resolvedOptions.search.enabled || !searchIndexJson) return;
12899
13264
  const root = config?.root || process.cwd();
12900
- const outDir = path$1.resolve(root, resolvedOptions.outDir);
13265
+ const outDir = path$2.resolve(root, resolvedOptions.outDir);
12901
13266
  try {
12902
13267
  await writeSearchIndex(searchIndexJson, outDir);
12903
- console.log("[ox-content] Search index written to", path$1.join(outDir, "search-index.json"));
13268
+ console.log("[ox-content] Search index written to", path$2.join(outDir, "search-index.json"));
12904
13269
  } catch (err) {
12905
13270
  console.warn("[ox-content] Failed to write search index:", err);
12906
13271
  }
@@ -12980,6 +13345,6 @@ function generateVirtualModule(path, options) {
12980
13345
  return "export default {};";
12981
13346
  }
12982
13347
  //#endregion
12983
- export { DEFAULT_HTML_TEMPLATE, DefaultTheme, Fragment, buildSearchIndex, buildSsg, clearRenderContext, collectGitHubRepos, collectOgpUrls, createI18nPlugin, createMarkdownEnvironment, createTheme, defaultTheme, defineTheme, each, extractDocs, extractIslandInfo, extractVideoId, fetchOgpData, fetchRepoData, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, hasIslands, inferType, jsx, jsxs, mergeThemes, mermaidClientScript, oxContent, prefetchGitHubRepos, prefetchOgpData, raw, renderAllPages, renderPage, renderToString, resolveDocsOptions, resolveI18nOptions, resolveOgImageOptions, resolveSearchOptions, resolveSsgOptions, resolveTheme, setRenderContext, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeSearchIndex };
13348
+ export { DEFAULT_HTML_TEMPLATE, DefaultTheme, Fragment, buildSearchIndex, buildSsg, clearRenderContext, collectGitHubRepos, collectOgpUrls, createI18nPlugin, createMarkdownEnvironment, createTheme, defaultTheme, defineTheme, each, extractDocs, extractIslandInfo, extractVideoId, fetchOgpData, fetchRepoData, generateFrontmatterTypes, generateHydrationScript, generateMarkdown, generateOgImages, generateTabsCSS, generateTypes, hasIslands, inferType, jsx, jsxs, lintMarkdown, lintMarkdownAsync, lintMarkdownFile, lintMarkdownFiles, mergeThemes, mermaidClientScript, oxContent, prefetchGitHubRepos, prefetchOgpData, raw, renderAllPages, renderPage, renderToString, resolveDocsOptions, resolveI18nOptions, resolveOgImageOptions, resolveSearchOptions, resolveSsgOptions, resolveTheme, setRenderContext, shouldLintMarkdownFile, transformAllPlugins, transformGitHub, transformIslands, transformMarkdown, transformMermaidStatic, transformOgp, transformTabs, transformYouTube, useIsActive, useNav, usePageProps, useRenderContext, useSiteConfig, when, writeDocs, writeSearchIndex };
12984
13349
 
12985
13350
  //# sourceMappingURL=index.mjs.map