sxy-test-runner 2.3.20 → 2.3.22

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/AGENTS.md CHANGED
@@ -85,6 +85,12 @@ npx sxy-test-runner once --filter-describe "users"
85
85
  # Run tests whose names contain "creates a user"
86
86
  npx sxy-test-runner once --filter-it "creates a user"
87
87
 
88
+ # Watch mode, reporting each run as single agent readable lines
89
+ npx sxy-test-runner watch --agent
90
+
91
+ # Watch mode, recording each run's result where another process can read it
92
+ npx sxy-test-runner watch --status-file .sxyt-status.txt
93
+
88
94
  # Generate an initial config
89
95
  npx sxy-test-runner init
90
96
  ```
@@ -95,7 +101,13 @@ configured pattern. Both options are resolved relative to `testsBase` and can be
95
101
 
96
102
  `--filter-describe` and `--filter-it` apply case-insensitive substring filters to describe
97
103
  and test names. They work in both watch and `once` modes and can be combined. If the
98
- filters select no runnable tests, `once` exits with code `4`.
104
+ filters select no runnable tests, `once` exits with code `5` - `4` means no test file
105
+ matched the `tests` pattern in the first place.
106
+
107
+ `--agent` and `--status-file` are watch-only, and are the two ways to follow a watch session
108
+ from outside it: `--agent` streams each run to stdout as single lines, `--status-file` writes
109
+ the latest run's result to a file. Both are covered under "Watch mode" below. `once` ignores
110
+ them - it already ends with an exit code.
99
111
 
100
112
  Describe and test filters run after the selected test files have loaded. The runner gives
101
113
  each test file a fresh ESM instance and propagates it through the file's static dependency
@@ -233,9 +245,14 @@ on parallel `it` execution.
233
245
 
234
246
  Watch mode builds a static dependency tree for every test. When a file changes, it reruns
235
247
  the changed test itself and tests whose static import graph includes that file. If
236
- `watch.reRunFailingTests` is true, previously failing files are included until they pass.
237
- Keyboard commands normally require Enter; set `watch.ttyActionsOnKeypress` to `true` to
238
- trigger `r`, `l`, `a`, and `f` immediately on keypress in a TTY session.
248
+ `watch.reRunFailingTests` is true (the default), test files that failed earlier in the
249
+ session are included in every run until they pass, so a failure stays visible instead of
250
+ disappearing the moment an unrelated file is edited. Keyboard commands normally require
251
+ Enter; set `watch.ttyActionsOnKeypress` to `true` to trigger `r`, `l`, `a`, and `f`
252
+ immediately on keypress in a TTY session.
253
+
254
+ Changes that arrive while a run is in flight are folded into the next run rather than
255
+ queueing a run each, so a burst of edits produces one rerun covering all of them.
239
256
 
240
257
  Important limitations:
241
258
 
@@ -247,7 +264,8 @@ Important limitations:
247
264
  process. Keep mutable access sequential or provide application-level synchronization,
248
265
  and arrange explicit cleanup for resources such as servers and database connections.
249
266
  - Use `once` instead of watch mode for autonomous agent verification so the command
250
- terminates and returns a meaningful status.
267
+ terminates and returns a meaningful status. To follow someone else's long running watch
268
+ session instead, see "Following a watch session as an agent" below.
251
269
 
252
270
  ### Watching someone else's watch session
253
271
 
@@ -290,6 +308,86 @@ than the code under test. Watch mode writes `-1` on startup, so a status from a
290
308
  session is never read as current, and each write is renamed into place, so a poller only
291
309
  ever sees a complete status.
292
310
 
311
+ ### Following a watch session as an agent
312
+
313
+ `--agent` (or `watch.agent` in the config) replaces watch mode's usual output with one line
314
+ per event, for an agent reading the process's stdout rather than a terminal. It applies to
315
+ watch mode only - `once` already ends with an exit code, so a config setting it is ignored
316
+ there.
317
+
318
+ ```
319
+ [sxyt] init | pid=16744
320
+ [sxyt] found | files=2
321
+ [sxyt] run | n=1 | files=2 | why=startup
322
+ [sxyt] fail | n=1 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
323
+ [sxyt] done | n=1 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=150
324
+ [sxyt] watching | files=2
325
+ [sxyt] change | file=src/orders.js | event=update
326
+ [sxyt] run | n=2 | files=2 | why=auto
327
+ [sxyt] fail | n=2 | file=tests/users.test.js | describe=creating a user | test=rejects a duplicate email | error=AssertionError: expected 409 to equal 200
328
+ [sxyt] done | n=2 | code=1 | desc=1 test failed | tests=2/3 | items=1/2 | files=1/2 | failing=1 | ms=169
329
+ ```
330
+
331
+ The grammar is `[sxyt] <event>` followed by ` | <key>=<value>` pairs. Values are never
332
+ quoted: newlines in free text are collapsed to spaces and any `|` becomes `/`, so one event
333
+ is always exactly one line and a field boundary is always a real one.
334
+
335
+ | event | meaning |
336
+ | --- | --- |
337
+ | `init` | the process is up, before the config is read - proof it started at all |
338
+ | `found` | test discovery finished |
339
+ | `run` | a run has started. `why` is `startup`, `auto` (a file changed), or `all`/`last`/`failed` from a keyboard command |
340
+ | `fail` | one failure. `describe` is absent when a file would not load, `test` when the describe itself threw |
341
+ | `done` | the run finished |
342
+ | `change` | a watched file changed |
343
+ | `watching` | startup is complete and the watcher is live |
344
+
345
+ `n` ties a run's lines together. Failures are emitted before their `done`, so `done` is a
346
+ complete end-of-run marker: wait for it and everything about that run has already arrived.
347
+
348
+ `code` on `done` is the same status code the status file uses, so `0` is a pass and `2`
349
+ still means the run died partway rather than merely failing.
350
+
351
+ `covers` on the `run` line says what the run was built from, and so how far its result
352
+ generalises:
353
+
354
+ | covers | the run contained |
355
+ | --- | --- |
356
+ | `all` | every discovered test file - the startup run, or `a` from the keyboard |
357
+ | `changed+failing` | the changed files and every file still failing (`watch.reRunFailingTests` on, the default) |
358
+ | `changed` | only the changed files (`watch.reRunFailingTests` off) |
359
+ | `last` | the previous run's file set |
360
+ | `failing` | only the files still failing |
361
+
362
+ **No incremental run can tell you the suite is green.** `code` and the tallies describe
363
+ only the files that run contained. `failing` is broader - it counts the test files still
364
+ known to be failing across the session - but it is still only knowledge from files that
365
+ have actually been run, and a run happens only when the watcher both notices a change and
366
+ maps it to a test. Either half can miss:
367
+
368
+ - Nothing is watched but `watch.watchFiles` under `watch.watchFilesBase` - `**/*.js` from
369
+ the project root by default. Editing `.env`, a `.json` fixture, a template, a database
370
+ seed, or anything outside that base changes what the tests do and produces **no run at
371
+ all**. The stream simply stays quiet, and the previous `done` remains the last word while
372
+ silently going stale.
373
+ - Even for a watched file, the changed-to-test mapping is the static import graph, so it
374
+ misses dynamic imports and anything reached through a `._cached_.*` boundary. A test file
375
+ that was neither changed nor already failing is not re-run, so a break in it never reaches
376
+ `failing`.
377
+
378
+ So `failing=0` means "nothing is known to be broken", not "everything passes", and silence
379
+ means "no watched file changed", not "still green". To actually verify the suite, use a run
380
+ with `covers=all` - the startup run, `a` at the keyboard, or a separate `once` - and read
381
+ `code` and `failing` from that. For the ordinary edit-test loop in between, `failing` is the
382
+ right signal: with the default `watch.reRunFailingTests`, a failure keeps being re-run and
383
+ re-reported until it passes.
384
+
385
+ Widening `watch.watchFiles` (for example to `**/*.{js,json,env}`) closes the first gap where
386
+ it matters, at the cost of more reruns.
387
+
388
+ Diffs, logs and stack traces are not in the agent stream; errors are reduced to one line.
389
+ Use `once` when a finite run and an exit code are all that is needed.
390
+
293
391
  ## Configuration reference
294
392
 
295
393
  The exported config type is available for JavaScript type checking:
@@ -326,7 +424,8 @@ export default {
326
424
  runAllOnStartup: true,
327
425
  reRunFailingTests: true,
328
426
  ttyActionsOnKeypress: false,
329
- statusFile: false
427
+ statusFile: false,
428
+ agent: false
330
429
  }
331
430
  }
332
431
  ```
package/dist/cli/help.js CHANGED
@@ -30,6 +30,7 @@ export function help() {
30
30
  ' -d, --filter-describe <substring> Run matching describe blocks',
31
31
  ' -i, --filter-it <substring> Run matching tests',
32
32
  ' -s, --status-file <file> Write the latest watch run status to a file',
33
+ ' --agent Report a watch session as single agent readable lines',
33
34
  '',
34
35
  'Watch controls:',
35
36
  ' r/l + Enter Re-run the last set of tests',
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAElC,MAAM,UAAU,IAAI;IAChB,MAAM,WAAW,GAAG,eAAe;SAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,eAAe,CAAC;SACnC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjB,MAAM,iBAAiB,GAAG,eAAe;SACpC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;SACtE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEjB,GAAG,CAAC;QACA,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;QAC7B,EAAE;QACF,QAAQ;QACR,KAAK,OAAO,oBAAoB;QAChC,KAAK,OAAO,iBAAiB;QAC7B,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,OAAO;QACnB,EAAE;QACF,WAAW;QACX,iEAAiE;QACjE,2CAA2C;QAC3C,+CAA+C;QAC/C,yBAAyB;QACzB,EAAE;QACF,UAAU;QACV,8DAA8D;QAC9D,iEAAiE;QACjE,0EAA0E;QAC1E,mEAAmE;QACnE,yDAAyD;QACzD,kFAAkF;QAClF,EAAE;QACF,iBAAiB;QACjB,sDAAsD;QACtD,0CAA0C;QAC1C,8CAA8C;QAC9C,qEAAqE;QACrE,EAAE;QACF,WAAW;QACX,KAAK,OAAO,EAAE;QACd,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,4CAA4C;QACxD,KAAK,OAAO,8BAA8B;QAC1C,KAAK,OAAO,sCAAsC;QAClD,EAAE;QACF,kBAAkB,WAAW,OAAO,iBAAiB,GAAG;QACxD,uBAAuB,OAAO,eAAe,WAAW,GAAG;KAC9D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AACjB,CAAC","sourcesContent":["import chalk from '../packages/chalk.js'\nimport { packageName, cliName, configFileNames, configLocations } from '../config.js'\nimport { out } from '../output.js'\n\nexport function help(): void {\n const configNames = configFileNames\n .map(name => `${name}.{js,cjs,mjs}`)\n .join(' or ')\n const configDirectories = configLocations\n .map(location => location === '' ? 'the project root' : `${location}/`)\n .join(' or ')\n\n out([\n chalk.brightblue(packageName),\n '',\n 'Usage:',\n ` ${cliName} [watch] [options]`,\n ` ${cliName} once [options]`,\n ` ${cliName} init`,\n ` ${cliName} help`,\n '',\n 'Commands:',\n ' watch Run tests and watch for related file changes (default)',\n ' once Run the test suite once and exit',\n ' init Create an initial configuration file',\n ' help Show this help',\n '',\n 'Options:',\n ' -c, --config <file> Use a specific configuration file',\n ' -t, --test-files <glob> Override the configured test pattern',\n ' -f, --filter <glob> Further constrain the configured test pattern',\n ' -d, --filter-describe <substring> Run matching describe blocks',\n ' -i, --filter-it <substring> Run matching tests',\n ' -s, --status-file <file> Write the latest watch run status to a file',\n '',\n 'Watch controls:',\n ' r/l + Enter Re-run the last set of tests',\n ' a + Enter Re-run all tests',\n ' f + Enter Re-run failing tests',\n ' Set watch.ttyActionsOnKeypress to true to run these without Enter',\n '',\n 'Examples:',\n ` ${cliName}`,\n ` ${cliName} once`,\n ` ${cliName} watch --test-files \"**/*.focused.test.js\"`,\n ` ${cliName} once --filter \"**/users/**\"`,\n ` ${cliName} once --config config/sxyt.config.js`,\n '',\n `Configuration: ${configNames} in ${configDirectories}.`,\n `Executable aliases: ${cliName}, sxy-test, ${packageName}.`\n ].join('\\n'))\n}\n"]}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAElC,MAAM,UAAU,IAAI;IAChB,MAAM,WAAW,GAAG,eAAe;SAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,eAAe,CAAC;SACnC,IAAI,CAAC,MAAM,CAAC,CAAA;IACjB,MAAM,iBAAiB,GAAG,eAAe;SACpC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;SACtE,IAAI,CAAC,MAAM,CAAC,CAAA;IAEjB,GAAG,CAAC;QACA,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;QAC7B,EAAE;QACF,QAAQ;QACR,KAAK,OAAO,oBAAoB;QAChC,KAAK,OAAO,iBAAiB;QAC7B,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,OAAO;QACnB,EAAE;QACF,WAAW;QACX,iEAAiE;QACjE,2CAA2C;QAC3C,+CAA+C;QAC/C,yBAAyB;QACzB,EAAE;QACF,UAAU;QACV,8DAA8D;QAC9D,iEAAiE;QACjE,0EAA0E;QAC1E,mEAAmE;QACnE,yDAAyD;QACzD,kFAAkF;QAClF,4FAA4F;QAC5F,EAAE;QACF,iBAAiB;QACjB,sDAAsD;QACtD,0CAA0C;QAC1C,8CAA8C;QAC9C,qEAAqE;QACrE,EAAE;QACF,WAAW;QACX,KAAK,OAAO,EAAE;QACd,KAAK,OAAO,OAAO;QACnB,KAAK,OAAO,4CAA4C;QACxD,KAAK,OAAO,8BAA8B;QAC1C,KAAK,OAAO,sCAAsC;QAClD,EAAE;QACF,kBAAkB,WAAW,OAAO,iBAAiB,GAAG;QACxD,uBAAuB,OAAO,eAAe,WAAW,GAAG;KAC9D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;AACjB,CAAC","sourcesContent":["import chalk from '../packages/chalk.js'\nimport { packageName, cliName, configFileNames, configLocations } from '../config.js'\nimport { out } from '../output.js'\n\nexport function help(): void {\n const configNames = configFileNames\n .map(name => `${name}.{js,cjs,mjs}`)\n .join(' or ')\n const configDirectories = configLocations\n .map(location => location === '' ? 'the project root' : `${location}/`)\n .join(' or ')\n\n out([\n chalk.brightblue(packageName),\n '',\n 'Usage:',\n ` ${cliName} [watch] [options]`,\n ` ${cliName} once [options]`,\n ` ${cliName} init`,\n ` ${cliName} help`,\n '',\n 'Commands:',\n ' watch Run tests and watch for related file changes (default)',\n ' once Run the test suite once and exit',\n ' init Create an initial configuration file',\n ' help Show this help',\n '',\n 'Options:',\n ' -c, --config <file> Use a specific configuration file',\n ' -t, --test-files <glob> Override the configured test pattern',\n ' -f, --filter <glob> Further constrain the configured test pattern',\n ' -d, --filter-describe <substring> Run matching describe blocks',\n ' -i, --filter-it <substring> Run matching tests',\n ' -s, --status-file <file> Write the latest watch run status to a file',\n ' --agent Report a watch session as single agent readable lines',\n '',\n 'Watch controls:',\n ' r/l + Enter Re-run the last set of tests',\n ' a + Enter Re-run all tests',\n ' f + Enter Re-run failing tests',\n ' Set watch.ttyActionsOnKeypress to true to run these without Enter',\n '',\n 'Examples:',\n ` ${cliName}`,\n ` ${cliName} once`,\n ` ${cliName} watch --test-files \"**/*.focused.test.js\"`,\n ` ${cliName} once --filter \"**/users/**\"`,\n ` ${cliName} once --config config/sxyt.config.js`,\n '',\n `Configuration: ${configNames} in ${configDirectories}.`,\n `Executable aliases: ${cliName}, sxy-test, ${packageName}.`\n ].join('\\n'))\n}\n"]}
package/dist/cli/init.js CHANGED
@@ -129,6 +129,9 @@ export default {
129
129
  // file to write the latest run status to, or false for none
130
130
  statusFile: false,
131
131
 
132
+ // report the session as single agent readable lines instead of the usual output
133
+ agent: false,
134
+
132
135
  },
133
136
 
134
137
  }`;
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,MAAM,UAAU,IAAI;IAChB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEhC,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;QAC3C,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;YAC3C,IAAI,GAAG,CAAA;YACP,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,KAAK,CAAC,CAAA;YAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IAED,SAAS,mBAAmB,CAAC,GAAW;QACpC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,yCAAyC,GAAG,EAAE;cACvE,kEAAkE,OAAO,SAAS,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,0BAA0B,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,2CAA2C;cACpE,gEAAgE;cAChE,iFAAiF,CAAC,CAAA;IAC5F,CAAC;IAED,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;IAC1E,mEAAmE;IACnE,MAAM,WAAW,GAAsB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IAEjE,MAAM,gBAAgB,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,CAAC;QACpD,aAAa;QACb,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACxE,SAAS;QACT,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;IAE7E,MAAM,aAAa,GACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2FE,CAAA;IAEE,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAA;IAEjD,GAAG,CAAC,KAAK,CAAC,WAAW,CAAA,IAAI,WAAW,sBAAsB,GAAG,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAA;AAC9F,CAAC","sourcesContent":["import { join } from 'path'\nimport chalk from '../packages/chalk.js'\nimport { packageName, cliName, configFileNames, configLocations } from '../config.js'\nimport { out } from '../output.js'\nimport fs from 'fs'\n\nexport function init(): void {\n const projectDir = process.cwd()\n\n for (const configLocation of configLocations) {\n for (const configFileName of configFileNames) {\n let loc\n loc = join(projectDir, configLocation, configFileName + '.js')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n loc = join(projectDir, configLocation, configFileName + '.cjs')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n loc = join(projectDir, configLocation, configFileName + '.mjs')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n }\n }\n\n function configAlreadyExists(loc: string): void {\n out(chalk.orange`[${packageName}] A matching config already exists at ${loc}`\n + `\\nPlease remove this config before generating a new one with \\`${cliName} init\\``)\n }\n\n const projectPackageJsonLocation = join(projectDir, 'package.json')\n if (!fs.existsSync(projectPackageJsonLocation)) {\n out(chalk.orange`[${packageName}] No package.json exists in this location`\n + '\\nPlease initialise the package with `yarn init` on `npm init`'\n + ' before running this command, and run this command from the project root folder')\n }\n\n const packageJsonRaw = fs.readFileSync(projectPackageJsonLocation, 'utf8')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const packageJson: { type?: string } = JSON.parse(packageJsonRaw)\n\n const targetConfigName = (packageJson.type === 'module')\n // ES6 module\n ? join(projectDir, configLocations[0] ?? '', configFileNames[0] + '.js')\n // common\n : join(projectDir, configLocations[0] ?? '', configFileNames[0] + '.mjs')\n\n const defaultConfig =\n`\n// @ts-check\n\n/** @satisfies {import('sxy-test-runner').Config} */\nexport default {\n\n // base folder for tests match and ignore patterns\n testsBase: '.',\n\n // glob pattern or array of glob patterns of test files\n tests: [\n '**/*.sxyt.{js,mjs,jsx}',\n '**/*.test.{js,mjs,jsx}',\n '**/*.spec.{js,mjs,jsx}',\n '**/tests?/*.{js,mjs,jsx}',\n '**/__tests?__/*.{js,mjs,jsx}'\n ],\n\n // glob pattern or array of patterns of test files to ignore\n testsIgnore: ['**/node_modules/**/*'],\n\n // whether to show the full error trace when an error occurs in a test file\n showErrorTrace: false,\n\n // tasks to run on startup\n // accepts a function, file location (relative to project base), or array of functions and or files\n setup: undefined,\n\n // tasks to run before each test file is run\n beforeEachFile: undefined,\n\n // tasks to run after each test file is run\n afterEachFile: undefined,\n\n // tasks to run before each describe block is processed\n // if the function returns an object with named properties, or the file exports named exports,\n // these will be passed to the callback function in argument 0, and can be access by destructruing e.g.\n // describe('thing', ({exportA, exportB}) => { it('should', ...) })\n beforeEachDescribe: undefined,\n\n // tasks to run after each describe block\n afterEachDescribe: undefined,\n\n // tasks to run before each it/test block\n // function returns and file exports will be passed to the it/test block as with describe above, e.g.\n // it('should', ({exportA, exportB}) => { assert('something') })\n beforeEachTest: undefined,\n\n // tasks to run after each it/test block\n afterEachTest: undefined,\n\n // how to execute the different parts of test files\n execution: {\n\n // run test files in 'parallel' or 'sequential'ly\n files: 'sequential',\n\n // run describe blocks within a test file in 'parallel' or 'sequential'ly\n describes: 'sequential',\n\n // run it/test blocks within a describe blocks in 'parallel' or 'sequential'ly\n tests: 'sequential'\n\n },\n\n // watch mode configurations\n watch: {\n\n //// the base directory to watch files in\n watchFilesBase: '.',\n\n //// glob filter or array of glob filters of files to watch\n watchFiles: '**/*.js',\n\n //// glob filter or array of globl filters of files to ignore\n watchFilesIgnore: '**/node_modules/**/*',\n\n // run all tests when starting the watcher\n runAllOnStartup: true,\n\n // re run all previously failed tests on each test run, until they pass\n reRunFailingTests: true,\n\n // run watch mode keyboard commands immediately, without waiting for Enter\n ttyActionsOnKeypress: false,\n\n // file to write the latest run status to, or false for none\n statusFile: false,\n\n },\n\n}`\n\n fs.writeFileSync(targetConfigName, defaultConfig)\n\n out(chalk.mediumgreen`[${packageName}] Created config at ` + chalk.blue(targetConfigName))\n}\n"]}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,KAAK,MAAM,sBAAsB,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACrF,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAClC,OAAO,EAAE,MAAM,IAAI,CAAA;AAEnB,MAAM,UAAU,IAAI;IAChB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEhC,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;QAC3C,KAAK,MAAM,cAAc,IAAI,eAAe,EAAE,CAAC;YAC3C,IAAI,GAAG,CAAA;YACP,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,KAAK,CAAC,CAAA;YAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;YACD,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAAC,CAAA;YAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,mBAAmB,CAAC,GAAG,CAAC,CAAA;YAC5B,CAAC;QACL,CAAC;IACL,CAAC;IAED,SAAS,mBAAmB,CAAC,GAAW;QACpC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,yCAAyC,GAAG,EAAE;cACvE,kEAAkE,OAAO,SAAS,CAAC,CAAA;IAC7F,CAAC;IAED,MAAM,0BAA0B,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAA;IACnE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,KAAK,CAAC,MAAM,CAAA,IAAI,WAAW,2CAA2C;cACpE,gEAAgE;cAChE,iFAAiF,CAAC,CAAA;IAC5F,CAAC;IAED,MAAM,cAAc,GAAG,EAAE,CAAC,YAAY,CAAC,0BAA0B,EAAE,MAAM,CAAC,CAAA;IAC1E,mEAAmE;IACnE,MAAM,WAAW,GAAsB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAA;IAEjE,MAAM,gBAAgB,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,QAAQ,CAAC;QACpD,aAAa;QACb,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACxE,SAAS;QACT,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAA;IAE7E,MAAM,aAAa,GACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8FE,CAAA;IAEE,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAA;IAEjD,GAAG,CAAC,KAAK,CAAC,WAAW,CAAA,IAAI,WAAW,sBAAsB,GAAG,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAA;AAC9F,CAAC","sourcesContent":["import { join } from 'path'\nimport chalk from '../packages/chalk.js'\nimport { packageName, cliName, configFileNames, configLocations } from '../config.js'\nimport { out } from '../output.js'\nimport fs from 'fs'\n\nexport function init(): void {\n const projectDir = process.cwd()\n\n for (const configLocation of configLocations) {\n for (const configFileName of configFileNames) {\n let loc\n loc = join(projectDir, configLocation, configFileName + '.js')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n loc = join(projectDir, configLocation, configFileName + '.cjs')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n loc = join(projectDir, configLocation, configFileName + '.mjs')\n if (fs.existsSync(loc)) {\n configAlreadyExists(loc)\n }\n }\n }\n\n function configAlreadyExists(loc: string): void {\n out(chalk.orange`[${packageName}] A matching config already exists at ${loc}`\n + `\\nPlease remove this config before generating a new one with \\`${cliName} init\\``)\n }\n\n const projectPackageJsonLocation = join(projectDir, 'package.json')\n if (!fs.existsSync(projectPackageJsonLocation)) {\n out(chalk.orange`[${packageName}] No package.json exists in this location`\n + '\\nPlease initialise the package with `yarn init` on `npm init`'\n + ' before running this command, and run this command from the project root folder')\n }\n\n const packageJsonRaw = fs.readFileSync(projectPackageJsonLocation, 'utf8')\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const packageJson: { type?: string } = JSON.parse(packageJsonRaw)\n\n const targetConfigName = (packageJson.type === 'module')\n // ES6 module\n ? join(projectDir, configLocations[0] ?? '', configFileNames[0] + '.js')\n // common\n : join(projectDir, configLocations[0] ?? '', configFileNames[0] + '.mjs')\n\n const defaultConfig =\n`\n// @ts-check\n\n/** @satisfies {import('sxy-test-runner').Config} */\nexport default {\n\n // base folder for tests match and ignore patterns\n testsBase: '.',\n\n // glob pattern or array of glob patterns of test files\n tests: [\n '**/*.sxyt.{js,mjs,jsx}',\n '**/*.test.{js,mjs,jsx}',\n '**/*.spec.{js,mjs,jsx}',\n '**/tests?/*.{js,mjs,jsx}',\n '**/__tests?__/*.{js,mjs,jsx}'\n ],\n\n // glob pattern or array of patterns of test files to ignore\n testsIgnore: ['**/node_modules/**/*'],\n\n // whether to show the full error trace when an error occurs in a test file\n showErrorTrace: false,\n\n // tasks to run on startup\n // accepts a function, file location (relative to project base), or array of functions and or files\n setup: undefined,\n\n // tasks to run before each test file is run\n beforeEachFile: undefined,\n\n // tasks to run after each test file is run\n afterEachFile: undefined,\n\n // tasks to run before each describe block is processed\n // if the function returns an object with named properties, or the file exports named exports,\n // these will be passed to the callback function in argument 0, and can be access by destructruing e.g.\n // describe('thing', ({exportA, exportB}) => { it('should', ...) })\n beforeEachDescribe: undefined,\n\n // tasks to run after each describe block\n afterEachDescribe: undefined,\n\n // tasks to run before each it/test block\n // function returns and file exports will be passed to the it/test block as with describe above, e.g.\n // it('should', ({exportA, exportB}) => { assert('something') })\n beforeEachTest: undefined,\n\n // tasks to run after each it/test block\n afterEachTest: undefined,\n\n // how to execute the different parts of test files\n execution: {\n\n // run test files in 'parallel' or 'sequential'ly\n files: 'sequential',\n\n // run describe blocks within a test file in 'parallel' or 'sequential'ly\n describes: 'sequential',\n\n // run it/test blocks within a describe blocks in 'parallel' or 'sequential'ly\n tests: 'sequential'\n\n },\n\n // watch mode configurations\n watch: {\n\n //// the base directory to watch files in\n watchFilesBase: '.',\n\n //// glob filter or array of glob filters of files to watch\n watchFiles: '**/*.js',\n\n //// glob filter or array of globl filters of files to ignore\n watchFilesIgnore: '**/node_modules/**/*',\n\n // run all tests when starting the watcher\n runAllOnStartup: true,\n\n // re run all previously failed tests on each test run, until they pass\n reRunFailingTests: true,\n\n // run watch mode keyboard commands immediately, without waiting for Enter\n ttyActionsOnKeypress: false,\n\n // file to write the latest run status to, or false for none\n statusFile: false,\n\n // report the session as single agent readable lines instead of the usual output\n agent: false,\n\n },\n\n}`\n\n fs.writeFileSync(targetConfigName, defaultConfig)\n\n out(chalk.mediumgreen`[${packageName}] Created config at ` + chalk.blue(targetConfigName))\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { FinalConfig, TestsResultsSummary } from '../../types.js';
2
+ import type { FailureEntry } from './failureEntries.js';
3
+ import type { RunType } from './reRunManager.js';
4
+ import type { StatusCode } from './statusFile.js';
5
+ export type RunReason = RunType | 'startup';
6
+ export declare function agentEnabled(config: FinalConfig): boolean;
7
+ export declare function agentInit(enabled: boolean): void;
8
+ export declare function agentFound(config: FinalConfig, files: number): void;
9
+ export declare function agentSetRunReason(reason: RunReason): void;
10
+ export declare function agentRunStart(config: FinalConfig, files: number): number;
11
+ export declare function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void;
12
+ export declare function agentDone(config: FinalConfig, run: number, code: StatusCode, description: string, summary: TestsResultsSummary | null, durationMs: number, failingFiles: number): void;
13
+ export declare function agentWatching(config: FinalConfig, files: number): void;
14
+ export declare function agentChange(config: FinalConfig, file: string, event: string): void;
15
+ export declare function agentSilentOut(): void;
16
+ //# sourceMappingURL=agentReport.d.ts.map
@@ -0,0 +1,117 @@
1
+ import { out as mainOut } from '../../output.js';
2
+ // Agent mode reports a watch session as a stream of single lines, for a reader tailing the
3
+ // process rather than watching the terminal. Every line is one complete event:
4
+ //
5
+ // [sxyt] <event> | <key>=<value> | <key>=<value>
6
+ //
7
+ // Values are never quoted, so free text is flattened to keep the grammar intact - one event
8
+ // can never span two lines, and a stray `|` can never look like a field boundary.
9
+ const prefix = '[sxyt]';
10
+ let runCounter = 0;
11
+ // requestRun sets this before handing a run to runTests; the initial run never goes through
12
+ // the queue, so 'startup' is the right default
13
+ let nextRunReason = 'startup';
14
+ function emit(event, fields = []) {
15
+ mainOut(prefix + ` ${event}` + fields.map(([key, value]) => ` | ${key}=${value}`).join(''));
16
+ }
17
+ // newlines would split one event across two lines, and a `|` would read as a field boundary
18
+ function flatten(text) {
19
+ return text.replace(/\s*[\r\n]+\s*/g, ' ').replace(/\|/g, '/').trim();
20
+ }
21
+ export function agentEnabled(config) {
22
+ return config.watch.agent;
23
+ }
24
+ // the process is up, before the config is even loaded - so a slow startup is
25
+ // distinguishable from one that never began. Takes the flag rather than the config for that
26
+ // reason, and resets the counters so repeated in process sessions start from 1.
27
+ export function agentInit(enabled) {
28
+ runCounter = 0;
29
+ nextRunReason = 'startup';
30
+ if (!enabled)
31
+ return;
32
+ emit('init', [['pid', process.pid]]);
33
+ }
34
+ export function agentFound(config, files) {
35
+ if (!agentEnabled(config))
36
+ return;
37
+ emit('found', [['files', files]]);
38
+ }
39
+ export function agentSetRunReason(reason) {
40
+ nextRunReason = reason;
41
+ }
42
+ // claims the next run number and announces the run, so a reader knows work is in flight
43
+ // before any result arrives
44
+ export function agentRunStart(config, files) {
45
+ if (!agentEnabled(config))
46
+ return 0;
47
+ runCounter++;
48
+ emit('run', [
49
+ ['n', runCounter],
50
+ ['files', files],
51
+ ['why', nextRunReason],
52
+ ['covers', runScope(config)]
53
+ ]);
54
+ return runCounter;
55
+ }
56
+ // what the run was built from. A reader needs this to know how far the run's own `code`
57
+ // generalises - only a run covering everything can speak for the whole suite.
58
+ function runScope(config) {
59
+ switch (nextRunReason) {
60
+ case 'startup':
61
+ case 'all': return 'all';
62
+ case 'last': return 'last';
63
+ case 'failed': return 'failing';
64
+ // the reRunFailingTests case is the one worth spelling out: without it a run says
65
+ // nothing about files that were already failing
66
+ case 'auto': return (config.watch.reRunFailingTests) ? 'changed+failing' : 'changed';
67
+ default: return 'changed';
68
+ }
69
+ }
70
+ // failures are emitted before the run's done line, so done is a complete end of run marker
71
+ export function agentFailure(config, run, entry) {
72
+ if (!agentEnabled(config))
73
+ return;
74
+ emit('fail', [
75
+ ['n', run],
76
+ ['file', flatten(entry.file)],
77
+ ...(entry.describe === undefined)
78
+ ? []
79
+ : [['describe', flatten(entry.describe)]],
80
+ ...(entry.test === undefined) ? [] : [['test', flatten(entry.test)]],
81
+ ['error', flatten(entry.error)]
82
+ ]);
83
+ }
84
+ // `failing` counts the test files still failing across the whole session, not just this
85
+ // run. A watch re-run usually covers only the changed files, so its own tallies can read as
86
+ // a clean pass while the suite is still broken elsewhere.
87
+ export function agentDone(config, run, code, description, summary, durationMs, failingFiles) {
88
+ if (!agentEnabled(config))
89
+ return;
90
+ emit('done', [
91
+ ['n', run],
92
+ ['code', code],
93
+ ['desc', flatten(description)],
94
+ ...(summary === null) ? [] : [
95
+ ['tests', `${summary.itsPasses}/${summary.itsTotal}`],
96
+ ['items', `${summary.describesPasses}/${summary.describesTotal}`],
97
+ ['files', `${summary.passes}/${summary.total}`]
98
+ ],
99
+ ['failing', failingFiles],
100
+ ['ms', durationMs]
101
+ ]);
102
+ }
103
+ export function agentWatching(config, files) {
104
+ if (!agentEnabled(config))
105
+ return;
106
+ emit('watching', [['files', files]]);
107
+ }
108
+ export function agentChange(config, file, event) {
109
+ if (!agentEnabled(config))
110
+ return;
111
+ emit('change', [['file', flatten(file)], ['event', flatten(event)]]);
112
+ }
113
+ // agent mode owns stdout, so the ordinary coloured output is routed into a sink
114
+ export function agentSilentOut() {
115
+ // deliberately nothing - the agent lines are the whole stream
116
+ }
117
+ //# sourceMappingURL=agentReport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentReport.js","sourceRoot":"","sources":["../../../src/cli/lib/agentReport.ts"],"names":[],"mappings":"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","sourcesContent":["import { out as mainOut } from '../../output.js'\n\nimport type { FinalConfig, TestsResultsSummary } from '../../types.js'\nimport type { FailureEntry } from './failureEntries.js'\nimport type { RunType } from './reRunManager.js'\nimport type { StatusCode } from './statusFile.js'\n\n// Agent mode reports a watch session as a stream of single lines, for a reader tailing the\n// process rather than watching the terminal. Every line is one complete event:\n//\n// [sxyt] <event> | <key>=<value> | <key>=<value>\n//\n// Values are never quoted, so free text is flattened to keep the grammar intact - one event\n// can never span two lines, and a stray `|` can never look like a field boundary.\n\nconst prefix = '[sxyt]'\n\nexport type RunReason = RunType | 'startup'\n\nlet runCounter = 0\n// requestRun sets this before handing a run to runTests; the initial run never goes through\n// the queue, so 'startup' is the right default\nlet nextRunReason: RunReason = 'startup'\n\ntype Field = [string, string | number]\n\nfunction emit(event: string, fields: Field[] = []): void {\n mainOut(prefix + ` ${event}` + fields.map(([key, value]) => ` | ${key}=${value}`).join(''))\n}\n\n// newlines would split one event across two lines, and a `|` would read as a field boundary\nfunction flatten(text: string): string {\n return text.replace(/\\s*[\\r\\n]+\\s*/g, ' ').replace(/\\|/g, '/').trim()\n}\n\nexport function agentEnabled(config: FinalConfig): boolean {\n return config.watch.agent\n}\n\n// the process is up, before the config is even loaded - so a slow startup is\n// distinguishable from one that never began. Takes the flag rather than the config for that\n// reason, and resets the counters so repeated in process sessions start from 1.\nexport function agentInit(enabled: boolean): void {\n runCounter = 0\n nextRunReason = 'startup'\n if (!enabled) return\n emit('init', [['pid', process.pid]])\n}\n\nexport function agentFound(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('found', [['files', files]])\n}\n\nexport function agentSetRunReason(reason: RunReason): void {\n nextRunReason = reason\n}\n\n// claims the next run number and announces the run, so a reader knows work is in flight\n// before any result arrives\nexport function agentRunStart(config: FinalConfig, files: number): number {\n if (!agentEnabled(config)) return 0\n runCounter++\n emit('run', [\n ['n', runCounter],\n ['files', files],\n ['why', nextRunReason],\n ['covers', runScope(config)]\n ])\n return runCounter\n}\n\n// what the run was built from. A reader needs this to know how far the run's own `code`\n// generalises - only a run covering everything can speak for the whole suite.\nfunction runScope(config: FinalConfig): string {\n switch (nextRunReason) {\n case 'startup':\n case 'all': return 'all'\n case 'last': return 'last'\n case 'failed': return 'failing'\n // the reRunFailingTests case is the one worth spelling out: without it a run says\n // nothing about files that were already failing\n case 'auto': return (config.watch.reRunFailingTests) ? 'changed+failing' : 'changed'\n default: return 'changed'\n }\n}\n\n// failures are emitted before the run's done line, so done is a complete end of run marker\nexport function agentFailure(config: FinalConfig, run: number, entry: FailureEntry): void {\n if (!agentEnabled(config)) return\n emit('fail', [\n ['n', run],\n ['file', flatten(entry.file)],\n ...(entry.describe === undefined)\n ? []\n : [['describe', flatten(entry.describe)] as Field],\n ...(entry.test === undefined) ? [] : [['test', flatten(entry.test)] as Field],\n ['error', flatten(entry.error)]\n ])\n}\n\n// `failing` counts the test files still failing across the whole session, not just this\n// run. A watch re-run usually covers only the changed files, so its own tallies can read as\n// a clean pass while the suite is still broken elsewhere.\nexport function agentDone(\n config: FinalConfig,\n run: number,\n code: StatusCode,\n description: string,\n summary: TestsResultsSummary | null,\n durationMs: number,\n failingFiles: number\n): void {\n if (!agentEnabled(config)) return\n emit('done', [\n ['n', run],\n ['code', code],\n ['desc', flatten(description)],\n ...(summary === null) ? [] : [\n ['tests', `${summary.itsPasses}/${summary.itsTotal}`] as Field,\n ['items', `${summary.describesPasses}/${summary.describesTotal}`] as Field,\n ['files', `${summary.passes}/${summary.total}`] as Field\n ],\n ['failing', failingFiles],\n ['ms', durationMs]\n ])\n}\n\nexport function agentWatching(config: FinalConfig, files: number): void {\n if (!agentEnabled(config)) return\n emit('watching', [['files', files]])\n}\n\nexport function agentChange(config: FinalConfig, file: string, event: string): void {\n if (!agentEnabled(config)) return\n emit('change', [['file', flatten(file)], ['event', flatten(event)]])\n}\n\n// agent mode owns stdout, so the ordinary coloured output is routed into a sink\nexport function agentSilentOut(): void {\n // deliberately nothing - the agent lines are the whole stream\n}\n"]}
@@ -22,7 +22,8 @@ export const defaultConfig = {
22
22
  runAllOnStartup: true,
23
23
  reRunFailingTests: true,
24
24
  ttyActionsOnKeypress: false,
25
- statusFile: false
25
+ statusFile: false,
26
+ agent: false
26
27
  }
27
28
  };
28
29
  //# sourceMappingURL=defaultConfig.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"defaultConfig.js","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACtC,SAAS,EAAE,GAAG;IACd,KAAK,EAAE;QACH,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,0BAA0B;QAC1B,8BAA8B;KACjC;IACD,WAAW,EAAE,CAAC,sBAAsB,CAAC;IACrC,cAAc,EAAE,KAAK;IACrB,OAAO,EAAE,KAAK;IACd,SAAS,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,YAAY;KACtB;IACD,KAAK,EAAE;QACH,cAAc,EAAE,GAAG;QACnB,UAAU,EAAE,SAAS;QACrB,gBAAgB,EAAE,sBAAsB;QACxC,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,IAAI;QACvB,oBAAoB,EAAE,KAAK;QAC3B,UAAU,EAAE,KAAK;KACpB;CACJ,CAAA","sourcesContent":["import type { FinalConfig } from '../../types.js'\n\nexport const defaultConfig: FinalConfig = {\n testsBase: '.',\n tests: [\n '**/*.sxyt.{js,mjs,jsx}',\n '**/*.test.{js,mjs,jsx}',\n '**/*.spec.{js,mjs,jsx}',\n '**/tests?/*.{js,mjs,jsx}',\n '**/__tests?__/*.{js,mjs,jsx}'\n ],\n testsIgnore: ['**/node_modules/**/*'],\n showErrorTrace: false,\n compact: false,\n execution: {\n files: 'sequential',\n describes: 'sequential',\n tests: 'sequential'\n },\n watch: {\n watchFilesBase: '.',\n watchFiles: '**/*.js',\n watchFilesIgnore: '**/node_modules/**/*',\n runAllOnStartup: true,\n reRunFailingTests: true,\n ttyActionsOnKeypress: false,\n statusFile: false\n }\n}\n"]}
1
+ {"version":3,"file":"defaultConfig.js","sourceRoot":"","sources":["../../../src/cli/lib/defaultConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAgB;IACtC,SAAS,EAAE,GAAG;IACd,KAAK,EAAE;QACH,wBAAwB;QACxB,wBAAwB;QACxB,wBAAwB;QACxB,0BAA0B;QAC1B,8BAA8B;KACjC;IACD,WAAW,EAAE,CAAC,sBAAsB,CAAC;IACrC,cAAc,EAAE,KAAK;IACrB,OAAO,EAAE,KAAK;IACd,SAAS,EAAE;QACP,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,YAAY;KACtB;IACD,KAAK,EAAE;QACH,cAAc,EAAE,GAAG;QACnB,UAAU,EAAE,SAAS;QACrB,gBAAgB,EAAE,sBAAsB;QACxC,eAAe,EAAE,IAAI;QACrB,iBAAiB,EAAE,IAAI;QACvB,oBAAoB,EAAE,KAAK;QAC3B,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,KAAK;KACf;CACJ,CAAA","sourcesContent":["import type { FinalConfig } from '../../types.js'\n\nexport const defaultConfig: FinalConfig = {\n testsBase: '.',\n tests: [\n '**/*.sxyt.{js,mjs,jsx}',\n '**/*.test.{js,mjs,jsx}',\n '**/*.spec.{js,mjs,jsx}',\n '**/tests?/*.{js,mjs,jsx}',\n '**/__tests?__/*.{js,mjs,jsx}'\n ],\n testsIgnore: ['**/node_modules/**/*'],\n showErrorTrace: false,\n compact: false,\n execution: {\n files: 'sequential',\n describes: 'sequential',\n tests: 'sequential'\n },\n watch: {\n watchFilesBase: '.',\n watchFiles: '**/*.js',\n watchFilesIgnore: '**/node_modules/**/*',\n runAllOnStartup: true,\n reRunFailingTests: true,\n ttyActionsOnKeypress: false,\n statusFile: false,\n agent: false\n }\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { TestFile, TestResult } from '../../types.js';
2
+ export interface FailureEntry {
3
+ file: string;
4
+ describe?: string;
5
+ test?: string;
6
+ error: string;
7
+ }
8
+ export declare function failureEntries(testResults: TestResult[], erroringTests?: Record<string, TestFile>): FailureEntry[];
9
+ //# sourceMappingURL=failureEntries.d.ts.map
@@ -0,0 +1,42 @@
1
+ export function failureEntries(testResults, erroringTests = {}) {
2
+ const entries = [];
3
+ for (const [file, test] of Object.entries(erroringTests)) {
4
+ entries.push({ file, error: `did not load: ${simplifyError(test.error)}` });
5
+ }
6
+ for (const testResult of testResults) {
7
+ for (const describeResult of testResult.describeResults) {
8
+ if (describeResult.error !== undefined) {
9
+ entries.push({
10
+ file: testResult.file,
11
+ describe: describeResult.thing,
12
+ error: `threw: ${simplifyError(describeResult.error)}`
13
+ });
14
+ }
15
+ for (const itResult of describeResult.itResults) {
16
+ if (itResult.success !== false)
17
+ continue;
18
+ entries.push({
19
+ file: testResult.file,
20
+ describe: describeResult.thing,
21
+ test: itResult.should,
22
+ error: simplifyError(itResult.error)
23
+ });
24
+ }
25
+ }
26
+ }
27
+ return entries;
28
+ }
29
+ function simplifyError(error) {
30
+ if (error === undefined || error === null || error === false)
31
+ return 'unknown error';
32
+ if (typeof error === 'object' && 'message' in error && typeof error.message === 'string') {
33
+ const name = ('name' in error && typeof error.name === 'string') ? error.name : 'Error';
34
+ return `${name}: ${firstLine(error.message)}`;
35
+ }
36
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string -- only a fallback
37
+ return firstLine(String(error));
38
+ }
39
+ function firstLine(text) {
40
+ return text.split('\n')[0]?.trim() ?? '';
41
+ }
42
+ //# sourceMappingURL=failureEntries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failureEntries.js","sourceRoot":"","sources":["../../../src/cli/lib/failureEntries.ts"],"names":[],"mappings":"AAYA,MAAM,UAAU,cAAc,CAC1B,WAAyB,EACzB,gBAA0C,EAAE;IAE5C,MAAM,OAAO,GAAmB,EAAE,CAAA;IAElC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAA;IAC/E,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,KAAK,MAAM,cAAc,IAAI,UAAU,CAAC,eAAe,EAAE,CAAC;YACtD,IAAI,cAAc,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACrC,OAAO,CAAC,IAAI,CAAC;oBACT,IAAI,EAAE,UAAU,CAAC,IAAI;oBACrB,QAAQ,EAAE,cAAc,CAAC,KAAK;oBAC9B,KAAK,EAAE,UAAU,aAAa,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE;iBACzD,CAAC,CAAA;YACN,CAAC;YAED,KAAK,MAAM,QAAQ,IAAI,cAAc,CAAC,SAAS,EAAE,CAAC;gBAC9C,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK;oBAAE,SAAQ;gBACxC,OAAO,CAAC,IAAI,CAAC;oBACT,IAAI,EAAE,UAAU,CAAC,IAAI;oBACrB,QAAQ,EAAE,cAAc,CAAC,KAAK;oBAC9B,IAAI,EAAE,QAAQ,CAAC,MAAM;oBACrB,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;iBACvC,CAAC,CAAA;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAA;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,eAAe,CAAA;IAEpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvF,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAA;QACvF,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAA;IACjD,CAAC;IAED,mFAAmF;IACnF,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AAC5C,CAAC","sourcesContent":["import type { TestFile, TestResult } from '../../types.js'\n\n// One failure, flattened. `describe` is absent when a file would not load, and `test` is\n// absent when the describe itself threw. Shared by the status file's nested tree and the\n// agent reporter's flat lines, so both describe a failure the same way.\nexport interface FailureEntry {\n file: string\n describe?: string\n test?: string\n error: string\n}\n\nexport function failureEntries(\n testResults: TestResult[],\n erroringTests: Record<string, TestFile> = {}\n): FailureEntry[] {\n const entries: FailureEntry[] = []\n\n for (const [file, test] of Object.entries(erroringTests)) {\n entries.push({ file, error: `did not load: ${simplifyError(test.error)}` })\n }\n\n for (const testResult of testResults) {\n for (const describeResult of testResult.describeResults) {\n if (describeResult.error !== undefined) {\n entries.push({\n file: testResult.file,\n describe: describeResult.thing,\n error: `threw: ${simplifyError(describeResult.error)}`\n })\n }\n\n for (const itResult of describeResult.itResults) {\n if (itResult.success !== false) continue\n entries.push({\n file: testResult.file,\n describe: describeResult.thing,\n test: itResult.should,\n error: simplifyError(itResult.error)\n })\n }\n }\n }\n\n return entries\n}\n\nfunction simplifyError(error: unknown): string {\n if (error === undefined || error === null || error === false) return 'unknown error'\n\n if (typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n const name = ('name' in error && typeof error.name === 'string') ? error.name : 'Error'\n return `${name}: ${firstLine(error.message)}`\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- only a fallback\n return firstLine(String(error))\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n')[0]?.trim() ?? ''\n}\n"]}
@@ -1,43 +1,33 @@
1
+ import { failureEntries } from './failureEntries.js';
1
2
  // The failures for the status file, as f: file > d: describe > t: test > error. Errors are
2
3
  // reduced to a single line - anything wanting the diff, the logs or the trace reads the
3
4
  // terminal.
4
5
  export function failureLines(testResults, erroringTests = {}) {
5
6
  const lines = [];
6
- for (const [file, test] of Object.entries(erroringTests)) {
7
- lines.push(` f: ${file}`, ` e: did not load: ${simplifyError(test.error)}`);
8
- }
9
- for (const testResult of testResults) {
10
- const fileLines = [];
11
- for (const describeResult of testResult.describeResults) {
12
- const describeLines = [];
13
- if (describeResult.error !== undefined) {
14
- describeLines.push(` e: threw: ${simplifyError(describeResult.error)}`);
15
- }
16
- for (const itResult of describeResult.itResults) {
17
- if (itResult.success !== false)
18
- continue;
19
- describeLines.push(` t: ${itResult.should}`, ` e: ${simplifyError(itResult.error)}`);
20
- }
21
- if (describeLines.length > 0) {
22
- fileLines.push(` d: ${describeResult.thing}`, ...describeLines);
23
- }
7
+ let currentFile;
8
+ let currentDescribe;
9
+ // entries arrive in tree order, so a heading is needed only when its value changes
10
+ for (const entry of failureEntries(testResults, erroringTests)) {
11
+ if (entry.file !== currentFile) {
12
+ lines.push(` f: ${entry.file}`);
13
+ currentFile = entry.file;
14
+ currentDescribe = undefined;
15
+ }
16
+ if (entry.describe === undefined) {
17
+ lines.push(` e: ${entry.error}`);
18
+ continue;
19
+ }
20
+ if (entry.describe !== currentDescribe) {
21
+ lines.push(` d: ${entry.describe}`);
22
+ currentDescribe = entry.describe;
23
+ }
24
+ if (entry.test === undefined) {
25
+ lines.push(` e: ${entry.error}`);
26
+ }
27
+ else {
28
+ lines.push(` t: ${entry.test}`, ` e: ${entry.error}`);
24
29
  }
25
- if (fileLines.length > 0)
26
- lines.push(` f: ${testResult.file}`, ...fileLines);
27
30
  }
28
31
  return (lines.length > 0) ? ['Failures:', ...lines] : [];
29
32
  }
30
- function simplifyError(error) {
31
- if (error === undefined || error === null || error === false)
32
- return 'unknown error';
33
- if (typeof error === 'object' && 'message' in error && typeof error.message === 'string') {
34
- const name = ('name' in error && typeof error.name === 'string') ? error.name : 'Error';
35
- return `${name}: ${firstLine(error.message)}`;
36
- }
37
- // eslint-disable-next-line @typescript-eslint/no-base-to-string -- only a fallback
38
- return firstLine(String(error));
39
- }
40
- function firstLine(text) {
41
- return text.split('\n')[0]?.trim() ?? '';
42
- }
43
33
  //# sourceMappingURL=failureLines.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"failureLines.js","sourceRoot":"","sources":["../../../src/cli/lib/failureLines.ts"],"names":[],"mappings":"AAEA,2FAA2F;AAC3F,wFAAwF;AACxF,YAAY;AACZ,MAAM,UAAU,YAAY,CACxB,WAAyB,EACzB,gBAA0C,EAAE;IAE5C,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACvD,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,wBAAwB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IACnF,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,SAAS,GAAa,EAAE,CAAA;QAE9B,KAAK,MAAM,cAAc,IAAI,UAAU,CAAC,eAAe,EAAE,CAAC;YACtD,MAAM,aAAa,GAAa,EAAE,CAAA;YAElC,IAAI,cAAc,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACrC,aAAa,CAAC,IAAI,CAAC,mBAAmB,aAAa,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;YAChF,CAAC;YAED,KAAK,MAAM,QAAQ,IAAI,cAAc,CAAC,SAAS,EAAE,CAAC;gBAC9C,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK;oBAAE,SAAQ;gBACxC,aAAa,CAAC,IAAI,CACd,YAAY,QAAQ,CAAC,MAAM,EAAE,EAC7B,cAAc,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAChD,CAAA;YACL,CAAC;YAED,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,SAAS,CAAC,IAAI,CAAC,UAAU,cAAc,CAAC,KAAK,EAAE,EAAE,GAAG,aAAa,CAAC,CAAA;YACtE,CAAC;QACL,CAAC;QAED,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,UAAU,CAAC,IAAI,EAAE,EAAE,GAAG,SAAS,CAAC,CAAA;IACjF,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC5D,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACjC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,eAAe,CAAA;IAEpF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,SAAS,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvF,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAA;QACvF,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAA;IACjD,CAAC;IAED,mFAAmF;IACnF,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;AACnC,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AAC5C,CAAC","sourcesContent":["import type { TestFile, TestResult } from '../../types.js'\n\n// The failures for the status file, as f: file > d: describe > t: test > error. Errors are\n// reduced to a single line - anything wanting the diff, the logs or the trace reads the\n// terminal.\nexport function failureLines(\n testResults: TestResult[],\n erroringTests: Record<string, TestFile> = {}\n): string[] {\n const lines: string[] = []\n\n for (const [file, test] of Object.entries(erroringTests)) {\n lines.push(` f: ${file}`, ` e: did not load: ${simplifyError(test.error)}`)\n }\n\n for (const testResult of testResults) {\n const fileLines: string[] = []\n\n for (const describeResult of testResult.describeResults) {\n const describeLines: string[] = []\n\n if (describeResult.error !== undefined) {\n describeLines.push(` e: threw: ${simplifyError(describeResult.error)}`)\n }\n\n for (const itResult of describeResult.itResults) {\n if (itResult.success !== false) continue\n describeLines.push(\n ` t: ${itResult.should}`,\n ` e: ${simplifyError(itResult.error)}`\n )\n }\n\n if (describeLines.length > 0) {\n fileLines.push(` d: ${describeResult.thing}`, ...describeLines)\n }\n }\n\n if (fileLines.length > 0) lines.push(` f: ${testResult.file}`, ...fileLines)\n }\n\n return (lines.length > 0) ? ['Failures:', ...lines] : []\n}\n\nfunction simplifyError(error: unknown): string {\n if (error === undefined || error === null || error === false) return 'unknown error'\n\n if (typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n const name = ('name' in error && typeof error.name === 'string') ? error.name : 'Error'\n return `${name}: ${firstLine(error.message)}`\n }\n\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- only a fallback\n return firstLine(String(error))\n}\n\nfunction firstLine(text: string): string {\n return text.split('\\n')[0]?.trim() ?? ''\n}\n"]}
1
+ {"version":3,"file":"failureLines.js","sourceRoot":"","sources":["../../../src/cli/lib/failureLines.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEpD,2FAA2F;AAC3F,wFAAwF;AACxF,YAAY;AACZ,MAAM,UAAU,YAAY,CACxB,WAAyB,EACzB,gBAA0C,EAAE;IAE5C,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,WAA+B,CAAA;IACnC,IAAI,eAAmC,CAAA;IAEvC,mFAAmF;IACnF,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,WAAW,EAAE,aAAa,CAAC,EAAE,CAAC;QAC7D,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA;YAChC,WAAW,GAAG,KAAK,CAAC,IAAI,CAAA;YACxB,eAAe,GAAG,SAAS,CAAA;QAC/B,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;YACnC,SAAQ;QACZ,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAA;YACtC,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAA;QACpC,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACzC,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,IAAI,EAAE,EAAE,cAAc,KAAK,CAAC,KAAK,EAAE,CAAC,CAAA;QACrE,CAAC;IACL,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC5D,CAAC","sourcesContent":["import type { TestFile, TestResult } from '../../types.js'\n\nimport { failureEntries } from './failureEntries.js'\n\n// The failures for the status file, as f: file > d: describe > t: test > error. Errors are\n// reduced to a single line - anything wanting the diff, the logs or the trace reads the\n// terminal.\nexport function failureLines(\n testResults: TestResult[],\n erroringTests: Record<string, TestFile> = {}\n): string[] {\n const lines: string[] = []\n let currentFile: string | undefined\n let currentDescribe: string | undefined\n\n // entries arrive in tree order, so a heading is needed only when its value changes\n for (const entry of failureEntries(testResults, erroringTests)) {\n if (entry.file !== currentFile) {\n lines.push(` f: ${entry.file}`)\n currentFile = entry.file\n currentDescribe = undefined\n }\n\n if (entry.describe === undefined) {\n lines.push(` e: ${entry.error}`)\n continue\n }\n\n if (entry.describe !== currentDescribe) {\n lines.push(` d: ${entry.describe}`)\n currentDescribe = entry.describe\n }\n\n if (entry.test === undefined) {\n lines.push(` e: ${entry.error}`)\n } else {\n lines.push(` t: ${entry.test}`, ` e: ${entry.error}`)\n }\n }\n\n return (lines.length > 0) ? ['Failures:', ...lines] : []\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { ReRunManager } from './reRunManager.js';
2
2
  import { RunTestsPromise } from './runTests.js';
3
+ import type { OutFn } from '../../types.js';
3
4
  export type RerunTestsFactory = (testFiles: Set<string>) => () => RunTestsPromise;
4
- export declare function listenToUser(rerunManager: ReRunManager, rerunTestsCallback: RerunTestsFactory, ttyActionsOnKeypress: boolean | undefined, initialTestRun: Promise<boolean | null> | null, input?: NodeJS.ReadStream): Promise<void>;
5
+ export declare function listenToUser(rerunManager: ReRunManager, rerunTestsCallback: RerunTestsFactory, ttyActionsOnKeypress: boolean | undefined, initialTestRun: Promise<boolean | null> | null, input?: NodeJS.ReadStream, customOut?: OutFn): Promise<void>;
5
6
  //# sourceMappingURL=listenToUser.d.ts.map
@@ -1,11 +1,14 @@
1
1
  import { createInterface, emitKeypressEvents } from 'node:readline';
2
2
  import { showTtyHelp } from './showTtyHelp.js';
3
3
  import { handleUserInput } from './handleUserInput.js';
4
- import { out } from '../../output.js';
4
+ import { out as mainOut } from '../../output.js';
5
5
  export async function listenToUser(
6
6
  //allTestFiles: Set<string>,
7
7
  //failingTests: Set<string>,
8
- rerunManager, rerunTestsCallback, ttyActionsOnKeypress = false, initialTestRun, input = process.stdin) {
8
+ rerunManager, rerunTestsCallback, ttyActionsOnKeypress = false, initialTestRun, input = process.stdin, customOut) {
9
+ // an agent spawned session has no TTY, so these notices would otherwise be the only
10
+ // lines in agent mode that are not agent events
11
+ const out = customOut ?? mainOut;
9
12
  //console.log('initia test run', initialTestRun)
10
13
  //console.log('listen to user')
11
14
  //setInterval(() => console.log('active runs: ', rerunManager.state.activeRuns), 10_000)
@@ -58,7 +61,7 @@ rerunManager, rerunTestsCallback, ttyActionsOnKeypress = false, initialTestRun,
58
61
  }
59
62
  //console.log('initialTestRun 2', initialTestRun)
60
63
  await initialTestRun;
61
- showTtyHelp();
64
+ showTtyHelp(input.isTTY, out);
62
65
  return;
63
66
  }
64
67
  //# sourceMappingURL=listenToUser.js.map