@ox-content/vite-plugin 2.32.0 → 2.34.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
@@ -817,7 +817,8 @@ async function extractDocs(srcDirs, options) {
817
817
  return extractDocsFromEntryPoints(options.entryPoints, {
818
818
  root: process.cwd(),
819
819
  private: options.private,
820
- internal: options.internal
820
+ internal: options.internal,
821
+ typeParameters: options.typeParameters
821
822
  }).map((doc) => ({
822
823
  file: doc.file,
823
824
  entries: doc.entries
@@ -825,7 +826,7 @@ async function extractDocs(srcDirs, options) {
825
826
  }
826
827
  const extractDocsFromDirectories = napi.extractDocsFromDirectories;
827
828
  if (!extractDocsFromDirectories) throw new Error("[ox-content] extractDocsFromDirectories is not available from @ox-content/napi.");
828
- return extractDocsFromDirectories(srcDirs, options.include, options.exclude, options.private, options.internal).map((doc) => ({
829
+ return extractDocsFromDirectories(srcDirs, options.include, options.exclude, options.private, options.internal, options.typeParameters).map((doc) => ({
829
830
  file: doc.file,
830
831
  entries: doc.entries
831
832
  }));
@@ -906,6 +907,7 @@ function resolveDocsOptions(options) {
906
907
  basePath: opts.basePath,
907
908
  pathStrategy: opts.pathStrategy ?? "flat",
908
909
  renderStyle: opts.renderStyle ?? "html",
910
+ typeParameters: opts.typeParameters ?? false,
909
911
  generateNav: opts.generateNav ?? true
910
912
  };
911
913
  }
@@ -2986,6 +2988,337 @@ function generateI18nModule(options, root) {
2986
2988
  throw new Error("[ox-content:i18n] @ox-content/napi does not expose generateI18nModule. Please rebuild the NAPI package.");
2987
2989
  }
2988
2990
  //#endregion
2991
+ //#region src/docs-tests.ts
2992
+ var DocsTestRunError = class extends Error {
2993
+ result;
2994
+ constructor(result) {
2995
+ const command = [result.command, ...result.args].join(" ");
2996
+ super(`[ox-content] Docs tests failed with exit code ${result.exitCode}: ${command}`);
2997
+ this.name = "DocsTestRunError";
2998
+ this.result = result;
2999
+ }
3000
+ };
3001
+ async function collectDocsTests(options) {
3002
+ const cwd = node_path.resolve(options.cwd ?? process.cwd());
3003
+ if ((options.source ?? "markdown") === "jsdoc") return collectJsdocDocsTests(options, cwd);
3004
+ return collectMarkdownDocsTests(options, cwd);
3005
+ }
3006
+ async function collectMarkdownDocsTests(options, cwd) {
3007
+ const include = toArray(options.include);
3008
+ const ignore = toArray(options.ignore);
3009
+ const files = /* @__PURE__ */ new Map();
3010
+ if (include.length === 0) throw new Error("[ox-content] Docs test include patterns are required for markdown sources.");
3011
+ for (const pattern of include) {
3012
+ const matches = await (0, glob.glob)(pattern, {
3013
+ absolute: true,
3014
+ cwd,
3015
+ ignore,
3016
+ nodir: true
3017
+ });
3018
+ for (const filePath of matches) {
3019
+ const absolutePath = node_path.resolve(filePath);
3020
+ files.set(absolutePath, normalizePath$1(node_path.relative(cwd, absolutePath)));
3021
+ }
3022
+ }
3023
+ const blocks = [];
3024
+ let index = 0;
3025
+ for (const [sourcePath, relativePath] of [...files.entries()].sort((left, right) => left[0].localeCompare(right[0]))) {
3026
+ const extracted = await extractDocsTests(await node_fs_promises.readFile(sourcePath, "utf-8"), {
3027
+ languages: options.languages,
3028
+ requireMeta: options.requireMeta
3029
+ });
3030
+ for (const block of extracted) {
3031
+ blocks.push({
3032
+ ...block,
3033
+ sourcePath,
3034
+ relativePath,
3035
+ index
3036
+ });
3037
+ index += 1;
3038
+ }
3039
+ }
3040
+ return blocks;
3041
+ }
3042
+ async function collectJsdocDocsTests(options, cwd) {
3043
+ const docsOptions = resolveJsdocDocsOptions(options, cwd);
3044
+ const docs = await extractDocs(docsOptions.src, docsOptions);
3045
+ const blocks = [];
3046
+ let index = 0;
3047
+ for (const doc of sortDocs(docs)) for (const entry of sortEntries(doc.entries)) for (const example of entry.examples ?? []) {
3048
+ const extracted = await extractDocsTests(example, {
3049
+ languages: options.languages,
3050
+ requireMeta: options.requireMeta
3051
+ });
3052
+ const sourcePath = resolveEntrySourcePath(entry, doc, cwd);
3053
+ const relativePath = relativeSourcePath(cwd, sourcePath);
3054
+ for (const block of extracted) {
3055
+ blocks.push({
3056
+ ...block,
3057
+ sourcePath,
3058
+ relativePath,
3059
+ startLine: entry.line,
3060
+ endLine: entry.endLine,
3061
+ index
3062
+ });
3063
+ index += 1;
3064
+ }
3065
+ }
3066
+ return blocks;
3067
+ }
3068
+ function resolveJsdocDocsOptions(options, cwd) {
3069
+ const docsOptions = { ...options.docs };
3070
+ if (options.src !== void 0) docsOptions.src = toArray(options.src);
3071
+ if (options.include !== void 0) docsOptions.include = toArray(options.include);
3072
+ if (options.ignore !== void 0) docsOptions.exclude = toArray(options.ignore);
3073
+ const resolved = resolveDocsOptions(docsOptions);
3074
+ return {
3075
+ ...resolved,
3076
+ src: resolved.src.map((sourceDir) => node_path.resolve(cwd, sourceDir)),
3077
+ entryPoints: resolved.entryPoints?.map((entryPoint) => ({
3078
+ ...entryPoint,
3079
+ path: node_path.resolve(cwd, entryPoint.path)
3080
+ }))
3081
+ };
3082
+ }
3083
+ function sortDocs(docs) {
3084
+ return [...docs].sort((left, right) => left.file.localeCompare(right.file));
3085
+ }
3086
+ function sortEntries(entries) {
3087
+ return [...entries].sort((left, right) => {
3088
+ const byFile = left.file.localeCompare(right.file);
3089
+ if (byFile !== 0) return byFile;
3090
+ const byLine = left.line - right.line;
3091
+ if (byLine !== 0) return byLine;
3092
+ return left.name.localeCompare(right.name);
3093
+ });
3094
+ }
3095
+ function resolveEntrySourcePath(entry, doc, cwd) {
3096
+ const sourcePath = entry.file || doc.file;
3097
+ return node_path.isAbsolute(sourcePath) ? node_path.resolve(sourcePath) : node_path.resolve(cwd, sourcePath);
3098
+ }
3099
+ function relativeSourcePath(cwd, sourcePath) {
3100
+ const relativePath = node_path.relative(cwd, sourcePath);
3101
+ if (!relativePath.startsWith("..") && !node_path.isAbsolute(relativePath)) return normalizePath$1(relativePath);
3102
+ return normalizePath$1(sourcePath);
3103
+ }
3104
+ async function writeDocsTestFiles(options) {
3105
+ const cwd = node_path.resolve(options.cwd ?? process.cwd());
3106
+ const generatedDir = node_path.resolve(cwd, options.generatedDir ?? ".cache/ox-content-docs-tests");
3107
+ const clean = options.clean ?? true;
3108
+ const blocks = await collectDocsTests({
3109
+ ...options,
3110
+ cwd
3111
+ });
3112
+ if (clean) await node_fs_promises.rm(generatedDir, {
3113
+ recursive: true,
3114
+ force: true
3115
+ });
3116
+ await node_fs_promises.mkdir(generatedDir, { recursive: true });
3117
+ return {
3118
+ cwd,
3119
+ generatedDir,
3120
+ blocks,
3121
+ files: await Promise.all(blocks.map(async (block) => {
3122
+ const filePath = node_path.join(generatedDir, docsTestFileName(block));
3123
+ await node_fs_promises.writeFile(filePath, renderDocsTestFile(block, options), "utf-8");
3124
+ return {
3125
+ filePath,
3126
+ sourcePath: block.sourcePath,
3127
+ relativePath: block.relativePath,
3128
+ startLine: block.startLine,
3129
+ endLine: block.endLine,
3130
+ language: block.language
3131
+ };
3132
+ }))
3133
+ };
3134
+ }
3135
+ async function runDocsTests(options) {
3136
+ const writeResult = await writeDocsTestFiles(options);
3137
+ const command = options.vitestCommand ?? "vitest";
3138
+ const leadingArgs = options.vitestArgs ?? ["run"];
3139
+ const fileArgs = writeResult.files.map((file) => file.filePath);
3140
+ const args = [...leadingArgs, ...fileArgs];
3141
+ if (fileArgs.length === 0) {
3142
+ if (options.allowEmpty) return {
3143
+ ...writeResult,
3144
+ command,
3145
+ args,
3146
+ exitCode: 0,
3147
+ stdout: "",
3148
+ stderr: ""
3149
+ };
3150
+ throw new Error("[ox-content] No runnable docs test blocks were found.");
3151
+ }
3152
+ const result = await runCommand(command, args, {
3153
+ cwd: writeResult.cwd,
3154
+ env: mergeEnv(options.env)
3155
+ });
3156
+ const runResult = {
3157
+ ...writeResult,
3158
+ command,
3159
+ args,
3160
+ ...result
3161
+ };
3162
+ if (runResult.exitCode !== 0) throw new DocsTestRunError(runResult);
3163
+ return runResult;
3164
+ }
3165
+ function renderDocsTestFile(block, options) {
3166
+ const parts = [
3167
+ "// Generated by @ox-content/vite-plugin docs test harness.",
3168
+ `// Source: ${block.relativePath}:${block.startLine}-${block.endLine}`,
3169
+ ""
3170
+ ];
3171
+ const setupCode = options.setupCode?.trimEnd();
3172
+ const code = rewriteImports(block.code.trimEnd(), options.importRewrites);
3173
+ if (setupCode) parts.push(setupCode, "");
3174
+ if ((options.executionMode ?? "test") === "module") {
3175
+ parts.push(code, "");
3176
+ return parts.join("\n");
3177
+ }
3178
+ const { imports, body } = partitionImports(code);
3179
+ parts.push(`import { test } from ${JSON.stringify(rewriteSpecifier(options.testImport ?? "vitest", options.importRewrites))};`);
3180
+ if (imports.length > 0) parts.push(...imports);
3181
+ parts.push("", `test(${JSON.stringify(`${block.relativePath}:${block.startLine}`)}, async () => {`);
3182
+ if (body.trim().length > 0) parts.push(indentCode(body.trimEnd()));
3183
+ parts.push("});", "");
3184
+ return parts.join("\n");
3185
+ }
3186
+ function partitionImports(source) {
3187
+ const imports = [];
3188
+ const body = [];
3189
+ const lines = source.split(/\r?\n/);
3190
+ let currentImport;
3191
+ for (const line of lines) {
3192
+ if (currentImport) {
3193
+ currentImport.push(line);
3194
+ if (endsImportDeclaration(line)) {
3195
+ imports.push(currentImport.join("\n"));
3196
+ currentImport = void 0;
3197
+ }
3198
+ continue;
3199
+ }
3200
+ if (startsStaticImport(line)) {
3201
+ if (endsImportDeclaration(line)) imports.push(line);
3202
+ else currentImport = [line];
3203
+ continue;
3204
+ }
3205
+ body.push(line);
3206
+ }
3207
+ if (currentImport) body.push(...currentImport);
3208
+ return {
3209
+ imports,
3210
+ body: body.join("\n")
3211
+ };
3212
+ }
3213
+ function startsStaticImport(line) {
3214
+ const trimmed = line.trimStart();
3215
+ return trimmed.startsWith("import ") && !trimmed.startsWith("import(");
3216
+ }
3217
+ function endsImportDeclaration(line) {
3218
+ const trimmed = line.trim();
3219
+ return trimmed.endsWith(";") || /^import\s+["'][^"']+["']$/.test(trimmed) || /\sfrom\s+["'][^"']+["']$/.test(trimmed);
3220
+ }
3221
+ function indentCode(source) {
3222
+ return source.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
3223
+ }
3224
+ function rewriteImports(source, rewrites) {
3225
+ if (!rewrites) return source;
3226
+ let result = source;
3227
+ for (const [from, to] of Object.entries(rewrites)) {
3228
+ const escaped = escapeRegExp(from);
3229
+ result = result.replace(new RegExp(`(from\\s+["'])${escaped}(["'])`, "g"), `$1${to}$2`).replace(new RegExp(`(import\\s+["'])${escaped}(["'])`, "g"), `$1${to}$2`).replace(new RegExp(`(import\\(\\s*["'])${escaped}(["']\\s*\\))`, "g"), `$1${to}$2`);
3230
+ }
3231
+ return result;
3232
+ }
3233
+ /**
3234
+ * Applies the same import rewrite table to harness-owned imports.
3235
+ *
3236
+ * @internal
3237
+ * @example
3238
+ * ```ts docs-test
3239
+ * import { expect } from "vitest";
3240
+ * import { extractDocsTests } from "../../src/code-blocks";
3241
+ *
3242
+ * const markdown = [
3243
+ * "```ts docs-test",
3244
+ * "expect(1 + 1).toBe(2);",
3245
+ * "```",
3246
+ * ].join("\n");
3247
+ *
3248
+ * const blocks = await extractDocsTests(markdown);
3249
+ *
3250
+ * expect(blocks).toHaveLength(1);
3251
+ * expect(blocks[0]?.code).toContain("1 + 1");
3252
+ * ```
3253
+ */
3254
+ function rewriteSpecifier(specifier, rewrites) {
3255
+ return rewrites?.[specifier] ?? specifier;
3256
+ }
3257
+ function escapeRegExp(value) {
3258
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3259
+ }
3260
+ function docsTestFileName(block) {
3261
+ return `${block.relativePath.replace(/^\.\//, "").replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "docs-test"}-L${block.startLine}-${block.index + 1}.test.${extensionForLanguage(block.language)}`;
3262
+ }
3263
+ function extensionForLanguage(language) {
3264
+ switch (language.toLowerCase()) {
3265
+ case "jsx": return "jsx";
3266
+ case "tsx": return "tsx";
3267
+ case "mjs": return "mjs";
3268
+ case "mts": return "mts";
3269
+ case "js": return "js";
3270
+ default: return "ts";
3271
+ }
3272
+ }
3273
+ function toArray(value) {
3274
+ if (!value) return [];
3275
+ return Array.isArray(value) ? value : [value];
3276
+ }
3277
+ function normalizePath$1(value) {
3278
+ return value.split(node_path.sep).join("/");
3279
+ }
3280
+ function mergeEnv(overrides) {
3281
+ const env = { ...process.env };
3282
+ for (const [key, value] of Object.entries(overrides ?? {})) if (value === void 0) delete env[key];
3283
+ else env[key] = value;
3284
+ return env;
3285
+ }
3286
+ function runCommand(command, args, options) {
3287
+ return new Promise((resolve, reject) => {
3288
+ const child = (0, node_child_process.spawn)(command, args, {
3289
+ cwd: options.cwd,
3290
+ env: options.env,
3291
+ stdio: [
3292
+ "ignore",
3293
+ "pipe",
3294
+ "pipe"
3295
+ ]
3296
+ });
3297
+ let stdout = "";
3298
+ let stderr = "";
3299
+ if (child.stdout) {
3300
+ child.stdout.setEncoding("utf-8");
3301
+ child.stdout.on("data", (chunk) => {
3302
+ stdout += chunk;
3303
+ });
3304
+ }
3305
+ if (child.stderr) {
3306
+ child.stderr.setEncoding("utf-8");
3307
+ child.stderr.on("data", (chunk) => {
3308
+ stderr += chunk;
3309
+ });
3310
+ }
3311
+ child.on("error", reject);
3312
+ child.on("close", (exitCode) => {
3313
+ resolve({
3314
+ exitCode: exitCode ?? 1,
3315
+ stdout,
3316
+ stderr
3317
+ });
3318
+ });
3319
+ });
3320
+ }
3321
+ //#endregion
2989
3322
  //#region src/lint.ts
2990
3323
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
2991
3324
  const SUPPORTED_MARKDOWN_LINT_LANGUAGES = [
@@ -4405,10 +4738,12 @@ function normalizeRuntimeBase(base) {
4405
4738
  exports.DEFAULT_HTML_TEMPLATE = DEFAULT_HTML_TEMPLATE;
4406
4739
  exports.DEFAULT_MARKDOWN_EXTENSIONS = DEFAULT_MARKDOWN_EXTENSIONS;
4407
4740
  exports.DefaultTheme = DefaultTheme;
4741
+ exports.DocsTestRunError = DocsTestRunError;
4408
4742
  exports.Fragment = Fragment;
4409
4743
  exports.buildSearchIndex = buildSearchIndex;
4410
4744
  exports.buildSsg = buildSsg;
4411
4745
  exports.clearRenderContext = clearRenderContext;
4746
+ exports.collectDocsTests = collectDocsTests;
4412
4747
  exports.collectGitHubRepos = require_github.collectGitHubRepos;
4413
4748
  exports.collectGitHubSources = require_github.collectGitHubSources;
4414
4749
  exports.collectOgpUrls = require_ogp.collectOgpUrls;
@@ -4468,6 +4803,7 @@ exports.resolveOgImageOptions = resolveOgImageOptions;
4468
4803
  exports.resolveSearchOptions = resolveSearchOptions;
4469
4804
  exports.resolveSsgOptions = resolveSsgOptions;
4470
4805
  exports.resolveTheme = require_vitepress.resolveTheme;
4806
+ exports.runDocsTests = runDocsTests;
4471
4807
  exports.setRenderContext = setRenderContext;
4472
4808
  exports.shouldLintMarkdownFile = shouldLintMarkdownFile;
4473
4809
  exports.stripMarkdownExtension = stripMarkdownExtension;
@@ -4487,6 +4823,7 @@ exports.useRenderContext = useRenderContext;
4487
4823
  exports.useSiteConfig = useSiteConfig;
4488
4824
  exports.when = when;
4489
4825
  exports.writeDocs = writeDocs;
4826
+ exports.writeDocsTestFiles = writeDocsTestFiles;
4490
4827
  exports.writeSearchIndex = writeSearchIndex;
4491
4828
 
4492
4829
  //# sourceMappingURL=index.cjs.map