@ox-content/vite-plugin 2.3.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.cjs CHANGED
@@ -14,6 +14,7 @@ let rehype_stringify = require("rehype-stringify");
14
14
  rehype_stringify = require_chunk.__toESM(rehype_stringify);
15
15
  let shiki = require("shiki");
16
16
  let node_path = require("node:path");
17
+ node_path = require_chunk.__toESM(node_path);
17
18
  let fs = require("fs");
18
19
  fs = require_chunk.__toESM(fs);
19
20
  let node_crypto = require("node:crypto");
@@ -22,7 +23,9 @@ fs_promises = require_chunk.__toESM(fs_promises);
22
23
  let glob = require("glob");
23
24
  let crypto = require("crypto");
24
25
  crypto = require_chunk.__toESM(crypto);
26
+ let node_module = require("node:module");
25
27
  let node_fs_promises = require("node:fs/promises");
28
+ node_fs_promises = require_chunk.__toESM(node_fs_promises);
26
29
  //#region src/environment.ts
27
30
  /**
28
31
  * Creates the Markdown processing environment configuration.
@@ -12146,6 +12149,358 @@ function walkDir(dir, pattern, callback) {
12146
12149
  }
12147
12150
  }
12148
12151
  //#endregion
12152
+ //#region src/lint.ts
12153
+ const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
12154
+ const SUPPORTED_MARKDOWN_LINT_LANGUAGES = [
12155
+ "en",
12156
+ "ja",
12157
+ "zh",
12158
+ "fr",
12159
+ "de",
12160
+ "pl"
12161
+ ];
12162
+ const DEFAULT_LANGUAGES = ["en"];
12163
+ const DEFAULT_RULES = {
12164
+ duplicateHeadings: true,
12165
+ headingIncrement: true,
12166
+ maxConsecutiveBlankLines: 1,
12167
+ repeatedPunctuation: true,
12168
+ repeatedWords: true,
12169
+ spellcheck: true,
12170
+ trailingSpaces: true
12171
+ };
12172
+ const DEFAULT_CSPELL_IMPORTS = {
12173
+ de: "@cspell/dict-de-de/cspell-ext.json",
12174
+ en: "@cspell/dict-en_us/cspell-ext.json",
12175
+ fr: "@cspell/dict-fr-fr/cspell-ext.json",
12176
+ pl: "@cspell/dict-pl_pl/cspell-ext.json"
12177
+ };
12178
+ let napiBinding;
12179
+ let cspellLibPromise;
12180
+ /**
12181
+ * Lints Markdown prose with the Rust-backed built-in rule engine.
12182
+ */
12183
+ function lintMarkdown(source, options = {}) {
12184
+ return lintMarkdownWithNormalizedOptions(source, normalizeLintOptions(options));
12185
+ }
12186
+ /**
12187
+ * Async Markdown linter that supports opt-in standard dictionaries.
12188
+ */
12189
+ async function lintMarkdownAsync(source, options = {}) {
12190
+ const normalizedOptions = normalizeLintOptions(options);
12191
+ const [result] = await lintMarkdownDocumentsWithNormalizedOptions([source], normalizedOptions);
12192
+ return result ?? createEmptyLintResult$1();
12193
+ }
12194
+ /**
12195
+ * Internal batched Markdown linting entry point used by file-based workflows.
12196
+ */
12197
+ async function lintMarkdownDocumentsAsync(sources, options = {}) {
12198
+ return lintMarkdownDocumentsWithNormalizedOptions(sources, normalizeLintOptions(options));
12199
+ }
12200
+ function lintMarkdownWithNormalizedOptions(source, normalizedOptions) {
12201
+ if (normalizedOptions.dictionary.standard) throw new Error("[ox-content] lintMarkdownAsync is required when dictionary.standard is enabled.");
12202
+ return stripMaskedDocument(loadNapiBindingSync().lintMarkdown(source, toNapiMarkdownLintOptions(normalizedOptions)));
12203
+ }
12204
+ async function lintMarkdownDocumentsWithNormalizedOptions(sources, normalizedOptions) {
12205
+ if (sources.length === 0) return [];
12206
+ const napi = loadNapiBindingSync();
12207
+ const napiOptions = toNapiMarkdownLintOptions(normalizedOptions, Boolean(normalizedOptions.dictionary.standard));
12208
+ const builtInResults = typeof napi.lintMarkdownDocuments === "function" ? napi.lintMarkdownDocuments(sources, napiOptions) : sources.map((source) => napi.lintMarkdown(source, napiOptions));
12209
+ if (!normalizedOptions.rules.spellcheck || !normalizedOptions.dictionary.standard) return builtInResults.map(stripMaskedDocument);
12210
+ const standardDiagnostics = await runStandardSpellcheckDocuments(builtInResults.map((result) => result.maskedDocument), normalizedOptions);
12211
+ return builtInResults.map((result, index) => summarizeDiagnostics(sortDiagnostics(result.diagnostics.concat(standardDiagnostics[index] ?? []))));
12212
+ }
12213
+ function loadNapiBindingSync() {
12214
+ if (napiBinding) return napiBinding;
12215
+ if (napiBinding === null) throw new Error("[ox-content] @ox-content/napi is required for Markdown linting. Please ensure the NAPI module is built.");
12216
+ try {
12217
+ const loaded = require$1("@ox-content/napi");
12218
+ napiBinding = loaded.default && typeof loaded.default === "object" ? {
12219
+ ...loaded.default,
12220
+ ...loaded
12221
+ } : loaded;
12222
+ return napiBinding;
12223
+ } catch {
12224
+ napiBinding = null;
12225
+ throw new Error("[ox-content] @ox-content/napi is required for Markdown linting. Please ensure the NAPI module is built.");
12226
+ }
12227
+ }
12228
+ function toNapiMarkdownLintOptions(options, disableBuiltinSpellcheck = false) {
12229
+ return {
12230
+ dictionary: {
12231
+ byLanguage: Object.entries(options.dictionary.byLanguage ?? {}).map(([language, words]) => ({
12232
+ language,
12233
+ words
12234
+ })),
12235
+ ignoredWords: options.dictionary.ignoredWords,
12236
+ words: options.dictionary.words
12237
+ },
12238
+ languages: options.languages,
12239
+ rules: {
12240
+ ...options.rules,
12241
+ spellcheck: disableBuiltinSpellcheck ? false : options.rules.spellcheck
12242
+ }
12243
+ };
12244
+ }
12245
+ function stripMaskedDocument(result) {
12246
+ return {
12247
+ diagnostics: result.diagnostics,
12248
+ errorCount: result.errorCount,
12249
+ infoCount: result.infoCount,
12250
+ warningCount: result.warningCount
12251
+ };
12252
+ }
12253
+ function normalizeLintOptions(options) {
12254
+ 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];
12255
+ const standard = normalizeStandardDictionaryOptions(options.dictionary?.standard, languages);
12256
+ return {
12257
+ dictionary: {
12258
+ ...options.dictionary,
12259
+ standard
12260
+ },
12261
+ languages: [...new Set(languages)],
12262
+ rules: {
12263
+ duplicateHeadings: options.rules?.duplicateHeadings ?? DEFAULT_RULES.duplicateHeadings,
12264
+ headingIncrement: options.rules?.headingIncrement ?? DEFAULT_RULES.headingIncrement,
12265
+ maxConsecutiveBlankLines: options.rules?.maxConsecutiveBlankLines ?? DEFAULT_RULES.maxConsecutiveBlankLines,
12266
+ repeatedPunctuation: options.rules?.repeatedPunctuation ?? DEFAULT_RULES.repeatedPunctuation,
12267
+ repeatedWords: options.rules?.repeatedWords ?? DEFAULT_RULES.repeatedWords,
12268
+ spellcheck: options.rules?.spellcheck ?? DEFAULT_RULES.spellcheck,
12269
+ trailingSpaces: options.rules?.trailingSpaces ?? DEFAULT_RULES.trailingSpaces
12270
+ }
12271
+ };
12272
+ }
12273
+ function normalizeStandardDictionaryOptions(standard, fallbackLanguages) {
12274
+ if (!standard) return false;
12275
+ const languages = standard.languages?.filter((language) => SUPPORTED_MARKDOWN_LINT_LANGUAGES.includes(language)) ?? fallbackLanguages;
12276
+ const customImports = standard.imports ?? [];
12277
+ const missingPresetLanguages = languages.filter((language) => !DEFAULT_CSPELL_IMPORTS[language]);
12278
+ 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.`);
12279
+ const imports = [...languages.map((language) => DEFAULT_CSPELL_IMPORTS[language]).filter((value) => Boolean(value)), ...customImports];
12280
+ if (imports.length === 0) throw new Error("[ox-content] dictionary.standard requires at least one bundled preset language or custom import.");
12281
+ return {
12282
+ imports: [...new Set(imports)],
12283
+ languages: [...new Set(languages)],
12284
+ provider: standard.provider ?? "cspell",
12285
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo ?? new URL(".", require("url").pathToFileURL(__filename).href)
12286
+ };
12287
+ }
12288
+ async function runStandardSpellcheckDocuments(maskedDocuments, options) {
12289
+ const standard = options.dictionary.standard;
12290
+ if (!standard || maskedDocuments.length === 0) return maskedDocuments.map(() => []);
12291
+ try {
12292
+ const { spellCheckDocument } = await loadCspellLib();
12293
+ const locale = standard.languages.join(",");
12294
+ const settings = createStandardSpellcheckSettings(options, locale);
12295
+ return Promise.all(maskedDocuments.map(async (maskedDocument, index) => {
12296
+ if (maskedDocument.trim().length === 0) return [];
12297
+ return (await spellCheckDocument({
12298
+ languageId: "plaintext",
12299
+ locale,
12300
+ text: maskedDocument,
12301
+ uri: `file:///ox-content-lint-${index}.md`
12302
+ }, {
12303
+ generateSuggestions: true,
12304
+ noConfigSearch: true,
12305
+ numSuggestions: 3,
12306
+ resolveImportsRelativeTo: standard.resolveImportsRelativeTo
12307
+ }, settings)).issues.map((issue) => mapStandardIssueToDiagnostic(issue, standard.languages));
12308
+ }));
12309
+ } catch (error) {
12310
+ const imports = standard.imports.join(", ");
12311
+ 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.";
12312
+ throw new Error(message, { cause: error });
12313
+ }
12314
+ }
12315
+ function createStandardSpellcheckSettings(options, locale) {
12316
+ return {
12317
+ import: options.dictionary.standard ? options.dictionary.standard.imports : [],
12318
+ ignoreWords: options.dictionary.ignoredWords,
12319
+ language: locale,
12320
+ version: "0.2",
12321
+ words: [...options.dictionary.words ?? [], ...Object.values(options.dictionary.byLanguage ?? {}).flat()]
12322
+ };
12323
+ }
12324
+ async function loadCspellLib() {
12325
+ cspellLibPromise ??= import("cspell-lib");
12326
+ return cspellLibPromise;
12327
+ }
12328
+ function mapStandardIssueToDiagnostic(issue, languages) {
12329
+ const line = issue.line.position.line + 1;
12330
+ const column = issue.offset - issue.line.offset + 1;
12331
+ return {
12332
+ column,
12333
+ endColumn: column + (issue.length ?? issue.text.length),
12334
+ endLine: line,
12335
+ language: inferStandardIssueLanguage(issue.text, languages),
12336
+ line,
12337
+ message: `Unknown word "${issue.text}".`,
12338
+ ruleId: "spellcheck",
12339
+ severity: "warning",
12340
+ suggestions: issue.suggestions?.slice(0, 3)
12341
+ };
12342
+ }
12343
+ function inferStandardIssueLanguage(word, languages) {
12344
+ if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(word) && languages.includes("ja")) return "ja";
12345
+ if (/[\p{Script=Han}]/u.test(word)) {
12346
+ if (languages.includes("zh") && !languages.includes("ja")) return "zh";
12347
+ if (languages.includes("ja") && !languages.includes("zh")) return "ja";
12348
+ }
12349
+ if (/[\p{Script=Latin}]/u.test(word)) {
12350
+ const latinLanguages = languages.filter((language) => language !== "ja" && language !== "zh");
12351
+ if (latinLanguages.length === 1) return latinLanguages[0];
12352
+ return inferLatinLanguageFromCharacters(word, latinLanguages);
12353
+ }
12354
+ }
12355
+ function inferLatinLanguageFromCharacters(word, languages) {
12356
+ if (languages.includes("pl") && /[ąćęłńóśźż]/iu.test(word)) return "pl";
12357
+ if (languages.includes("de") && /[äöüß]/iu.test(word)) return "de";
12358
+ if (languages.includes("fr") && /[àâæçéèêëîïôœùûüÿ]/iu.test(word)) return "fr";
12359
+ }
12360
+ function summarizeDiagnostics(diagnostics) {
12361
+ let errorCount = 0;
12362
+ let warningCount = 0;
12363
+ let infoCount = 0;
12364
+ for (const diagnostic of diagnostics) if (diagnostic.severity === "error") errorCount += 1;
12365
+ else if (diagnostic.severity === "warning") warningCount += 1;
12366
+ else infoCount += 1;
12367
+ return {
12368
+ diagnostics,
12369
+ errorCount,
12370
+ infoCount,
12371
+ warningCount
12372
+ };
12373
+ }
12374
+ function createEmptyLintResult$1() {
12375
+ return summarizeDiagnostics([]);
12376
+ }
12377
+ function sortDiagnostics(diagnostics) {
12378
+ return [...diagnostics].sort((left, right) => {
12379
+ if (left.line !== right.line) return left.line - right.line;
12380
+ if (left.column !== right.column) return left.column - right.column;
12381
+ return left.ruleId.localeCompare(right.ruleId);
12382
+ });
12383
+ }
12384
+ //#endregion
12385
+ //#region src/lint-files.ts
12386
+ const DEFAULT_LINT_FILE_INCLUDE = ["**/*.md", "**/*.markdown"];
12387
+ const DEFAULT_LINT_FILE_EXCLUDE = [
12388
+ "**/node_modules/**",
12389
+ "**/.git/**",
12390
+ "**/dist/**"
12391
+ ];
12392
+ /**
12393
+ * Returns true if the file path is included by the configured glob filters.
12394
+ */
12395
+ function shouldLintMarkdownFile(filePath, options = {}) {
12396
+ const resolvedOptions = resolveMarkdownLintFileOptions(options);
12397
+ return shouldLintAbsoluteFile(node_path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
12398
+ }
12399
+ /**
12400
+ * Lints a single Markdown file using project-style include/exclude settings.
12401
+ *
12402
+ * If the file is filtered out by `include` / `exclude`, the returned result is
12403
+ * marked as `skipped` and contains no diagnostics.
12404
+ */
12405
+ async function lintMarkdownFile(filePath, options = {}) {
12406
+ const resolvedOptions = resolveMarkdownLintFileOptions(options);
12407
+ return lintMarkdownFileWithResolvedOptions(node_path.resolve(resolvedOptions.cwd, filePath), resolvedOptions);
12408
+ }
12409
+ /**
12410
+ * Lints all Markdown files matched by the configured include/exclude patterns.
12411
+ */
12412
+ async function lintMarkdownFiles(options = {}) {
12413
+ const resolvedOptions = resolveMarkdownLintFileOptions(options);
12414
+ const matchedFiles = await collectMarkdownLintFileEntries(resolvedOptions);
12415
+ const results = await lintMarkdownDocumentsAsync(await Promise.all(matchedFiles.map((file) => node_fs_promises.readFile(file.filePath, "utf-8"))), resolvedOptions.lintOptions);
12416
+ const files = matchedFiles.map((file, index) => ({
12417
+ ...results[index] ?? createEmptyLintResult(),
12418
+ filePath: file.filePath,
12419
+ relativePath: file.relativePath,
12420
+ skipped: false
12421
+ }));
12422
+ const diagnostics = files.flatMap((fileResult) => fileResult.diagnostics.map((diagnostic) => ({
12423
+ ...diagnostic,
12424
+ filePath: fileResult.filePath,
12425
+ relativePath: fileResult.relativePath
12426
+ })));
12427
+ return {
12428
+ checkedFileCount: files.length,
12429
+ diagnostics,
12430
+ errorCount: files.reduce((count, fileResult) => count + fileResult.errorCount, 0),
12431
+ files,
12432
+ infoCount: files.reduce((count, fileResult) => count + fileResult.infoCount, 0),
12433
+ warningCount: files.reduce((count, fileResult) => count + fileResult.warningCount, 0)
12434
+ };
12435
+ }
12436
+ function resolveMarkdownLintFileOptions(options) {
12437
+ return {
12438
+ cwd: node_path.resolve(options.cwd ?? process.cwd()),
12439
+ exclude: [...new Set([...options.exclude ?? DEFAULT_LINT_FILE_EXCLUDE, ...options.ignore ?? []])],
12440
+ include: [...new Set(options.include ?? DEFAULT_LINT_FILE_INCLUDE)],
12441
+ lintOptions: {
12442
+ dictionary: options.dictionary,
12443
+ languages: options.languages,
12444
+ rules: options.rules
12445
+ }
12446
+ };
12447
+ }
12448
+ async function lintMarkdownFileWithResolvedOptions(filePath, options) {
12449
+ const absoluteFilePath = node_path.resolve(filePath);
12450
+ const relativePath = normalizePath(node_path.relative(options.cwd, absoluteFilePath));
12451
+ if (!shouldLintAbsoluteFile(absoluteFilePath, options)) return {
12452
+ ...createEmptyLintResult(),
12453
+ filePath: absoluteFilePath,
12454
+ relativePath,
12455
+ skipped: true
12456
+ };
12457
+ return {
12458
+ ...await lintMarkdownAsync(await node_fs_promises.readFile(absoluteFilePath, "utf-8"), options.lintOptions),
12459
+ filePath: absoluteFilePath,
12460
+ relativePath,
12461
+ skipped: false
12462
+ };
12463
+ }
12464
+ async function collectMarkdownLintFileEntries(options) {
12465
+ const files = /* @__PURE__ */ new Map();
12466
+ for (const pattern of options.include) {
12467
+ const matches = await (0, glob.glob)(pattern, {
12468
+ absolute: true,
12469
+ cwd: options.cwd,
12470
+ ignore: options.exclude,
12471
+ nodir: true
12472
+ });
12473
+ for (const filePath of matches) {
12474
+ const absoluteFilePath = node_path.resolve(filePath);
12475
+ if (shouldLintAbsoluteFile(absoluteFilePath, options)) files.set(absoluteFilePath, {
12476
+ filePath: absoluteFilePath,
12477
+ relativePath: normalizePath(node_path.relative(options.cwd, absoluteFilePath))
12478
+ });
12479
+ }
12480
+ }
12481
+ return [...files.values()].sort((left, right) => left.filePath.localeCompare(right.filePath));
12482
+ }
12483
+ function shouldLintAbsoluteFile(filePath, options) {
12484
+ const absolutePath = normalizePath(node_path.resolve(filePath));
12485
+ const relativePath = normalizePath(node_path.relative(options.cwd, absolutePath));
12486
+ const matches = (patterns) => patterns.some((pattern) => {
12487
+ const normalizedPattern = normalizePath(pattern);
12488
+ return node_path.matchesGlob(relativePath, normalizedPattern) || node_path.matchesGlob(absolutePath, normalizedPattern);
12489
+ });
12490
+ return matches(options.include) && !matches(options.exclude);
12491
+ }
12492
+ function normalizePath(value) {
12493
+ return value.split(node_path.sep).join("/");
12494
+ }
12495
+ function createEmptyLintResult() {
12496
+ return {
12497
+ diagnostics: [],
12498
+ errorCount: 0,
12499
+ infoCount: 0,
12500
+ warningCount: 0
12501
+ };
12502
+ }
12503
+ //#endregion
12149
12504
  //#region src/jsx-runtime.ts
12150
12505
  /**
12151
12506
  * Custom JSX Runtime for Static HTML Generation
@@ -12994,6 +13349,10 @@ exports.hasIslands = hasIslands;
12994
13349
  exports.inferType = inferType;
12995
13350
  exports.jsx = jsx;
12996
13351
  exports.jsxs = jsxs;
13352
+ exports.lintMarkdown = lintMarkdown;
13353
+ exports.lintMarkdownAsync = lintMarkdownAsync;
13354
+ exports.lintMarkdownFile = lintMarkdownFile;
13355
+ exports.lintMarkdownFiles = lintMarkdownFiles;
12997
13356
  exports.mergeThemes = mergeThemes;
12998
13357
  exports.mermaidClientScript = require_mermaid.mermaidClientScript;
12999
13358
  exports.oxContent = oxContent;
@@ -13010,6 +13369,7 @@ exports.resolveSearchOptions = resolveSearchOptions;
13010
13369
  exports.resolveSsgOptions = resolveSsgOptions;
13011
13370
  exports.resolveTheme = resolveTheme;
13012
13371
  exports.setRenderContext = setRenderContext;
13372
+ exports.shouldLintMarkdownFile = shouldLintMarkdownFile;
13013
13373
  exports.transformAllPlugins = transformAllPlugins;
13014
13374
  exports.transformGitHub = require_github.transformGitHub;
13015
13375
  exports.transformIslands = transformIslands;