tryscript 0.0.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/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # tryscript
2
+
3
+ Golden testing for CLI applications - a TypeScript port of [trycmd](https://github.com/assert-rs/trycmd).
4
+
5
+ ## Requirements
6
+
7
+ - **Node.js 20+** (tested with v20, v22, v24)
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install tryscript
13
+ # or
14
+ pnpm add tryscript
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ Create a test file with the `.tryscript.md` extension:
20
+
21
+ ```markdown
22
+ # Test: Help command
23
+
24
+ \`\`\`console
25
+ $ my-cli --help
26
+ Usage: my-cli [options]
27
+
28
+ Options:
29
+ --version Show version
30
+ --help Show help
31
+ ? 0
32
+ \`\`\`
33
+ ```
34
+
35
+ Run the tests:
36
+
37
+ ```bash
38
+ npx tryscript tests/
39
+ ```
40
+
41
+ ## Test File Format
42
+
43
+ Test files are Markdown documents with console code blocks. Each code block represents a test case:
44
+
45
+ ```markdown
46
+ \`\`\`console
47
+ $ <command>
48
+ <expected output>
49
+ ? <exit code>
50
+ \`\`\`
51
+ ```
52
+
53
+ ### Example
54
+
55
+ ```markdown
56
+ # Test: Echo command
57
+
58
+ \`\`\`console
59
+ $ echo "hello world"
60
+ hello world
61
+ ? 0
62
+ \`\`\`
63
+
64
+ # Test: Exit with error
65
+
66
+ \`\`\`console
67
+ $ exit 1
68
+ ? 1
69
+ \`\`\`
70
+ ```
71
+
72
+ ## Elision Patterns
73
+
74
+ Use elision patterns to match dynamic or platform-specific output:
75
+
76
+ | Pattern | Description | Example |
77
+ | -------- | ---------------------------------------- | -------------------------- |
78
+ | `[..]` | Match any characters on the current line | `Built in [..]ms` |
79
+ | `...` | Match zero or more complete lines | `...\nDone` |
80
+ | `[EXE]` | Match `.exe` on Windows, empty otherwise | `my-cli[EXE] --help` |
81
+ | `[ROOT]` | Match the test's root directory | `[ROOT]/output.txt` |
82
+ | `[CWD]` | Match the current working directory | `[CWD]/file.txt` |
83
+
84
+ ### Example with Elision
85
+
86
+ ```markdown
87
+ \`\`\`console
88
+ $ time-command
89
+ Elapsed: [..]ms
90
+ ? 0
91
+ \`\`\`
92
+ ```
93
+
94
+ ## Configuration
95
+
96
+ ### YAML Frontmatter
97
+
98
+ Add configuration at the top of your test file:
99
+
100
+ ```markdown
101
+ ---
102
+ bin: ./my-cli
103
+ env:
104
+ NO_COLOR: "1"
105
+ timeout: 5000
106
+ ---
107
+
108
+ # Test: Custom binary
109
+
110
+ \`\`\`console
111
+ $ my-cli --version
112
+ 1.0.0
113
+ ? 0
114
+ \`\`\`
115
+ ```
116
+
117
+ ### Config File
118
+
119
+ Create `tryscript.config.ts` in your project root:
120
+
121
+ ```typescript
122
+ import { defineConfig } from 'tryscript';
123
+
124
+ export default defineConfig({
125
+ bin: './dist/cli.js',
126
+ env: {
127
+ NO_COLOR: '1',
128
+ },
129
+ timeout: 30000,
130
+ patterns: {
131
+ VERSION: '\\d+\\.\\d+\\.\\d+',
132
+ UUID: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}',
133
+ },
134
+ });
135
+ ```
136
+
137
+ ## CLI Options
138
+
139
+ ```
140
+ tryscript [options] [files...]
141
+
142
+ Arguments:
143
+ files Test files to run (default: **/*.tryscript.md)
144
+
145
+ Options:
146
+ --version Show version number
147
+ --update Update golden files with actual output
148
+ --diff Show diff on failure (default: true)
149
+ --no-diff Hide diff on failure
150
+ --fail-fast Stop on first failure
151
+ --filter <pattern> Filter tests by name pattern
152
+ --verbose Show detailed output
153
+ --quiet Suppress non-essential output
154
+ --help Show help
155
+ ```
156
+
157
+ ## Update Mode
158
+
159
+ When your CLI output changes, update all test files at once:
160
+
161
+ ```bash
162
+ npx tryscript --update
163
+ ```
164
+
165
+ This rewrites test files with the actual output from running the commands.
166
+
167
+ ## Programmatic API
168
+
169
+ ```typescript
170
+ import { parseTestFile, runBlock, createExecutionContext, matchOutput } from 'tryscript';
171
+
172
+ const content = await fs.readFile('test.tryscript.md', 'utf-8');
173
+ const testFile = parseTestFile(content, 'test.tryscript.md');
174
+
175
+ const ctx = await createExecutionContext({}, 'test.tryscript.md');
176
+ for (const block of testFile.blocks) {
177
+ const result = await runBlock(block, ctx);
178
+ const matches = matchOutput(
179
+ result.actualOutput,
180
+ block.expectedOutput,
181
+ { root: ctx.tempDir, cwd: ctx.tempDir },
182
+ );
183
+ console.log(`${block.name}: ${matches ? 'PASS' : 'FAIL'}`);
184
+ }
185
+ ```
186
+
187
+ ## Measuring Coverage
188
+
189
+ When testing CLI tools as subprocesses, standard coverage tools don't track execution. Use [c8](https://github.com/bcoe/c8) which leverages Node's V8 coverage collection:
190
+
191
+ ```bash
192
+ npm install -D c8
193
+ ```
194
+
195
+ Add scripts to your `package.json`:
196
+
197
+ ```json
198
+ {
199
+ "scripts": {
200
+ "test:golden": "tryscript 'tests/**/*.tryscript.md'",
201
+ "test:golden:coverage": "c8 --src src --all --include 'dist/**' tryscript 'tests/**/*.tryscript.md'"
202
+ }
203
+ }
204
+ ```
205
+
206
+ Key c8 flags:
207
+ - `--src src` — Map coverage back to source files
208
+ - `--all` — Include files with 0% coverage
209
+ - `--include 'dist/**'` — Track your built CLI output
210
+
211
+ This captures coverage from actual CLI usage—the most realistic testing possible.
212
+
213
+ ## Comparison with trycmd
214
+
215
+ tryscript is a TypeScript port of the Rust [trycmd](https://github.com/assert-rs/trycmd) crate. Key differences:
216
+
217
+ - **Language**: TypeScript/Node.js instead of Rust
218
+ - **Format**: Uses console code blocks (trycmd uses `.toml` or `.trycmd` files)
219
+ - **Integration**: Works with Node.js test frameworks (Vitest, Jest)
220
+
221
+ ## License
222
+
223
+ MIT
package/dist/bin.cjs ADDED
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ const require_src = require('./src-CeUA446P.cjs');
5
+ let node_url = require("node:url");
6
+ let node_fs = require("node:fs");
7
+ let node_path = require("node:path");
8
+ let node_fs_promises = require("node:fs/promises");
9
+ let commander = require("commander");
10
+ let picocolors = require("picocolors");
11
+ picocolors = require_src.__toESM(picocolors);
12
+ let fast_glob = require("fast-glob");
13
+ fast_glob = require_src.__toESM(fast_glob);
14
+ let diff = require("diff");
15
+ let atomically = require("atomically");
16
+
17
+ //#region src/lib/reporter.ts
18
+ /**
19
+ * Create a unified diff between expected and actual output.
20
+ */
21
+ function createDiff(expected, actual, filename) {
22
+ return (0, diff.createPatch)(filename, expected, actual, "expected", "actual").split("\n").slice(4).map((line) => {
23
+ if (line.startsWith("+")) return picocolors.default.green(line);
24
+ if (line.startsWith("-")) return picocolors.default.red(line);
25
+ if (line.startsWith("@")) return picocolors.default.cyan(line);
26
+ return line;
27
+ }).join("\n");
28
+ }
29
+ /**
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
+ * Report results for a single file.
38
+ */
39
+ function reportFile(result, options) {
40
+ const filename = result.file.path;
41
+ const status = result.passed ? picocolors.default.green(picocolors.default.bold("PASS")) : picocolors.default.red(picocolors.default.bold("FAIL"));
42
+ if (options.quiet && result.passed) return;
43
+ console.error(`${status} ${filename}`);
44
+ for (const blockResult of result.results) {
45
+ const name = blockResult.block.name ?? `Line ${blockResult.block.lineNumber}`;
46
+ if (blockResult.passed) {
47
+ if (!options.quiet) console.error(` ${picocolors.default.green("✓")} ${name}`);
48
+ } else {
49
+ console.error(` ${picocolors.default.red("✗")} ${name}`);
50
+ if (blockResult.error) console.error(` ${picocolors.default.red(blockResult.error)}`);
51
+ else {
52
+ if (blockResult.actualExitCode !== blockResult.block.expectedExitCode) console.error(` Expected exit code ${blockResult.block.expectedExitCode}, got ${blockResult.actualExitCode}`);
53
+ if (options.diff && blockResult.diff) {
54
+ console.error("");
55
+ console.error(blockResult.diff);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ console.error("");
61
+ }
62
+ /**
63
+ * Report final summary.
64
+ */
65
+ function reportSummary(summary, _options) {
66
+ const parts = [];
67
+ if (summary.totalPassed > 0) parts.push(picocolors.default.green(`${summary.totalPassed} passed`));
68
+ if (summary.totalFailed > 0) parts.push(picocolors.default.red(`${summary.totalFailed} failed`));
69
+ const duration = formatDuration(summary.duration);
70
+ const line = `${parts.join(", ")} (${duration})`;
71
+ console.log(line);
72
+ }
73
+
74
+ //#endregion
75
+ //#region src/lib/updater.ts
76
+ /**
77
+ * Update a test file with actual output from test results.
78
+ */
79
+ async function updateTestFile(file, results) {
80
+ let content = file.rawContent;
81
+ const changes = [];
82
+ const blocksWithResults = file.blocks.map((block, i) => ({
83
+ block,
84
+ result: results[i]
85
+ })).reverse();
86
+ for (const { block, result } of blocksWithResults) {
87
+ if (!result) continue;
88
+ if (result.passed) continue;
89
+ if (result.error) continue;
90
+ const newBlockContent = buildUpdatedBlock(block, result);
91
+ const blockStart = content.indexOf(block.rawContent);
92
+ if (blockStart !== -1) {
93
+ content = content.slice(0, blockStart) + newBlockContent + content.slice(blockStart + block.rawContent.length);
94
+ changes.push(block.name ?? `Line ${block.lineNumber}`);
95
+ }
96
+ }
97
+ if (changes.length > 0) await (0, atomically.writeFile)(file.path, content);
98
+ return {
99
+ updated: changes.length > 0,
100
+ changes
101
+ };
102
+ }
103
+ /**
104
+ * Build an updated console block with new expected output.
105
+ */
106
+ function buildUpdatedBlock(block, result) {
107
+ const lines = ["```console", ...block.command.split("\n").map((line, i) => {
108
+ return i === 0 ? `$ ${line}` : `> ${line}`;
109
+ })];
110
+ const trimmedOutput = result.actualOutput.trimEnd();
111
+ if (trimmedOutput) lines.push(trimmedOutput);
112
+ lines.push(`? ${result.actualExitCode}`, "```");
113
+ return lines.join("\n");
114
+ }
115
+
116
+ //#endregion
117
+ //#region src/cli/commands/run.ts
118
+ async function runCommand(files, options) {
119
+ const startTime = Date.now();
120
+ const opts = {
121
+ diff: options.diff !== false,
122
+ verbose: options.verbose ?? false,
123
+ quiet: options.quiet ?? false,
124
+ update: options.update ?? false,
125
+ failFast: options.failFast ?? false,
126
+ filter: options.filter
127
+ };
128
+ const testFiles = await (0, fast_glob.default)(files.length > 0 ? files : ["**/*.tryscript.md"], {
129
+ ignore: ["**/node_modules/**", "**/dist/**"],
130
+ absolute: true,
131
+ dot: false
132
+ });
133
+ if (testFiles.length === 0) {
134
+ console.error(picocolors.default.yellow("No test files found"));
135
+ process.exit(1);
136
+ }
137
+ const globalConfig = await require_src.loadConfig(process.cwd());
138
+ const fileResults = [];
139
+ let shouldStop = false;
140
+ for (const filePath of testFiles) {
141
+ if (shouldStop) break;
142
+ const testFile = require_src.parseTestFile(await (0, node_fs_promises.readFile)(filePath, "utf-8"), filePath);
143
+ const config = require_src.mergeConfig(globalConfig, testFile.config);
144
+ let blocksToRun = testFile.blocks;
145
+ if (opts.filter) {
146
+ const filterPattern = new RegExp(opts.filter, "i");
147
+ blocksToRun = blocksToRun.filter((b) => b.name ? filterPattern.test(b.name) : true);
148
+ }
149
+ if (blocksToRun.length === 0) continue;
150
+ const ctx = await require_src.createExecutionContext(config, filePath);
151
+ const results = [];
152
+ try {
153
+ for (const block of blocksToRun) {
154
+ const result = await require_src.runBlock(block, ctx);
155
+ const matches = require_src.matchOutput(result.actualOutput, block.expectedOutput, {
156
+ root: ctx.tempDir,
157
+ cwd: ctx.tempDir
158
+ }, config.patterns ?? {});
159
+ const exitCodeMatches = result.actualExitCode === block.expectedExitCode;
160
+ result.passed = matches && exitCodeMatches && !result.error;
161
+ if (!result.passed && opts.diff) result.diff = createDiff(block.expectedOutput, result.actualOutput, `${filePath}:${block.lineNumber}`);
162
+ results.push(result);
163
+ if (!result.passed && opts.failFast) {
164
+ shouldStop = true;
165
+ break;
166
+ }
167
+ }
168
+ } finally {
169
+ await require_src.cleanupExecutionContext(ctx);
170
+ }
171
+ const fileResult = {
172
+ file: testFile,
173
+ results,
174
+ passed: results.every((r) => r.passed),
175
+ duration: results.reduce((sum, r) => sum + r.duration, 0)
176
+ };
177
+ fileResults.push(fileResult);
178
+ reportFile(fileResult, opts);
179
+ if (opts.update && !fileResult.passed) {
180
+ const { updated, changes } = await updateTestFile(testFile, results);
181
+ if (updated) console.error(picocolors.default.yellow(` ↻ Updated: ${changes.join(", ")}`));
182
+ }
183
+ }
184
+ const summary = {
185
+ files: fileResults,
186
+ totalPassed: fileResults.reduce((sum, f) => sum + f.results.filter((r) => r.passed).length, 0),
187
+ totalFailed: fileResults.reduce((sum, f) => sum + f.results.filter((r) => !r.passed).length, 0),
188
+ totalBlocks: fileResults.reduce((sum, f) => sum + f.results.length, 0),
189
+ duration: Date.now() - startTime
190
+ };
191
+ reportSummary(summary, opts);
192
+ process.exit(summary.totalFailed > 0 ? 1 : 0);
193
+ }
194
+
195
+ //#endregion
196
+ //#region src/cli/commands/readme.ts
197
+ /**
198
+ * Get the path to the README.md file.
199
+ * Works both during development and when installed as a package.
200
+ */
201
+ function getReadmePath() {
202
+ const thisDir = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
203
+ if (thisDir.split(/[/\\]/).pop() === "dist") return (0, node_path.join)((0, node_path.dirname)(thisDir), "README.md");
204
+ return (0, node_path.join)((0, node_path.dirname)((0, node_path.dirname)((0, node_path.dirname)(thisDir))), "README.md");
205
+ }
206
+ /**
207
+ * Load the README content.
208
+ */
209
+ function loadReadme() {
210
+ const readmePath = getReadmePath();
211
+ try {
212
+ return (0, node_fs.readFileSync)(readmePath, "utf-8");
213
+ } catch (error) {
214
+ const message = error instanceof Error ? error.message : String(error);
215
+ throw new Error(`Failed to load README from ${readmePath}: ${message}`);
216
+ }
217
+ }
218
+ /**
219
+ * Apply basic terminal formatting to markdown content.
220
+ * Colorizes headers, code blocks, and other elements for better readability.
221
+ */
222
+ function formatMarkdown$1(content, useColors) {
223
+ if (!useColors) return content;
224
+ const lines = content.split("\n");
225
+ const formatted = [];
226
+ let inCodeBlock = false;
227
+ for (const line of lines) {
228
+ if (line.startsWith("```")) {
229
+ inCodeBlock = !inCodeBlock;
230
+ formatted.push(picocolors.default.dim(line));
231
+ continue;
232
+ }
233
+ if (inCodeBlock) {
234
+ formatted.push(picocolors.default.dim(line));
235
+ continue;
236
+ }
237
+ if (line.startsWith("# ")) {
238
+ formatted.push(picocolors.default.bold(picocolors.default.cyan(line)));
239
+ continue;
240
+ }
241
+ if (line.startsWith("## ")) {
242
+ formatted.push(picocolors.default.bold(picocolors.default.blue(line)));
243
+ continue;
244
+ }
245
+ if (line.startsWith("### ")) {
246
+ formatted.push(picocolors.default.bold(line));
247
+ continue;
248
+ }
249
+ let formattedLine = line.replace(/`([^`]+)`/g, (_match, code) => {
250
+ return picocolors.default.yellow(code);
251
+ });
252
+ formattedLine = formattedLine.replace(/\*\*([^*]+)\*\*/g, (_match, text) => {
253
+ return picocolors.default.bold(text);
254
+ });
255
+ formattedLine = formattedLine.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
256
+ return `${picocolors.default.cyan(text)} ${picocolors.default.dim(`(${url})`)}`;
257
+ });
258
+ formatted.push(formattedLine);
259
+ }
260
+ return formatted.join("\n");
261
+ }
262
+ /**
263
+ * Check if stdout is an interactive terminal.
264
+ */
265
+ function isInteractive$1() {
266
+ return process.stdout.isTTY === true;
267
+ }
268
+ /**
269
+ * Register the readme command.
270
+ */
271
+ function registerReadmeCommand(program) {
272
+ program.command("readme").description("Display README documentation").option("--raw", "Output raw markdown without formatting").action((options) => {
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
+ });
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/cli/commands/docs.ts
286
+ /**
287
+ * Get the path to the tryscript-reference.md file.
288
+ * Works both during development and when installed as a package.
289
+ */
290
+ function getDocsPath() {
291
+ const thisDir = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
292
+ if (thisDir.split(/[/\\]/).pop() === "dist") return (0, node_path.join)((0, node_path.dirname)(thisDir), "docs", "tryscript-reference.md");
293
+ return (0, node_path.join)((0, node_path.dirname)((0, node_path.dirname)((0, node_path.dirname)(thisDir))), "docs", "tryscript-reference.md");
294
+ }
295
+ /**
296
+ * Load the docs content.
297
+ */
298
+ function loadDocs() {
299
+ const docsPath = getDocsPath();
300
+ try {
301
+ return (0, node_fs.readFileSync)(docsPath, "utf-8");
302
+ } catch (error) {
303
+ const message = error instanceof Error ? error.message : String(error);
304
+ throw new Error(`Failed to load reference docs from ${docsPath}: ${message}`);
305
+ }
306
+ }
307
+ /**
308
+ * Apply basic terminal formatting to markdown content.
309
+ * Colorizes headers, code blocks, and other elements for better readability.
310
+ */
311
+ function formatMarkdown(content, useColors) {
312
+ if (!useColors) return content;
313
+ const lines = content.split("\n");
314
+ const formatted = [];
315
+ let inCodeBlock = false;
316
+ for (const line of lines) {
317
+ if (line.startsWith("```")) {
318
+ inCodeBlock = !inCodeBlock;
319
+ formatted.push(picocolors.default.dim(line));
320
+ continue;
321
+ }
322
+ if (inCodeBlock) {
323
+ formatted.push(picocolors.default.dim(line));
324
+ continue;
325
+ }
326
+ if (line.startsWith("# ")) {
327
+ formatted.push(picocolors.default.bold(picocolors.default.cyan(line)));
328
+ continue;
329
+ }
330
+ if (line.startsWith("## ")) {
331
+ formatted.push(picocolors.default.bold(picocolors.default.blue(line)));
332
+ continue;
333
+ }
334
+ if (line.startsWith("### ")) {
335
+ formatted.push(picocolors.default.bold(line));
336
+ continue;
337
+ }
338
+ let formattedLine = line.replace(/`([^`]+)`/g, (_match, code) => {
339
+ return picocolors.default.yellow(code);
340
+ });
341
+ formattedLine = formattedLine.replace(/\*\*([^*]+)\*\*/g, (_match, text) => {
342
+ return picocolors.default.bold(text);
343
+ });
344
+ formattedLine = formattedLine.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_match, text, url) => {
345
+ return `${picocolors.default.cyan(text)} ${picocolors.default.dim(`(${url})`)}`;
346
+ });
347
+ formatted.push(formattedLine);
348
+ }
349
+ return formatted.join("\n");
350
+ }
351
+ /**
352
+ * Check if stdout is an interactive terminal.
353
+ */
354
+ function isInteractive() {
355
+ return process.stdout.isTTY === true;
356
+ }
357
+ /**
358
+ * Register the docs command.
359
+ */
360
+ function registerDocsCommand(program) {
361
+ program.command("docs").description("Display concise syntax reference").option("--raw", "Output raw markdown without formatting").action((options) => {
362
+ try {
363
+ const formatted = formatMarkdown(loadDocs(), !options.raw && isInteractive());
364
+ console.log(formatted);
365
+ } catch (error) {
366
+ const message = error instanceof Error ? error.message : String(error);
367
+ console.error(picocolors.default.red(`Error: ${message}`));
368
+ process.exit(1);
369
+ }
370
+ });
371
+ }
372
+
373
+ //#endregion
374
+ //#region src/cli/cli.ts
375
+ 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)").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)").action(runCommand);
377
+ registerReadmeCommand(program);
378
+ registerDocsCommand(program);
379
+ program.parseAsync(argv).catch((err) => {
380
+ console.error(picocolors.default.red(`Error: ${err.message}`));
381
+ process.exit(2);
382
+ });
383
+ }
384
+
385
+ //#endregion
386
+ //#region src/bin.ts
387
+ run(process.argv);
388
+
389
+ //#endregion
390
+ //# sourceMappingURL=bin.cjs.map
@@ -0,0 +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":"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"}
package/dist/bin.d.cts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { };
package/dist/bin.d.mts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { };