tryscript 0.1.2 → 0.1.3

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,12 +1,12 @@
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-DKrim0QL.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-BBeKy_V9.mjs";
5
5
  import { fileURLToPath } from "node:url";
6
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
- import { access, mkdtemp, readFile, rm } from "node:fs/promises";
9
+ import { access, mkdtemp, readFile, readdir, rm, stat } from "node:fs/promises";
10
10
  import { tmpdir } from "node:os";
11
11
  import { Command } from "commander";
12
12
  import fg from "fast-glob";
@@ -408,7 +408,7 @@ async function runCommand$1(files, options) {
408
408
  * Register the coverage command.
409
409
  */
410
410
  function registerCoverageCommand(program) {
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)").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);
412
412
  }
413
413
  /**
414
414
  * Run a command with inherited coverage environment.
@@ -450,6 +450,74 @@ function findC8Path() {
450
450
  return null;
451
451
  }
452
452
  /**
453
+ * Get coverage file statistics from temp directory.
454
+ */
455
+ async function getCoverageStats(tempDir) {
456
+ try {
457
+ const coverageFiles = (await readdir(tempDir)).filter((f) => f.startsWith("coverage-") && f.endsWith(".json"));
458
+ let totalBytes = 0;
459
+ for (const file of coverageFiles) {
460
+ const fileStat = await stat(join(tempDir, file));
461
+ totalBytes += fileStat.size;
462
+ }
463
+ return {
464
+ fileCount: coverageFiles.length,
465
+ totalBytes,
466
+ files: coverageFiles
467
+ };
468
+ } catch {
469
+ return {
470
+ fileCount: 0,
471
+ totalBytes: 0,
472
+ files: []
473
+ };
474
+ }
475
+ }
476
+ /**
477
+ * Generate a text-only coverage report for debugging (doesn't write files).
478
+ */
479
+ async function generateTextReport(tempDir, options, label) {
480
+ const c8Path = findC8Path();
481
+ if (!c8Path) return;
482
+ const include = options.include ?? ["dist/**"];
483
+ const exclude = options.exclude ?? [];
484
+ const tempReportsDir = await mkdtemp(join(tmpdir(), "tryscript-coverage-report-"));
485
+ const reportArgs = [
486
+ "report",
487
+ "--temp-directory",
488
+ tempDir,
489
+ "--reports-dir",
490
+ tempReportsDir,
491
+ "--src",
492
+ options.src ?? "src",
493
+ "--all",
494
+ ...include.flatMap((pattern) => ["--include", pattern]),
495
+ ...exclude.flatMap((pattern) => ["--exclude", pattern]),
496
+ ...options.excludeNodeModules !== false ? ["--exclude-node-modules"] : ["--no-exclude-node-modules"],
497
+ ...options.excludeAfterRemap ? ["--exclude-after-remap"] : [],
498
+ ...options.monocart ? ["--experimental-monocart"] : [],
499
+ "--reporter",
500
+ "text"
501
+ ];
502
+ console.error(colors.info(`\n--- Coverage after: ${label} ---`));
503
+ await new Promise((resolve$1) => {
504
+ const proc = spawn(c8Path, reportArgs, {
505
+ stdio: "inherit",
506
+ shell: false
507
+ });
508
+ proc.on("close", () => {
509
+ resolve$1();
510
+ });
511
+ proc.on("error", () => {
512
+ resolve$1();
513
+ });
514
+ });
515
+ await rm(tempReportsDir, {
516
+ recursive: true,
517
+ force: true
518
+ });
519
+ }
520
+ /**
453
521
  * Generate c8 coverage report.
454
522
  */
455
523
  async function generateReport(tempDir, options) {
@@ -508,10 +576,17 @@ async function coverageCommand(commands, options) {
508
576
  logError("Coverage requires c8. Install with: npm install -D c8");
509
577
  process.exit(1);
510
578
  }
579
+ const parsedOptions = {
580
+ ...options,
581
+ reporters: options.reporters ? typeof options.reporters === "string" ? options.reporters.split(",") : options.reporters : void 0,
582
+ include: options.include ? typeof options.include === "string" ? options.include.split(",") : options.include : void 0,
583
+ exclude: options.exclude ? typeof options.exclude === "string" ? options.exclude.split(",") : options.exclude : void 0
584
+ };
511
585
  const coverageTemp = await mkdtemp(join(tmpdir(), "tryscript-coverage-"));
512
586
  const coverageEnv = { NODE_V8_COVERAGE: coverageTemp };
513
587
  console.error(colors.info(`Collecting V8 coverage to ${coverageTemp}`));
514
588
  let hasFailures = false;
589
+ let previousFileCount = 0;
515
590
  try {
516
591
  for (let i = 0; i < commands.length; i++) {
517
592
  const command = commands[i];
@@ -521,14 +596,15 @@ async function coverageCommand(commands, options) {
521
596
  logWarn(`Command exited with code ${result.code}: ${command}`);
522
597
  hasFailures = true;
523
598
  }
599
+ const stats = await getCoverageStats(coverageTemp);
600
+ const newFiles = stats.fileCount - previousFileCount;
601
+ const bytesKB = (stats.totalBytes / 1024).toFixed(1);
602
+ console.error(colors.info(`\nV8 coverage: ${stats.fileCount} files (${newFiles} new), ${bytesKB} KB total`));
603
+ if (newFiles === 0) logWarn("No new coverage files from this command. This may indicate the command doesn't write to NODE_V8_COVERAGE.");
604
+ if (parsedOptions.verbose && stats.fileCount > 0) await generateTextReport(coverageTemp, parsedOptions, command);
605
+ previousFileCount = stats.fileCount;
524
606
  }
525
607
  console.error(colors.info("\n=== Generating merged coverage report ==="));
526
- const parsedOptions = {
527
- ...options,
528
- reporters: options.reporters ? typeof options.reporters === "string" ? options.reporters.split(",") : options.reporters : void 0,
529
- include: options.include ? typeof options.include === "string" ? options.include.split(",") : options.include : void 0,
530
- exclude: options.exclude ? typeof options.exclude === "string" ? options.exclude.split(",") : options.exclude : void 0
531
- };
532
608
  if (!await generateReport(coverageTemp, parsedOptions)) {
533
609
  logError("Failed to generate coverage report");
534
610
  process.exit(1);
package/dist/bin.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.mjs","names":["status","parts: string[]","changes: string[]","lines: string[]","findC8Path","runCommand","coverageCtx: CoverageContext | undefined","coverageEnv: Record<string, string>","fileResults: TestFileResult[]","results: TestBlockResult[]","fileResult: TestFileResult","statusIndicators","summary: TestRunSummary","parsedOptions: CoverageOptions","formatMarkdown","formatted: string[]","isInteractive","formatted: string[]"],"sources":["../src/cli/lib/shared.ts","../src/lib/reporter.ts","../src/lib/updater.ts","../src/lib/coverage.ts","../src/cli/commands/run.ts","../src/cli/commands/coverage.ts","../src/cli/commands/readme.ts","../src/cli/commands/docs.ts","../src/cli/cli.ts","../src/bin.ts"],"sourcesContent":["/**\n * Shared CLI utilities for consistent output formatting and color usage.\n *\n * This module provides:\n * - Color utilities via picocolors\n * - Commander.js help text styling\n * - Logging helpers for consistent output\n */\n\nimport type { Command } from 'commander';\nimport pc from 'picocolors';\n\n/**\n * Shared color utilities for consistent terminal output.\n */\nexport const colors = {\n success: (s: string) => pc.green(s),\n error: (s: string) => pc.red(s),\n warn: (s: string) => pc.yellow(s),\n info: (s: string) => pc.cyan(s),\n};\n\n/**\n * Status indicators with emoji.\n */\nexport const status = {\n pass: pc.green('✓'),\n fail: pc.red('✗'),\n skip: pc.yellow('○'),\n update: pc.yellow('↻'),\n};\n\n/**\n * Configure Commander.js with colored help text.\n * Applies consistent styling: cyan titles, green commands, yellow options.\n */\nexport function withColoredHelp<T extends Command>(cmd: T): T {\n cmd.configureHelp({\n styleTitle: (str) => pc.bold(pc.cyan(str)),\n styleCommandText: (str) => pc.green(str),\n styleOptionText: (str) => pc.yellow(str),\n showGlobalOptions: true,\n });\n return cmd;\n}\n\n/**\n * Format a duration in milliseconds for display.\n */\nexport function formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/**\n * Log a warning message to stderr.\n */\nexport function logWarn(message: string): void {\n console.error(colors.warn(message));\n}\n\n/**\n * Log an error message to stderr.\n */\nexport function logError(message: string): void {\n console.error(colors.error(message));\n}\n","/**\n * Test result reporting utilities.\n *\n * Handles output formatting for test results, diffs, and summaries.\n */\n\nimport pc from 'picocolors';\nimport { createPatch } from 'diff';\nimport type { TestFileResult, TestRunSummary } from './types.js';\n\nexport interface ReporterOptions {\n diff: boolean;\n verbose: boolean;\n quiet: boolean;\n}\n\n// Status indicators for consistent output\nconst statusIcon = {\n pass: pc.green('✓'),\n fail: pc.red('✗'),\n};\n\n/**\n * Format a duration in milliseconds for display.\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/**\n * Create a unified diff between expected and actual output.\n */\nexport function createDiff(expected: string, actual: string, filename: string): string {\n const patch = createPatch(filename, expected, actual, 'expected', 'actual');\n // Remove the header lines (first 4 lines)\n const lines = patch.split('\\n').slice(4);\n return lines\n .map((line) => {\n if (line.startsWith('+')) {\n return pc.green(line);\n }\n if (line.startsWith('-')) {\n return pc.red(line);\n }\n if (line.startsWith('@')) {\n return pc.cyan(line);\n }\n return line;\n })\n .join('\\n');\n}\n\n/**\n * Report results for a single file.\n */\nexport function reportFile(result: TestFileResult, options: ReporterOptions): void {\n const filename = result.file.path;\n const status = result.passed ? pc.green(pc.bold('PASS')) : pc.red(pc.bold('FAIL'));\n\n if (options.quiet && result.passed) {\n return;\n }\n\n // File header\n console.error(`${status} ${filename}`);\n\n // Individual block results\n for (const blockResult of result.results) {\n const name = blockResult.block.name ?? `Line ${blockResult.block.lineNumber}`;\n\n if (blockResult.passed) {\n if (!options.quiet) {\n console.error(` ${statusIcon.pass} ${name}`);\n }\n } else {\n console.error(` ${statusIcon.fail} ${name}`);\n\n // Show error details\n if (blockResult.error) {\n console.error(` ${pc.red(blockResult.error)}`);\n } else {\n // Exit code mismatch\n if (blockResult.actualExitCode !== blockResult.block.expectedExitCode) {\n console.error(\n ` Expected exit code ${blockResult.block.expectedExitCode}, got ${blockResult.actualExitCode}`,\n );\n }\n\n // Output mismatch with diff\n if (options.diff && blockResult.diff) {\n console.error('');\n console.error(blockResult.diff);\n }\n }\n }\n }\n\n console.error('');\n}\n\n/**\n * Report final summary.\n */\nexport function reportSummary(summary: TestRunSummary, _options: ReporterOptions): void {\n const parts: string[] = [];\n\n if (summary.totalPassed > 0) {\n parts.push(pc.green(`${summary.totalPassed} passed`));\n }\n if (summary.totalFailed > 0) {\n parts.push(pc.red(`${summary.totalFailed} failed`));\n }\n\n const duration = formatDuration(summary.duration);\n const line = `${parts.join(', ')} (${duration})`;\n\n // Summary goes to stdout (can be piped/parsed)\n console.log(line);\n}\n","import { writeFile } from 'atomically';\nimport type { TestFile, TestBlock, TestBlockResult } from './types.js';\n\n/**\n * Update a test file with actual output from test results.\n */\nexport async function updateTestFile(\n file: TestFile,\n results: TestBlockResult[],\n): Promise<{ updated: boolean; changes: string[] }> {\n let content = file.rawContent;\n const changes: string[] = [];\n\n // Process blocks in reverse order to maintain correct offsets\n const blocksWithResults = file.blocks\n .map((block, i) => ({ block, result: results[i] }))\n .reverse();\n\n for (const { block, result } of blocksWithResults) {\n if (!result) {\n continue;\n }\n\n if (result.passed) {\n continue; // Don't touch passing tests\n }\n\n if (result.error) {\n // Execution error, can't update\n continue;\n }\n\n // Build the new block content\n const newBlockContent = buildUpdatedBlock(block, result);\n\n // Find and replace the block in the file\n const blockStart = content.indexOf(block.rawContent);\n if (blockStart !== -1) {\n content =\n content.slice(0, blockStart) +\n newBlockContent +\n content.slice(blockStart + block.rawContent.length);\n\n changes.push(block.name ?? `Line ${block.lineNumber}`);\n }\n }\n\n if (changes.length > 0) {\n await writeFile(file.path, content);\n }\n\n return { updated: changes.length > 0, changes };\n}\n\n/**\n * Build an updated console block with new expected output.\n */\nfunction buildUpdatedBlock(block: TestBlock, result: TestBlockResult): string {\n // Reconstruct the command line(s)\n const commandLines = block.command.split('\\n').map((line, i) => {\n return i === 0 ? `$ ${line}` : `> ${line}`;\n });\n\n // Build the block\n const lines: string[] = ['```console', ...commandLines];\n\n // Add output if present\n const trimmedOutput = result.actualOutput.trimEnd();\n if (trimmedOutput) {\n lines.push(trimmedOutput);\n }\n\n // Add exit code\n lines.push(`? ${result.actualExitCode}`, '```');\n\n return lines.join('\\n');\n}\n","/**\n * Coverage collection for CLI subprocess testing.\n *\n * Uses c8 and NODE_V8_COVERAGE to collect coverage from spawned processes.\n */\n\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, rm, access } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport type { CoverageContext, CoverageConfig } from './types.js';\nimport { resolveCoverageConfig } from './config.js';\n\n/**\n * Find the c8 executable path.\n * Checks local node_modules/.bin first, then falls back to npx.\n */\nfunction findC8Path(): string {\n // Check common locations for local c8\n const localPaths = [\n resolve(process.cwd(), 'node_modules', '.bin', 'c8'),\n resolve(process.cwd(), '..', '..', 'node_modules', '.bin', 'c8'), // monorepo root\n ];\n\n for (const localPath of localPaths) {\n if (existsSync(localPath)) {\n return localPath;\n }\n }\n\n // Fall back to npx which will find c8 in node_modules\n return 'npx c8';\n}\n\n/**\n * Check if c8 is available in the current environment.\n */\nexport async function isC8Available(): Promise<boolean> {\n const c8Path = findC8Path();\n\n return new Promise((resolve) => {\n // Use npx to run c8 if we fell back to npx\n const isNpx = c8Path === 'npx c8';\n const command = isNpx ? 'npx' : c8Path;\n const args = isNpx ? ['c8', '--version'] : ['--version'];\n\n const proc = spawn(command, args, {\n shell: false,\n stdio: 'ignore',\n });\n proc.on('close', (code) => {\n resolve(code === 0);\n });\n proc.on('error', () => {\n resolve(false);\n });\n });\n}\n\n/**\n * Create a coverage context for collecting V8 coverage data.\n */\nexport async function createCoverageContext(config?: CoverageConfig): Promise<CoverageContext> {\n const options = resolveCoverageConfig(config);\n const tempDir = await mkdtemp(join(tmpdir(), 'tryscript-coverage-'));\n\n return {\n tempDir,\n options,\n };\n}\n\n/**\n * Get environment variables for enabling V8 coverage in spawned processes.\n */\nexport function getCoverageEnv(ctx: CoverageContext): Record<string, string> {\n return {\n NODE_V8_COVERAGE: ctx.tempDir,\n };\n}\n\n/**\n * Generate coverage report from collected V8 coverage data using c8.\n * Throws an error if coverage report generation fails.\n */\nexport async function generateCoverageReport(ctx: CoverageContext): Promise<void> {\n const { options, tempDir } = ctx;\n const c8Path = findC8Path();\n\n // Base args for c8 report\n const reportArgs = [\n 'report',\n '--temp-directory',\n tempDir,\n '--reports-dir',\n options.reportsDir,\n '--src',\n options.src,\n '--all',\n // Include patterns\n ...options.include.flatMap((pattern) => ['--include', pattern]),\n // Exclude patterns\n ...options.exclude.flatMap((pattern) => ['--exclude', pattern]),\n // Boolean flags (only add if explicitly set)\n ...(options.excludeNodeModules ? ['--exclude-node-modules'] : ['--no-exclude-node-modules']),\n ...(options.excludeAfterRemap ? ['--exclude-after-remap'] : []),\n ...(options.skipFull ? ['--skip-full'] : []),\n ...(options.allowExternal ? ['--allowExternal'] : []),\n ...(options.monocart ? ['--experimental-monocart'] : []),\n // Reporters\n ...options.reporters.flatMap((reporter) => ['--reporter', reporter]),\n ];\n\n // Handle 'npx c8' vs direct c8 path\n // Use shell: false to prevent glob expansion of patterns like dist/**\n const isNpx = c8Path === 'npx c8';\n const command = isNpx ? 'npx' : c8Path;\n const args = isNpx ? ['c8', ...reportArgs] : reportArgs;\n\n await new Promise<void>((resolvePromise, reject) => {\n const proc = spawn(command, args, {\n shell: false,\n stdio: 'inherit',\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolvePromise();\n } else {\n reject(new Error(`c8 report exited with code ${code}`));\n }\n });\n\n proc.on('error', (err) => {\n reject(new Error(`Failed to run c8 report: ${err.message}`));\n });\n });\n}\n\n/**\n * Clean up coverage context by removing the temporary directory.\n */\nexport async function cleanupCoverageContext(ctx: CoverageContext): Promise<void> {\n try {\n await access(ctx.tempDir);\n await rm(ctx.tempDir, { recursive: true, force: true });\n } catch {\n // Directory doesn't exist, nothing to clean up\n }\n}\n","/**\n * Run command - executes golden tests against CLI applications.\n *\n * Supports filtering, update mode, and detailed diff output for failures.\n */\n\nimport type { Command } from 'commander';\n\nimport { readFile } from 'node:fs/promises';\nimport fg from 'fast-glob';\nimport { loadConfig, mergeConfig } from '../../lib/config.js';\nimport { logWarn, logError, colors, status as statusIndicators } from '../lib/shared.js';\nimport { parseTestFile } from '../../lib/parser.js';\nimport {\n runBlock,\n createExecutionContext,\n cleanupExecutionContext,\n runAfterHook,\n} from '../../lib/runner.js';\nimport { matchOutput } from '../../lib/matcher.js';\nimport { createDiff, reportFile, reportSummary } from '../../lib/reporter.js';\nimport { updateTestFile } from '../../lib/updater.js';\nimport {\n isC8Available,\n createCoverageContext,\n getCoverageEnv,\n generateCoverageReport,\n cleanupCoverageContext,\n} from '../../lib/coverage.js';\nimport type {\n TestBlockResult,\n TestFileResult,\n TestRunSummary,\n CoverageContext,\n} from '../../lib/types.js';\n\ninterface RunOptions {\n update?: boolean;\n diff?: boolean;\n failFast?: boolean;\n filter?: string;\n verbose?: boolean;\n quiet?: boolean;\n coverage?: boolean;\n coverageDir?: string;\n coverageReporter?: string[];\n coverageExclude?: string[];\n coverageExcludeNodeModules?: boolean;\n coverageExcludeAfterRemap?: boolean;\n coverageSkipFull?: boolean;\n coverageAllowExternal?: boolean;\n coverageMonocart?: boolean;\n}\n\n/**\n * Register the run command.\n */\nexport function registerRunCommand(program: Command): void {\n program\n .command('run')\n .description('Run golden tests')\n .argument('[files...]', 'Test files to run (default: **/*.tryscript.md)')\n .option('--update', 'Update golden files with actual output')\n .option('--diff', 'Show diff on failure (default: true)')\n .option('--no-diff', 'Hide diff on failure')\n .option('--fail-fast', 'Stop on first failure')\n .option('--filter <pattern>', 'Filter tests by name pattern')\n .option('--verbose', 'Show detailed output including passing test output')\n .option('--quiet', 'Suppress non-essential output (only show failures)')\n .option('--coverage', 'Enable code coverage collection (requires c8)')\n .option('--coverage-dir <dir>', 'Coverage output directory (default: coverage-tryscript)')\n .option(\n '--coverage-reporter <reporter...>',\n 'Coverage reporters (default: text, html). Can be specified multiple times.',\n )\n .option(\n '--coverage-exclude <pattern...>',\n 'Patterns to exclude from coverage (c8 --exclude). Can be specified multiple times.',\n )\n .option(\n '--coverage-exclude-node-modules',\n 'Exclude node_modules from coverage (c8 --exclude-node-modules, default: true)',\n )\n .option(\n '--no-coverage-exclude-node-modules',\n 'Include node_modules in coverage (c8 --no-exclude-node-modules)',\n )\n .option(\n '--coverage-exclude-after-remap',\n 'Apply exclude logic after sourcemap remapping (c8 --exclude-after-remap)',\n )\n .option('--coverage-skip-full', 'Hide files with 100% coverage (c8 --skip-full)')\n .option('--coverage-allow-external', 'Allow files from outside cwd (c8 --allowExternal)')\n .option(\n '--coverage-monocart',\n 'Use monocart for accurate line counts, better for merging with vitest (c8 --experimental-monocart)',\n )\n .action(runCommand);\n}\n\nasync function runCommand(files: string[], options: RunOptions): Promise<void> {\n const startTime = Date.now();\n\n // Default options\n const opts = {\n diff: options.diff !== false,\n verbose: options.verbose ?? false,\n quiet: options.quiet ?? false,\n update: options.update ?? false,\n failFast: options.failFast ?? false,\n filter: options.filter,\n };\n\n // Find test files (fast-glob respects .gitignore by default)\n const patterns = files.length > 0 ? files : ['**/*.tryscript.md'];\n const testFiles = await fg(patterns, {\n ignore: ['**/node_modules/**', '**/dist/**'],\n absolute: true,\n dot: false,\n });\n\n if (testFiles.length === 0) {\n logWarn('No test files found');\n process.exit(1);\n }\n\n // Load global config\n const globalConfig = await loadConfig(process.cwd());\n\n // Setup coverage if enabled\n let coverageCtx: CoverageContext | undefined;\n let coverageEnv: Record<string, string> = {};\n\n if (options.coverage) {\n // Check if c8 is available\n const c8Available = await isC8Available();\n if (!c8Available) {\n logError('Coverage requires c8. Install with: npm install -D c8');\n process.exit(1);\n }\n\n // Create coverage context with CLI options overriding config\n coverageCtx = await createCoverageContext({\n ...globalConfig.coverage,\n reportsDir: options.coverageDir ?? globalConfig.coverage?.reportsDir,\n reporters: options.coverageReporter ?? globalConfig.coverage?.reporters,\n exclude: options.coverageExclude ?? globalConfig.coverage?.exclude,\n excludeNodeModules:\n options.coverageExcludeNodeModules ?? globalConfig.coverage?.excludeNodeModules,\n excludeAfterRemap:\n options.coverageExcludeAfterRemap ?? globalConfig.coverage?.excludeAfterRemap,\n skipFull: options.coverageSkipFull ?? globalConfig.coverage?.skipFull,\n allowExternal: options.coverageAllowExternal ?? globalConfig.coverage?.allowExternal,\n monocart: options.coverageMonocart ?? globalConfig.coverage?.monocart,\n });\n coverageEnv = getCoverageEnv(coverageCtx);\n }\n\n // Run tests\n const fileResults: TestFileResult[] = [];\n let shouldStop = false;\n\n for (const filePath of testFiles) {\n if (shouldStop) {\n break;\n }\n\n const content = await readFile(filePath, 'utf-8');\n const testFile = parseTestFile(content, filePath);\n const config = mergeConfig(globalConfig, testFile.config);\n\n // Filter blocks by name if specified\n let blocksToRun = testFile.blocks;\n if (opts.filter) {\n const filterPattern = new RegExp(opts.filter, 'i');\n blocksToRun = blocksToRun.filter((b) => (b.name ? filterPattern.test(b.name) : true));\n }\n\n // Handle \"only\" mode - if any block has only=true, run only those\n const onlyBlocks = blocksToRun.filter((b) => b.only);\n if (onlyBlocks.length > 0) {\n blocksToRun = onlyBlocks;\n }\n\n if (blocksToRun.length === 0) {\n continue;\n }\n\n const ctx = await createExecutionContext(config, filePath, coverageEnv);\n const results: TestBlockResult[] = [];\n\n try {\n for (const block of blocksToRun) {\n const result = await runBlock(block, ctx);\n\n // Skip checking for skipped tests\n if (result.skipped) {\n results.push(result);\n continue;\n }\n\n // Check if output matches expected\n // [ROOT] = test file directory, [CWD] = command working directory\n // If expectedStderr is set, compare stdout only (not combined output)\n const outputToCheck = block.expectedStderr\n ? (result.actualStdout ?? '')\n : result.actualOutput;\n const outputMatches = matchOutput(\n outputToCheck,\n block.expectedOutput,\n { root: ctx.testDir, cwd: ctx.cwd },\n config.patterns ?? {},\n );\n\n // Check stderr if expected (using actualStderr if available)\n let stderrMatches = true;\n if (block.expectedStderr) {\n stderrMatches = matchOutput(\n result.actualStderr ?? '',\n block.expectedStderr,\n { root: ctx.testDir, cwd: ctx.cwd },\n config.patterns ?? {},\n );\n }\n\n const exitCodeMatches = result.actualExitCode === block.expectedExitCode;\n result.passed = outputMatches && stderrMatches && exitCodeMatches && !result.error;\n\n if (!result.passed && opts.diff) {\n result.diff = createDiff(\n block.expectedOutput,\n result.actualOutput,\n `${filePath}:${block.lineNumber}`,\n );\n }\n\n results.push(result);\n\n if (!result.passed && opts.failFast) {\n shouldStop = true;\n break;\n }\n }\n\n // Run after hook if configured\n await runAfterHook(ctx);\n } finally {\n await cleanupExecutionContext(ctx);\n }\n\n const fileResult: TestFileResult = {\n file: testFile,\n results,\n passed: results.every((r) => r.passed),\n duration: results.reduce((sum, r) => sum + r.duration, 0),\n };\n\n fileResults.push(fileResult);\n reportFile(fileResult, opts);\n\n // Update mode\n if (opts.update && !fileResult.passed) {\n const { updated, changes } = await updateTestFile(testFile, results);\n if (updated) {\n console.error(colors.warn(` ${statusIndicators.update} Updated: ${changes.join(', ')}`));\n }\n }\n }\n\n // Summary\n const summary: TestRunSummary = {\n files: fileResults,\n totalPassed: fileResults.reduce((sum, f) => sum + f.results.filter((r) => r.passed).length, 0),\n totalFailed: fileResults.reduce((sum, f) => sum + f.results.filter((r) => !r.passed).length, 0),\n totalBlocks: fileResults.reduce((sum, f) => sum + f.results.length, 0),\n duration: Date.now() - startTime,\n };\n\n reportSummary(summary, opts);\n\n // Generate coverage report if enabled\n if (coverageCtx) {\n console.error('\\nGenerating coverage report...');\n try {\n await generateCoverageReport(coverageCtx);\n console.error(\n colors.success(`Coverage report written to ${coverageCtx.options.reportsDir}/`),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logError(`Failed to generate coverage report: ${message}`);\n } finally {\n await cleanupCoverageContext(coverageCtx);\n }\n }\n\n // Exit code\n process.exit(summary.totalFailed > 0 ? 1 : 0);\n}\n","/**\n * Coverage command - runs multiple commands with merged V8 coverage.\n *\n * This command provides a simple way to collect coverage from multiple sources\n * (e.g., unit tests + CLI tests) and generate a merged coverage report.\n *\n * Example usage:\n * tryscript coverage \"vitest run\" \"tryscript run tests/\"\n * tryscript coverage --monocart --reporters text,html \"vitest run\" \"node dist/bin.mjs run tests/\"\n */\n\nimport type { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { logError, logWarn, colors } from '../lib/shared.js';\n\ninterface CoverageOptions {\n reportsDir?: string;\n reporters?: string[];\n include?: string[];\n exclude?: string[];\n excludeNodeModules?: boolean;\n excludeAfterRemap?: boolean;\n skipFull?: boolean;\n allowExternal?: boolean;\n monocart?: boolean;\n src?: string;\n}\n\n/**\n * Register the coverage command.\n */\nexport function registerCoverageCommand(program: Command): void {\n program\n .command('coverage')\n .description('Run commands with merged V8 coverage')\n .argument('<commands...>', 'Commands to run (each will inherit coverage environment)')\n .option('--reports-dir <dir>', 'Coverage output directory (default: coverage)')\n .option(\n '--reporters <reporters>',\n 'Comma-separated coverage reporters (default: text,json,json-summary,lcov,html)',\n )\n .option('--include <patterns>', 'Comma-separated patterns to include in coverage')\n .option('--exclude <patterns>', 'Comma-separated patterns to exclude from coverage')\n .option('--exclude-node-modules', 'Exclude node_modules from coverage (default: true)', true)\n .option('--no-exclude-node-modules', 'Include node_modules in coverage')\n .option('--exclude-after-remap', 'Apply exclude logic after sourcemap remapping')\n .option('--skip-full', 'Hide files with 100% coverage')\n .option('--allow-external', 'Allow files from outside cwd')\n .option('--monocart', 'Use monocart for accurate line counts (recommended for merging)')\n .option('--src <dir>', 'Source directory for sourcemap remapping (default: src)')\n .action(coverageCommand);\n}\n\n/**\n * Run a command with inherited coverage environment.\n */\nasync function runCommand(\n command: string,\n env: Record<string, string>,\n): Promise<{ success: boolean; code: number }> {\n return new Promise((resolve) => {\n const proc = spawn(command, [], {\n stdio: 'inherit',\n env: { ...process.env, ...env },\n shell: true,\n });\n\n proc.on('close', (code) => {\n resolve({ success: code === 0, code: code ?? 1 });\n });\n\n proc.on('error', (err) => {\n logError(`Failed to run command: ${err.message}`);\n resolve({ success: false, code: 1 });\n });\n });\n}\n\n/**\n * Find the c8 executable path.\n * Can be overridden via TRYSCRIPT_C8_COMMAND env var for testing.\n */\nfunction findC8Path(): string | null {\n // Allow override for testing - when set, we trust it exists\n const override = process.env.TRYSCRIPT_C8_COMMAND;\n if (override) {\n return override;\n }\n\n const localPaths = [\n resolve(process.cwd(), 'node_modules', '.bin', 'c8'),\n resolve(process.cwd(), '..', '..', 'node_modules', '.bin', 'c8'),\n ];\n\n for (const localPath of localPaths) {\n if (existsSync(localPath)) {\n return localPath;\n }\n }\n\n return null;\n}\n\n/**\n * Generate c8 coverage report.\n */\nasync function generateReport(tempDir: string, options: CoverageOptions): Promise<boolean> {\n const c8Path = findC8Path();\n if (!c8Path) {\n logError('c8 not found. Install with: npm install -D c8');\n return false;\n }\n\n const reporters = options.reporters ?? ['text', 'json', 'json-summary', 'lcov', 'html'];\n const include = options.include ?? ['dist/**'];\n const exclude = options.exclude ?? [];\n\n const reportArgs = [\n 'report',\n '--temp-directory',\n tempDir,\n '--reports-dir',\n options.reportsDir ?? 'coverage',\n '--src',\n options.src ?? 'src',\n '--all',\n ...include.flatMap((pattern) => ['--include', pattern]),\n ...exclude.flatMap((pattern) => ['--exclude', pattern]),\n ...(options.excludeNodeModules !== false\n ? ['--exclude-node-modules']\n : ['--no-exclude-node-modules']),\n ...(options.excludeAfterRemap ? ['--exclude-after-remap'] : []),\n ...(options.skipFull ? ['--skip-full'] : []),\n ...(options.allowExternal ? ['--allowExternal'] : []),\n ...(options.monocart ? ['--experimental-monocart'] : []),\n ...reporters.flatMap((reporter) => ['--reporter', reporter]),\n ];\n\n return new Promise((resolve) => {\n const proc = spawn(c8Path, reportArgs, {\n stdio: 'inherit',\n shell: false,\n });\n\n proc.on('close', (code) => {\n resolve(code === 0);\n });\n\n proc.on('error', (err) => {\n logError(`Failed to generate coverage report: ${err.message}`);\n resolve(false);\n });\n });\n}\n\nasync function coverageCommand(commands: string[], options: CoverageOptions): Promise<void> {\n // Validate we have commands to run\n if (commands.length === 0) {\n logError('No commands specified. Usage: tryscript coverage \"cmd1\" \"cmd2\"');\n process.exit(1);\n }\n\n // Check for c8\n const c8Path = findC8Path();\n if (!c8Path) {\n logError('Coverage requires c8. Install with: npm install -D c8');\n process.exit(1);\n }\n\n // Create temp directory for V8 coverage data\n const coverageTemp = await mkdtemp(join(tmpdir(), 'tryscript-coverage-'));\n const coverageEnv = { NODE_V8_COVERAGE: coverageTemp };\n\n console.error(colors.info(`Collecting V8 coverage to ${coverageTemp}`));\n\n let hasFailures = false;\n\n try {\n // Run each command with shared coverage environment\n for (let i = 0; i < commands.length; i++) {\n const command = commands[i]!;\n console.error(\n colors.info(`\\n=== Running command ${i + 1}/${commands.length}: ${command} ===`),\n );\n\n const result = await runCommand(command, coverageEnv);\n if (!result.success) {\n logWarn(`Command exited with code ${result.code}: ${command}`);\n hasFailures = true;\n }\n }\n\n // Generate merged coverage report\n console.error(colors.info('\\n=== Generating merged coverage report ==='));\n\n // Parse comma-separated options\n const parsedOptions: CoverageOptions = {\n ...options,\n reporters: options.reporters\n ? typeof options.reporters === 'string'\n ? (options.reporters as string).split(',')\n : options.reporters\n : undefined,\n include: options.include\n ? typeof options.include === 'string'\n ? (options.include as string).split(',')\n : options.include\n : undefined,\n exclude: options.exclude\n ? typeof options.exclude === 'string'\n ? (options.exclude as string).split(',')\n : options.exclude\n : undefined,\n };\n\n const reportSuccess = await generateReport(coverageTemp, parsedOptions);\n if (!reportSuccess) {\n logError('Failed to generate coverage report');\n process.exit(1);\n }\n\n console.error(\n colors.success(`\\nCoverage report written to ${parsedOptions.reportsDir ?? 'coverage'}/`),\n );\n } finally {\n // Cleanup temp directory\n await rm(coverageTemp, { recursive: true, force: true });\n }\n\n // Exit with failure if any command failed\n if (hasFailures) {\n process.exit(1);\n }\n}\n","/**\n * Readme command - Display the README documentation.\n *\n * Shows the package README.md, formatted for the terminal when interactive,\n * or as plain text when piped.\n */\n\nimport type { Command } from 'commander';\n\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\n\n/**\n * Get the path to the README.md file.\n * Works both during development and when installed as a package.\n */\nfunction getReadmePath(): string {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n const dirName = thisDir.split(/[/\\\\]/).pop();\n\n if (dirName === 'dist') {\n // Bundled: dist -> package root -> README.md\n return join(dirname(thisDir), 'README.md');\n }\n\n // Development: src/cli/commands -> src/cli -> src -> package root -> README.md\n return join(dirname(dirname(dirname(thisDir))), 'README.md');\n}\n\n/**\n * Load the README content.\n */\nfunction loadReadme(): string {\n const readmePath = getReadmePath();\n try {\n return readFileSync(readmePath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load README from ${readmePath}: ${message}`);\n }\n}\n\n/**\n * Apply basic terminal formatting to markdown content.\n * Colorizes headers, code blocks, and other elements for better readability.\n */\nfunction formatMarkdown(content: string, useColors: boolean): string {\n if (!useColors) {\n return content;\n }\n\n const lines = content.split('\\n');\n const formatted: string[] = [];\n let inCodeBlock = false;\n\n for (const line of lines) {\n // Track code blocks\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n formatted.push(pc.dim(line));\n continue;\n }\n\n if (inCodeBlock) {\n formatted.push(pc.dim(line));\n continue;\n }\n\n // Headers\n if (line.startsWith('# ')) {\n formatted.push(pc.bold(pc.cyan(line)));\n continue;\n }\n if (line.startsWith('## ')) {\n formatted.push(pc.bold(pc.blue(line)));\n continue;\n }\n if (line.startsWith('### ')) {\n formatted.push(pc.bold(line));\n continue;\n }\n\n // Inline code (backticks)\n let formattedLine = line.replace(/`([^`]+)`/g, (_match, code: string) => {\n return pc.yellow(code);\n });\n\n // Bold text\n formattedLine = formattedLine.replace(/\\*\\*([^*]+)\\*\\*/g, (_match, text: string) => {\n return pc.bold(text);\n });\n\n // Links - show text in cyan, URL dimmed\n formattedLine = formattedLine.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, url: string) => {\n return `${pc.cyan(text)} ${pc.dim(`(${url})`)}`;\n },\n );\n\n formatted.push(formattedLine);\n }\n\n return formatted.join('\\n');\n}\n\n/**\n * Check if stdout is an interactive terminal.\n */\nfunction isInteractive(): boolean {\n return process.stdout.isTTY === true;\n}\n\n/**\n * Display the README content.\n * Exported for use as the default command.\n */\nexport function showReadme(options?: { raw?: boolean; color?: boolean }): void {\n try {\n const readme = loadReadme();\n\n // Determine if we should colorize\n const shouldColorize = options?.color ?? (!options?.raw && isInteractive());\n\n const formatted = formatMarkdown(readme, shouldColorize);\n console.log(formatted);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(pc.red(`Error: ${message}`));\n process.exit(1);\n }\n}\n\n/**\n * Register the readme command.\n */\nexport function registerReadmeCommand(program: Command): void {\n program\n .command('readme')\n .description('Display README documentation')\n .option('--raw', 'Output raw markdown without formatting')\n .option('--color', 'Force colorized output (for testing)')\n .action(showReadme);\n}\n","/**\n * Docs command - Display the tryscript quick reference.\n *\n * Shows the tryscript-reference.md file, formatted for the terminal when interactive,\n * or as plain text when piped.\n */\n\nimport type { Command } from 'commander';\n\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\n\n/**\n * Get the path to the tryscript-reference.md file.\n * Works both during development and when installed as a package.\n */\nfunction getDocsPath(): string {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n const dirName = thisDir.split(/[/\\\\]/).pop();\n\n if (dirName === 'dist') {\n // Bundled: dist -> package root -> docs/tryscript-reference.md\n return join(dirname(thisDir), 'docs', 'tryscript-reference.md');\n }\n\n // Development: src/cli/commands -> src/cli -> src -> package root -> docs/tryscript-reference.md\n return join(dirname(dirname(dirname(thisDir))), 'docs', 'tryscript-reference.md');\n}\n\n/**\n * Load the docs content.\n */\nfunction loadDocs(): string {\n const docsPath = getDocsPath();\n try {\n return readFileSync(docsPath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load reference docs from ${docsPath}: ${message}`);\n }\n}\n\n/**\n * Apply basic terminal formatting to markdown content.\n * Colorizes headers, code blocks, and other elements for better readability.\n */\nfunction formatMarkdown(content: string, useColors: boolean): string {\n if (!useColors) {\n return content;\n }\n\n const lines = content.split('\\n');\n const formatted: string[] = [];\n let inCodeBlock = false;\n\n for (const line of lines) {\n // Track code blocks\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n formatted.push(pc.dim(line));\n continue;\n }\n\n if (inCodeBlock) {\n formatted.push(pc.dim(line));\n continue;\n }\n\n // Headers\n if (line.startsWith('# ')) {\n formatted.push(pc.bold(pc.cyan(line)));\n continue;\n }\n if (line.startsWith('## ')) {\n formatted.push(pc.bold(pc.blue(line)));\n continue;\n }\n if (line.startsWith('### ')) {\n formatted.push(pc.bold(line));\n continue;\n }\n\n // Inline code (backticks)\n let formattedLine = line.replace(/`([^`]+)`/g, (_match, code: string) => {\n return pc.yellow(code);\n });\n\n // Bold text\n formattedLine = formattedLine.replace(/\\*\\*([^*]+)\\*\\*/g, (_match, text: string) => {\n return pc.bold(text);\n });\n\n // Links - show text in cyan, URL dimmed\n formattedLine = formattedLine.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, url: string) => {\n return `${pc.cyan(text)} ${pc.dim(`(${url})`)}`;\n },\n );\n\n formatted.push(formattedLine);\n }\n\n return formatted.join('\\n');\n}\n\n/**\n * Check if stdout is an interactive terminal.\n */\nfunction isInteractive(): boolean {\n return process.stdout.isTTY === true;\n}\n\n/**\n * Register the docs command.\n */\nexport function registerDocsCommand(program: Command): void {\n program\n .command('docs')\n .description('Display concise syntax reference')\n .option('--raw', 'Output raw markdown without formatting')\n .option('--color', 'Force colorized output (for testing)')\n .action((options: { raw?: boolean; color?: boolean }) => {\n try {\n const docs = loadDocs();\n\n // Determine if we should colorize\n const shouldColorize = options.color ?? (!options.raw && isInteractive());\n\n const formatted = formatMarkdown(docs, shouldColorize);\n console.log(formatted);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(pc.red(`Error: ${message}`));\n process.exit(1);\n }\n });\n}\n","/**\n * CLI entry point for tryscript.\n *\n * Configures Commander.js with colored help and registers all subcommands.\n */\n\nimport { Command } from 'commander';\nimport { VERSION } from '../index.js';\nimport { registerRunCommand } from './commands/run.js';\nimport { registerCoverageCommand } from './commands/coverage.js';\nimport { registerReadmeCommand } from './commands/readme.js';\nimport { registerDocsCommand } from './commands/docs.js';\nimport { withColoredHelp, logError } from './lib/shared.js';\n\nexport function run(argv: string[]): void {\n const program = withColoredHelp(\n new Command()\n .name('tryscript')\n .version(VERSION, '--version', 'Show version number')\n .description('Golden testing for CLI applications')\n .showHelpAfterError('(use --help for usage)'),\n );\n\n // Register subcommands\n registerRunCommand(program);\n registerCoverageCommand(program);\n registerReadmeCommand(program);\n registerDocsCommand(program);\n\n // Default action: show help when no command given\n program.action(() => {\n program.help();\n });\n\n program.parseAsync(argv).catch((err: Error) => {\n logError(`Error: ${err.message}`);\n process.exit(2);\n });\n}\n","#!/usr/bin/env node\nimport { run } from './cli/cli.js';\n\nrun(process.argv);\n"],"mappings":"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"}
1
+ {"version":3,"file":"bin.mjs","names":["status","parts: string[]","changes: string[]","lines: string[]","findC8Path","runCommand","coverageCtx: CoverageContext | undefined","coverageEnv: Record<string, string>","fileResults: TestFileResult[]","results: TestBlockResult[]","fileResult: TestFileResult","statusIndicators","summary: TestRunSummary","parsedOptions: CoverageOptions","formatMarkdown","formatted: string[]","isInteractive","formatted: string[]"],"sources":["../src/cli/lib/shared.ts","../src/lib/reporter.ts","../src/lib/updater.ts","../src/lib/coverage.ts","../src/cli/commands/run.ts","../src/cli/commands/coverage.ts","../src/cli/commands/readme.ts","../src/cli/commands/docs.ts","../src/cli/cli.ts","../src/bin.ts"],"sourcesContent":["/**\n * Shared CLI utilities for consistent output formatting and color usage.\n *\n * This module provides:\n * - Color utilities via picocolors\n * - Commander.js help text styling\n * - Logging helpers for consistent output\n */\n\nimport type { Command } from 'commander';\nimport pc from 'picocolors';\n\n/**\n * Shared color utilities for consistent terminal output.\n */\nexport const colors = {\n success: (s: string) => pc.green(s),\n error: (s: string) => pc.red(s),\n warn: (s: string) => pc.yellow(s),\n info: (s: string) => pc.cyan(s),\n};\n\n/**\n * Status indicators with emoji.\n */\nexport const status = {\n pass: pc.green('✓'),\n fail: pc.red('✗'),\n skip: pc.yellow('○'),\n update: pc.yellow('↻'),\n};\n\n/**\n * Configure Commander.js with colored help text.\n * Applies consistent styling: cyan titles, green commands, yellow options.\n */\nexport function withColoredHelp<T extends Command>(cmd: T): T {\n cmd.configureHelp({\n styleTitle: (str) => pc.bold(pc.cyan(str)),\n styleCommandText: (str) => pc.green(str),\n styleOptionText: (str) => pc.yellow(str),\n showGlobalOptions: true,\n });\n return cmd;\n}\n\n/**\n * Format a duration in milliseconds for display.\n */\nexport function formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/**\n * Log a warning message to stderr.\n */\nexport function logWarn(message: string): void {\n console.error(colors.warn(message));\n}\n\n/**\n * Log an error message to stderr.\n */\nexport function logError(message: string): void {\n console.error(colors.error(message));\n}\n","/**\n * Test result reporting utilities.\n *\n * Handles output formatting for test results, diffs, and summaries.\n */\n\nimport pc from 'picocolors';\nimport { createPatch } from 'diff';\nimport type { TestFileResult, TestRunSummary } from './types.js';\n\nexport interface ReporterOptions {\n diff: boolean;\n verbose: boolean;\n quiet: boolean;\n}\n\n// Status indicators for consistent output\nconst statusIcon = {\n pass: pc.green('✓'),\n fail: pc.red('✗'),\n};\n\n/**\n * Format a duration in milliseconds for display.\n */\nfunction formatDuration(ms: number): string {\n if (ms < 1000) {\n return `${ms}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\n/**\n * Create a unified diff between expected and actual output.\n */\nexport function createDiff(expected: string, actual: string, filename: string): string {\n const patch = createPatch(filename, expected, actual, 'expected', 'actual');\n // Remove the header lines (first 4 lines)\n const lines = patch.split('\\n').slice(4);\n return lines\n .map((line) => {\n if (line.startsWith('+')) {\n return pc.green(line);\n }\n if (line.startsWith('-')) {\n return pc.red(line);\n }\n if (line.startsWith('@')) {\n return pc.cyan(line);\n }\n return line;\n })\n .join('\\n');\n}\n\n/**\n * Report results for a single file.\n */\nexport function reportFile(result: TestFileResult, options: ReporterOptions): void {\n const filename = result.file.path;\n const status = result.passed ? pc.green(pc.bold('PASS')) : pc.red(pc.bold('FAIL'));\n\n if (options.quiet && result.passed) {\n return;\n }\n\n // File header\n console.error(`${status} ${filename}`);\n\n // Individual block results\n for (const blockResult of result.results) {\n const name = blockResult.block.name ?? `Line ${blockResult.block.lineNumber}`;\n\n if (blockResult.passed) {\n if (!options.quiet) {\n console.error(` ${statusIcon.pass} ${name}`);\n }\n } else {\n console.error(` ${statusIcon.fail} ${name}`);\n\n // Show error details\n if (blockResult.error) {\n console.error(` ${pc.red(blockResult.error)}`);\n } else {\n // Exit code mismatch\n if (blockResult.actualExitCode !== blockResult.block.expectedExitCode) {\n console.error(\n ` Expected exit code ${blockResult.block.expectedExitCode}, got ${blockResult.actualExitCode}`,\n );\n }\n\n // Output mismatch with diff\n if (options.diff && blockResult.diff) {\n console.error('');\n console.error(blockResult.diff);\n }\n }\n }\n }\n\n console.error('');\n}\n\n/**\n * Report final summary.\n */\nexport function reportSummary(summary: TestRunSummary, _options: ReporterOptions): void {\n const parts: string[] = [];\n\n if (summary.totalPassed > 0) {\n parts.push(pc.green(`${summary.totalPassed} passed`));\n }\n if (summary.totalFailed > 0) {\n parts.push(pc.red(`${summary.totalFailed} failed`));\n }\n\n const duration = formatDuration(summary.duration);\n const line = `${parts.join(', ')} (${duration})`;\n\n // Summary goes to stdout (can be piped/parsed)\n console.log(line);\n}\n","import { writeFile } from 'atomically';\nimport type { TestFile, TestBlock, TestBlockResult } from './types.js';\n\n/**\n * Update a test file with actual output from test results.\n */\nexport async function updateTestFile(\n file: TestFile,\n results: TestBlockResult[],\n): Promise<{ updated: boolean; changes: string[] }> {\n let content = file.rawContent;\n const changes: string[] = [];\n\n // Process blocks in reverse order to maintain correct offsets\n const blocksWithResults = file.blocks\n .map((block, i) => ({ block, result: results[i] }))\n .reverse();\n\n for (const { block, result } of blocksWithResults) {\n if (!result) {\n continue;\n }\n\n if (result.passed) {\n continue; // Don't touch passing tests\n }\n\n if (result.error) {\n // Execution error, can't update\n continue;\n }\n\n // Build the new block content\n const newBlockContent = buildUpdatedBlock(block, result);\n\n // Find and replace the block in the file\n const blockStart = content.indexOf(block.rawContent);\n if (blockStart !== -1) {\n content =\n content.slice(0, blockStart) +\n newBlockContent +\n content.slice(blockStart + block.rawContent.length);\n\n changes.push(block.name ?? `Line ${block.lineNumber}`);\n }\n }\n\n if (changes.length > 0) {\n await writeFile(file.path, content);\n }\n\n return { updated: changes.length > 0, changes };\n}\n\n/**\n * Build an updated console block with new expected output.\n */\nfunction buildUpdatedBlock(block: TestBlock, result: TestBlockResult): string {\n // Reconstruct the command line(s)\n const commandLines = block.command.split('\\n').map((line, i) => {\n return i === 0 ? `$ ${line}` : `> ${line}`;\n });\n\n // Build the block\n const lines: string[] = ['```console', ...commandLines];\n\n // Add output if present\n const trimmedOutput = result.actualOutput.trimEnd();\n if (trimmedOutput) {\n lines.push(trimmedOutput);\n }\n\n // Add exit code\n lines.push(`? ${result.actualExitCode}`, '```');\n\n return lines.join('\\n');\n}\n","/**\n * Coverage collection for CLI subprocess testing.\n *\n * Uses c8 and NODE_V8_COVERAGE to collect coverage from spawned processes.\n */\n\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, rm, access } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport type { CoverageContext, CoverageConfig } from './types.js';\nimport { resolveCoverageConfig } from './config.js';\n\n/**\n * Find the c8 executable path.\n * Checks local node_modules/.bin first, then falls back to npx.\n */\nfunction findC8Path(): string {\n // Check common locations for local c8\n const localPaths = [\n resolve(process.cwd(), 'node_modules', '.bin', 'c8'),\n resolve(process.cwd(), '..', '..', 'node_modules', '.bin', 'c8'), // monorepo root\n ];\n\n for (const localPath of localPaths) {\n if (existsSync(localPath)) {\n return localPath;\n }\n }\n\n // Fall back to npx which will find c8 in node_modules\n return 'npx c8';\n}\n\n/**\n * Check if c8 is available in the current environment.\n */\nexport async function isC8Available(): Promise<boolean> {\n const c8Path = findC8Path();\n\n return new Promise((resolve) => {\n // Use npx to run c8 if we fell back to npx\n const isNpx = c8Path === 'npx c8';\n const command = isNpx ? 'npx' : c8Path;\n const args = isNpx ? ['c8', '--version'] : ['--version'];\n\n const proc = spawn(command, args, {\n shell: false,\n stdio: 'ignore',\n });\n proc.on('close', (code) => {\n resolve(code === 0);\n });\n proc.on('error', () => {\n resolve(false);\n });\n });\n}\n\n/**\n * Create a coverage context for collecting V8 coverage data.\n */\nexport async function createCoverageContext(config?: CoverageConfig): Promise<CoverageContext> {\n const options = resolveCoverageConfig(config);\n const tempDir = await mkdtemp(join(tmpdir(), 'tryscript-coverage-'));\n\n return {\n tempDir,\n options,\n };\n}\n\n/**\n * Get environment variables for enabling V8 coverage in spawned processes.\n */\nexport function getCoverageEnv(ctx: CoverageContext): Record<string, string> {\n return {\n NODE_V8_COVERAGE: ctx.tempDir,\n };\n}\n\n/**\n * Generate coverage report from collected V8 coverage data using c8.\n * Throws an error if coverage report generation fails.\n */\nexport async function generateCoverageReport(ctx: CoverageContext): Promise<void> {\n const { options, tempDir } = ctx;\n const c8Path = findC8Path();\n\n // Base args for c8 report\n const reportArgs = [\n 'report',\n '--temp-directory',\n tempDir,\n '--reports-dir',\n options.reportsDir,\n '--src',\n options.src,\n '--all',\n // Include patterns\n ...options.include.flatMap((pattern) => ['--include', pattern]),\n // Exclude patterns\n ...options.exclude.flatMap((pattern) => ['--exclude', pattern]),\n // Boolean flags (only add if explicitly set)\n ...(options.excludeNodeModules ? ['--exclude-node-modules'] : ['--no-exclude-node-modules']),\n ...(options.excludeAfterRemap ? ['--exclude-after-remap'] : []),\n ...(options.skipFull ? ['--skip-full'] : []),\n ...(options.allowExternal ? ['--allowExternal'] : []),\n ...(options.monocart ? ['--experimental-monocart'] : []),\n // Reporters\n ...options.reporters.flatMap((reporter) => ['--reporter', reporter]),\n ];\n\n // Handle 'npx c8' vs direct c8 path\n // Use shell: false to prevent glob expansion of patterns like dist/**\n const isNpx = c8Path === 'npx c8';\n const command = isNpx ? 'npx' : c8Path;\n const args = isNpx ? ['c8', ...reportArgs] : reportArgs;\n\n await new Promise<void>((resolvePromise, reject) => {\n const proc = spawn(command, args, {\n shell: false,\n stdio: 'inherit',\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolvePromise();\n } else {\n reject(new Error(`c8 report exited with code ${code}`));\n }\n });\n\n proc.on('error', (err) => {\n reject(new Error(`Failed to run c8 report: ${err.message}`));\n });\n });\n}\n\n/**\n * Clean up coverage context by removing the temporary directory.\n */\nexport async function cleanupCoverageContext(ctx: CoverageContext): Promise<void> {\n try {\n await access(ctx.tempDir);\n await rm(ctx.tempDir, { recursive: true, force: true });\n } catch {\n // Directory doesn't exist, nothing to clean up\n }\n}\n","/**\n * Run command - executes golden tests against CLI applications.\n *\n * Supports filtering, update mode, and detailed diff output for failures.\n */\n\nimport type { Command } from 'commander';\n\nimport { readFile } from 'node:fs/promises';\nimport fg from 'fast-glob';\nimport { loadConfig, mergeConfig } from '../../lib/config.js';\nimport { logWarn, logError, colors, status as statusIndicators } from '../lib/shared.js';\nimport { parseTestFile } from '../../lib/parser.js';\nimport {\n runBlock,\n createExecutionContext,\n cleanupExecutionContext,\n runAfterHook,\n} from '../../lib/runner.js';\nimport { matchOutput } from '../../lib/matcher.js';\nimport { createDiff, reportFile, reportSummary } from '../../lib/reporter.js';\nimport { updateTestFile } from '../../lib/updater.js';\nimport {\n isC8Available,\n createCoverageContext,\n getCoverageEnv,\n generateCoverageReport,\n cleanupCoverageContext,\n} from '../../lib/coverage.js';\nimport type {\n TestBlockResult,\n TestFileResult,\n TestRunSummary,\n CoverageContext,\n} from '../../lib/types.js';\n\ninterface RunOptions {\n update?: boolean;\n diff?: boolean;\n failFast?: boolean;\n filter?: string;\n verbose?: boolean;\n quiet?: boolean;\n coverage?: boolean;\n coverageDir?: string;\n coverageReporter?: string[];\n coverageExclude?: string[];\n coverageExcludeNodeModules?: boolean;\n coverageExcludeAfterRemap?: boolean;\n coverageSkipFull?: boolean;\n coverageAllowExternal?: boolean;\n coverageMonocart?: boolean;\n}\n\n/**\n * Register the run command.\n */\nexport function registerRunCommand(program: Command): void {\n program\n .command('run')\n .description('Run golden tests')\n .argument('[files...]', 'Test files to run (default: **/*.tryscript.md)')\n .option('--update', 'Update golden files with actual output')\n .option('--diff', 'Show diff on failure (default: true)')\n .option('--no-diff', 'Hide diff on failure')\n .option('--fail-fast', 'Stop on first failure')\n .option('--filter <pattern>', 'Filter tests by name pattern')\n .option('--verbose', 'Show detailed output including passing test output')\n .option('--quiet', 'Suppress non-essential output (only show failures)')\n .option('--coverage', 'Enable code coverage collection (requires c8)')\n .option('--coverage-dir <dir>', 'Coverage output directory (default: coverage-tryscript)')\n .option(\n '--coverage-reporter <reporter...>',\n 'Coverage reporters (default: text, html). Can be specified multiple times.',\n )\n .option(\n '--coverage-exclude <pattern...>',\n 'Patterns to exclude from coverage (c8 --exclude). Can be specified multiple times.',\n )\n .option(\n '--coverage-exclude-node-modules',\n 'Exclude node_modules from coverage (c8 --exclude-node-modules, default: true)',\n )\n .option(\n '--no-coverage-exclude-node-modules',\n 'Include node_modules in coverage (c8 --no-exclude-node-modules)',\n )\n .option(\n '--coverage-exclude-after-remap',\n 'Apply exclude logic after sourcemap remapping (c8 --exclude-after-remap)',\n )\n .option('--coverage-skip-full', 'Hide files with 100% coverage (c8 --skip-full)')\n .option('--coverage-allow-external', 'Allow files from outside cwd (c8 --allowExternal)')\n .option(\n '--coverage-monocart',\n 'Use monocart for accurate line counts, better for merging with vitest (c8 --experimental-monocart)',\n )\n .action(runCommand);\n}\n\nasync function runCommand(files: string[], options: RunOptions): Promise<void> {\n const startTime = Date.now();\n\n // Default options\n const opts = {\n diff: options.diff !== false,\n verbose: options.verbose ?? false,\n quiet: options.quiet ?? false,\n update: options.update ?? false,\n failFast: options.failFast ?? false,\n filter: options.filter,\n };\n\n // Find test files (fast-glob respects .gitignore by default)\n const patterns = files.length > 0 ? files : ['**/*.tryscript.md'];\n const testFiles = await fg(patterns, {\n ignore: ['**/node_modules/**', '**/dist/**'],\n absolute: true,\n dot: false,\n });\n\n if (testFiles.length === 0) {\n logWarn('No test files found');\n process.exit(1);\n }\n\n // Load global config\n const globalConfig = await loadConfig(process.cwd());\n\n // Setup coverage if enabled\n let coverageCtx: CoverageContext | undefined;\n let coverageEnv: Record<string, string> = {};\n\n if (options.coverage) {\n // Check if c8 is available\n const c8Available = await isC8Available();\n if (!c8Available) {\n logError('Coverage requires c8. Install with: npm install -D c8');\n process.exit(1);\n }\n\n // Create coverage context with CLI options overriding config\n coverageCtx = await createCoverageContext({\n ...globalConfig.coverage,\n reportsDir: options.coverageDir ?? globalConfig.coverage?.reportsDir,\n reporters: options.coverageReporter ?? globalConfig.coverage?.reporters,\n exclude: options.coverageExclude ?? globalConfig.coverage?.exclude,\n excludeNodeModules:\n options.coverageExcludeNodeModules ?? globalConfig.coverage?.excludeNodeModules,\n excludeAfterRemap:\n options.coverageExcludeAfterRemap ?? globalConfig.coverage?.excludeAfterRemap,\n skipFull: options.coverageSkipFull ?? globalConfig.coverage?.skipFull,\n allowExternal: options.coverageAllowExternal ?? globalConfig.coverage?.allowExternal,\n monocart: options.coverageMonocart ?? globalConfig.coverage?.monocart,\n });\n coverageEnv = getCoverageEnv(coverageCtx);\n }\n\n // Run tests\n const fileResults: TestFileResult[] = [];\n let shouldStop = false;\n\n for (const filePath of testFiles) {\n if (shouldStop) {\n break;\n }\n\n const content = await readFile(filePath, 'utf-8');\n const testFile = parseTestFile(content, filePath);\n const config = mergeConfig(globalConfig, testFile.config);\n\n // Filter blocks by name if specified\n let blocksToRun = testFile.blocks;\n if (opts.filter) {\n const filterPattern = new RegExp(opts.filter, 'i');\n blocksToRun = blocksToRun.filter((b) => (b.name ? filterPattern.test(b.name) : true));\n }\n\n // Handle \"only\" mode - if any block has only=true, run only those\n const onlyBlocks = blocksToRun.filter((b) => b.only);\n if (onlyBlocks.length > 0) {\n blocksToRun = onlyBlocks;\n }\n\n if (blocksToRun.length === 0) {\n continue;\n }\n\n const ctx = await createExecutionContext(config, filePath, coverageEnv);\n const results: TestBlockResult[] = [];\n\n try {\n for (const block of blocksToRun) {\n const result = await runBlock(block, ctx);\n\n // Skip checking for skipped tests\n if (result.skipped) {\n results.push(result);\n continue;\n }\n\n // Check if output matches expected\n // [ROOT] = test file directory, [CWD] = command working directory\n // If expectedStderr is set, compare stdout only (not combined output)\n const outputToCheck = block.expectedStderr\n ? (result.actualStdout ?? '')\n : result.actualOutput;\n const outputMatches = matchOutput(\n outputToCheck,\n block.expectedOutput,\n { root: ctx.testDir, cwd: ctx.cwd },\n config.patterns ?? {},\n );\n\n // Check stderr if expected (using actualStderr if available)\n let stderrMatches = true;\n if (block.expectedStderr) {\n stderrMatches = matchOutput(\n result.actualStderr ?? '',\n block.expectedStderr,\n { root: ctx.testDir, cwd: ctx.cwd },\n config.patterns ?? {},\n );\n }\n\n const exitCodeMatches = result.actualExitCode === block.expectedExitCode;\n result.passed = outputMatches && stderrMatches && exitCodeMatches && !result.error;\n\n if (!result.passed && opts.diff) {\n result.diff = createDiff(\n block.expectedOutput,\n result.actualOutput,\n `${filePath}:${block.lineNumber}`,\n );\n }\n\n results.push(result);\n\n if (!result.passed && opts.failFast) {\n shouldStop = true;\n break;\n }\n }\n\n // Run after hook if configured\n await runAfterHook(ctx);\n } finally {\n await cleanupExecutionContext(ctx);\n }\n\n const fileResult: TestFileResult = {\n file: testFile,\n results,\n passed: results.every((r) => r.passed),\n duration: results.reduce((sum, r) => sum + r.duration, 0),\n };\n\n fileResults.push(fileResult);\n reportFile(fileResult, opts);\n\n // Update mode\n if (opts.update && !fileResult.passed) {\n const { updated, changes } = await updateTestFile(testFile, results);\n if (updated) {\n console.error(colors.warn(` ${statusIndicators.update} Updated: ${changes.join(', ')}`));\n }\n }\n }\n\n // Summary\n const summary: TestRunSummary = {\n files: fileResults,\n totalPassed: fileResults.reduce((sum, f) => sum + f.results.filter((r) => r.passed).length, 0),\n totalFailed: fileResults.reduce((sum, f) => sum + f.results.filter((r) => !r.passed).length, 0),\n totalBlocks: fileResults.reduce((sum, f) => sum + f.results.length, 0),\n duration: Date.now() - startTime,\n };\n\n reportSummary(summary, opts);\n\n // Generate coverage report if enabled\n if (coverageCtx) {\n console.error('\\nGenerating coverage report...');\n try {\n await generateCoverageReport(coverageCtx);\n console.error(\n colors.success(`Coverage report written to ${coverageCtx.options.reportsDir}/`),\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logError(`Failed to generate coverage report: ${message}`);\n } finally {\n await cleanupCoverageContext(coverageCtx);\n }\n }\n\n // Exit code\n process.exit(summary.totalFailed > 0 ? 1 : 0);\n}\n","/**\n * Coverage command - runs multiple commands with merged V8 coverage.\n *\n * This command provides a simple way to collect coverage from multiple sources\n * (e.g., unit tests + CLI tests) and generate a merged coverage report.\n *\n * Example usage:\n * tryscript coverage \"vitest run\" \"tryscript run tests/\"\n * tryscript coverage --monocart --reporters text,html \"vitest run\" \"node dist/bin.mjs run tests/\"\n */\n\nimport type { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport { mkdtemp, rm, readdir, stat } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { logError, logWarn, colors } from '../lib/shared.js';\n\ninterface CoverageOptions {\n reportsDir?: string;\n reporters?: string[];\n include?: string[];\n exclude?: string[];\n excludeNodeModules?: boolean;\n excludeAfterRemap?: boolean;\n skipFull?: boolean;\n allowExternal?: boolean;\n monocart?: boolean;\n src?: string;\n verbose?: boolean;\n}\n\n/**\n * Register the coverage command.\n */\nexport function registerCoverageCommand(program: Command): void {\n program\n .command('coverage')\n .description('Run commands with merged V8 coverage')\n .argument('<commands...>', 'Commands to run (each will inherit coverage environment)')\n .option('--reports-dir <dir>', 'Coverage output directory (default: coverage)')\n .option(\n '--reporters <reporters>',\n 'Comma-separated coverage reporters (default: text,json,json-summary,lcov,html)',\n )\n .option('--include <patterns>', 'Comma-separated patterns to include in coverage')\n .option('--exclude <patterns>', 'Comma-separated patterns to exclude from coverage')\n .option('--exclude-node-modules', 'Exclude node_modules from coverage (default: true)', true)\n .option('--no-exclude-node-modules', 'Include node_modules in coverage')\n .option('--exclude-after-remap', 'Apply exclude logic after sourcemap remapping')\n .option('--skip-full', 'Hide files with 100% coverage')\n .option('--allow-external', 'Allow files from outside cwd')\n .option('--monocart', 'Use monocart for accurate line counts (recommended for merging)')\n .option('--src <dir>', 'Source directory for sourcemap remapping (default: src)')\n .option('--verbose', 'Show coverage summary after each command for debugging')\n .action(coverageCommand);\n}\n\n/**\n * Run a command with inherited coverage environment.\n */\nasync function runCommand(\n command: string,\n env: Record<string, string>,\n): Promise<{ success: boolean; code: number }> {\n return new Promise((resolve) => {\n const proc = spawn(command, [], {\n stdio: 'inherit',\n env: { ...process.env, ...env },\n shell: true,\n });\n\n proc.on('close', (code) => {\n resolve({ success: code === 0, code: code ?? 1 });\n });\n\n proc.on('error', (err) => {\n logError(`Failed to run command: ${err.message}`);\n resolve({ success: false, code: 1 });\n });\n });\n}\n\n/**\n * Find the c8 executable path.\n * Can be overridden via TRYSCRIPT_C8_COMMAND env var for testing.\n */\nfunction findC8Path(): string | null {\n // Allow override for testing - when set, we trust it exists\n const override = process.env.TRYSCRIPT_C8_COMMAND;\n if (override) {\n return override;\n }\n\n const localPaths = [\n resolve(process.cwd(), 'node_modules', '.bin', 'c8'),\n resolve(process.cwd(), '..', '..', 'node_modules', '.bin', 'c8'),\n ];\n\n for (const localPath of localPaths) {\n if (existsSync(localPath)) {\n return localPath;\n }\n }\n\n return null;\n}\n\n/**\n * Get coverage file statistics from temp directory.\n */\nasync function getCoverageStats(\n tempDir: string,\n): Promise<{ fileCount: number; totalBytes: number; files: string[] }> {\n try {\n const files = await readdir(tempDir);\n const coverageFiles = files.filter((f) => f.startsWith('coverage-') && f.endsWith('.json'));\n let totalBytes = 0;\n\n for (const file of coverageFiles) {\n const fileStat = await stat(join(tempDir, file));\n totalBytes += fileStat.size;\n }\n\n return { fileCount: coverageFiles.length, totalBytes, files: coverageFiles };\n } catch {\n return { fileCount: 0, totalBytes: 0, files: [] };\n }\n}\n\n/**\n * Generate a text-only coverage report for debugging (doesn't write files).\n */\nasync function generateTextReport(\n tempDir: string,\n options: CoverageOptions,\n label: string,\n): Promise<void> {\n const c8Path = findC8Path();\n if (!c8Path) {\n return;\n }\n\n const include = options.include ?? ['dist/**'];\n const exclude = options.exclude ?? [];\n\n // Create a temporary reports dir that we'll discard\n const tempReportsDir = await mkdtemp(join(tmpdir(), 'tryscript-coverage-report-'));\n\n const reportArgs = [\n 'report',\n '--temp-directory',\n tempDir,\n '--reports-dir',\n tempReportsDir,\n '--src',\n options.src ?? 'src',\n '--all',\n ...include.flatMap((pattern) => ['--include', pattern]),\n ...exclude.flatMap((pattern) => ['--exclude', pattern]),\n ...(options.excludeNodeModules !== false\n ? ['--exclude-node-modules']\n : ['--no-exclude-node-modules']),\n ...(options.excludeAfterRemap ? ['--exclude-after-remap'] : []),\n ...(options.monocart ? ['--experimental-monocart'] : []),\n '--reporter',\n 'text',\n ];\n\n console.error(colors.info(`\\n--- Coverage after: ${label} ---`));\n\n await new Promise<void>((resolve) => {\n const proc = spawn(c8Path, reportArgs, {\n stdio: 'inherit',\n shell: false,\n });\n proc.on('close', () => {\n resolve();\n });\n proc.on('error', () => {\n resolve();\n });\n });\n\n // Cleanup temp reports dir\n await rm(tempReportsDir, { recursive: true, force: true });\n}\n\n/**\n * Generate c8 coverage report.\n */\nasync function generateReport(tempDir: string, options: CoverageOptions): Promise<boolean> {\n const c8Path = findC8Path();\n if (!c8Path) {\n logError('c8 not found. Install with: npm install -D c8');\n return false;\n }\n\n const reporters = options.reporters ?? ['text', 'json', 'json-summary', 'lcov', 'html'];\n const include = options.include ?? ['dist/**'];\n const exclude = options.exclude ?? [];\n\n const reportArgs = [\n 'report',\n '--temp-directory',\n tempDir,\n '--reports-dir',\n options.reportsDir ?? 'coverage',\n '--src',\n options.src ?? 'src',\n '--all',\n ...include.flatMap((pattern) => ['--include', pattern]),\n ...exclude.flatMap((pattern) => ['--exclude', pattern]),\n ...(options.excludeNodeModules !== false\n ? ['--exclude-node-modules']\n : ['--no-exclude-node-modules']),\n ...(options.excludeAfterRemap ? ['--exclude-after-remap'] : []),\n ...(options.skipFull ? ['--skip-full'] : []),\n ...(options.allowExternal ? ['--allowExternal'] : []),\n ...(options.monocart ? ['--experimental-monocart'] : []),\n ...reporters.flatMap((reporter) => ['--reporter', reporter]),\n ];\n\n return new Promise((resolve) => {\n const proc = spawn(c8Path, reportArgs, {\n stdio: 'inherit',\n shell: false,\n });\n\n proc.on('close', (code) => {\n resolve(code === 0);\n });\n\n proc.on('error', (err) => {\n logError(`Failed to generate coverage report: ${err.message}`);\n resolve(false);\n });\n });\n}\n\nasync function coverageCommand(commands: string[], options: CoverageOptions): Promise<void> {\n // Validate we have commands to run\n if (commands.length === 0) {\n logError('No commands specified. Usage: tryscript coverage \"cmd1\" \"cmd2\"');\n process.exit(1);\n }\n\n // Check for c8\n const c8Path = findC8Path();\n if (!c8Path) {\n logError('Coverage requires c8. Install with: npm install -D c8');\n process.exit(1);\n }\n\n // Parse comma-separated options early so we can use them for intermediate reports\n const parsedOptions: CoverageOptions = {\n ...options,\n reporters: options.reporters\n ? typeof options.reporters === 'string'\n ? (options.reporters as string).split(',')\n : options.reporters\n : undefined,\n include: options.include\n ? typeof options.include === 'string'\n ? (options.include as string).split(',')\n : options.include\n : undefined,\n exclude: options.exclude\n ? typeof options.exclude === 'string'\n ? (options.exclude as string).split(',')\n : options.exclude\n : undefined,\n };\n\n // Create temp directory for V8 coverage data\n const coverageTemp = await mkdtemp(join(tmpdir(), 'tryscript-coverage-'));\n const coverageEnv = { NODE_V8_COVERAGE: coverageTemp };\n\n console.error(colors.info(`Collecting V8 coverage to ${coverageTemp}`));\n\n let hasFailures = false;\n let previousFileCount = 0;\n\n try {\n // Run each command with shared coverage environment\n for (let i = 0; i < commands.length; i++) {\n const command = commands[i]!;\n console.error(\n colors.info(`\\n=== Running command ${i + 1}/${commands.length}: ${command} ===`),\n );\n\n const result = await runCommand(command, coverageEnv);\n if (!result.success) {\n logWarn(`Command exited with code ${result.code}: ${command}`);\n hasFailures = true;\n }\n\n // Show coverage stats after each command\n const stats = await getCoverageStats(coverageTemp);\n const newFiles = stats.fileCount - previousFileCount;\n const bytesKB = (stats.totalBytes / 1024).toFixed(1);\n\n console.error(\n colors.info(\n `\\nV8 coverage: ${stats.fileCount} files (${newFiles} new), ${bytesKB} KB total`,\n ),\n );\n\n if (newFiles === 0) {\n logWarn(\n `No new coverage files from this command. ` +\n `This may indicate the command doesn't write to NODE_V8_COVERAGE.`,\n );\n }\n\n // Show intermediate coverage report if verbose\n if (parsedOptions.verbose && stats.fileCount > 0) {\n await generateTextReport(coverageTemp, parsedOptions, command);\n }\n\n previousFileCount = stats.fileCount;\n }\n\n // Generate merged coverage report\n console.error(colors.info('\\n=== Generating merged coverage report ==='));\n\n const reportSuccess = await generateReport(coverageTemp, parsedOptions);\n if (!reportSuccess) {\n logError('Failed to generate coverage report');\n process.exit(1);\n }\n\n console.error(\n colors.success(`\\nCoverage report written to ${parsedOptions.reportsDir ?? 'coverage'}/`),\n );\n } finally {\n // Cleanup temp directory\n await rm(coverageTemp, { recursive: true, force: true });\n }\n\n // Exit with failure if any command failed\n if (hasFailures) {\n process.exit(1);\n }\n}\n","/**\n * Readme command - Display the README documentation.\n *\n * Shows the package README.md, formatted for the terminal when interactive,\n * or as plain text when piped.\n */\n\nimport type { Command } from 'commander';\n\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\n\n/**\n * Get the path to the README.md file.\n * Works both during development and when installed as a package.\n */\nfunction getReadmePath(): string {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n const dirName = thisDir.split(/[/\\\\]/).pop();\n\n if (dirName === 'dist') {\n // Bundled: dist -> package root -> README.md\n return join(dirname(thisDir), 'README.md');\n }\n\n // Development: src/cli/commands -> src/cli -> src -> package root -> README.md\n return join(dirname(dirname(dirname(thisDir))), 'README.md');\n}\n\n/**\n * Load the README content.\n */\nfunction loadReadme(): string {\n const readmePath = getReadmePath();\n try {\n return readFileSync(readmePath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load README from ${readmePath}: ${message}`);\n }\n}\n\n/**\n * Apply basic terminal formatting to markdown content.\n * Colorizes headers, code blocks, and other elements for better readability.\n */\nfunction formatMarkdown(content: string, useColors: boolean): string {\n if (!useColors) {\n return content;\n }\n\n const lines = content.split('\\n');\n const formatted: string[] = [];\n let inCodeBlock = false;\n\n for (const line of lines) {\n // Track code blocks\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n formatted.push(pc.dim(line));\n continue;\n }\n\n if (inCodeBlock) {\n formatted.push(pc.dim(line));\n continue;\n }\n\n // Headers\n if (line.startsWith('# ')) {\n formatted.push(pc.bold(pc.cyan(line)));\n continue;\n }\n if (line.startsWith('## ')) {\n formatted.push(pc.bold(pc.blue(line)));\n continue;\n }\n if (line.startsWith('### ')) {\n formatted.push(pc.bold(line));\n continue;\n }\n\n // Inline code (backticks)\n let formattedLine = line.replace(/`([^`]+)`/g, (_match, code: string) => {\n return pc.yellow(code);\n });\n\n // Bold text\n formattedLine = formattedLine.replace(/\\*\\*([^*]+)\\*\\*/g, (_match, text: string) => {\n return pc.bold(text);\n });\n\n // Links - show text in cyan, URL dimmed\n formattedLine = formattedLine.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, url: string) => {\n return `${pc.cyan(text)} ${pc.dim(`(${url})`)}`;\n },\n );\n\n formatted.push(formattedLine);\n }\n\n return formatted.join('\\n');\n}\n\n/**\n * Check if stdout is an interactive terminal.\n */\nfunction isInteractive(): boolean {\n return process.stdout.isTTY === true;\n}\n\n/**\n * Display the README content.\n * Exported for use as the default command.\n */\nexport function showReadme(options?: { raw?: boolean; color?: boolean }): void {\n try {\n const readme = loadReadme();\n\n // Determine if we should colorize\n const shouldColorize = options?.color ?? (!options?.raw && isInteractive());\n\n const formatted = formatMarkdown(readme, shouldColorize);\n console.log(formatted);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(pc.red(`Error: ${message}`));\n process.exit(1);\n }\n}\n\n/**\n * Register the readme command.\n */\nexport function registerReadmeCommand(program: Command): void {\n program\n .command('readme')\n .description('Display README documentation')\n .option('--raw', 'Output raw markdown without formatting')\n .option('--color', 'Force colorized output (for testing)')\n .action(showReadme);\n}\n","/**\n * Docs command - Display the tryscript quick reference.\n *\n * Shows the tryscript-reference.md file, formatted for the terminal when interactive,\n * or as plain text when piped.\n */\n\nimport type { Command } from 'commander';\n\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\n\n/**\n * Get the path to the tryscript-reference.md file.\n * Works both during development and when installed as a package.\n */\nfunction getDocsPath(): string {\n const thisDir = dirname(fileURLToPath(import.meta.url));\n const dirName = thisDir.split(/[/\\\\]/).pop();\n\n if (dirName === 'dist') {\n // Bundled: dist -> package root -> docs/tryscript-reference.md\n return join(dirname(thisDir), 'docs', 'tryscript-reference.md');\n }\n\n // Development: src/cli/commands -> src/cli -> src -> package root -> docs/tryscript-reference.md\n return join(dirname(dirname(dirname(thisDir))), 'docs', 'tryscript-reference.md');\n}\n\n/**\n * Load the docs content.\n */\nfunction loadDocs(): string {\n const docsPath = getDocsPath();\n try {\n return readFileSync(docsPath, 'utf-8');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`Failed to load reference docs from ${docsPath}: ${message}`);\n }\n}\n\n/**\n * Apply basic terminal formatting to markdown content.\n * Colorizes headers, code blocks, and other elements for better readability.\n */\nfunction formatMarkdown(content: string, useColors: boolean): string {\n if (!useColors) {\n return content;\n }\n\n const lines = content.split('\\n');\n const formatted: string[] = [];\n let inCodeBlock = false;\n\n for (const line of lines) {\n // Track code blocks\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n formatted.push(pc.dim(line));\n continue;\n }\n\n if (inCodeBlock) {\n formatted.push(pc.dim(line));\n continue;\n }\n\n // Headers\n if (line.startsWith('# ')) {\n formatted.push(pc.bold(pc.cyan(line)));\n continue;\n }\n if (line.startsWith('## ')) {\n formatted.push(pc.bold(pc.blue(line)));\n continue;\n }\n if (line.startsWith('### ')) {\n formatted.push(pc.bold(line));\n continue;\n }\n\n // Inline code (backticks)\n let formattedLine = line.replace(/`([^`]+)`/g, (_match, code: string) => {\n return pc.yellow(code);\n });\n\n // Bold text\n formattedLine = formattedLine.replace(/\\*\\*([^*]+)\\*\\*/g, (_match, text: string) => {\n return pc.bold(text);\n });\n\n // Links - show text in cyan, URL dimmed\n formattedLine = formattedLine.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_match, text: string, url: string) => {\n return `${pc.cyan(text)} ${pc.dim(`(${url})`)}`;\n },\n );\n\n formatted.push(formattedLine);\n }\n\n return formatted.join('\\n');\n}\n\n/**\n * Check if stdout is an interactive terminal.\n */\nfunction isInteractive(): boolean {\n return process.stdout.isTTY === true;\n}\n\n/**\n * Register the docs command.\n */\nexport function registerDocsCommand(program: Command): void {\n program\n .command('docs')\n .description('Display concise syntax reference')\n .option('--raw', 'Output raw markdown without formatting')\n .option('--color', 'Force colorized output (for testing)')\n .action((options: { raw?: boolean; color?: boolean }) => {\n try {\n const docs = loadDocs();\n\n // Determine if we should colorize\n const shouldColorize = options.color ?? (!options.raw && isInteractive());\n\n const formatted = formatMarkdown(docs, shouldColorize);\n console.log(formatted);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(pc.red(`Error: ${message}`));\n process.exit(1);\n }\n });\n}\n","/**\n * CLI entry point for tryscript.\n *\n * Configures Commander.js with colored help and registers all subcommands.\n */\n\nimport { Command } from 'commander';\nimport { VERSION } from '../index.js';\nimport { registerRunCommand } from './commands/run.js';\nimport { registerCoverageCommand } from './commands/coverage.js';\nimport { registerReadmeCommand } from './commands/readme.js';\nimport { registerDocsCommand } from './commands/docs.js';\nimport { withColoredHelp, logError } from './lib/shared.js';\n\nexport function run(argv: string[]): void {\n const program = withColoredHelp(\n new Command()\n .name('tryscript')\n .version(VERSION, '--version', 'Show version number')\n .description('Golden testing for CLI applications')\n .showHelpAfterError('(use --help for usage)'),\n );\n\n // Register subcommands\n registerRunCommand(program);\n registerCoverageCommand(program);\n registerReadmeCommand(program);\n registerDocsCommand(program);\n\n // Default action: show help when no command given\n program.action(() => {\n program.help();\n });\n\n program.parseAsync(argv).catch((err: Error) => {\n logError(`Error: ${err.message}`);\n process.exit(2);\n });\n}\n","#!/usr/bin/env node\nimport { run } from './cli/cli.js';\n\nrun(process.argv);\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- const require_src = require('./src-BR3CZ3tc.cjs');
2
+ const require_src = require('./src-rTwoOhL4.cjs');
3
3
 
4
4
  exports.VERSION = require_src.VERSION;
5
5
  exports.cleanupExecutionContext = require_src.cleanupExecutionContext;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
 
2
- import { a as createExecutionContext, c as parseTestFile, i as cleanupExecutionContext, l as defineConfig, n as matchOutput, r as normalizeOutput, s as runBlock, t as VERSION } from "./src-DKrim0QL.mjs";
2
+ import { a as createExecutionContext, c as parseTestFile, i as cleanupExecutionContext, l as defineConfig, n as matchOutput, r as normalizeOutput, s as runBlock, t as VERSION } from "./src-BBeKy_V9.mjs";
3
3
 
4
4
  export { VERSION, cleanupExecutionContext, createExecutionContext, defineConfig, matchOutput, normalizeOutput, parseTestFile, runBlock };
@@ -451,8 +451,8 @@ function matchOutput(actual, expected, context, customPatterns = {}) {
451
451
 
452
452
  //#endregion
453
453
  //#region src/index.ts
454
- const VERSION = "0.1.2";
454
+ const VERSION = "0.1.3";
455
455
 
456
456
  //#endregion
457
457
  export { createExecutionContext as a, parseTestFile as c, mergeConfig as d, resolveCoverageConfig as f, cleanupExecutionContext as i, defineConfig as l, matchOutput as n, runAfterHook as o, normalizeOutput as r, runBlock as s, VERSION as t, loadConfig as u };
458
- //# sourceMappingURL=src-DKrim0QL.mjs.map
458
+ //# sourceMappingURL=src-BBeKy_V9.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"src-DKrim0QL.mjs","names":["DEFAULT_COVERAGE_CONFIG: Required<CoverageConfig>","config: TestConfig","parseYaml","blocks: TestBlock[]","match: RegExpExecArray | null","commandLines: string[]","outputLines: string[]","stderrLines: string[]","expectedStderr: string | undefined","cwd: string","combinedChunks: { data: Buffer; type: 'stdout' | 'stderr' }[]","stdoutChunks: Buffer[]","stderrChunks: Buffer[]","VERSION: string"],"sources":["../src/lib/config.ts","../src/lib/parser.ts","../src/lib/runner.ts","../src/lib/matcher.ts","../src/index.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { TestConfig, CoverageConfig } from './types.js';\n\n/** Fixture configuration for copying files to sandbox directory */\nexport interface Fixture {\n /** Source path (resolved relative to test file) */\n source: string;\n /** Destination path (resolved relative to sandbox dir) */\n dest?: string;\n}\n\nexport interface TryscriptConfig {\n /** Working directory for commands (default: test file directory) */\n cwd?: string;\n /** Run in isolated sandbox: true = empty temp, path = copy to temp */\n sandbox?: boolean | string;\n /** Fixtures to copy to sandbox directory before tests */\n fixtures?: (string | Fixture)[];\n /** Script to run before first test block */\n before?: string;\n /** Script to run after all test blocks */\n after?: string;\n env?: Record<string, string>;\n timeout?: number;\n patterns?: Record<string, RegExp | string>;\n tests?: string[];\n /** Coverage configuration (used with --coverage flag) */\n coverage?: CoverageConfig;\n}\n\n/** Default coverage configuration values. */\nexport const DEFAULT_COVERAGE_CONFIG: Required<CoverageConfig> = {\n reportsDir: 'coverage-tryscript',\n reporters: ['text', 'html'],\n include: ['dist/**'],\n exclude: [],\n excludeNodeModules: true,\n excludeAfterRemap: false,\n skipFull: false,\n allowExternal: false,\n src: 'src',\n monocart: false,\n};\n\n/**\n * Resolve coverage options by merging user config with defaults.\n */\nexport function resolveCoverageConfig(config?: CoverageConfig): Required<CoverageConfig> {\n return {\n reportsDir: config?.reportsDir ?? DEFAULT_COVERAGE_CONFIG.reportsDir,\n reporters: config?.reporters ?? DEFAULT_COVERAGE_CONFIG.reporters,\n include: config?.include ?? DEFAULT_COVERAGE_CONFIG.include,\n exclude: config?.exclude ?? DEFAULT_COVERAGE_CONFIG.exclude,\n excludeNodeModules: config?.excludeNodeModules ?? DEFAULT_COVERAGE_CONFIG.excludeNodeModules,\n excludeAfterRemap: config?.excludeAfterRemap ?? DEFAULT_COVERAGE_CONFIG.excludeAfterRemap,\n skipFull: config?.skipFull ?? DEFAULT_COVERAGE_CONFIG.skipFull,\n allowExternal: config?.allowExternal ?? DEFAULT_COVERAGE_CONFIG.allowExternal,\n src: config?.src ?? DEFAULT_COVERAGE_CONFIG.src,\n monocart: config?.monocart ?? DEFAULT_COVERAGE_CONFIG.monocart,\n };\n}\n\nconst CONFIG_FILES = ['tryscript.config.ts', 'tryscript.config.js', 'tryscript.config.mjs'];\n\n/**\n * Load config file using dynamic import.\n * Supports both TypeScript (via tsx/ts-node) and JavaScript configs.\n */\nexport async function loadConfig(baseDir: string): Promise<TryscriptConfig> {\n for (const filename of CONFIG_FILES) {\n const configPath = resolve(baseDir, filename);\n if (existsSync(configPath)) {\n const configUrl = pathToFileURL(configPath).href;\n const module = (await import(configUrl)) as { default?: TryscriptConfig } | TryscriptConfig;\n return (module as { default?: TryscriptConfig }).default ?? (module as TryscriptConfig);\n }\n }\n return {};\n}\n\n/**\n * Merge config with frontmatter overrides.\n * Frontmatter takes precedence over config file.\n */\nexport function mergeConfig(base: TryscriptConfig, frontmatter: TestConfig): TryscriptConfig {\n return {\n ...base,\n ...frontmatter,\n env: { ...base.env, ...frontmatter.env },\n patterns: { ...base.patterns, ...frontmatter.patterns },\n fixtures: [...(base.fixtures ?? []), ...(frontmatter.fixtures ?? [])],\n };\n}\n\n/**\n * Helper for typed config files.\n */\nexport function defineConfig(config: TryscriptConfig): TryscriptConfig {\n return config;\n}\n","import { parse as parseYaml } from 'yaml';\nimport type { TestConfig, TestBlock, TestFile } from './types.js';\n\n/** Regex to match YAML frontmatter at the start of a file */\nconst FRONTMATTER_REGEX = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n/;\n\n/** Regex to match fenced code blocks with console/bash info string */\nconst CODE_BLOCK_REGEX = /```(console|bash)\\r?\\n([\\s\\S]*?)```/g;\n\n/** Regex to match markdown headings (for test names) */\nconst HEADING_REGEX = /^#+\\s+(?:Test:\\s*)?(.+)$/m;\n\n/** Regex to match skip annotation in heading or nearby HTML comment */\nconst SKIP_ANNOTATION_REGEX = /<!--\\s*skip\\s*-->/i;\n\n/** Regex to match only annotation in heading or nearby HTML comment */\nconst ONLY_ANNOTATION_REGEX = /<!--\\s*only\\s*-->/i;\n\n/**\n * Parse a .tryscript.md file into structured test data.\n */\nexport function parseTestFile(content: string, filePath: string): TestFile {\n const rawContent = content;\n let config: TestConfig = {};\n let body = content;\n\n // Extract frontmatter if present\n const frontmatterMatch = FRONTMATTER_REGEX.exec(content);\n if (frontmatterMatch) {\n const yamlContent = frontmatterMatch[1] ?? '';\n config = parseYaml(yamlContent) as TestConfig;\n body = content.slice(frontmatterMatch[0].length);\n }\n\n // Parse all console blocks\n const blocks: TestBlock[] = [];\n\n // Reset regex lastIndex\n CODE_BLOCK_REGEX.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = CODE_BLOCK_REGEX.exec(body)) !== null) {\n const blockContent = match[2] ?? '';\n const blockStart = match.index;\n\n // Find the line number (1-indexed)\n const precedingContent = content.slice(0, content.indexOf(match[0]));\n const lineNumber = precedingContent.split('\\n').length;\n\n // Look for a heading before this block (for test name)\n const contentBefore = body.slice(0, blockStart);\n const lastHeadingMatch = [\n ...contentBefore.matchAll(new RegExp(HEADING_REGEX.source, 'gm')),\n ].pop();\n const name = lastHeadingMatch?.[1]?.trim();\n\n // Check for skip/only annotations in the heading line or nearby comments\n const headingContext = lastHeadingMatch\n ? contentBefore.slice(contentBefore.lastIndexOf(lastHeadingMatch[0]))\n : '';\n const skip = SKIP_ANNOTATION_REGEX.test(headingContext);\n const only = ONLY_ANNOTATION_REGEX.test(headingContext);\n\n // Parse the block content\n const parsed = parseBlockContent(blockContent);\n if (parsed) {\n blocks.push({\n name,\n command: parsed.command,\n expectedOutput: parsed.expectedOutput,\n expectedStderr: parsed.expectedStderr,\n expectedExitCode: parsed.expectedExitCode,\n lineNumber,\n rawContent: match[0],\n skip,\n only,\n });\n }\n }\n\n return { path: filePath, config, blocks, rawContent };\n}\n\n/**\n * Parse the content of a single console block.\n */\nfunction parseBlockContent(content: string): {\n command: string;\n expectedOutput: string;\n expectedStderr?: string;\n expectedExitCode: number;\n} | null {\n const lines = content.split('\\n');\n const commandLines: string[] = [];\n const outputLines: string[] = [];\n const stderrLines: string[] = [];\n let expectedExitCode = 0;\n let inCommand = false;\n\n for (const line of lines) {\n if (line.startsWith('$ ')) {\n // Start of a command\n inCommand = true;\n commandLines.push(line.slice(2));\n } else if (line.startsWith('> ') && inCommand) {\n // Continuation of a multi-line command\n commandLines.push(line.slice(2));\n } else if (line.startsWith('? ')) {\n // Exit code specification\n inCommand = false;\n expectedExitCode = parseInt(line.slice(2).trim(), 10);\n } else if (line.startsWith('! ')) {\n // Stderr line (prefixed with !)\n inCommand = false;\n stderrLines.push(line.slice(2));\n } else {\n // Output line (stdout or combined)\n inCommand = false;\n outputLines.push(line);\n }\n }\n\n if (commandLines.length === 0) {\n return null;\n }\n\n // Join command lines, handling shell continuations\n let command = '';\n for (let i = 0; i < commandLines.length; i++) {\n const line = commandLines[i] ?? '';\n if (line.endsWith('\\\\')) {\n command += line.slice(0, -1) + ' ';\n } else {\n command += line;\n if (i < commandLines.length - 1) {\n command += ' ';\n }\n }\n }\n\n // Join output lines, preserving blank lines but trimming trailing empty lines\n let expectedOutput = outputLines.join('\\n');\n expectedOutput = expectedOutput.replace(/\\n+$/, '');\n if (expectedOutput) {\n expectedOutput += '\\n';\n }\n\n // Join stderr lines if any\n let expectedStderr: string | undefined;\n if (stderrLines.length > 0) {\n expectedStderr = stderrLines.join('\\n');\n expectedStderr = expectedStderr.replace(/\\n+$/, '');\n if (expectedStderr) {\n expectedStderr += '\\n';\n }\n }\n\n return { command: command.trim(), expectedOutput, expectedStderr, expectedExitCode };\n}\n","import { spawn } from 'node:child_process';\nimport { mkdtemp, realpath, rm, cp } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, dirname, resolve, basename } from 'node:path';\nimport treeKill from 'tree-kill';\nimport type { TestBlock, TestBlockResult } from './types.js';\nimport type { TryscriptConfig, Fixture } from './config.js';\n\n/** Default timeout in milliseconds */\nconst DEFAULT_TIMEOUT = 30_000;\n\n/**\n * Execution context for a test file.\n * Created once per file, contains directory paths and config.\n */\nexport interface ExecutionContext {\n /** Temporary directory for this test file (resolved, no symlinks) */\n tempDir: string;\n /** Directory containing the test file */\n testDir: string;\n /** Working directory for command execution */\n cwd: string;\n /** Whether running in sandbox mode */\n sandbox: boolean;\n /** Environment variables */\n env: Record<string, string>;\n /** Timeout per command */\n timeout: number;\n /** Before hook script */\n before?: string;\n /** After hook script */\n after?: string;\n /** Whether before hook has been run */\n beforeRan?: boolean;\n}\n\n/**\n * Normalize fixture config to Fixture object.\n */\nfunction normalizeFixture(fixture: string | Fixture): Fixture {\n if (typeof fixture === 'string') {\n return { source: fixture };\n }\n return fixture;\n}\n\n/**\n * Setup fixtures by copying files to sandbox directory.\n */\nasync function setupFixtures(\n fixtures: (string | Fixture)[] | undefined,\n testDir: string,\n sandboxDir: string,\n): Promise<void> {\n if (!fixtures || fixtures.length === 0) {\n return;\n }\n\n for (const f of fixtures) {\n const fixture = normalizeFixture(f);\n const src = resolve(testDir, fixture.source);\n const destName = fixture.dest ?? basename(fixture.source);\n const dst = resolve(sandboxDir, destName);\n await cp(src, dst, { recursive: true });\n }\n}\n\n/**\n * Create an execution context for a test file.\n * @param config - Test configuration\n * @param testFilePath - Path to the test file\n * @param coverageEnv - Optional coverage environment variables (e.g., NODE_V8_COVERAGE)\n */\nexport async function createExecutionContext(\n config: TryscriptConfig,\n testFilePath: string,\n coverageEnv?: Record<string, string>,\n): Promise<ExecutionContext> {\n // Create temp directory and resolve symlinks (e.g., /var -> /private/var on macOS)\n // This ensures [CWD] and [ROOT] patterns match pwd output\n const rawTempDir = await mkdtemp(join(tmpdir(), 'tryscript-'));\n const tempDir = await realpath(rawTempDir);\n\n // Resolve test file directory for portable test commands\n const testDir = resolve(dirname(testFilePath));\n\n // Determine working directory based on sandbox config\n let cwd: string;\n let sandbox = false;\n\n if (config.sandbox === true) {\n // Empty sandbox: run in temp directory\n cwd = tempDir;\n sandbox = true;\n } else if (typeof config.sandbox === 'string') {\n // Copy directory to sandbox: copy source to temp, run in temp\n const srcPath = resolve(testDir, config.sandbox);\n await cp(srcPath, tempDir, { recursive: true });\n cwd = tempDir;\n sandbox = true;\n } else if (config.cwd) {\n // Run in specified directory (relative to test file)\n cwd = resolve(testDir, config.cwd);\n } else {\n // Default: run in test file directory\n cwd = testDir;\n }\n\n // Copy additional fixtures to sandbox (only if sandbox enabled)\n if (sandbox && config.fixtures) {\n await setupFixtures(config.fixtures, testDir, tempDir);\n }\n\n const ctx: ExecutionContext = {\n tempDir,\n testDir,\n cwd,\n sandbox,\n env: {\n ...process.env,\n ...config.env,\n ...coverageEnv,\n // Disable colors by default for deterministic output\n NO_COLOR: config.env?.NO_COLOR ?? '1',\n FORCE_COLOR: '0',\n // Provide test directory for portable test commands\n TRYSCRIPT_TEST_DIR: testDir,\n } as Record<string, string>,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n before: config.before,\n after: config.after,\n };\n\n return ctx;\n}\n\n/**\n * Clean up execution context (remove temp directory).\n */\nexport async function cleanupExecutionContext(ctx: ExecutionContext): Promise<void> {\n await rm(ctx.tempDir, { recursive: true, force: true });\n}\n\n/**\n * Run the before hook if it hasn't run yet.\n */\nexport async function runBeforeHook(ctx: ExecutionContext): Promise<void> {\n if (ctx.before && !ctx.beforeRan) {\n ctx.beforeRan = true;\n await executeCommand(ctx.before, ctx);\n }\n}\n\n/**\n * Run the after hook.\n */\nexport async function runAfterHook(ctx: ExecutionContext): Promise<void> {\n if (ctx.after) {\n await executeCommand(ctx.after, ctx);\n }\n}\n\n/**\n * Run a single test block and return the result.\n */\nexport async function runBlock(block: TestBlock, ctx: ExecutionContext): Promise<TestBlockResult> {\n const startTime = Date.now();\n\n // Handle skip annotation\n if (block.skip) {\n return {\n block,\n passed: true,\n actualOutput: '',\n actualExitCode: 0,\n duration: 0,\n skipped: true,\n };\n }\n\n try {\n // Run before hook if this is the first test\n await runBeforeHook(ctx);\n\n // Execute command directly (shell handles $VAR expansion)\n const { output, stdout, stderr, exitCode } = await executeCommand(block.command, ctx);\n\n const duration = Date.now() - startTime;\n\n return {\n block,\n passed: true, // Matching handled separately\n actualOutput: output,\n actualStdout: stdout,\n actualStderr: stderr,\n actualExitCode: exitCode,\n duration,\n };\n } catch (error) {\n const duration = Date.now() - startTime;\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n block,\n passed: false,\n actualOutput: '',\n actualExitCode: -1,\n duration,\n error: message,\n };\n }\n}\n\n/** Command execution result with separate stdout/stderr */\ninterface CommandResult {\n output: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command and capture output.\n */\nasync function executeCommand(command: string, ctx: ExecutionContext): Promise<CommandResult> {\n return new Promise((resolve, reject) => {\n const proc = spawn(command, {\n shell: true,\n cwd: ctx.cwd,\n env: ctx.env as NodeJS.ProcessEnv,\n // Pipe both to capture\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const combinedChunks: { data: Buffer; type: 'stdout' | 'stderr' }[] = [];\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n\n // Capture data as it comes in to preserve order\n proc.stdout.on('data', (data: Buffer) => {\n combinedChunks.push({ data, type: 'stdout' });\n stdoutChunks.push(data);\n });\n proc.stderr.on('data', (data: Buffer) => {\n combinedChunks.push({ data, type: 'stderr' });\n stderrChunks.push(data);\n });\n\n const timeoutId = setTimeout(() => {\n if (proc.pid) {\n treeKill(proc.pid, 'SIGKILL');\n }\n reject(new Error(`Command timed out after ${ctx.timeout}ms`));\n }, ctx.timeout);\n\n proc.on('close', (code) => {\n clearTimeout(timeoutId);\n const output = Buffer.concat(combinedChunks.map((c) => c.data)).toString('utf-8');\n const stdout = Buffer.concat(stdoutChunks).toString('utf-8');\n const stderr = Buffer.concat(stderrChunks).toString('utf-8');\n resolve({\n output,\n stdout,\n stderr,\n exitCode: code ?? 0,\n });\n });\n\n proc.on('error', (err) => {\n clearTimeout(timeoutId);\n reject(err);\n });\n });\n}\n","import stripAnsi from 'strip-ansi';\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// Marker prefix for patterns (uses Unicode private use chars that won't appear in normal output)\nconst MARKER = '\\uE000';\n\n/**\n * Convert expected output with elision patterns to a regex.\n *\n * Handles (matching trycmd):\n * - [..] — matches any characters on the same line (trycmd: [^\\n]*?)\n * - ... — matches zero or more complete lines (trycmd: \\n(([^\\n]*\\n)*)?)\n * - [EXE] — matches .exe on Windows, empty otherwise\n * - [ROOT] — replaced with test root directory (pre-processed)\n * - [CWD] — replaced with current working directory (pre-processed)\n * - Custom [NAME] patterns from config (trycmd: TestCases::insert_var)\n */\nfunction patternToRegex(\n expected: string,\n customPatterns: Record<string, string | RegExp> = {},\n): RegExp {\n // Build a map of markers to their regex replacements\n const replacements = new Map<string, string>();\n let markerIndex = 0;\n\n const getMarker = (): string => {\n return `${MARKER}${markerIndex++}${MARKER}`;\n };\n\n let processed = expected;\n\n // Replace [..] with marker\n const dotdotMarker = getMarker();\n replacements.set(dotdotMarker, '[^\\\\n]*');\n processed = processed.replaceAll('[..]', dotdotMarker);\n\n // Replace ... (followed by newline) with marker\n const ellipsisMarker = getMarker();\n replacements.set(ellipsisMarker, '(?:[^\\\\n]*\\\\n)*');\n processed = processed.replace(/\\.\\.\\.\\n/g, ellipsisMarker);\n\n // Replace [EXE] with marker\n const exeMarker = getMarker();\n const exe = process.platform === 'win32' ? '\\\\.exe' : '';\n replacements.set(exeMarker, exe);\n processed = processed.replaceAll('[EXE]', exeMarker);\n\n // Replace custom patterns with markers\n for (const [name, pattern] of Object.entries(customPatterns)) {\n const placeholder = `[${name}]`;\n const patternStr = pattern instanceof RegExp ? pattern.source : pattern;\n const marker = getMarker();\n replacements.set(marker, `(${patternStr})`);\n processed = processed.replaceAll(placeholder, marker);\n }\n\n // Escape special regex characters\n let regex = escapeRegex(processed);\n\n // Restore markers to their regex replacements\n for (const [marker, replacement] of replacements) {\n regex = regex.replaceAll(escapeRegex(marker), replacement);\n }\n\n // Match the entire string (dotall mode for . to match newlines if needed)\n return new RegExp(`^${regex}$`, 's');\n}\n\n/**\n * Pre-process expected output to replace path placeholders with actual paths.\n * This happens BEFORE pattern matching.\n */\nfunction preprocessPaths(expected: string, context: { root: string; cwd: string }): string {\n let result = expected;\n // Normalize paths for comparison (use forward slashes)\n const normalizedRoot = context.root.replace(/\\\\/g, '/');\n const normalizedCwd = context.cwd.replace(/\\\\/g, '/');\n result = result.replaceAll('[ROOT]', normalizedRoot);\n result = result.replaceAll('[CWD]', normalizedCwd);\n return result;\n}\n\n/**\n * Normalize actual output for comparison.\n * - Remove ANSI escape codes (colors, etc.)\n * - Normalize line endings to \\n\n * - Normalize paths (Windows backslashes to forward slashes)\n * - Trim trailing whitespace from lines\n * - Ensure single trailing newline\n */\nexport function normalizeOutput(output: string): string {\n // Remove ANSI escape codes first\n let normalized = stripAnsi(output);\n\n normalized = normalized\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n')\n .map((line) => line.trimEnd())\n .join('\\n')\n .replace(/\\n+$/, '\\n');\n\n // Handle empty output\n if (normalized === '\\n') {\n normalized = '';\n }\n\n return normalized;\n}\n\n/**\n * Check if actual output matches expected pattern.\n */\nexport function matchOutput(\n actual: string,\n expected: string,\n context: { root: string; cwd: string },\n customPatterns: Record<string, string | RegExp> = {},\n): boolean {\n const normalizedActual = normalizeOutput(actual);\n const normalizedExpected = normalizeOutput(expected);\n\n // Empty expected matches empty actual\n if (normalizedExpected === '' && normalizedActual === '') {\n return true;\n }\n\n const preprocessed = preprocessPaths(normalizedExpected, context);\n const regex = patternToRegex(preprocessed, customPatterns);\n return regex.test(normalizedActual);\n}\n","// Public API exports\n\n// Version constant (injected at build time)\ndeclare const __VERSION__: string;\nexport const VERSION: string = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'development';\n\n// Config helper\nexport { defineConfig } from './lib/config.js';\nexport type { TryscriptConfig } from './lib/config.js';\n\n// Types\nexport type {\n TestConfig,\n TestBlock,\n TestFile,\n TestBlockResult,\n TestFileResult,\n TestRunSummary,\n CoverageConfig,\n CoverageContext,\n} from './lib/types.js';\n\n// Core functions (for programmatic use)\nexport { parseTestFile } from './lib/parser.js';\nexport { runBlock, createExecutionContext, cleanupExecutionContext } from './lib/runner.js';\nexport type { ExecutionContext } from './lib/runner.js';\nexport { matchOutput, normalizeOutput } from './lib/matcher.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"src-BBeKy_V9.mjs","names":["DEFAULT_COVERAGE_CONFIG: Required<CoverageConfig>","config: TestConfig","parseYaml","blocks: TestBlock[]","match: RegExpExecArray | null","commandLines: string[]","outputLines: string[]","stderrLines: string[]","expectedStderr: string | undefined","cwd: string","combinedChunks: { data: Buffer; type: 'stdout' | 'stderr' }[]","stdoutChunks: Buffer[]","stderrChunks: Buffer[]","VERSION: string"],"sources":["../src/lib/config.ts","../src/lib/parser.ts","../src/lib/runner.ts","../src/lib/matcher.ts","../src/index.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { TestConfig, CoverageConfig } from './types.js';\n\n/** Fixture configuration for copying files to sandbox directory */\nexport interface Fixture {\n /** Source path (resolved relative to test file) */\n source: string;\n /** Destination path (resolved relative to sandbox dir) */\n dest?: string;\n}\n\nexport interface TryscriptConfig {\n /** Working directory for commands (default: test file directory) */\n cwd?: string;\n /** Run in isolated sandbox: true = empty temp, path = copy to temp */\n sandbox?: boolean | string;\n /** Fixtures to copy to sandbox directory before tests */\n fixtures?: (string | Fixture)[];\n /** Script to run before first test block */\n before?: string;\n /** Script to run after all test blocks */\n after?: string;\n env?: Record<string, string>;\n timeout?: number;\n patterns?: Record<string, RegExp | string>;\n tests?: string[];\n /** Coverage configuration (used with --coverage flag) */\n coverage?: CoverageConfig;\n}\n\n/** Default coverage configuration values. */\nexport const DEFAULT_COVERAGE_CONFIG: Required<CoverageConfig> = {\n reportsDir: 'coverage-tryscript',\n reporters: ['text', 'html'],\n include: ['dist/**'],\n exclude: [],\n excludeNodeModules: true,\n excludeAfterRemap: false,\n skipFull: false,\n allowExternal: false,\n src: 'src',\n monocart: false,\n};\n\n/**\n * Resolve coverage options by merging user config with defaults.\n */\nexport function resolveCoverageConfig(config?: CoverageConfig): Required<CoverageConfig> {\n return {\n reportsDir: config?.reportsDir ?? DEFAULT_COVERAGE_CONFIG.reportsDir,\n reporters: config?.reporters ?? DEFAULT_COVERAGE_CONFIG.reporters,\n include: config?.include ?? DEFAULT_COVERAGE_CONFIG.include,\n exclude: config?.exclude ?? DEFAULT_COVERAGE_CONFIG.exclude,\n excludeNodeModules: config?.excludeNodeModules ?? DEFAULT_COVERAGE_CONFIG.excludeNodeModules,\n excludeAfterRemap: config?.excludeAfterRemap ?? DEFAULT_COVERAGE_CONFIG.excludeAfterRemap,\n skipFull: config?.skipFull ?? DEFAULT_COVERAGE_CONFIG.skipFull,\n allowExternal: config?.allowExternal ?? DEFAULT_COVERAGE_CONFIG.allowExternal,\n src: config?.src ?? DEFAULT_COVERAGE_CONFIG.src,\n monocart: config?.monocart ?? DEFAULT_COVERAGE_CONFIG.monocart,\n };\n}\n\nconst CONFIG_FILES = ['tryscript.config.ts', 'tryscript.config.js', 'tryscript.config.mjs'];\n\n/**\n * Load config file using dynamic import.\n * Supports both TypeScript (via tsx/ts-node) and JavaScript configs.\n */\nexport async function loadConfig(baseDir: string): Promise<TryscriptConfig> {\n for (const filename of CONFIG_FILES) {\n const configPath = resolve(baseDir, filename);\n if (existsSync(configPath)) {\n const configUrl = pathToFileURL(configPath).href;\n const module = (await import(configUrl)) as { default?: TryscriptConfig } | TryscriptConfig;\n return (module as { default?: TryscriptConfig }).default ?? (module as TryscriptConfig);\n }\n }\n return {};\n}\n\n/**\n * Merge config with frontmatter overrides.\n * Frontmatter takes precedence over config file.\n */\nexport function mergeConfig(base: TryscriptConfig, frontmatter: TestConfig): TryscriptConfig {\n return {\n ...base,\n ...frontmatter,\n env: { ...base.env, ...frontmatter.env },\n patterns: { ...base.patterns, ...frontmatter.patterns },\n fixtures: [...(base.fixtures ?? []), ...(frontmatter.fixtures ?? [])],\n };\n}\n\n/**\n * Helper for typed config files.\n */\nexport function defineConfig(config: TryscriptConfig): TryscriptConfig {\n return config;\n}\n","import { parse as parseYaml } from 'yaml';\nimport type { TestConfig, TestBlock, TestFile } from './types.js';\n\n/** Regex to match YAML frontmatter at the start of a file */\nconst FRONTMATTER_REGEX = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n/;\n\n/** Regex to match fenced code blocks with console/bash info string */\nconst CODE_BLOCK_REGEX = /```(console|bash)\\r?\\n([\\s\\S]*?)```/g;\n\n/** Regex to match markdown headings (for test names) */\nconst HEADING_REGEX = /^#+\\s+(?:Test:\\s*)?(.+)$/m;\n\n/** Regex to match skip annotation in heading or nearby HTML comment */\nconst SKIP_ANNOTATION_REGEX = /<!--\\s*skip\\s*-->/i;\n\n/** Regex to match only annotation in heading or nearby HTML comment */\nconst ONLY_ANNOTATION_REGEX = /<!--\\s*only\\s*-->/i;\n\n/**\n * Parse a .tryscript.md file into structured test data.\n */\nexport function parseTestFile(content: string, filePath: string): TestFile {\n const rawContent = content;\n let config: TestConfig = {};\n let body = content;\n\n // Extract frontmatter if present\n const frontmatterMatch = FRONTMATTER_REGEX.exec(content);\n if (frontmatterMatch) {\n const yamlContent = frontmatterMatch[1] ?? '';\n config = parseYaml(yamlContent) as TestConfig;\n body = content.slice(frontmatterMatch[0].length);\n }\n\n // Parse all console blocks\n const blocks: TestBlock[] = [];\n\n // Reset regex lastIndex\n CODE_BLOCK_REGEX.lastIndex = 0;\n\n let match: RegExpExecArray | null;\n while ((match = CODE_BLOCK_REGEX.exec(body)) !== null) {\n const blockContent = match[2] ?? '';\n const blockStart = match.index;\n\n // Find the line number (1-indexed)\n const precedingContent = content.slice(0, content.indexOf(match[0]));\n const lineNumber = precedingContent.split('\\n').length;\n\n // Look for a heading before this block (for test name)\n const contentBefore = body.slice(0, blockStart);\n const lastHeadingMatch = [\n ...contentBefore.matchAll(new RegExp(HEADING_REGEX.source, 'gm')),\n ].pop();\n const name = lastHeadingMatch?.[1]?.trim();\n\n // Check for skip/only annotations in the heading line or nearby comments\n const headingContext = lastHeadingMatch\n ? contentBefore.slice(contentBefore.lastIndexOf(lastHeadingMatch[0]))\n : '';\n const skip = SKIP_ANNOTATION_REGEX.test(headingContext);\n const only = ONLY_ANNOTATION_REGEX.test(headingContext);\n\n // Parse the block content\n const parsed = parseBlockContent(blockContent);\n if (parsed) {\n blocks.push({\n name,\n command: parsed.command,\n expectedOutput: parsed.expectedOutput,\n expectedStderr: parsed.expectedStderr,\n expectedExitCode: parsed.expectedExitCode,\n lineNumber,\n rawContent: match[0],\n skip,\n only,\n });\n }\n }\n\n return { path: filePath, config, blocks, rawContent };\n}\n\n/**\n * Parse the content of a single console block.\n */\nfunction parseBlockContent(content: string): {\n command: string;\n expectedOutput: string;\n expectedStderr?: string;\n expectedExitCode: number;\n} | null {\n const lines = content.split('\\n');\n const commandLines: string[] = [];\n const outputLines: string[] = [];\n const stderrLines: string[] = [];\n let expectedExitCode = 0;\n let inCommand = false;\n\n for (const line of lines) {\n if (line.startsWith('$ ')) {\n // Start of a command\n inCommand = true;\n commandLines.push(line.slice(2));\n } else if (line.startsWith('> ') && inCommand) {\n // Continuation of a multi-line command\n commandLines.push(line.slice(2));\n } else if (line.startsWith('? ')) {\n // Exit code specification\n inCommand = false;\n expectedExitCode = parseInt(line.slice(2).trim(), 10);\n } else if (line.startsWith('! ')) {\n // Stderr line (prefixed with !)\n inCommand = false;\n stderrLines.push(line.slice(2));\n } else {\n // Output line (stdout or combined)\n inCommand = false;\n outputLines.push(line);\n }\n }\n\n if (commandLines.length === 0) {\n return null;\n }\n\n // Join command lines, handling shell continuations\n let command = '';\n for (let i = 0; i < commandLines.length; i++) {\n const line = commandLines[i] ?? '';\n if (line.endsWith('\\\\')) {\n command += line.slice(0, -1) + ' ';\n } else {\n command += line;\n if (i < commandLines.length - 1) {\n command += ' ';\n }\n }\n }\n\n // Join output lines, preserving blank lines but trimming trailing empty lines\n let expectedOutput = outputLines.join('\\n');\n expectedOutput = expectedOutput.replace(/\\n+$/, '');\n if (expectedOutput) {\n expectedOutput += '\\n';\n }\n\n // Join stderr lines if any\n let expectedStderr: string | undefined;\n if (stderrLines.length > 0) {\n expectedStderr = stderrLines.join('\\n');\n expectedStderr = expectedStderr.replace(/\\n+$/, '');\n if (expectedStderr) {\n expectedStderr += '\\n';\n }\n }\n\n return { command: command.trim(), expectedOutput, expectedStderr, expectedExitCode };\n}\n","import { spawn } from 'node:child_process';\nimport { mkdtemp, realpath, rm, cp } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, dirname, resolve, basename } from 'node:path';\nimport treeKill from 'tree-kill';\nimport type { TestBlock, TestBlockResult } from './types.js';\nimport type { TryscriptConfig, Fixture } from './config.js';\n\n/** Default timeout in milliseconds */\nconst DEFAULT_TIMEOUT = 30_000;\n\n/**\n * Execution context for a test file.\n * Created once per file, contains directory paths and config.\n */\nexport interface ExecutionContext {\n /** Temporary directory for this test file (resolved, no symlinks) */\n tempDir: string;\n /** Directory containing the test file */\n testDir: string;\n /** Working directory for command execution */\n cwd: string;\n /** Whether running in sandbox mode */\n sandbox: boolean;\n /** Environment variables */\n env: Record<string, string>;\n /** Timeout per command */\n timeout: number;\n /** Before hook script */\n before?: string;\n /** After hook script */\n after?: string;\n /** Whether before hook has been run */\n beforeRan?: boolean;\n}\n\n/**\n * Normalize fixture config to Fixture object.\n */\nfunction normalizeFixture(fixture: string | Fixture): Fixture {\n if (typeof fixture === 'string') {\n return { source: fixture };\n }\n return fixture;\n}\n\n/**\n * Setup fixtures by copying files to sandbox directory.\n */\nasync function setupFixtures(\n fixtures: (string | Fixture)[] | undefined,\n testDir: string,\n sandboxDir: string,\n): Promise<void> {\n if (!fixtures || fixtures.length === 0) {\n return;\n }\n\n for (const f of fixtures) {\n const fixture = normalizeFixture(f);\n const src = resolve(testDir, fixture.source);\n const destName = fixture.dest ?? basename(fixture.source);\n const dst = resolve(sandboxDir, destName);\n await cp(src, dst, { recursive: true });\n }\n}\n\n/**\n * Create an execution context for a test file.\n * @param config - Test configuration\n * @param testFilePath - Path to the test file\n * @param coverageEnv - Optional coverage environment variables (e.g., NODE_V8_COVERAGE)\n */\nexport async function createExecutionContext(\n config: TryscriptConfig,\n testFilePath: string,\n coverageEnv?: Record<string, string>,\n): Promise<ExecutionContext> {\n // Create temp directory and resolve symlinks (e.g., /var -> /private/var on macOS)\n // This ensures [CWD] and [ROOT] patterns match pwd output\n const rawTempDir = await mkdtemp(join(tmpdir(), 'tryscript-'));\n const tempDir = await realpath(rawTempDir);\n\n // Resolve test file directory for portable test commands\n const testDir = resolve(dirname(testFilePath));\n\n // Determine working directory based on sandbox config\n let cwd: string;\n let sandbox = false;\n\n if (config.sandbox === true) {\n // Empty sandbox: run in temp directory\n cwd = tempDir;\n sandbox = true;\n } else if (typeof config.sandbox === 'string') {\n // Copy directory to sandbox: copy source to temp, run in temp\n const srcPath = resolve(testDir, config.sandbox);\n await cp(srcPath, tempDir, { recursive: true });\n cwd = tempDir;\n sandbox = true;\n } else if (config.cwd) {\n // Run in specified directory (relative to test file)\n cwd = resolve(testDir, config.cwd);\n } else {\n // Default: run in test file directory\n cwd = testDir;\n }\n\n // Copy additional fixtures to sandbox (only if sandbox enabled)\n if (sandbox && config.fixtures) {\n await setupFixtures(config.fixtures, testDir, tempDir);\n }\n\n const ctx: ExecutionContext = {\n tempDir,\n testDir,\n cwd,\n sandbox,\n env: {\n ...process.env,\n ...config.env,\n ...coverageEnv,\n // Disable colors by default for deterministic output\n NO_COLOR: config.env?.NO_COLOR ?? '1',\n FORCE_COLOR: '0',\n // Provide test directory for portable test commands\n TRYSCRIPT_TEST_DIR: testDir,\n } as Record<string, string>,\n timeout: config.timeout ?? DEFAULT_TIMEOUT,\n before: config.before,\n after: config.after,\n };\n\n return ctx;\n}\n\n/**\n * Clean up execution context (remove temp directory).\n */\nexport async function cleanupExecutionContext(ctx: ExecutionContext): Promise<void> {\n await rm(ctx.tempDir, { recursive: true, force: true });\n}\n\n/**\n * Run the before hook if it hasn't run yet.\n */\nexport async function runBeforeHook(ctx: ExecutionContext): Promise<void> {\n if (ctx.before && !ctx.beforeRan) {\n ctx.beforeRan = true;\n await executeCommand(ctx.before, ctx);\n }\n}\n\n/**\n * Run the after hook.\n */\nexport async function runAfterHook(ctx: ExecutionContext): Promise<void> {\n if (ctx.after) {\n await executeCommand(ctx.after, ctx);\n }\n}\n\n/**\n * Run a single test block and return the result.\n */\nexport async function runBlock(block: TestBlock, ctx: ExecutionContext): Promise<TestBlockResult> {\n const startTime = Date.now();\n\n // Handle skip annotation\n if (block.skip) {\n return {\n block,\n passed: true,\n actualOutput: '',\n actualExitCode: 0,\n duration: 0,\n skipped: true,\n };\n }\n\n try {\n // Run before hook if this is the first test\n await runBeforeHook(ctx);\n\n // Execute command directly (shell handles $VAR expansion)\n const { output, stdout, stderr, exitCode } = await executeCommand(block.command, ctx);\n\n const duration = Date.now() - startTime;\n\n return {\n block,\n passed: true, // Matching handled separately\n actualOutput: output,\n actualStdout: stdout,\n actualStderr: stderr,\n actualExitCode: exitCode,\n duration,\n };\n } catch (error) {\n const duration = Date.now() - startTime;\n const message = error instanceof Error ? error.message : String(error);\n\n return {\n block,\n passed: false,\n actualOutput: '',\n actualExitCode: -1,\n duration,\n error: message,\n };\n }\n}\n\n/** Command execution result with separate stdout/stderr */\ninterface CommandResult {\n output: string;\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Execute a command and capture output.\n */\nasync function executeCommand(command: string, ctx: ExecutionContext): Promise<CommandResult> {\n return new Promise((resolve, reject) => {\n const proc = spawn(command, {\n shell: true,\n cwd: ctx.cwd,\n env: ctx.env as NodeJS.ProcessEnv,\n // Pipe both to capture\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const combinedChunks: { data: Buffer; type: 'stdout' | 'stderr' }[] = [];\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n\n // Capture data as it comes in to preserve order\n proc.stdout.on('data', (data: Buffer) => {\n combinedChunks.push({ data, type: 'stdout' });\n stdoutChunks.push(data);\n });\n proc.stderr.on('data', (data: Buffer) => {\n combinedChunks.push({ data, type: 'stderr' });\n stderrChunks.push(data);\n });\n\n const timeoutId = setTimeout(() => {\n if (proc.pid) {\n treeKill(proc.pid, 'SIGKILL');\n }\n reject(new Error(`Command timed out after ${ctx.timeout}ms`));\n }, ctx.timeout);\n\n proc.on('close', (code) => {\n clearTimeout(timeoutId);\n const output = Buffer.concat(combinedChunks.map((c) => c.data)).toString('utf-8');\n const stdout = Buffer.concat(stdoutChunks).toString('utf-8');\n const stderr = Buffer.concat(stderrChunks).toString('utf-8');\n resolve({\n output,\n stdout,\n stderr,\n exitCode: code ?? 0,\n });\n });\n\n proc.on('error', (err) => {\n clearTimeout(timeoutId);\n reject(err);\n });\n });\n}\n","import stripAnsi from 'strip-ansi';\n\n/**\n * Escape special regex characters in a string.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n// Marker prefix for patterns (uses Unicode private use chars that won't appear in normal output)\nconst MARKER = '\\uE000';\n\n/**\n * Convert expected output with elision patterns to a regex.\n *\n * Handles (matching trycmd):\n * - [..] — matches any characters on the same line (trycmd: [^\\n]*?)\n * - ... — matches zero or more complete lines (trycmd: \\n(([^\\n]*\\n)*)?)\n * - [EXE] — matches .exe on Windows, empty otherwise\n * - [ROOT] — replaced with test root directory (pre-processed)\n * - [CWD] — replaced with current working directory (pre-processed)\n * - Custom [NAME] patterns from config (trycmd: TestCases::insert_var)\n */\nfunction patternToRegex(\n expected: string,\n customPatterns: Record<string, string | RegExp> = {},\n): RegExp {\n // Build a map of markers to their regex replacements\n const replacements = new Map<string, string>();\n let markerIndex = 0;\n\n const getMarker = (): string => {\n return `${MARKER}${markerIndex++}${MARKER}`;\n };\n\n let processed = expected;\n\n // Replace [..] with marker\n const dotdotMarker = getMarker();\n replacements.set(dotdotMarker, '[^\\\\n]*');\n processed = processed.replaceAll('[..]', dotdotMarker);\n\n // Replace ... (followed by newline) with marker\n const ellipsisMarker = getMarker();\n replacements.set(ellipsisMarker, '(?:[^\\\\n]*\\\\n)*');\n processed = processed.replace(/\\.\\.\\.\\n/g, ellipsisMarker);\n\n // Replace [EXE] with marker\n const exeMarker = getMarker();\n const exe = process.platform === 'win32' ? '\\\\.exe' : '';\n replacements.set(exeMarker, exe);\n processed = processed.replaceAll('[EXE]', exeMarker);\n\n // Replace custom patterns with markers\n for (const [name, pattern] of Object.entries(customPatterns)) {\n const placeholder = `[${name}]`;\n const patternStr = pattern instanceof RegExp ? pattern.source : pattern;\n const marker = getMarker();\n replacements.set(marker, `(${patternStr})`);\n processed = processed.replaceAll(placeholder, marker);\n }\n\n // Escape special regex characters\n let regex = escapeRegex(processed);\n\n // Restore markers to their regex replacements\n for (const [marker, replacement] of replacements) {\n regex = regex.replaceAll(escapeRegex(marker), replacement);\n }\n\n // Match the entire string (dotall mode for . to match newlines if needed)\n return new RegExp(`^${regex}$`, 's');\n}\n\n/**\n * Pre-process expected output to replace path placeholders with actual paths.\n * This happens BEFORE pattern matching.\n */\nfunction preprocessPaths(expected: string, context: { root: string; cwd: string }): string {\n let result = expected;\n // Normalize paths for comparison (use forward slashes)\n const normalizedRoot = context.root.replace(/\\\\/g, '/');\n const normalizedCwd = context.cwd.replace(/\\\\/g, '/');\n result = result.replaceAll('[ROOT]', normalizedRoot);\n result = result.replaceAll('[CWD]', normalizedCwd);\n return result;\n}\n\n/**\n * Normalize actual output for comparison.\n * - Remove ANSI escape codes (colors, etc.)\n * - Normalize line endings to \\n\n * - Normalize paths (Windows backslashes to forward slashes)\n * - Trim trailing whitespace from lines\n * - Ensure single trailing newline\n */\nexport function normalizeOutput(output: string): string {\n // Remove ANSI escape codes first\n let normalized = stripAnsi(output);\n\n normalized = normalized\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .split('\\n')\n .map((line) => line.trimEnd())\n .join('\\n')\n .replace(/\\n+$/, '\\n');\n\n // Handle empty output\n if (normalized === '\\n') {\n normalized = '';\n }\n\n return normalized;\n}\n\n/**\n * Check if actual output matches expected pattern.\n */\nexport function matchOutput(\n actual: string,\n expected: string,\n context: { root: string; cwd: string },\n customPatterns: Record<string, string | RegExp> = {},\n): boolean {\n const normalizedActual = normalizeOutput(actual);\n const normalizedExpected = normalizeOutput(expected);\n\n // Empty expected matches empty actual\n if (normalizedExpected === '' && normalizedActual === '') {\n return true;\n }\n\n const preprocessed = preprocessPaths(normalizedExpected, context);\n const regex = patternToRegex(preprocessed, customPatterns);\n return regex.test(normalizedActual);\n}\n","// Public API exports\n\n// Version constant (injected at build time)\ndeclare const __VERSION__: string;\nexport const VERSION: string = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'development';\n\n// Config helper\nexport { defineConfig } from './lib/config.js';\nexport type { TryscriptConfig } from './lib/config.js';\n\n// Types\nexport type {\n TestConfig,\n TestBlock,\n TestFile,\n TestBlockResult,\n TestFileResult,\n TestRunSummary,\n CoverageConfig,\n CoverageContext,\n} from './lib/types.js';\n\n// Core functions (for programmatic use)\nexport { parseTestFile } from './lib/parser.js';\nexport { runBlock, createExecutionContext, cleanupExecutionContext } from './lib/runner.js';\nexport type { ExecutionContext } from './lib/runner.js';\nexport { matchOutput, normalizeOutput } from './lib/matcher.js';\n"],"mappings":"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"}