command-stream 1.3.0

Modern shell command execution library with streaming, async iteration, and event support
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# Changelog

## 0.9.5

### Patch Changes

- Document try/catch anti-pattern with errexit=false default (issue #156)
  - Add js/docs/case-studies/issue-156/README.md with comprehensive case study including:
    - Reconstructed timeline and sequence of events from calculator#78 silent bug
    - Root cause analysis with code evidence from command-stream source
    - Bash vs command-stream behavior comparison table
    - Full configuration API documentation (shell.errexit(), set(), unset())
    - Recommended patterns for mixed strict/optional error handling
    - Comparison with similar libraries (execa, zx, bash, child_process)
    - Proposed solutions ranked by impact
  - Add Pitfall #7 to js/BEST-PRACTICES.md: try/catch anti-pattern with errexit=false, with examples and correct fix patterns
  - Add 4 reproducible experiment scripts in experiments/issue-156/:
    - 01-default-behavior.mjs — demonstrates default errexit=false behavior
    - 02-errexit-enabled.mjs — demonstrates shell.errexit(true) configuration
    - 03-bash-comparison.sh — bash set -e reference comparison
    - 04-calculator-bug-repro.mjs — exact reproduction of calculator#78 bug

  Publish the Rust crate from CI, sync Cargo release versions, and harden shell option isolation in Bun tests.

## 0.9.4

### Patch Changes

- 3265939: Document Array.join() pitfall and add best practices (fixes #153)
  - Add js/BEST-PRACTICES.md with detailed usage patterns for arrays, security, and error handling
  - Add Common Pitfalls section to README.md explaining the Array.join() issue
  - Add js/docs/case-studies/issue-153/ with real-world bug investigation from hive-mind#1096
  - Add rust/BEST-PRACTICES.md for Rust-specific patterns
  - Add 34 tests for array interpolation covering correct usage and anti-patterns
  - Reorganize file structure: move JS-related docs to js/ folder, case studies to js/docs/case-studies/

## 0.9.2

### Patch Changes

- 535eb02: Reorganize Rust code with modular utilities (matching JS pattern)
  - Extract trace.rs (152 lines) - Logging and tracing utilities
  - Extract ansi.rs (194 lines) - ANSI escape code handling
  - Extract quote.rs (161 lines) - Shell quoting utilities
  - Update utils.rs to re-export from new modules and focus on CommandResult/VirtualUtils
  - Update lib.rs with new module declarations and re-exports

  The Rust structure now mirrors the JavaScript modular organization for consistency.
  All modules remain well under the 1500-line limit guideline.

## 0.9.1

### Patch Changes

- 38dc1c3: Reorganize codebase with modular utilities for better maintainability
  - Extract trace/logging utilities to $.trace.mjs
  - Extract shell detection to $.shell.mjs
  - Extract stream utilities to $.stream-utils.mjs and $.stream-emitter.mjs
  - Extract shell quoting to $.quote.mjs
  - Extract result creation to $.result.mjs
  - Extract ANSI utilities to $.ansi.mjs
  - Extract global state management to $.state.mjs
  - Extract shell settings to $.shell-settings.mjs
  - Extract virtual command registration to $.virtual-commands.mjs
  - Add commands/index.mjs for module exports
  - Update $.utils.mjs to use shared trace module

  All new modules follow the 1500-line limit guideline. The Rust code
  structure already follows best practices with tests in separate files.

## 0.9.0

### Minor Changes

- 60e2a36: Add Rust translation and reorganize codebase
  - Reorganize JavaScript source files into `js/` folder structure
  - Move tests from root `tests/` to `js/tests/`
  - Add complete Rust translation in `rust/` folder with:
    - Shell parser supporting &&, ||, ;, |, (), and redirections
    - All 21 virtual commands (cat, cp, mv, rm, touch, mkdir, ls, cd, pwd, echo, yes, seq, sleep, env, which, test, exit, basename, dirname, true, false)
    - ProcessRunner for async command execution with tokio
    - Comprehensive test suite mirroring JavaScript tests
    - Case study documentation in docs/case-studies/issue-146/

## 0.8.3

### Patch Changes

- 0e1c9e0: Fix trace logs interfering with output when CI=true
  - Removed automatic trace log enabling when CI environment variable is set
  - Trace logs no longer pollute stderr in CI/CD environments (GitHub Actions, GitLab CI, etc.)
  - Added COMMAND_STREAM_TRACE environment variable for explicit trace control
  - COMMAND_STREAM_TRACE=true explicitly enables tracing
  - COMMAND_STREAM_TRACE=false explicitly disables tracing (overrides COMMAND_STREAM_VERBOSE)
  - COMMAND_STREAM_VERBOSE=true continues to work as before
  - JSON parsing works reliably in CI environments

  Fixes #135

## 0.8.2

### Patch Changes

- b3dac3d: Add Windows shell detection support
  - Added Windows-specific shell detection (Git Bash, PowerShell, cmd.exe)
  - Use 'where' command on Windows instead of 'which' for PATH lookups
  - Fallback to cmd.exe on Windows when no Unix-compatible shell is found
  - Updated timing expectations in tests for slower Windows shell spawning
  - Created case study documentation for Windows CI failures (Issue #144)

## 0.8.1

### Patch Changes

- Test patch release

## 0.8.0

### Minor Changes

- f4dbb49: Transition to new CI/CD template with modern best practices

  Features:
  - Changeset-based versioning for semantic version management
  - OIDC trusted publishing to npm (no tokens required)
  - Manual and automatic release workflows
  - Multi-platform testing (Ubuntu, macOS, Windows)
  - Node.js compatibility testing (v20, v22, v24)
  - ESLint + Prettier with Husky pre-commit hooks
  - Code duplication detection with jscpd
  - Consolidated release workflow for all publishing

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.7.1

### Patch Changes

- Current stable release with streaming support, async iteration, and EventEmitter support



































## [1.3.0] - 2026-10-05

### Added

- `command_stream::zx`: a zx-compatible API (issue #26) with the `zx!` macro
  and `Shell` (the `$`), `ProcessPromise` (piping to processes and files,
  `nothrow`, `quiet`, `timeout`, `kill`), `ProcessOutput` (zx-style accessors
  and error messages), scoped `within`/`configure`/`cd`, the shell presets
  and the goods (`sleep`, `retry`, `exp_backoff`, `spinner`, `echo`,
  `tempdir`, `tempfile`, `which`, `glob`, `parse_argv`/`minimist`, `dotenv`,
  `transform_markdown`, `log`). Its tests port the zx unit vectors.
- `RunOptions::prefer_local` uses the same project-local executable resolver
  as the zx and Bun shells, so the default `ProcessRunner` and
  `StreamingRunner` can find local binaries.

## [1.2.0] - 2026-09-29

### Added

- `command_stream::bun_shell`: a port of Bun Shell (`Bun.$`) with the same
  template-literal API shape (`shell(&["echo ", ""], vec![name.into()])`,
  `ShellCommand::quiet/nothrow/cwd/env/text`, `ShellOutput`, `ShellError`),
  checked against the shared `conformance/bun-shell` corpus
  (`cargo test --test bun_shell_conformance -- --ignored`).

## [1.1.1] - 2026-09-28

### Fixed

- Release script now also bumps the crate version in `rust/benchmarks/Cargo.lock`, so the benchmark workflow's `--locked` checks keep passing after a Rust release.

## [1.1.0] - 2026-09-25

### Added

- Collect an exact-argument `StreamingRunner` synchronously with `collect_blocking()`.

## [1.0.0] - 2026-09-24

### Changed

- Expose readable stdout and stderr snapshots and a writable stdin record on completed command results.

## [0.25.0] - 2026-09-21

### Added

- `ProcessRunner::child()` and the borrowed `ProcessChild` handle, with access
  to Tokio's native child and signal-aware `kill()` / `kill_with()` methods that
  preserve the runner's process-group, grace-period, and escalation behavior.
- Cross-language tests and executable documentation for child-handle access and
  cancellation (issue #20).

## [0.24.0] - 2026-09-16

### Added

- Added live stdin writes with `ProcessRunner::write_stdin` and `ProcessRunner::close_stdin`.
- Added executable Rust counterparts for every feature in the generated language-parity guide.

### Fixed

- Pipelines now use the last stage's status by default and the rightmost failure with `pipefail`.
- Shell sequence operators are executed with shell-compatible behavior.
- `VirtualCommandRegistry::with_builtins` now returns the complete built-in catalog.

## [0.23.0] - 2026-09-16

### Added

- `ProcessRunner::pid()` reporting the process id of a started command, matching
  the JavaScript `command.pid` property (issue #18). The id is recorded at spawn
  time, so it stays readable after `run()` has consumed the child handle, and is
  `None` for built-in commands, which spawn no process.
- `OutputStream::pid()` and `OutputStream::wait_for_pid()` for streamed
  commands, whose child is spawned inside a background task: `pid()` reports what
  is known now, `wait_for_pid()` waits for the spawn and returns `None` if it
  fails.
- A `## Process ID of a Running Command` section in the README covering what the
  id names and why built-in commands have none, plus a runnable
  `examples/process_pid_access.rs`.

## [0.22.1] - 2026-09-16

### Added

- Tests covering parallel execution of sleeping commands (issue #22). Two and
  three commands started together — through the built-in `sleep`, through real
  `/bin/sleep` processes, and through `sh -c` scripts that sleep between writes
  — must all finish, keep their own output and environment, and overlap in time
  instead of running one after another.

## [0.22.0] - 2026-09-16

### Added

- `ProcessRunner::kill_with(signal)` to stop a running command with an explicit
  signal, matching `OutputStream::kill_with` and the JavaScript `kill(signal)`.
- A `## Signals` section in the README covering the delivery model, the
  `kill_signal` / `kill_grace_ms` options and the `128 + signal` exit codes,
  plus a runnable `examples/signals_graceful_shutdown.rs`.

### Fixed

- `ProcessRunner::kill()` sent `SIGKILL` unconditionally and ignored the
  configured `kill_signal`, so a child that trapped `SIGTERM` was destroyed
  before its handler could run. It now delivers the configured signal to the
  process group, waits `kill_grace_ms`, and escalates to `SIGKILL` only if the
  process is still running.
- `ProcessRunner` did not start its child in its own process group, so killing
  it never reached grandchildren: the worker behind a `sh -c` wrapper kept
  running. The child now leads its own group, as `StreamingRunner` already did.
  A command sharing the caller's terminal is deliberately left in the caller's
  group so that CTRL+C keeps reaching it.
- `kill_grace_ms: 0` still let the child run its handler: awaiting a zero-length
  timeout yields to the runtime, and that gap was enough for a shell to run its
  trap. `SIGKILL` now follows in the same step as the signal.
- Killing a command left grandchildren running on macOS. Group membership was
  looked up with `getpgid` at signal time, by which point the `sh` wrapper had
  usually exited; macOS reports `ESRCH` for a process in that state, so the
  group - including the still-running worker - was never signalled. Whether the
  child leads its own group is now recorded when it is spawned.

## [0.20.0] - 2026-09-15

### Added

- `tee` built-in command, mirroring the JavaScript implementation and GNU
  coreutils: `-a`/`--append`, `-i`/`--ignore-interrupts`, clustered short
  flags, `--` as an option terminator, and a bare `-` treated as a file named
  `-`. A write failure is reported on stderr and sets exit code 1 while the
  remaining files are still written.
- Tests covering the `StdinOption` invariant that keeps stdio modes and input
  content in separate variants, so a mode can never be read as command input.

## [0.19.0] - 2026-09-15

### Added

- Add a reproducible Rust benchmark suite for performance, crate footprint,
  feature coverage, and real-world process workloads, with CI base/head reports.

## [0.18.6] - 2026-09-15

### Fixed

- Guarantee that successful stderr-only CLI output remains separately captured,
  including pull request URLs, while `2>&1` retains normal shell merge behavior.

## [0.18.5] - 2026-09-15

### Added

- `Error::code()` and its `Error::exit_code()` alias report the exit status of a
  failed command, and `CommandResult::error_for_status()` turns a non-zero
  result into that error.

## [0.18.4] - 2026-09-14

### Fixed

- Lock in exact complex Markdown arguments across direct argv execution and
  shell-safe macro interpolation.

## [0.18.3] - 2026-09-14

### Added

- Added a pinned 14-project Rust competitor compatibility corpus, complete
  per-test disposition manifest, reproducible discovery snapshot, and
  missing-feature audit.

### Fixed

- Propagated exact-executable spawn errors from `StreamingRunner::collect()` instead of returning a false success.
- Preserved quoted command strings passed through `cmd.exe /c`, including
  executable paths that require Windows shell quoting.

## [0.18.2] - 2026-09-13

### Fixed

- Preserve stdout and stderr text without inventing a trailing newline.
- Keep multiline interpolations literal across echo, printf, redirection, and nested shell programs.

## [0.18.1] - 2026-09-13

### Fixed

- Reject terminal interactions that contain neither an action nor a wait
  before opening a PTY or sending live input.

## [0.18.0] - 2026-09-06

### Fixed

- Interpolate every value as exactly one literal argument, like `"$var"` in a
  POSIX shell. `quote` no longer treats a value that starts and ends with a
  matching quote as ready-made shell syntax, so paths with spaces (and
  pre-quoted paths) reach the command intact (issue #41). This also fixes
  `quote("\"it's\"")`, which used to emit the unterminated string `'"it's"'`,
  and closes an injection where a value like `"' ; touch /tmp/pwned ; '"` was
  spliced into the command and executed.

### Added

- `is_pre_quoted_passthrough_enabled` and `COMMAND_STREAM_PREQUOTED_PASSTHROUGH=1`
  restore the previous pre-quoted passthrough for balanced values only.

## [0.17.4] - 2026-09-05

### Fixed

- Perform POSIX quote removal per argument so interpolated values containing
  spaces (e.g. `gh issue list --label "help wanted"`) reach virtual commands as
  a single argument with quotes stripped, matching `/bin/sh` behavior (#48).
- Fix a tokenizer infinite loop on a lone `&` (as in `2>&1` or backgrounding),
  which the word scanner previously neither consumed nor treated as an operator.
- Keep an unquoted backslash literal on Windows so virtual commands such as
  `cd C:\Users\foo` still receive a valid path (POSIX backslash escaping is
  unchanged on other platforms).

## [0.17.3] - 2026-09-05

### Fixed

- Redirections and expansions are no longer swallowed by virtual commands
  (#46). `ProcessRunner` dispatched to a virtual command before checking
  whether the command needed a real shell, and arguments came from splitting on
  whitespace, so `echo hello > out.txt` printed `hello > out.txt` instead of
  writing the file and `git push origin main 2>&1` reported success while
  nothing had been pushed. `needs_real_shell` now recognises `>` and `<` in all
  their forms, and the real-shell check runs before virtual dispatch, matching
  `/bin/sh`.

## [0.17.2] - 2026-09-05

### Fixed

- `bytes` bumped to 1.12.1, clearing RUSTSEC-2026-0007 (integer overflow in `BytesMut::reserve`). The advisory went unnoticed because nothing in the pipeline audited the lockfile; `cargo audit` now runs on every push, pull request and weekly.
- `ls` no longer computes a file type character it never used, and iterates directory entries with `.flatten()` instead of matching on each `Result`.

### Changed

- The Rust pipeline denies warnings: `RUSTFLAGS`/`RUSTDOCFLAGS` are `-Dwarnings`, clippy runs with `-- -D warnings`, and `cargo doc --no-deps` gates the rustdoc-only lints. Clippy previously printed 15 warnings and exited 0. `Cargo.toml` forbids `unsafe_code` and warns on `clippy::all`.
- `CommandContext::cwd` is covered by tests: `pwd` honours it, and `ls` resolves both a relative path and its default argument against it.

## [0.17.1] - 2026-09-04

### Fixed
- Track runner and pipeline virtual `cd` changes in invocation-local cwd and environment state, including overlapping commands and explicit working directories, without mutating the host working directory or `PWD`/`OLDPWD`.

## [0.17.0] - 2026-09-04

### Fixed
- Quote interpolated values according to the shell quoting context they land in:
  a value inside quotes written by the author is spliced in as escaped literal
  text, like `"$var"` in a POSIX shell, so `s!("bash -c \"{}\"", script)` runs
  the script instead of failing on an extra layer of quotes (issue #49).

### Added
- `QuoteContext`, `scan_quote_context`, `quote_for_context`,
  `escape_for_single_quotes`, `escape_for_double_quotes`, `has_shell_escapes`
  and `is_quote_context_enabled` are exported from the crate root.
- `COMMAND_STREAM_QUOTE_CONTEXT=0` restores the previous behavior of always
  quoting every interpolated value.

## [0.16.0] - 2026-08-11

### Added

- Add `StreamingRunner::from_argv` for shell-free streaming execution with exact executable and argument boundaries on every platform.

## [0.15.0] - 2026-08-07

### Added
- `open_terminal` returns a `TerminalSession` that keeps a PTY open with no implicit timeout, so input that only becomes available later can be sent with `send`, awaited with `wait_for` (the same readiness matcher as `interactions`, including the idle wait), and finalized with `close`/`finish`.

### Changed
- `TerminalCaptureOptions::timeout` is now `Option<Duration>` (`Some(30s)` by default, `None` for sessions), and `capture_terminal` is implemented on top of `TerminalSession`.

## [0.14.0] - 2026-07-25

### Added

- Add regex readiness markers and per-interaction output-idle waits to PTY
  terminal capture.

## [0.13.1] - 2026-07-24

### Fixed

- Preserve the current terminal frame before a later full-screen repaint,
  including when its control sequence is split across PTY output chunks.

## [0.13.0] - 2026-07-24

### Added

- Added PTY-backed terminal capture with input and resize controls, settled
  frames, unrolled transcripts, asciicast v2 recordings, SVG artifacts, and
  partial timeout diagnostics.

## [0.12.1] - 2026-06-21

### Added
- Diagnostic warning for Go/Docker template arguments with an internal space
  (issue #172). When a built command contains an unquoted `{{ … }}` token that
  contains a space (e.g. `--format {{json .Config.Env}}`), the shell — and
  command-stream, which mirrors shell word-splitting — splits it into multiple
  argv words. `command-stream` now prints a one-line warning to stderr pointing
  at the gotcha (fired once per unique token, silenced via
  `COMMAND_STREAM_NO_TEMPLATE_WARNING`). Quote the token (`'{{json .Config.Env}}'`)
  or interpolate it as a single value to pass it through untouched.

## [0.12.0] - 2026-06-11

### Added
- `CommandResult::exit_code()` accessor as an alias for the `code` field, mirroring the `exitCode` alias exposed by the JavaScript implementation (issue #36).

## [0.11.1] - 2026-06-10

### Fixed
- Handle `getcwd()`/`current_dir()` failures during command execution (issue #44). When the inherited working directory has been deleted or becomes inaccessible, the child process is now spawned from a valid fallback directory (`HOME`, `USERPROFILE`, the temp dir, then `/`) instead of failing at the OS level. Applies to both `ProcessRunner` and `Pipeline`.

## [0.11.0] - 2026-06-10

### Changed
- Make the built-in `cd` command fully `sh`/bash compatible so shell scripts translate directly to Rust (issue #50):
  - `cd -` switches to the previous directory and prints it, like `sh`
  - `~` and `~/path` tilde expansion
  - a successful `cd` updates the `PWD` and `OLDPWD` environment variables
  - relative targets resolve against the `cwd` option for consistency

## [0.10.0] - 2026-06-10

### Added
- `StreamingRunner::kill_signal` to configure the signal used to stop a process
  (default `SIGTERM`), mirroring the JavaScript `killSignal` option.
- `StreamingRunner::exit_pump_grace_ms` to configure the post-exit pipe drain
  grace period (default 100ms).
- `OutputStream::kill` / `OutputStream::kill_with` to stop a streaming process
  from inside the consumption loop; abandoning the stream (drop/`break`) now
  stops the process too.

### Fixed
- `OutputStream` no longer hangs when the process has exited but a grandchild
  keeps the stdio pipes open: readers are drained with a grace period and then
  aborted, and the exit chunk is always delivered (parity with the JavaScript
  fix for issue #155).

## [0.9.6] - 2026-06-09

### Changed

- Separate Rust crate documentation, release scripts, changelog fragments, and
  GitHub release tags from the JavaScript npm package release path.

### Fixed

- Ensure the Rust release job still evaluates on main pushes after the
  pull-request-only changelog gate is skipped.

### Fixed

- Rebase onto the latest `origin/<branch>` **before** staging the version bump
  in the Rust release script, so concurrent releases no longer abort with
  "cannot rebase: Your index contains uncommitted changes". The release now
  syncs on a clean working tree, matching the JavaScript release script.