tryscript 0.0.1 → 0.1.1
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/LICENSE +21 -0
- package/README.md +80 -171
- package/dist/bin.cjs +249 -33
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.mjs +251 -35
- package/dist/bin.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +115 -10
- package/dist/index.d.mts +115 -10
- package/dist/index.mjs +1 -1
- package/dist/{src-UjaSQrqA.mjs → src-CndHSuTT.mjs} +137 -19
- package/dist/src-CndHSuTT.mjs.map +1 -0
- package/dist/{src-CeUA446P.cjs → src-CxUUK92Q.cjs} +146 -16
- package/dist/src-CxUUK92Q.cjs.map +1 -0
- package/docs/tryscript-reference.md +402 -84
- package/package.json +23 -16
- package/dist/src-CeUA446P.cjs.map +0 -1
- package/dist/src-UjaSQrqA.mjs.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -1,21 +1,87 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
const require_src = require('./src-
|
|
4
|
+
const require_src = require('./src-CxUUK92Q.cjs');
|
|
5
5
|
let node_url = require("node:url");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
|
+
let node_child_process = require("node:child_process");
|
|
8
9
|
let node_fs_promises = require("node:fs/promises");
|
|
10
|
+
let node_os = require("node:os");
|
|
9
11
|
let commander = require("commander");
|
|
10
|
-
let picocolors = require("picocolors");
|
|
11
|
-
picocolors = require_src.__toESM(picocolors);
|
|
12
12
|
let fast_glob = require("fast-glob");
|
|
13
13
|
fast_glob = require_src.__toESM(fast_glob);
|
|
14
|
+
let picocolors = require("picocolors");
|
|
15
|
+
picocolors = require_src.__toESM(picocolors);
|
|
14
16
|
let diff = require("diff");
|
|
15
17
|
let atomically = require("atomically");
|
|
16
18
|
|
|
19
|
+
//#region src/cli/lib/shared.ts
|
|
20
|
+
/**
|
|
21
|
+
* Shared color utilities for consistent terminal output.
|
|
22
|
+
*/
|
|
23
|
+
const colors = {
|
|
24
|
+
success: (s) => picocolors.default.green(s),
|
|
25
|
+
error: (s) => picocolors.default.red(s),
|
|
26
|
+
info: (s) => picocolors.default.cyan(s),
|
|
27
|
+
warn: (s) => picocolors.default.yellow(s),
|
|
28
|
+
muted: (s) => picocolors.default.gray(s),
|
|
29
|
+
bold: (s) => picocolors.default.bold(s)
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Status indicators with emoji.
|
|
33
|
+
*/
|
|
34
|
+
const status = {
|
|
35
|
+
pass: picocolors.default.green("✓"),
|
|
36
|
+
fail: picocolors.default.red("✗"),
|
|
37
|
+
skip: picocolors.default.yellow("○"),
|
|
38
|
+
update: picocolors.default.yellow("↻")
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Configure Commander.js with colored help text.
|
|
42
|
+
* Applies consistent styling: cyan titles, green commands, yellow options.
|
|
43
|
+
*/
|
|
44
|
+
function withColoredHelp(cmd) {
|
|
45
|
+
cmd.configureHelp({
|
|
46
|
+
styleTitle: (str) => picocolors.default.bold(picocolors.default.cyan(str)),
|
|
47
|
+
styleCommandText: (str) => picocolors.default.green(str),
|
|
48
|
+
styleOptionText: (str) => picocolors.default.yellow(str),
|
|
49
|
+
showGlobalOptions: true
|
|
50
|
+
});
|
|
51
|
+
return cmd;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Log a warning message to stderr.
|
|
55
|
+
*/
|
|
56
|
+
function logWarn(message) {
|
|
57
|
+
console.error(colors.warn(message));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Log an error message to stderr.
|
|
61
|
+
*/
|
|
62
|
+
function logError(message) {
|
|
63
|
+
console.error(colors.error(message));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
17
67
|
//#region src/lib/reporter.ts
|
|
18
68
|
/**
|
|
69
|
+
* Test result reporting utilities.
|
|
70
|
+
*
|
|
71
|
+
* Handles output formatting for test results, diffs, and summaries.
|
|
72
|
+
*/
|
|
73
|
+
const statusIcon = {
|
|
74
|
+
pass: picocolors.default.green("✓"),
|
|
75
|
+
fail: picocolors.default.red("✗")
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Format a duration in milliseconds for display.
|
|
79
|
+
*/
|
|
80
|
+
function formatDuration(ms) {
|
|
81
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
82
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
19
85
|
* Create a unified diff between expected and actual output.
|
|
20
86
|
*/
|
|
21
87
|
function createDiff(expected, actual, filename) {
|
|
@@ -27,26 +93,19 @@ function createDiff(expected, actual, filename) {
|
|
|
27
93
|
}).join("\n");
|
|
28
94
|
}
|
|
29
95
|
/**
|
|
30
|
-
* Format a duration in milliseconds for display.
|
|
31
|
-
*/
|
|
32
|
-
function formatDuration(ms) {
|
|
33
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
34
|
-
return `${(ms / 1e3).toFixed(2)}s`;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
96
|
* Report results for a single file.
|
|
38
97
|
*/
|
|
39
98
|
function reportFile(result, options) {
|
|
40
99
|
const filename = result.file.path;
|
|
41
|
-
const status = result.passed ? picocolors.default.green(picocolors.default.bold("PASS")) : picocolors.default.red(picocolors.default.bold("FAIL"));
|
|
100
|
+
const status$1 = result.passed ? picocolors.default.green(picocolors.default.bold("PASS")) : picocolors.default.red(picocolors.default.bold("FAIL"));
|
|
42
101
|
if (options.quiet && result.passed) return;
|
|
43
|
-
console.error(`${status} ${filename}`);
|
|
102
|
+
console.error(`${status$1} ${filename}`);
|
|
44
103
|
for (const blockResult of result.results) {
|
|
45
104
|
const name = blockResult.block.name ?? `Line ${blockResult.block.lineNumber}`;
|
|
46
105
|
if (blockResult.passed) {
|
|
47
|
-
if (!options.quiet) console.error(` ${
|
|
106
|
+
if (!options.quiet) console.error(` ${statusIcon.pass} ${name}`);
|
|
48
107
|
} else {
|
|
49
|
-
console.error(` ${
|
|
108
|
+
console.error(` ${statusIcon.fail} ${name}`);
|
|
50
109
|
if (blockResult.error) console.error(` ${picocolors.default.red(blockResult.error)}`);
|
|
51
110
|
else {
|
|
52
111
|
if (blockResult.actualExitCode !== blockResult.block.expectedExitCode) console.error(` Expected exit code ${blockResult.block.expectedExitCode}, got ${blockResult.actualExitCode}`);
|
|
@@ -113,8 +172,114 @@ function buildUpdatedBlock(block, result) {
|
|
|
113
172
|
return lines.join("\n");
|
|
114
173
|
}
|
|
115
174
|
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/lib/coverage.ts
|
|
177
|
+
/**
|
|
178
|
+
* Coverage collection for CLI subprocess testing.
|
|
179
|
+
*
|
|
180
|
+
* Uses c8 and NODE_V8_COVERAGE to collect coverage from spawned processes.
|
|
181
|
+
*/
|
|
182
|
+
/**
|
|
183
|
+
* Find the c8 executable path.
|
|
184
|
+
* Checks local node_modules/.bin first, then falls back to npx.
|
|
185
|
+
*/
|
|
186
|
+
function findC8Path() {
|
|
187
|
+
const localPaths = [(0, node_path.resolve)(process.cwd(), "node_modules", ".bin", "c8"), (0, node_path.resolve)(process.cwd(), "..", "..", "node_modules", ".bin", "c8")];
|
|
188
|
+
for (const localPath of localPaths) if ((0, node_fs.existsSync)(localPath)) return localPath;
|
|
189
|
+
return "npx c8";
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Check if c8 is available in the current environment.
|
|
193
|
+
*/
|
|
194
|
+
async function isC8Available() {
|
|
195
|
+
const c8Path = findC8Path();
|
|
196
|
+
return new Promise((resolve$1) => {
|
|
197
|
+
const isNpx = c8Path === "npx c8";
|
|
198
|
+
const proc = (0, node_child_process.spawn)(isNpx ? "npx" : c8Path, isNpx ? ["c8", "--version"] : ["--version"], {
|
|
199
|
+
shell: false,
|
|
200
|
+
stdio: "ignore"
|
|
201
|
+
});
|
|
202
|
+
proc.on("close", (code) => {
|
|
203
|
+
resolve$1(code === 0);
|
|
204
|
+
});
|
|
205
|
+
proc.on("error", () => {
|
|
206
|
+
resolve$1(false);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Create a coverage context for collecting V8 coverage data.
|
|
212
|
+
*/
|
|
213
|
+
async function createCoverageContext(config) {
|
|
214
|
+
const options = require_src.resolveCoverageConfig(config);
|
|
215
|
+
return {
|
|
216
|
+
tempDir: await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "tryscript-coverage-")),
|
|
217
|
+
options
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get environment variables for enabling V8 coverage in spawned processes.
|
|
222
|
+
*/
|
|
223
|
+
function getCoverageEnv(ctx) {
|
|
224
|
+
return { NODE_V8_COVERAGE: ctx.tempDir };
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Generate coverage report from collected V8 coverage data using c8.
|
|
228
|
+
* Throws an error if coverage report generation fails.
|
|
229
|
+
*/
|
|
230
|
+
async function generateCoverageReport(ctx) {
|
|
231
|
+
const { options, tempDir } = ctx;
|
|
232
|
+
const c8Path = findC8Path();
|
|
233
|
+
const reportArgs = [
|
|
234
|
+
"report",
|
|
235
|
+
"--temp-directory",
|
|
236
|
+
tempDir,
|
|
237
|
+
"--reports-dir",
|
|
238
|
+
options.reportsDir,
|
|
239
|
+
"--src",
|
|
240
|
+
options.src,
|
|
241
|
+
"--all",
|
|
242
|
+
...options.include.flatMap((pattern) => ["--include", pattern]),
|
|
243
|
+
...options.reporters.flatMap((reporter) => ["--reporter", reporter])
|
|
244
|
+
];
|
|
245
|
+
const isNpx = c8Path === "npx c8";
|
|
246
|
+
const command = isNpx ? "npx" : c8Path;
|
|
247
|
+
const args = isNpx ? ["c8", ...reportArgs] : reportArgs;
|
|
248
|
+
await new Promise((resolvePromise, reject) => {
|
|
249
|
+
const proc = (0, node_child_process.spawn)(command, args, {
|
|
250
|
+
shell: false,
|
|
251
|
+
stdio: "inherit"
|
|
252
|
+
});
|
|
253
|
+
proc.on("close", (code) => {
|
|
254
|
+
if (code === 0) resolvePromise();
|
|
255
|
+
else reject(/* @__PURE__ */ new Error(`c8 report exited with code ${code}`));
|
|
256
|
+
});
|
|
257
|
+
proc.on("error", (err) => {
|
|
258
|
+
reject(/* @__PURE__ */ new Error(`Failed to run c8 report: ${err.message}`));
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Clean up coverage context by removing the temporary directory.
|
|
264
|
+
*/
|
|
265
|
+
async function cleanupCoverageContext(ctx) {
|
|
266
|
+
try {
|
|
267
|
+
await (0, node_fs_promises.access)(ctx.tempDir);
|
|
268
|
+
await (0, node_fs_promises.rm)(ctx.tempDir, {
|
|
269
|
+
recursive: true,
|
|
270
|
+
force: true
|
|
271
|
+
});
|
|
272
|
+
} catch {}
|
|
273
|
+
}
|
|
274
|
+
|
|
116
275
|
//#endregion
|
|
117
276
|
//#region src/cli/commands/run.ts
|
|
277
|
+
/**
|
|
278
|
+
* Register the run command.
|
|
279
|
+
*/
|
|
280
|
+
function registerRunCommand(program) {
|
|
281
|
+
program.command("run").description("Run golden tests").argument("[files...]", "Test files to run (default: **/*.tryscript.md)").option("--update", "Update golden files with actual output").option("--diff", "Show diff on failure (default: true)").option("--no-diff", "Hide diff on failure").option("--fail-fast", "Stop on first failure").option("--filter <pattern>", "Filter tests by name pattern").option("--verbose", "Show detailed output including passing test output").option("--quiet", "Suppress non-essential output (only show failures)").option("--coverage", "Enable code coverage collection (requires c8)").option("--coverage-dir <dir>", "Coverage output directory (default: coverage-tryscript)").option("--coverage-reporter <reporter...>", "Coverage reporters (default: text, html). Can be specified multiple times.").action(runCommand);
|
|
282
|
+
}
|
|
118
283
|
async function runCommand(files, options) {
|
|
119
284
|
const startTime = Date.now();
|
|
120
285
|
const opts = {
|
|
@@ -131,10 +296,24 @@ async function runCommand(files, options) {
|
|
|
131
296
|
dot: false
|
|
132
297
|
});
|
|
133
298
|
if (testFiles.length === 0) {
|
|
134
|
-
|
|
299
|
+
logWarn("No test files found");
|
|
135
300
|
process.exit(1);
|
|
136
301
|
}
|
|
137
302
|
const globalConfig = await require_src.loadConfig(process.cwd());
|
|
303
|
+
let coverageCtx;
|
|
304
|
+
let coverageEnv = {};
|
|
305
|
+
if (options.coverage) {
|
|
306
|
+
if (!await isC8Available()) {
|
|
307
|
+
logError("Coverage requires c8. Install with: npm install -D c8");
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
coverageCtx = await createCoverageContext({
|
|
311
|
+
...globalConfig.coverage,
|
|
312
|
+
reportsDir: options.coverageDir ?? globalConfig.coverage?.reportsDir,
|
|
313
|
+
reporters: options.coverageReporter ?? globalConfig.coverage?.reporters
|
|
314
|
+
});
|
|
315
|
+
coverageEnv = getCoverageEnv(coverageCtx);
|
|
316
|
+
}
|
|
138
317
|
const fileResults = [];
|
|
139
318
|
let shouldStop = false;
|
|
140
319
|
for (const filePath of testFiles) {
|
|
@@ -146,18 +325,29 @@ async function runCommand(files, options) {
|
|
|
146
325
|
const filterPattern = new RegExp(opts.filter, "i");
|
|
147
326
|
blocksToRun = blocksToRun.filter((b) => b.name ? filterPattern.test(b.name) : true);
|
|
148
327
|
}
|
|
328
|
+
const onlyBlocks = blocksToRun.filter((b) => b.only);
|
|
329
|
+
if (onlyBlocks.length > 0) blocksToRun = onlyBlocks;
|
|
149
330
|
if (blocksToRun.length === 0) continue;
|
|
150
|
-
const ctx = await require_src.createExecutionContext(config, filePath);
|
|
331
|
+
const ctx = await require_src.createExecutionContext(config, filePath, coverageEnv);
|
|
151
332
|
const results = [];
|
|
152
333
|
try {
|
|
153
334
|
for (const block of blocksToRun) {
|
|
154
335
|
const result = await require_src.runBlock(block, ctx);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
336
|
+
if (result.skipped) {
|
|
337
|
+
results.push(result);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const outputMatches = require_src.matchOutput(block.expectedStderr ? result.actualStdout ?? "" : result.actualOutput, block.expectedOutput, {
|
|
341
|
+
root: ctx.testDir,
|
|
342
|
+
cwd: ctx.cwd
|
|
343
|
+
}, config.patterns ?? {});
|
|
344
|
+
let stderrMatches = true;
|
|
345
|
+
if (block.expectedStderr) stderrMatches = require_src.matchOutput(result.actualStderr ?? "", block.expectedStderr, {
|
|
346
|
+
root: ctx.testDir,
|
|
347
|
+
cwd: ctx.cwd
|
|
158
348
|
}, config.patterns ?? {});
|
|
159
349
|
const exitCodeMatches = result.actualExitCode === block.expectedExitCode;
|
|
160
|
-
result.passed =
|
|
350
|
+
result.passed = outputMatches && stderrMatches && exitCodeMatches && !result.error;
|
|
161
351
|
if (!result.passed && opts.diff) result.diff = createDiff(block.expectedOutput, result.actualOutput, `${filePath}:${block.lineNumber}`);
|
|
162
352
|
results.push(result);
|
|
163
353
|
if (!result.passed && opts.failFast) {
|
|
@@ -165,6 +355,7 @@ async function runCommand(files, options) {
|
|
|
165
355
|
break;
|
|
166
356
|
}
|
|
167
357
|
}
|
|
358
|
+
await require_src.runAfterHook(ctx);
|
|
168
359
|
} finally {
|
|
169
360
|
await require_src.cleanupExecutionContext(ctx);
|
|
170
361
|
}
|
|
@@ -178,7 +369,7 @@ async function runCommand(files, options) {
|
|
|
178
369
|
reportFile(fileResult, opts);
|
|
179
370
|
if (opts.update && !fileResult.passed) {
|
|
180
371
|
const { updated, changes } = await updateTestFile(testFile, results);
|
|
181
|
-
if (updated) console.error(
|
|
372
|
+
if (updated) console.error(colors.warn(` ${status.update} Updated: ${changes.join(", ")}`));
|
|
182
373
|
}
|
|
183
374
|
}
|
|
184
375
|
const summary = {
|
|
@@ -189,6 +380,17 @@ async function runCommand(files, options) {
|
|
|
189
380
|
duration: Date.now() - startTime
|
|
190
381
|
};
|
|
191
382
|
reportSummary(summary, opts);
|
|
383
|
+
if (coverageCtx) {
|
|
384
|
+
console.error("\nGenerating coverage report...");
|
|
385
|
+
try {
|
|
386
|
+
await generateCoverageReport(coverageCtx);
|
|
387
|
+
console.error(colors.success(`Coverage report written to ${coverageCtx.options.reportsDir}/`));
|
|
388
|
+
} catch (error) {
|
|
389
|
+
logError(`Failed to generate coverage report: ${error instanceof Error ? error.message : String(error)}`);
|
|
390
|
+
} finally {
|
|
391
|
+
await cleanupCoverageContext(coverageCtx);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
192
394
|
process.exit(summary.totalFailed > 0 ? 1 : 0);
|
|
193
395
|
}
|
|
194
396
|
|
|
@@ -266,19 +468,24 @@ function isInteractive$1() {
|
|
|
266
468
|
return process.stdout.isTTY === true;
|
|
267
469
|
}
|
|
268
470
|
/**
|
|
471
|
+
* Display the README content.
|
|
472
|
+
* Exported for use as the default command.
|
|
473
|
+
*/
|
|
474
|
+
function showReadme(options) {
|
|
475
|
+
try {
|
|
476
|
+
const formatted = formatMarkdown$1(loadReadme(), !options?.raw && isInteractive$1());
|
|
477
|
+
console.log(formatted);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
480
|
+
console.error(picocolors.default.red(`Error: ${message}`));
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
269
485
|
* Register the readme command.
|
|
270
486
|
*/
|
|
271
487
|
function registerReadmeCommand(program) {
|
|
272
|
-
program.command("readme").description("Display README documentation").option("--raw", "Output raw markdown without formatting").action(
|
|
273
|
-
try {
|
|
274
|
-
const formatted = formatMarkdown$1(loadReadme(), !options.raw && isInteractive$1());
|
|
275
|
-
console.log(formatted);
|
|
276
|
-
} catch (error) {
|
|
277
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
278
|
-
console.error(picocolors.default.red(`Error: ${message}`));
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
});
|
|
488
|
+
program.command("readme").description("Display README documentation").option("--raw", "Output raw markdown without formatting").action(showReadme);
|
|
282
489
|
}
|
|
283
490
|
|
|
284
491
|
//#endregion
|
|
@@ -372,12 +579,21 @@ function registerDocsCommand(program) {
|
|
|
372
579
|
|
|
373
580
|
//#endregion
|
|
374
581
|
//#region src/cli/cli.ts
|
|
582
|
+
/**
|
|
583
|
+
* CLI entry point for tryscript.
|
|
584
|
+
*
|
|
585
|
+
* Configures Commander.js with colored help and registers all subcommands.
|
|
586
|
+
*/
|
|
375
587
|
function run(argv) {
|
|
376
|
-
const program = new commander.Command().name("tryscript").version(require_src.VERSION, "--version", "Show version number").description("Golden testing for CLI applications").showHelpAfterError("(use --help for usage)")
|
|
588
|
+
const program = withColoredHelp(new commander.Command().name("tryscript").version(require_src.VERSION, "--version", "Show version number").description("Golden testing for CLI applications").showHelpAfterError("(use --help for usage)"));
|
|
589
|
+
registerRunCommand(program);
|
|
377
590
|
registerReadmeCommand(program);
|
|
378
591
|
registerDocsCommand(program);
|
|
592
|
+
program.action(() => {
|
|
593
|
+
program.help();
|
|
594
|
+
});
|
|
379
595
|
program.parseAsync(argv).catch((err) => {
|
|
380
|
-
|
|
596
|
+
logError(`Error: ${err.message}`);
|
|
381
597
|
process.exit(2);
|
|
382
598
|
});
|
|
383
599
|
}
|
package/dist/bin.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.cjs","names":["pc","parts: string[]","changes: string[]","lines: string[]","pc","loadConfig","fileResults: TestFileResult[]","parseTestFile","mergeConfig","createExecutionContext","results: TestBlockResult[]","runBlock","matchOutput","cleanupExecutionContext","fileResult: TestFileResult","summary: TestRunSummary","formatMarkdown","formatted: string[]","pc","isInteractive","formatted: string[]","pc","Command","VERSION","pc"],"sources":["../src/lib/reporter.ts","../src/lib/updater.ts","../src/cli/commands/run.ts","../src/cli/commands/readme.ts","../src/cli/commands/docs.ts","../src/cli/cli.ts","../src/bin.ts"],"sourcesContent":["import 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/**\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 * 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 * 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(` ${pc.green('✓')} ${name}`);\n }\n } else {\n console.error(` ${pc.red('✗')} ${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","import { readFile } from 'node:fs/promises';\nimport fg from 'fast-glob';\nimport pc from 'picocolors';\nimport { loadConfig, mergeConfig } from '../../lib/config.js';\nimport { parseTestFile } from '../../lib/parser.js';\nimport { runBlock, createExecutionContext, cleanupExecutionContext } 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 type { TestBlockResult, TestFileResult, TestRunSummary } 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}\n\nexport async 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 console.error(pc.yellow('No test files found'));\n process.exit(1);\n }\n\n // Load global config\n const globalConfig = await loadConfig(process.cwd());\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 if (blocksToRun.length === 0) {\n continue;\n }\n\n const ctx = await createExecutionContext(config, filePath);\n const results: TestBlockResult[] = [];\n\n try {\n for (const block of blocksToRun) {\n const result = await runBlock(block, ctx);\n\n // Check if output matches expected\n const matches = matchOutput(\n result.actualOutput,\n block.expectedOutput,\n { root: ctx.tempDir, cwd: ctx.tempDir },\n config.patterns ?? {},\n );\n\n const exitCodeMatches = result.actualExitCode === block.expectedExitCode;\n result.passed = matches && 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 } 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(pc.yellow(` ↻ 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 // Exit code\n process.exit(summary.totalFailed > 0 ? 1 : 0);\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 * 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 .action((options: { raw?: boolean }) => {\n try {\n const readme = loadReadme();\n\n // Determine if we should colorize\n const shouldColorize = !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 * 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 .action((options: { raw?: boolean }) => {\n try {\n const docs = loadDocs();\n\n // Determine if we should colorize\n const shouldColorize = !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","import { Command } from 'commander';\nimport pc from 'picocolors';\nimport { VERSION } from '../index.js';\nimport { runCommand } from './commands/run.js';\nimport { registerReadmeCommand } from './commands/readme.js';\nimport { registerDocsCommand } from './commands/docs.js';\n\nexport function run(argv: string[]): void {\n const program = 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 .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 .action(runCommand);\n\n // Register subcommands\n registerReadmeCommand(program);\n registerDocsCommand(program);\n\n program.parseAsync(argv).catch((err: Error) => {\n console.error(pc.red(`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":";;;;;;;;;;;;;;;;;;;;;AAaA,SAAgB,WAAW,UAAkB,QAAgB,UAA0B;AAIrF,8BAH0B,UAAU,UAAU,QAAQ,YAAY,SAAS,CAEvD,MAAM,KAAK,CAAC,MAAM,EAAE,CAErC,KAAK,SAAS;AACb,MAAI,KAAK,WAAW,IAAI,CACtB,QAAOA,mBAAG,MAAM,KAAK;AAEvB,MAAI,KAAK,WAAW,IAAI,CACtB,QAAOA,mBAAG,IAAI,KAAK;AAErB,MAAI,KAAK,WAAW,IAAI,CACtB,QAAOA,mBAAG,KAAK,KAAK;AAEtB,SAAO;GACP,CACD,KAAK,KAAK;;;;;AAMf,SAAS,eAAe,IAAoB;AAC1C,KAAI,KAAK,IACP,QAAO,GAAG,GAAG;AAEf,QAAO,IAAI,KAAK,KAAM,QAAQ,EAAE,CAAC;;;;;AAMnC,SAAgB,WAAW,QAAwB,SAAgC;CACjF,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,SAAS,OAAO,SAASA,mBAAG,MAAMA,mBAAG,KAAK,OAAO,CAAC,GAAGA,mBAAG,IAAIA,mBAAG,KAAK,OAAO,CAAC;AAElF,KAAI,QAAQ,SAAS,OAAO,OAC1B;AAIF,SAAQ,MAAM,GAAG,OAAO,GAAG,WAAW;AAGtC,MAAK,MAAM,eAAe,OAAO,SAAS;EACxC,MAAM,OAAO,YAAY,MAAM,QAAQ,QAAQ,YAAY,MAAM;AAEjE,MAAI,YAAY,QACd;OAAI,CAAC,QAAQ,MACX,SAAQ,MAAM,KAAKA,mBAAG,MAAM,IAAI,CAAC,GAAG,OAAO;SAExC;AACL,WAAQ,MAAM,KAAKA,mBAAG,IAAI,IAAI,CAAC,GAAG,OAAO;AAGzC,OAAI,YAAY,MACd,SAAQ,MAAM,OAAOA,mBAAG,IAAI,YAAY,MAAM,GAAG;QAC5C;AAEL,QAAI,YAAY,mBAAmB,YAAY,MAAM,iBACnD,SAAQ,MACN,0BAA0B,YAAY,MAAM,iBAAiB,QAAQ,YAAY,iBAClF;AAIH,QAAI,QAAQ,QAAQ,YAAY,MAAM;AACpC,aAAQ,MAAM,GAAG;AACjB,aAAQ,MAAM,YAAY,KAAK;;;;;AAMvC,SAAQ,MAAM,GAAG;;;;;AAMnB,SAAgB,cAAc,SAAyB,UAAiC;CACtF,MAAMC,QAAkB,EAAE;AAE1B,KAAI,QAAQ,cAAc,EACxB,OAAM,KAAKD,mBAAG,MAAM,GAAG,QAAQ,YAAY,SAAS,CAAC;AAEvD,KAAI,QAAQ,cAAc,EACxB,OAAM,KAAKA,mBAAG,IAAI,GAAG,QAAQ,YAAY,SAAS,CAAC;CAGrD,MAAM,WAAW,eAAe,QAAQ,SAAS;CACjD,MAAM,OAAO,GAAG,MAAM,KAAK,KAAK,CAAC,IAAI,SAAS;AAG9C,SAAQ,IAAI,KAAK;;;;;;;;ACtGnB,eAAsB,eACpB,MACA,SACkD;CAClD,IAAI,UAAU,KAAK;CACnB,MAAME,UAAoB,EAAE;CAG5B,MAAM,oBAAoB,KAAK,OAC5B,KAAK,OAAO,OAAO;EAAE;EAAO,QAAQ,QAAQ;EAAI,EAAE,CAClD,SAAS;AAEZ,MAAK,MAAM,EAAE,OAAO,YAAY,mBAAmB;AACjD,MAAI,CAAC,OACH;AAGF,MAAI,OAAO,OACT;AAGF,MAAI,OAAO,MAET;EAIF,MAAM,kBAAkB,kBAAkB,OAAO,OAAO;EAGxD,MAAM,aAAa,QAAQ,QAAQ,MAAM,WAAW;AACpD,MAAI,eAAe,IAAI;AACrB,aACE,QAAQ,MAAM,GAAG,WAAW,GAC5B,kBACA,QAAQ,MAAM,aAAa,MAAM,WAAW,OAAO;AAErD,WAAQ,KAAK,MAAM,QAAQ,QAAQ,MAAM,aAAa;;;AAI1D,KAAI,QAAQ,SAAS,EACnB,iCAAgB,KAAK,MAAM,QAAQ;AAGrC,QAAO;EAAE,SAAS,QAAQ,SAAS;EAAG;EAAS;;;;;AAMjD,SAAS,kBAAkB,OAAkB,QAAiC;CAO5E,MAAMC,QAAkB,CAAC,cAAc,GALlB,MAAM,QAAQ,MAAM,KAAK,CAAC,KAAK,MAAM,MAAM;AAC9D,SAAO,MAAM,IAAI,KAAK,SAAS,KAAK;GACpC,CAGqD;CAGvD,MAAM,gBAAgB,OAAO,aAAa,SAAS;AACnD,KAAI,cACF,OAAM,KAAK,cAAc;AAI3B,OAAM,KAAK,KAAK,OAAO,kBAAkB,MAAM;AAE/C,QAAO,MAAM,KAAK,KAAK;;;;;ACvDzB,eAAsB,WAAW,OAAiB,SAAoC;CACpF,MAAM,YAAY,KAAK,KAAK;CAG5B,MAAM,OAAO;EACX,MAAM,QAAQ,SAAS;EACvB,SAAS,QAAQ,WAAW;EAC5B,OAAO,QAAQ,SAAS;EACxB,QAAQ,QAAQ,UAAU;EAC1B,UAAU,QAAQ,YAAY;EAC9B,QAAQ,QAAQ;EACjB;CAID,MAAM,YAAY,6BADD,MAAM,SAAS,IAAI,QAAQ,CAAC,oBAAoB,EAC5B;EACnC,QAAQ,CAAC,sBAAsB,aAAa;EAC5C,UAAU;EACV,KAAK;EACN,CAAC;AAEF,KAAI,UAAU,WAAW,GAAG;AAC1B,UAAQ,MAAMC,mBAAG,OAAO,sBAAsB,CAAC;AAC/C,UAAQ,KAAK,EAAE;;CAIjB,MAAM,eAAe,MAAMC,uBAAW,QAAQ,KAAK,CAAC;CAGpD,MAAMC,cAAgC,EAAE;CACxC,IAAI,aAAa;AAEjB,MAAK,MAAM,YAAY,WAAW;AAChC,MAAI,WACF;EAIF,MAAM,WAAWC,0BADD,qCAAe,UAAU,QAAQ,EACT,SAAS;EACjD,MAAM,SAASC,wBAAY,cAAc,SAAS,OAAO;EAGzD,IAAI,cAAc,SAAS;AAC3B,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,IAAI,OAAO,KAAK,QAAQ,IAAI;AAClD,iBAAc,YAAY,QAAQ,MAAO,EAAE,OAAO,cAAc,KAAK,EAAE,KAAK,GAAG,KAAM;;AAGvF,MAAI,YAAY,WAAW,EACzB;EAGF,MAAM,MAAM,MAAMC,mCAAuB,QAAQ,SAAS;EAC1D,MAAMC,UAA6B,EAAE;AAErC,MAAI;AACF,QAAK,MAAM,SAAS,aAAa;IAC/B,MAAM,SAAS,MAAMC,qBAAS,OAAO,IAAI;IAGzC,MAAM,UAAUC,wBACd,OAAO,cACP,MAAM,gBACN;KAAE,MAAM,IAAI;KAAS,KAAK,IAAI;KAAS,EACvC,OAAO,YAAY,EAAE,CACtB;IAED,MAAM,kBAAkB,OAAO,mBAAmB,MAAM;AACxD,WAAO,SAAS,WAAW,mBAAmB,CAAC,OAAO;AAEtD,QAAI,CAAC,OAAO,UAAU,KAAK,KACzB,QAAO,OAAO,WACZ,MAAM,gBACN,OAAO,cACP,GAAG,SAAS,GAAG,MAAM,aACtB;AAGH,YAAQ,KAAK,OAAO;AAEpB,QAAI,CAAC,OAAO,UAAU,KAAK,UAAU;AACnC,kBAAa;AACb;;;YAGI;AACR,SAAMC,oCAAwB,IAAI;;EAGpC,MAAMC,aAA6B;GACjC,MAAM;GACN;GACA,QAAQ,QAAQ,OAAO,MAAM,EAAE,OAAO;GACtC,UAAU,QAAQ,QAAQ,KAAK,MAAM,MAAM,EAAE,UAAU,EAAE;GAC1D;AAED,cAAY,KAAK,WAAW;AAC5B,aAAW,YAAY,KAAK;AAG5B,MAAI,KAAK,UAAU,CAAC,WAAW,QAAQ;GACrC,MAAM,EAAE,SAAS,YAAY,MAAM,eAAe,UAAU,QAAQ;AACpE,OAAI,QACF,SAAQ,MAAMV,mBAAG,OAAO,gBAAgB,QAAQ,KAAK,KAAK,GAAG,CAAC;;;CAMpE,MAAMW,UAA0B;EAC9B,OAAO;EACP,aAAa,YAAY,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,QAAQ,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE;EAC9F,aAAa,YAAY,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,QAAQ,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,EAAE;EAC/F,aAAa,YAAY,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,QAAQ,EAAE;EACtE,UAAU,KAAK,KAAK,GAAG;EACxB;AAED,eAAc,SAAS,KAAK;AAG5B,SAAQ,KAAK,QAAQ,cAAc,IAAI,IAAI,EAAE;;;;;;;;;AC3H/C,SAAS,gBAAwB;CAC/B,MAAM,2GAAgD,CAAC;AAGvD,KAFgB,QAAQ,MAAM,QAAQ,CAAC,KAAK,KAE5B,OAEd,mDAAoB,QAAQ,EAAE,YAAY;AAI5C,iGAAoC,QAAQ,CAAC,CAAC,EAAE,YAAY;;;;;AAM9D,SAAS,aAAqB;CAC5B,MAAM,aAAa,eAAe;AAClC,KAAI;AACF,mCAAoB,YAAY,QAAQ;UACjC,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,QAAM,IAAI,MAAM,8BAA8B,WAAW,IAAI,UAAU;;;;;;;AAQ3E,SAASC,iBAAe,SAAiB,WAA4B;AACnE,KAAI,CAAC,UACH,QAAO;CAGT,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAMC,YAAsB,EAAE;CAC9B,IAAI,cAAc;AAElB,MAAK,MAAM,QAAQ,OAAO;AAExB,MAAI,KAAK,WAAW,MAAM,EAAE;AAC1B,iBAAc,CAAC;AACf,aAAU,KAAKC,mBAAG,IAAI,KAAK,CAAC;AAC5B;;AAGF,MAAI,aAAa;AACf,aAAU,KAAKA,mBAAG,IAAI,KAAK,CAAC;AAC5B;;AAIF,MAAI,KAAK,WAAW,KAAK,EAAE;AACzB,aAAU,KAAKA,mBAAG,KAAKA,mBAAG,KAAK,KAAK,CAAC,CAAC;AACtC;;AAEF,MAAI,KAAK,WAAW,MAAM,EAAE;AAC1B,aAAU,KAAKA,mBAAG,KAAKA,mBAAG,KAAK,KAAK,CAAC,CAAC;AACtC;;AAEF,MAAI,KAAK,WAAW,OAAO,EAAE;AAC3B,aAAU,KAAKA,mBAAG,KAAK,KAAK,CAAC;AAC7B;;EAIF,IAAI,gBAAgB,KAAK,QAAQ,eAAe,QAAQ,SAAiB;AACvE,UAAOA,mBAAG,OAAO,KAAK;IACtB;AAGF,kBAAgB,cAAc,QAAQ,qBAAqB,QAAQ,SAAiB;AAClF,UAAOA,mBAAG,KAAK,KAAK;IACpB;AAGF,kBAAgB,cAAc,QAC5B,6BACC,QAAQ,MAAc,QAAgB;AACrC,UAAO,GAAGA,mBAAG,KAAK,KAAK,CAAC,GAAGA,mBAAG,IAAI,IAAI,IAAI,GAAG;IAEhD;AAED,YAAU,KAAK,cAAc;;AAG/B,QAAO,UAAU,KAAK,KAAK;;;;;AAM7B,SAASC,kBAAyB;AAChC,QAAO,QAAQ,OAAO,UAAU;;;;;AAMlC,SAAgB,sBAAsB,SAAwB;AAC5D,SACG,QAAQ,SAAS,CACjB,YAAY,+BAA+B,CAC3C,OAAO,SAAS,yCAAyC,CACzD,QAAQ,YAA+B;AACtC,MAAI;GAMF,MAAM,YAAYH,iBALH,YAAY,EAGJ,CAAC,QAAQ,OAAOG,iBAAe,CAEE;AACxD,WAAQ,IAAI,UAAU;WACf,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,WAAQ,MAAMD,mBAAG,IAAI,UAAU,UAAU,CAAC;AAC1C,WAAQ,KAAK,EAAE;;GAEjB;;;;;;;;;ACvHN,SAAS,cAAsB;CAC7B,MAAM,2GAAgD,CAAC;AAGvD,KAFgB,QAAQ,MAAM,QAAQ,CAAC,KAAK,KAE5B,OAEd,mDAAoB,QAAQ,EAAE,QAAQ,yBAAyB;AAIjE,iGAAoC,QAAQ,CAAC,CAAC,EAAE,QAAQ,yBAAyB;;;;;AAMnF,SAAS,WAAmB;CAC1B,MAAM,WAAW,aAAa;AAC9B,KAAI;AACF,mCAAoB,UAAU,QAAQ;UAC/B,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,QAAM,IAAI,MAAM,sCAAsC,SAAS,IAAI,UAAU;;;;;;;AAQjF,SAAS,eAAe,SAAiB,WAA4B;AACnE,KAAI,CAAC,UACH,QAAO;CAGT,MAAM,QAAQ,QAAQ,MAAM,KAAK;CACjC,MAAME,YAAsB,EAAE;CAC9B,IAAI,cAAc;AAElB,MAAK,MAAM,QAAQ,OAAO;AAExB,MAAI,KAAK,WAAW,MAAM,EAAE;AAC1B,iBAAc,CAAC;AACf,aAAU,KAAKC,mBAAG,IAAI,KAAK,CAAC;AAC5B;;AAGF,MAAI,aAAa;AACf,aAAU,KAAKA,mBAAG,IAAI,KAAK,CAAC;AAC5B;;AAIF,MAAI,KAAK,WAAW,KAAK,EAAE;AACzB,aAAU,KAAKA,mBAAG,KAAKA,mBAAG,KAAK,KAAK,CAAC,CAAC;AACtC;;AAEF,MAAI,KAAK,WAAW,MAAM,EAAE;AAC1B,aAAU,KAAKA,mBAAG,KAAKA,mBAAG,KAAK,KAAK,CAAC,CAAC;AACtC;;AAEF,MAAI,KAAK,WAAW,OAAO,EAAE;AAC3B,aAAU,KAAKA,mBAAG,KAAK,KAAK,CAAC;AAC7B;;EAIF,IAAI,gBAAgB,KAAK,QAAQ,eAAe,QAAQ,SAAiB;AACvE,UAAOA,mBAAG,OAAO,KAAK;IACtB;AAGF,kBAAgB,cAAc,QAAQ,qBAAqB,QAAQ,SAAiB;AAClF,UAAOA,mBAAG,KAAK,KAAK;IACpB;AAGF,kBAAgB,cAAc,QAC5B,6BACC,QAAQ,MAAc,QAAgB;AACrC,UAAO,GAAGA,mBAAG,KAAK,KAAK,CAAC,GAAGA,mBAAG,IAAI,IAAI,IAAI,GAAG;IAEhD;AAED,YAAU,KAAK,cAAc;;AAG/B,QAAO,UAAU,KAAK,KAAK;;;;;AAM7B,SAAS,gBAAyB;AAChC,QAAO,QAAQ,OAAO,UAAU;;;;;AAMlC,SAAgB,oBAAoB,SAAwB;AAC1D,SACG,QAAQ,OAAO,CACf,YAAY,mCAAmC,CAC/C,OAAO,SAAS,yCAAyC,CACzD,QAAQ,YAA+B;AACtC,MAAI;GAMF,MAAM,YAAY,eALL,UAAU,EAGA,CAAC,QAAQ,OAAO,eAAe,CAEA;AACtD,WAAQ,IAAI,UAAU;WACf,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,WAAQ,MAAMA,mBAAG,IAAI,UAAU,UAAU,CAAC;AAC1C,WAAQ,KAAK,EAAE;;GAEjB;;;;;AClIN,SAAgB,IAAI,MAAsB;CACxC,MAAM,UAAU,IAAIC,mBAAS,CAC1B,KAAK,YAAY,CACjB,QAAQC,qBAAS,aAAa,sBAAsB,CACpD,YAAY,sCAAsC,CAClD,mBAAmB,yBAAyB,CAC5C,SAAS,cAAc,iDAAiD,CACxE,OAAO,YAAY,yCAAyC,CAC5D,OAAO,UAAU,uCAAuC,CACxD,OAAO,aAAa,uBAAuB,CAC3C,OAAO,eAAe,wBAAwB,CAC9C,OAAO,sBAAsB,+BAA+B,CAC5D,OAAO,aAAa,qDAAqD,CACzE,OAAO,WAAW,qDAAqD,CACvE,OAAO,WAAW;AAGrB,uBAAsB,QAAQ;AAC9B,qBAAoB,QAAQ;AAE5B,SAAQ,WAAW,KAAK,CAAC,OAAO,QAAe;AAC7C,UAAQ,MAAMC,mBAAG,IAAI,UAAU,IAAI,UAAU,CAAC;AAC9C,UAAQ,KAAK,EAAE;GACf;;;;;AC3BJ,IAAI,QAAQ,KAAK"}
|
|
1
|
+
{"version":3,"file":"bin.cjs","names":["pc","pc","status","parts: string[]","changes: string[]","lines: string[]","resolveCoverageConfig","loadConfig","coverageCtx: CoverageContext | undefined","coverageEnv: Record<string, string>","fileResults: TestFileResult[]","parseTestFile","mergeConfig","createExecutionContext","results: TestBlockResult[]","runBlock","matchOutput","runAfterHook","cleanupExecutionContext","fileResult: TestFileResult","statusIndicators","summary: TestRunSummary","formatMarkdown","formatted: string[]","pc","isInteractive","formatted: string[]","pc","Command","VERSION"],"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/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 info: (s: string) => pc.cyan(s),\n warn: (s: string) => pc.yellow(s),\n muted: (s: string) => pc.gray(s),\n bold: (s: string) => pc.bold(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 an informational message to stderr.\n */\nexport function logInfo(message: string): void {\n console.error(colors.info(message));\n}\n\n/**\n * Log a success message to stderr.\n */\nexport function logSuccess(message: string): void {\n console.error(colors.success(message));\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/**\n * Log timing information with emoji.\n */\nexport function logTiming(label: string, durationMs: number): void {\n console.error(colors.info(`⏰ ${label}: ${formatDuration(durationMs)}`));\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 ...options.include.flatMap((pattern) => ['--include', pattern]),\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}\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 .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 });\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 * 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 }): void {\n try {\n const readme = loadReadme();\n\n // Determine if we should colorize\n const shouldColorize = !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 .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 .action((options: { raw?: boolean }) => {\n try {\n const docs = loadDocs();\n\n // Determine if we should colorize\n const shouldColorize = !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 { 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 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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"}
|