tryscript 0.1.4 → 0.1.5

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/bin.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
 
4
- import { a as createExecutionContext, c as parseTestFile, d as mergeConfig, f as resolveCoverageConfig, i as cleanupExecutionContext, n as matchOutput, o as runAfterHook, s as runBlock, t as VERSION, u as loadConfig } from "./src-D60Uy8QA.mjs";
4
+ import { a as createExecutionContext, c as parseTestFile, d as mergeConfig, f as resolveCoverageConfig, i as cleanupExecutionContext, n as matchOutput, o as runAfterHook, s as runBlock, t as VERSION, u as loadConfig } from "./src-CC3xA1cp.mjs";
5
5
  import { fileURLToPath } from "node:url";
6
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { existsSync, readFileSync } from "node:fs";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import { spawn } from "node:child_process";
9
9
  import { access, mkdtemp, readFile, readdir, rm, stat } from "node:fs/promises";
@@ -168,237 +168,6 @@ function buildUpdatedBlock(block, result) {
168
168
  return lines.join("\n");
169
169
  }
170
170
 
171
- //#endregion
172
- //#region src/lib/lcov.ts
173
- /**
174
- * LCOV parsing, merging, and writing utilities.
175
- *
176
- * LCOV format reference:
177
- * - SF: Source file path
178
- * - DA:linenum,hitcount - Line data
179
- * - FN:linenum,funcname - Function definition
180
- * - FNDA:hitcount,funcname - Function hit data
181
- * - FNF: Functions found count
182
- * - FNH: Functions hit count
183
- * - BRF: Branches found count
184
- * - BRH: Branches hit count
185
- * - BRDA:line,block,branch,taken - Branch data
186
- * - LF: Lines found count
187
- * - LH: Lines hit count
188
- * - end_of_record - End of file record
189
- */
190
- /**
191
- * Parse LCOV content into structured data.
192
- */
193
- function parseLcov(content) {
194
- const files = /* @__PURE__ */ new Map();
195
- let currentFile = null;
196
- for (const line of content.split("\n")) {
197
- const trimmed = line.trim();
198
- if (trimmed.startsWith("SF:")) {
199
- const path = trimmed.slice(3);
200
- currentFile = {
201
- path,
202
- lines: /* @__PURE__ */ new Map(),
203
- functions: /* @__PURE__ */ new Map(),
204
- branches: []
205
- };
206
- files.set(path, currentFile);
207
- } else if (trimmed.startsWith("DA:") && currentFile) {
208
- const parts = trimmed.slice(3).split(",");
209
- const lineNumber = parseInt(parts[0], 10);
210
- const hitCount = parseInt(parts[1], 10);
211
- currentFile.lines.set(lineNumber, {
212
- lineNumber,
213
- hitCount
214
- });
215
- } else if (trimmed.startsWith("FN:") && currentFile) {
216
- const parts = trimmed.slice(3).split(",");
217
- const lineNumber = parseInt(parts[0], 10);
218
- const name = parts.slice(1).join(",");
219
- if (!currentFile.functions.has(name)) currentFile.functions.set(name, {
220
- name,
221
- lineNumber,
222
- hitCount: 0
223
- });
224
- else currentFile.functions.get(name).lineNumber = lineNumber;
225
- } else if (trimmed.startsWith("FNDA:") && currentFile) {
226
- const parts = trimmed.slice(5).split(",");
227
- const hitCount = parseInt(parts[0], 10);
228
- const name = parts.slice(1).join(",");
229
- if (currentFile.functions.has(name)) currentFile.functions.get(name).hitCount = hitCount;
230
- else currentFile.functions.set(name, {
231
- name,
232
- lineNumber: 0,
233
- hitCount
234
- });
235
- } else if (trimmed.startsWith("BRDA:") && currentFile) {
236
- const parts = trimmed.slice(5).split(",");
237
- currentFile.branches.push({
238
- line: parseInt(parts[0], 10),
239
- block: parseInt(parts[1], 10),
240
- branch: parseInt(parts[2], 10),
241
- taken: parts[3] === "-" ? -1 : parseInt(parts[3], 10)
242
- });
243
- } else if (trimmed === "end_of_record") currentFile = null;
244
- }
245
- return { files };
246
- }
247
- /**
248
- * Merge multiple LCOV data structures, taking max hit counts.
249
- */
250
- function mergeLcov(...lcovs) {
251
- const merged = /* @__PURE__ */ new Map();
252
- for (const lcov of lcovs) for (const [path, file] of lcov.files) if (!merged.has(path)) merged.set(path, {
253
- path,
254
- lines: new Map(file.lines),
255
- functions: new Map(file.functions),
256
- branches: [...file.branches]
257
- });
258
- else {
259
- const existing = merged.get(path);
260
- for (const [lineNum, lineData] of file.lines) {
261
- const existingLine = existing.lines.get(lineNum);
262
- if (existingLine) existingLine.hitCount = Math.max(existingLine.hitCount, lineData.hitCount);
263
- else existing.lines.set(lineNum, { ...lineData });
264
- }
265
- for (const [name, funcData] of file.functions) {
266
- const existingFunc = existing.functions.get(name);
267
- if (existingFunc) existingFunc.hitCount = Math.max(existingFunc.hitCount, funcData.hitCount);
268
- else existing.functions.set(name, { ...funcData });
269
- }
270
- for (const branch of file.branches) {
271
- const existingBranch = existing.branches.find((b) => b.line === branch.line && b.block === branch.block && b.branch === branch.branch);
272
- if (existingBranch) {
273
- if (branch.taken >= 0) existingBranch.taken = existingBranch.taken >= 0 ? Math.max(existingBranch.taken, branch.taken) : branch.taken;
274
- } else existing.branches.push({ ...branch });
275
- }
276
- }
277
- return { files: merged };
278
- }
279
- /**
280
- * Convert LCOV data back to LCOV format string.
281
- */
282
- function formatLcov(lcov) {
283
- const lines = [];
284
- for (const file of lcov.files.values()) {
285
- lines.push(`SF:${file.path}`);
286
- const sortedFunctions = [...file.functions.values()].sort((a, b) => a.lineNumber - b.lineNumber);
287
- for (const func of sortedFunctions) lines.push(`FN:${func.lineNumber},${func.name}`);
288
- for (const func of sortedFunctions) lines.push(`FNDA:${func.hitCount},${func.name}`);
289
- const fnf = file.functions.size;
290
- const fnh = [...file.functions.values()].filter((f) => f.hitCount > 0).length;
291
- lines.push(`FNF:${fnf}`);
292
- lines.push(`FNH:${fnh}`);
293
- for (const branch of file.branches) {
294
- const taken = branch.taken < 0 ? "-" : branch.taken.toString();
295
- lines.push(`BRDA:${branch.line},${branch.block},${branch.branch},${taken}`);
296
- }
297
- const brf = file.branches.length;
298
- const brh = file.branches.filter((b) => b.taken > 0).length;
299
- lines.push(`BRF:${brf}`);
300
- lines.push(`BRH:${brh}`);
301
- const sortedLines = [...file.lines.values()].sort((a, b) => a.lineNumber - b.lineNumber);
302
- for (const line of sortedLines) lines.push(`DA:${line.lineNumber},${line.hitCount}`);
303
- const lf = file.lines.size;
304
- const lh = [...file.lines.values()].filter((l) => l.hitCount > 0).length;
305
- lines.push(`LF:${lf}`);
306
- lines.push(`LH:${lh}`);
307
- lines.push("end_of_record");
308
- }
309
- return lines.join("\n") + "\n";
310
- }
311
- /**
312
- * Convert LCOV data to JSON summary format (compatible with istanbul/vitest).
313
- */
314
- function lcovToJsonSummary(lcov) {
315
- const withPct = (total, covered) => ({
316
- total,
317
- covered,
318
- skipped: 0,
319
- pct: total > 0 ? parseFloat((covered / total * 100).toFixed(2)) : 100
320
- });
321
- const totals = {
322
- lines: {
323
- total: 0,
324
- covered: 0
325
- },
326
- functions: {
327
- total: 0,
328
- covered: 0
329
- },
330
- branches: {
331
- total: 0,
332
- covered: 0
333
- }
334
- };
335
- const summary = { total: {
336
- lines: withPct(0, 0),
337
- statements: withPct(0, 0),
338
- functions: withPct(0, 0),
339
- branches: withPct(0, 0),
340
- branchesTrue: {
341
- total: 0,
342
- covered: 0,
343
- skipped: 0,
344
- pct: 100
345
- }
346
- } };
347
- for (const file of lcov.files.values()) {
348
- const linesTotal = file.lines.size;
349
- const linesCovered = [...file.lines.values()].filter((l) => l.hitCount > 0).length;
350
- const funcsTotal = file.functions.size;
351
- const funcsCovered = [...file.functions.values()].filter((f) => f.hitCount > 0).length;
352
- const branchesTotal = file.branches.length;
353
- const branchesCovered = file.branches.filter((b) => b.taken > 0).length;
354
- summary[file.path] = {
355
- lines: withPct(linesTotal, linesCovered),
356
- statements: withPct(linesTotal, linesCovered),
357
- functions: withPct(funcsTotal, funcsCovered),
358
- branches: withPct(branchesTotal, branchesCovered)
359
- };
360
- totals.lines.total += linesTotal;
361
- totals.lines.covered += linesCovered;
362
- totals.functions.total += funcsTotal;
363
- totals.functions.covered += funcsCovered;
364
- totals.branches.total += branchesTotal;
365
- totals.branches.covered += branchesCovered;
366
- }
367
- summary.total = {
368
- lines: withPct(totals.lines.total, totals.lines.covered),
369
- statements: withPct(totals.lines.total, totals.lines.covered),
370
- functions: withPct(totals.functions.total, totals.functions.covered),
371
- branches: withPct(totals.branches.total, totals.branches.covered),
372
- branchesTrue: {
373
- total: 0,
374
- covered: 0,
375
- skipped: 0,
376
- pct: 100
377
- }
378
- };
379
- return summary;
380
- }
381
- /**
382
- * Read and parse an LCOV file.
383
- */
384
- function readLcovFile(path) {
385
- return parseLcov(readFileSync(path, "utf8"));
386
- }
387
- /**
388
- * Write LCOV data to a file.
389
- */
390
- function writeLcovFile(path, lcov) {
391
- mkdirSync(dirname(path), { recursive: true });
392
- writeFileSync(path, formatLcov(lcov));
393
- }
394
- /**
395
- * Write JSON summary to a file.
396
- */
397
- function writeJsonSummary(path, summary) {
398
- mkdirSync(dirname(path), { recursive: true });
399
- writeFileSync(path, JSON.stringify(summary, null, 2));
400
- }
401
-
402
171
  //#endregion
403
172
  //#region src/lib/coverage.ts
404
173
  /**
@@ -504,33 +273,6 @@ async function cleanupCoverageContext(ctx) {
504
273
  });
505
274
  } catch {}
506
275
  }
507
- /**
508
- * Merge external LCOV file with generated coverage.
509
- * Reads the generated lcov.info, merges with external LCOV, and writes back.
510
- * Also generates coverage-summary.json for badge generation.
511
- *
512
- * @returns Object with merged coverage percentages, or null if merge failed
513
- */
514
- function mergeExternalCoverage(reportsDir, externalLcovPath) {
515
- const generatedLcovPath = join(reportsDir, "lcov.info");
516
- if (!existsSync(externalLcovPath)) {
517
- console.error(`External LCOV file not found: ${externalLcovPath}`);
518
- return null;
519
- }
520
- if (!existsSync(generatedLcovPath)) {
521
- console.error(`Generated LCOV file not found: ${generatedLcovPath}`);
522
- console.error("Make sure \"lcov\" is included in reporters");
523
- return null;
524
- }
525
- const mergedLcov = mergeLcov(readLcovFile(externalLcovPath), readLcovFile(generatedLcovPath));
526
- writeLcovFile(generatedLcovPath, mergedLcov);
527
- const summary = lcovToJsonSummary(mergedLcov);
528
- writeJsonSummary(join(reportsDir, "coverage-summary.json"), summary);
529
- return {
530
- lines: summary.total.lines.pct,
531
- functions: summary.total.functions.pct
532
- };
533
- }
534
276
 
535
277
  //#endregion
536
278
  //#region src/cli/commands/run.ts
@@ -538,7 +280,7 @@ function mergeExternalCoverage(reportsDir, externalLcovPath) {
538
280
  * Register the run command.
539
281
  */
540
282
  function registerRunCommand(program) {
541
- program.command("run").description("Run golden tests").argument("[files...]", "Test files to run (default: **/*.tryscript.md)").option("--update", "Update golden files with actual output").option("--diff", "Show diff on failure (default: true)").option("--no-diff", "Hide diff on failure").option("--fail-fast", "Stop on first failure").option("--filter <pattern>", "Filter tests by name pattern").option("--verbose", "Show detailed output including passing test output").option("--quiet", "Suppress non-essential output (only show failures)").option("--coverage", "Enable code coverage collection (requires c8)").option("--coverage-dir <dir>", "Coverage output directory (default: coverage-tryscript)").option("--coverage-reporter <reporter...>", "Coverage reporters (default: text, html). Can be specified multiple times.").option("--coverage-exclude <pattern...>", "Patterns to exclude from coverage (c8 --exclude). Can be specified multiple times.").option("--coverage-exclude-node-modules", "Exclude node_modules from coverage (c8 --exclude-node-modules, default: true)").option("--no-coverage-exclude-node-modules", "Include node_modules in coverage (c8 --no-exclude-node-modules)").option("--coverage-exclude-after-remap", "Apply exclude logic after sourcemap remapping (c8 --exclude-after-remap)").option("--coverage-skip-full", "Hide files with 100% coverage (c8 --skip-full)").option("--coverage-allow-external", "Allow files from outside cwd (c8 --allowExternal)").option("--coverage-monocart", "Use monocart for accurate line counts, better for merging with vitest (c8 --experimental-monocart)").option("--merge-lcov <path>", "Merge coverage from an existing LCOV file (e.g., from vitest --coverage)").action(runCommand$1);
283
+ program.command("run").description("Run golden tests").argument("[files...]", "Test files to run (default: **/*.tryscript.md)").option("--update", "Update golden files with actual output").option("--diff", "Show diff on failure (default: true)").option("--no-diff", "Hide diff on failure").option("--fail-fast", "Stop on first failure").option("--filter <pattern>", "Filter tests by name pattern").option("--verbose", "Show detailed output including passing test output").option("--quiet", "Suppress non-essential output (only show failures)").option("--coverage", "Enable code coverage collection (requires c8)").option("--coverage-dir <dir>", "Coverage output directory (default: coverage-tryscript)").option("--coverage-reporter <reporter...>", "Coverage reporters (default: text, html). Can be specified multiple times.").option("--coverage-exclude <pattern...>", "Patterns to exclude from coverage (c8 --exclude). Can be specified multiple times.").option("--coverage-exclude-node-modules", "Exclude node_modules from coverage (c8 --exclude-node-modules, default: true)").option("--no-coverage-exclude-node-modules", "Include node_modules in coverage (c8 --no-exclude-node-modules)").option("--coverage-exclude-after-remap", "Apply exclude logic after sourcemap remapping (c8 --exclude-after-remap)").option("--coverage-skip-full", "Hide files with 100% coverage (c8 --skip-full)").option("--coverage-allow-external", "Allow files from outside cwd (c8 --allowExternal)").option("--coverage-monocart", "Use monocart for accurate line counts, better for merging with vitest (c8 --experimental-monocart)").action(runCommand$1);
542
284
  }
543
285
  async function runCommand$1(files, options) {
544
286
  const startTime = Date.now();
@@ -567,26 +309,16 @@ async function runCommand$1(files, options) {
567
309
  logError("Coverage requires c8. Install with: npm install -D c8");
568
310
  process.exit(1);
569
311
  }
570
- let reporters = options.coverageReporter ?? globalConfig.coverage?.reporters;
571
- if (options.mergeLcov) {
572
- if (!reporters) reporters = [
573
- "text",
574
- "html",
575
- "lcov"
576
- ];
577
- else if (!reporters.includes("lcov")) reporters = [...reporters, "lcov"];
578
- }
579
312
  coverageCtx = await createCoverageContext({
580
313
  ...globalConfig.coverage,
581
314
  reportsDir: options.coverageDir ?? globalConfig.coverage?.reportsDir,
582
- reporters,
315
+ reporters: options.coverageReporter ?? globalConfig.coverage?.reporters,
583
316
  exclude: options.coverageExclude ?? globalConfig.coverage?.exclude,
584
317
  excludeNodeModules: options.coverageExcludeNodeModules ?? globalConfig.coverage?.excludeNodeModules,
585
318
  excludeAfterRemap: options.coverageExcludeAfterRemap ?? globalConfig.coverage?.excludeAfterRemap,
586
319
  skipFull: options.coverageSkipFull ?? globalConfig.coverage?.skipFull,
587
320
  allowExternal: options.coverageAllowExternal ?? globalConfig.coverage?.allowExternal,
588
- monocart: options.coverageMonocart ?? globalConfig.coverage?.monocart,
589
- mergeLcov: options.mergeLcov ?? globalConfig.coverage?.mergeLcov
321
+ monocart: options.coverageMonocart ?? globalConfig.coverage?.monocart
590
322
  });
591
323
  coverageEnv = getCoverageEnv(coverageCtx);
592
324
  }
@@ -660,11 +392,6 @@ async function runCommand$1(files, options) {
660
392
  console.error("\nGenerating coverage report...");
661
393
  try {
662
394
  await generateCoverageReport(coverageCtx);
663
- if (coverageCtx.options.mergeLcov) {
664
- console.error(`Merging with external coverage: ${coverageCtx.options.mergeLcov}`);
665
- const merged = mergeExternalCoverage(coverageCtx.options.reportsDir, coverageCtx.options.mergeLcov);
666
- if (merged) console.error(colors.success(`Merged coverage: ${merged.lines}% lines, ${merged.functions}% functions`));
667
- }
668
395
  console.error(colors.success(`Coverage report written to ${coverageCtx.options.reportsDir}/`));
669
396
  } catch (error) {
670
397
  logError(`Failed to generate coverage report: ${error instanceof Error ? error.message : String(error)}`);
@@ -681,7 +408,7 @@ async function runCommand$1(files, options) {
681
408
  * Register the coverage command.
682
409
  */
683
410
  function registerCoverageCommand(program) {
684
- program.command("coverage").description("Run commands with merged V8 coverage").argument("<commands...>", "Commands to run (each will inherit coverage environment)").option("--reports-dir <dir>", "Coverage output directory (default: coverage)").option("--reporters <reporters>", "Comma-separated coverage reporters (default: text,json,json-summary,lcov,html)").option("--include <patterns>", "Comma-separated patterns to include in coverage").option("--exclude <patterns>", "Comma-separated patterns to exclude from coverage").option("--exclude-node-modules", "Exclude node_modules from coverage (default: true)", true).option("--no-exclude-node-modules", "Include node_modules in coverage").option("--exclude-after-remap", "Apply exclude logic after sourcemap remapping").option("--skip-full", "Hide files with 100% coverage").option("--allow-external", "Allow files from outside cwd").option("--monocart", "Use monocart for accurate line counts (recommended for merging)").option("--src <dir>", "Source directory for sourcemap remapping (default: src)").option("--verbose", "Show coverage summary after each command for debugging").option("--merge-lcov <path>", "Merge coverage from an existing LCOV file (e.g., from vitest --coverage)").action(coverageCommand);
411
+ program.command("coverage").description("Run commands with merged V8 coverage").argument("<commands...>", "Commands to run (each will inherit coverage environment)").option("--reports-dir <dir>", "Coverage output directory (default: coverage)").option("--reporters <reporters>", "Comma-separated coverage reporters (default: text,json,json-summary,lcov,html)").option("--include <patterns>", "Comma-separated patterns to include in coverage").option("--exclude <patterns>", "Comma-separated patterns to exclude from coverage").option("--exclude-node-modules", "Exclude node_modules from coverage (default: true)", true).option("--no-exclude-node-modules", "Include node_modules in coverage").option("--exclude-after-remap", "Apply exclude logic after sourcemap remapping").option("--skip-full", "Hide files with 100% coverage").option("--allow-external", "Allow files from outside cwd").option("--monocart", "Use monocart for accurate line counts (recommended for merging)").option("--src <dir>", "Source directory for sourcemap remapping (default: src)").option("--verbose", "Show coverage summary after each command for debugging").action(coverageCommand);
685
412
  }
686
413
  /**
687
414
  * Run a command with inherited coverage environment.
@@ -877,32 +604,12 @@ async function coverageCommand(commands, options) {
877
604
  if (parsedOptions.verbose && stats.fileCount > 0) await generateTextReport(coverageTemp, parsedOptions, command);
878
605
  previousFileCount = stats.fileCount;
879
606
  }
880
- console.error(colors.info("\n=== Generating coverage report ==="));
607
+ console.error(colors.info("\n=== Generating merged coverage report ==="));
881
608
  if (!await generateReport(coverageTemp, parsedOptions)) {
882
609
  logError("Failed to generate coverage report");
883
610
  process.exit(1);
884
611
  }
885
- const reportsDir = parsedOptions.reportsDir ?? "coverage";
886
- if (parsedOptions.mergeLcov) {
887
- const externalLcovPath = parsedOptions.mergeLcov;
888
- const generatedLcovPath = join(reportsDir, "lcov.info");
889
- if (!existsSync(externalLcovPath)) {
890
- logError(`External LCOV file not found: ${externalLcovPath}`);
891
- process.exit(1);
892
- }
893
- if (!existsSync(generatedLcovPath)) {
894
- logError(`Generated LCOV file not found: ${generatedLcovPath}`);
895
- logError("Make sure \"lcov\" is included in reporters");
896
- process.exit(1);
897
- }
898
- console.error(colors.info(`\nMerging with external coverage: ${externalLcovPath}`));
899
- const mergedLcov = mergeLcov(readLcovFile(externalLcovPath), readLcovFile(generatedLcovPath));
900
- writeLcovFile(generatedLcovPath, mergedLcov);
901
- const summary = lcovToJsonSummary(mergedLcov);
902
- writeJsonSummary(join(reportsDir, "coverage-summary.json"), summary);
903
- console.error(colors.success(`\nMerged coverage: ${summary.total.lines.pct}% lines, ${summary.total.functions.pct}% functions`));
904
- }
905
- console.error(colors.success(`\nCoverage report written to ${reportsDir}/`));
612
+ console.error(colors.success(`\nCoverage report written to ${parsedOptions.reportsDir ?? "coverage"}/`));
906
613
  } finally {
907
614
  await rm(coverageTemp, {
908
615
  recursive: true,