@sharpee/transcript-tester 0.9.61-beta

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.
Files changed (137) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/LICENSE +21 -0
  3. package/dist/cli.d.ts +11 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +367 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/condition-evaluator.d.ts +30 -0
  8. package/dist/condition-evaluator.d.ts.map +1 -0
  9. package/dist/condition-evaluator.js +314 -0
  10. package/dist/condition-evaluator.js.map +1 -0
  11. package/dist/fast-cli.d.ts +13 -0
  12. package/dist/fast-cli.d.ts.map +1 -0
  13. package/dist/fast-cli.js +363 -0
  14. package/dist/fast-cli.js.map +1 -0
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +48 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/navigator.d.ts +27 -0
  20. package/dist/navigator.d.ts.map +1 -0
  21. package/dist/navigator.js +303 -0
  22. package/dist/navigator.js.map +1 -0
  23. package/dist/parser.d.ts +19 -0
  24. package/dist/parser.d.ts.map +1 -0
  25. package/dist/parser.js +453 -0
  26. package/dist/parser.js.map +1 -0
  27. package/dist/reporter.d.ts +41 -0
  28. package/dist/reporter.d.ts.map +1 -0
  29. package/dist/reporter.js +386 -0
  30. package/dist/reporter.js.map +1 -0
  31. package/dist/runner.d.ts +44 -0
  32. package/dist/runner.d.ts.map +1 -0
  33. package/dist/runner.js +977 -0
  34. package/dist/runner.js.map +1 -0
  35. package/dist/story-loader.d.ts +31 -0
  36. package/dist/story-loader.d.ts.map +1 -0
  37. package/dist/story-loader.js +169 -0
  38. package/dist/story-loader.js.map +1 -0
  39. package/dist/types.d.ts +204 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +8 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist-esm/cli.d.ts +11 -0
  44. package/dist-esm/cli.d.ts.map +1 -0
  45. package/dist-esm/cli.js +332 -0
  46. package/dist-esm/cli.js.map +1 -0
  47. package/dist-esm/condition-evaluator.d.ts +30 -0
  48. package/dist-esm/condition-evaluator.d.ts.map +1 -0
  49. package/dist-esm/condition-evaluator.js +311 -0
  50. package/dist-esm/condition-evaluator.js.map +1 -0
  51. package/dist-esm/fast-cli.d.ts +13 -0
  52. package/dist-esm/fast-cli.d.ts.map +1 -0
  53. package/dist-esm/fast-cli.js +328 -0
  54. package/dist-esm/fast-cli.js.map +1 -0
  55. package/dist-esm/index.d.ts +17 -0
  56. package/dist-esm/index.d.ts.map +1 -0
  57. package/dist-esm/index.js +21 -0
  58. package/dist-esm/index.js.map +1 -0
  59. package/dist-esm/navigator.d.ts +27 -0
  60. package/dist-esm/navigator.d.ts.map +1 -0
  61. package/dist-esm/navigator.js +300 -0
  62. package/dist-esm/navigator.js.map +1 -0
  63. package/dist-esm/parser.d.ts +19 -0
  64. package/dist-esm/parser.d.ts.map +1 -0
  65. package/dist-esm/parser.js +415 -0
  66. package/dist-esm/parser.js.map +1 -0
  67. package/dist-esm/reporter.d.ts +41 -0
  68. package/dist-esm/reporter.d.ts.map +1 -0
  69. package/dist-esm/reporter.js +342 -0
  70. package/dist-esm/reporter.js.map +1 -0
  71. package/dist-esm/runner.d.ts +44 -0
  72. package/dist-esm/runner.d.ts.map +1 -0
  73. package/dist-esm/runner.js +941 -0
  74. package/dist-esm/runner.js.map +1 -0
  75. package/dist-esm/story-loader.d.ts +31 -0
  76. package/dist-esm/story-loader.d.ts.map +1 -0
  77. package/dist-esm/story-loader.js +131 -0
  78. package/dist-esm/story-loader.js.map +1 -0
  79. package/dist-esm/types.d.ts +204 -0
  80. package/dist-esm/types.d.ts.map +1 -0
  81. package/dist-esm/types.js +7 -0
  82. package/dist-esm/types.js.map +1 -0
  83. package/dist-npm/cli.d.ts +11 -0
  84. package/dist-npm/cli.d.ts.map +1 -0
  85. package/dist-npm/cli.js +367 -0
  86. package/dist-npm/cli.js.map +1 -0
  87. package/dist-npm/condition-evaluator.d.ts +30 -0
  88. package/dist-npm/condition-evaluator.d.ts.map +1 -0
  89. package/dist-npm/condition-evaluator.js +314 -0
  90. package/dist-npm/condition-evaluator.js.map +1 -0
  91. package/dist-npm/fast-cli.d.ts +13 -0
  92. package/dist-npm/fast-cli.d.ts.map +1 -0
  93. package/dist-npm/fast-cli.js +363 -0
  94. package/dist-npm/fast-cli.js.map +1 -0
  95. package/dist-npm/index.d.ts +17 -0
  96. package/dist-npm/index.d.ts.map +1 -0
  97. package/dist-npm/index.js +48 -0
  98. package/dist-npm/index.js.map +1 -0
  99. package/dist-npm/navigator.d.ts +27 -0
  100. package/dist-npm/navigator.d.ts.map +1 -0
  101. package/dist-npm/navigator.js +303 -0
  102. package/dist-npm/navigator.js.map +1 -0
  103. package/dist-npm/parser.d.ts +19 -0
  104. package/dist-npm/parser.d.ts.map +1 -0
  105. package/dist-npm/parser.js +453 -0
  106. package/dist-npm/parser.js.map +1 -0
  107. package/dist-npm/reporter.d.ts +41 -0
  108. package/dist-npm/reporter.d.ts.map +1 -0
  109. package/dist-npm/reporter.js +386 -0
  110. package/dist-npm/reporter.js.map +1 -0
  111. package/dist-npm/runner.d.ts +44 -0
  112. package/dist-npm/runner.d.ts.map +1 -0
  113. package/dist-npm/runner.js +977 -0
  114. package/dist-npm/runner.js.map +1 -0
  115. package/dist-npm/story-loader.d.ts +31 -0
  116. package/dist-npm/story-loader.d.ts.map +1 -0
  117. package/dist-npm/story-loader.js +169 -0
  118. package/dist-npm/story-loader.js.map +1 -0
  119. package/dist-npm/types.d.ts +204 -0
  120. package/dist-npm/types.d.ts.map +1 -0
  121. package/dist-npm/types.js +8 -0
  122. package/dist-npm/types.js.map +1 -0
  123. package/package.json +49 -0
  124. package/src/cli.ts +385 -0
  125. package/src/condition-evaluator.ts +382 -0
  126. package/src/fast-cli.ts +403 -0
  127. package/src/index.ts +26 -0
  128. package/src/navigator.ts +365 -0
  129. package/src/parser.ts +488 -0
  130. package/src/reporter.ts +409 -0
  131. package/src/runner.ts +1152 -0
  132. package/src/story-loader.ts +168 -0
  133. package/src/types.ts +244 -0
  134. package/tsconfig.esm.json +11 -0
  135. package/tsconfig.esm.tsbuildinfo +1 -0
  136. package/tsconfig.json +22 -0
  137. package/tsconfig.tsbuildinfo +1 -0
package/src/cli.ts ADDED
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Transcript Tester CLI
5
+ *
6
+ * Usage:
7
+ * transcript-test <story-path> [transcript-files...]
8
+ * transcript-test <story-path> --all
9
+ * transcript-test <story-path> --verbose
10
+ */
11
+
12
+ import * as path from 'path';
13
+ import * as fs from 'fs';
14
+ import * as readline from 'readline';
15
+ import { parseTranscriptFile, validateTranscript } from './parser';
16
+ import { runTranscript } from './runner';
17
+ import {
18
+ reportTranscript,
19
+ reportTestRun,
20
+ getExitCode,
21
+ generateTimestamp,
22
+ writeResultsToJson,
23
+ writeReportToFile
24
+ } from './reporter';
25
+ import { loadStory, findTranscripts, TestableGame } from './story-loader';
26
+ import { TranscriptResult, TestRunResult } from './types';
27
+
28
+ interface CliOptions {
29
+ storyPath: string;
30
+ transcriptPaths: string[];
31
+ verbose: boolean;
32
+ stopOnFailure: boolean;
33
+ all: boolean;
34
+ chain: boolean;
35
+ outputDir: string | null;
36
+ play: boolean;
37
+ }
38
+
39
+ /**
40
+ * Parse command line arguments
41
+ */
42
+ function parseArgs(args: string[]): CliOptions {
43
+ const options: CliOptions = {
44
+ storyPath: '',
45
+ transcriptPaths: [],
46
+ verbose: false,
47
+ stopOnFailure: false,
48
+ all: false,
49
+ chain: false,
50
+ outputDir: null,
51
+ play: false
52
+ };
53
+
54
+ let i = 0;
55
+ while (i < args.length) {
56
+ const arg = args[i];
57
+
58
+ if (arg === '--verbose' || arg === '-v') {
59
+ options.verbose = true;
60
+ } else if (arg === '--stop-on-failure' || arg === '-s') {
61
+ options.stopOnFailure = true;
62
+ } else if (arg === '--all' || arg === '-a') {
63
+ options.all = true;
64
+ } else if (arg === '--chain' || arg === '-c') {
65
+ options.chain = true;
66
+ } else if (arg === '--play' || arg === '-p') {
67
+ options.play = true;
68
+ } else if (arg === '--output-dir' || arg === '-o') {
69
+ i++;
70
+ if (i < args.length) {
71
+ options.outputDir = args[i];
72
+ }
73
+ } else if (arg === '--help' || arg === '-h') {
74
+ printHelp();
75
+ process.exit(0);
76
+ } else if (!arg.startsWith('-')) {
77
+ if (!options.storyPath) {
78
+ options.storyPath = arg;
79
+ } else {
80
+ options.transcriptPaths.push(arg);
81
+ }
82
+ }
83
+
84
+ i++;
85
+ }
86
+
87
+ return options;
88
+ }
89
+
90
+ /**
91
+ * Print help message
92
+ */
93
+ function printHelp(): void {
94
+ console.log(`
95
+ Transcript Tester - Test Sharpee stories with transcript files
96
+
97
+ Usage:
98
+ transcript-test <story-path> [transcript-files...] [options]
99
+ transcript-test <story-path> --play
100
+
101
+ Arguments:
102
+ story-path Path to the story directory (e.g., stories/dungeo)
103
+ transcript-files One or more .transcript files to run
104
+
105
+ Options:
106
+ -p, --play Interactive play mode (REPL)
107
+ -a, --all Run all transcripts in the story's tests/ directory
108
+ -c, --chain Chain transcripts (don't reset game state between them)
109
+ -v, --verbose Show detailed output for each command
110
+ -s, --stop-on-failure Stop on first failure
111
+ -o, --output-dir <dir> Write timestamped results to directory (JSON + text report)
112
+ -h, --help Show this help message
113
+
114
+ Examples:
115
+ transcript-test stories/dungeo --play
116
+ transcript-test stories/dungeo tests/navigation.transcript
117
+ transcript-test stories/dungeo --all
118
+ transcript-test stories/dungeo tests/*.transcript --verbose
119
+ transcript-test stories/dungeo --all -o test-results
120
+ transcript-test stories/dungeo --chain tests/setup.transcript tests/puzzle.transcript
121
+ `);
122
+ }
123
+
124
+ /**
125
+ * Run interactive play mode (REPL)
126
+ */
127
+ async function runInteractiveMode(game: TestableGame): Promise<void> {
128
+ const rl = readline.createInterface({
129
+ input: process.stdin,
130
+ output: process.stdout
131
+ });
132
+
133
+ let debugMode = false;
134
+ let traceMode = false;
135
+
136
+ console.log('\n--- Interactive Mode ---');
137
+ console.log('Type commands to play. Special commands:');
138
+ console.log(' /quit, /q - Exit the game');
139
+ console.log(' /debug - Toggle debug mode (show events after each command)');
140
+ console.log(' /trace - Toggle parser trace mode (show grammar matching)');
141
+ console.log(' /events - Show events from last command');
142
+ console.log(' /look, /l - Shortcut for "look"');
143
+ console.log(' /inv, /i - Shortcut for "inventory"');
144
+ console.log('');
145
+
146
+ // Show initial room description
147
+ const initialOutput = await game.executeCommand('look');
148
+ console.log(initialOutput);
149
+
150
+ const prompt = (): void => {
151
+ rl.question('\n> ', async (input) => {
152
+ const trimmed = input.trim();
153
+
154
+ if (!trimmed) {
155
+ prompt();
156
+ return;
157
+ }
158
+
159
+ // Handle special commands
160
+ if (trimmed === '/quit' || trimmed === '/q') {
161
+ console.log('Goodbye!');
162
+ rl.close();
163
+ process.exit(0);
164
+ return;
165
+ }
166
+
167
+ if (trimmed === '/restart') {
168
+ console.log('(Restart not yet implemented - please exit and rerun)');
169
+ prompt();
170
+ return;
171
+ }
172
+
173
+ if (trimmed === '/debug') {
174
+ debugMode = !debugMode;
175
+ console.log(`Debug mode: ${debugMode ? 'ON' : 'OFF'}`);
176
+ prompt();
177
+ return;
178
+ }
179
+
180
+ if (trimmed === '/trace') {
181
+ traceMode = !traceMode;
182
+ process.env.PARSER_DEBUG = traceMode ? 'true' : '';
183
+ console.log(`Parser trace: ${traceMode ? 'ON' : 'OFF'}`);
184
+ prompt();
185
+ return;
186
+ }
187
+
188
+ if (trimmed === '/events') {
189
+ if (game.lastEvents && game.lastEvents.length > 0) {
190
+ console.log('\nEvents from last command:');
191
+ for (const event of game.lastEvents) {
192
+ console.log(` ${event.type}`);
193
+ if (event.data && Object.keys(event.data).length > 0) {
194
+ console.log(` ${JSON.stringify(event.data)}`);
195
+ }
196
+ }
197
+ } else {
198
+ console.log('(No events from last command)');
199
+ }
200
+ prompt();
201
+ return;
202
+ }
203
+
204
+ // Shortcuts
205
+ let command = trimmed;
206
+ if (trimmed === '/look' || trimmed === '/l') {
207
+ command = 'look';
208
+ } else if (trimmed === '/inv' || trimmed === '/i') {
209
+ command = 'inventory';
210
+ }
211
+
212
+ // Execute the command
213
+ try {
214
+ const output = await game.executeCommand(command);
215
+ console.log(output);
216
+
217
+ // Show events in debug mode
218
+ if (debugMode && game.lastEvents && game.lastEvents.length > 0) {
219
+ console.log('\n[Events]');
220
+ for (const event of game.lastEvents) {
221
+ const data = event.data && Object.keys(event.data).length > 0
222
+ ? ` ${JSON.stringify(event.data)}`
223
+ : '';
224
+ console.log(` ${event.type}${data}`);
225
+ }
226
+ }
227
+ } catch (error) {
228
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
229
+ }
230
+
231
+ prompt();
232
+ });
233
+ };
234
+
235
+ prompt();
236
+ }
237
+
238
+ /**
239
+ * Main entry point
240
+ */
241
+ async function main(): Promise<void> {
242
+ const args = process.argv.slice(2);
243
+
244
+ if (args.length === 0) {
245
+ printHelp();
246
+ process.exit(1);
247
+ }
248
+
249
+ const options = parseArgs(args);
250
+
251
+ if (!options.storyPath) {
252
+ console.error('Error: Story path is required');
253
+ printHelp();
254
+ process.exit(1);
255
+ }
256
+
257
+ // Interactive play mode
258
+ if (options.play) {
259
+ console.log(`Loading story from: ${options.storyPath}`);
260
+ let game: TestableGame;
261
+ try {
262
+ game = await loadStory(options.storyPath);
263
+ } catch (error) {
264
+ console.error(`Error loading story: ${error}`);
265
+ process.exit(3);
266
+ }
267
+ await runInteractiveMode(game);
268
+ return;
269
+ }
270
+
271
+ // Find transcript files
272
+ let transcriptPaths = options.transcriptPaths;
273
+
274
+ if (options.all || transcriptPaths.length === 0) {
275
+ const testsDir = path.join(options.storyPath, 'tests');
276
+ if (fs.existsSync(testsDir)) {
277
+ transcriptPaths = findTranscripts(testsDir);
278
+ } else {
279
+ // Check for transcripts directory
280
+ const transcriptsDir = path.join(options.storyPath, 'tests', 'transcripts');
281
+ if (fs.existsSync(transcriptsDir)) {
282
+ transcriptPaths = findTranscripts(transcriptsDir);
283
+ }
284
+ }
285
+ }
286
+
287
+ // Deduplicate paths
288
+ transcriptPaths = [...new Set(transcriptPaths)];
289
+
290
+ if (transcriptPaths.length === 0) {
291
+ console.error('Error: No transcript files found');
292
+ console.error(`Looked in: ${path.join(options.storyPath, 'tests')}`);
293
+ process.exit(2);
294
+ }
295
+
296
+ console.log(`Loading story from: ${options.storyPath}`);
297
+
298
+ // Load the story
299
+ let game: TestableGame;
300
+ try {
301
+ game = await loadStory(options.storyPath);
302
+ } catch (error) {
303
+ console.error(`Error loading story: ${error}`);
304
+ process.exit(3);
305
+ }
306
+
307
+ console.log(`Found ${transcriptPaths.length} transcript(s) to run`);
308
+ if (options.chain) {
309
+ console.log(`Chain mode: Game state will persist between transcripts`);
310
+ }
311
+
312
+ // Run all transcripts
313
+ const results: TranscriptResult[] = [];
314
+
315
+ for (const transcriptPath of transcriptPaths) {
316
+ // Parse the transcript
317
+ const transcript = parseTranscriptFile(transcriptPath);
318
+
319
+ // Validate
320
+ const errors = validateTranscript(transcript);
321
+ if (errors.length > 0) {
322
+ console.error(`\nErrors in ${transcriptPath}:`);
323
+ for (const err of errors) {
324
+ console.error(` - ${err}`);
325
+ }
326
+ continue;
327
+ }
328
+
329
+ // Reload story for each transcript to reset state (unless chaining)
330
+ if (!options.chain) {
331
+ game = await loadStory(options.storyPath);
332
+ }
333
+
334
+ // Run the transcript
335
+ const result = await runTranscript(transcript, game, {
336
+ verbose: options.verbose,
337
+ stopOnFailure: options.stopOnFailure
338
+ });
339
+
340
+ results.push(result);
341
+
342
+ // Report individual transcript results
343
+ reportTranscript(result, { verbose: options.verbose });
344
+
345
+ // Stop if requested and there was a failure
346
+ if (options.stopOnFailure && result.failed > 0) {
347
+ break;
348
+ }
349
+ }
350
+
351
+ // Aggregate results
352
+ const runResult: TestRunResult = {
353
+ transcripts: results,
354
+ totalPassed: results.reduce((sum, r) => sum + r.passed, 0),
355
+ totalFailed: results.reduce((sum, r) => sum + r.failed, 0),
356
+ totalExpectedFailures: results.reduce((sum, r) => sum + r.expectedFailures, 0),
357
+ totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
358
+ totalDuration: results.reduce((sum, r) => sum + r.duration, 0)
359
+ };
360
+
361
+ // Final report if multiple transcripts
362
+ if (results.length > 1) {
363
+ reportTestRun(runResult, { verbose: options.verbose });
364
+ }
365
+
366
+ // Write results to files if output directory specified
367
+ if (options.outputDir) {
368
+ const timestamp = generateTimestamp();
369
+ const jsonPath = writeResultsToJson(runResult, options.outputDir, timestamp);
370
+ const reportPath = writeReportToFile(runResult, options.outputDir, timestamp);
371
+ console.log();
372
+ console.log(`Results written to:`);
373
+ console.log(` JSON: ${jsonPath}`);
374
+ console.log(` Report: ${reportPath}`);
375
+ }
376
+
377
+ // Exit with appropriate code
378
+ process.exit(getExitCode(runResult));
379
+ }
380
+
381
+ // Run
382
+ main().catch(error => {
383
+ console.error('Fatal error:', error);
384
+ process.exit(1);
385
+ });