Releases: link-foundation/command-stream
[Rust] 0.12.0 (Rust)
Added
CommandResult::exit_code()accessor as an alias for thecodefield, mirroring theexitCodealias exposed by the JavaScript implementation (issue #36).
JavaScript 0.14.0
JavaScript 0.13.0
Handle getcwd() failed errors gracefully during subshell execution (issue #44).
process.cwd() throws getcwd() failed: No such file or directory when the current working directory has been deleted or becomes inaccessible (common in CI/CD with temporary directories). Subshell execution and directory restoration now degrade gracefully instead of crashing:
- capturing the working directory before a subshell no longer throws when
getcwd()fails - directory restoration falls back to a safe location (
HOME, then/) when the original directory is gone - simple commands fall back to the inherited
cwdwhengetcwd()is unavailable - spawning a child process no longer fails with
posix_spawn ENOENTwhen the inherited working directory has been deleted; the process is launched from a valid fallback directory (HOME,USERPROFILE, the temp dir, then/) instead
Make the built-in cd command fully sh/bash compatible so shell scripts translate directly to .mjs (issue #50):
cd -switches to the previous directory and prints it, likesh~and~/pathtilde expansion- successful
cdupdates thePWDandOLDPWDenvironment variables - relative targets resolve against the
cwdoption for consistency
Also documents the working-directory behavior (persistence across commands, subshell isolation, and cd vs. the cwd option) in the README.
Fix CI false positives where a global teardown preempted an in-flight, awaited command (issue #170).
Three related defects made tests intermittently report a SIGTERM result (exit code 143) and emit a MaxListenersExceeded warning, most visibly on Windows/Bun. All are now keyed on a new _awaited flag, set synchronously when user code starts consuming a runner (await/then/catch/finally/stream):
_handleParentStreamClosure()killed any active runner when a parentstdout/stderrcloseevent fired. On Windows/Bun the parentWriteStreamcan emit a spuriousclose(the same instability behind theMaxListenersExceededwarning), which preempted the awaited command and replaced its real exit code with143. It now skips runners that are being awaited, since theawaitis the authoritative consumer. This was the actual CI trigger.cleanupActiveRunners()(invoked byresetGlobalState()between tests) could force-kill a command that user code was still awaiting, replacing its real exit code with a synthetic SIGTERM result. The reaper now skips awaited, unfinished runners.monitorParentStreams()attached acloselistener toprocess.stdout/process.stderron everyProcessRunnerconstruction but never removed them on reset, so they accumulated until Node/Bun emitted aMaxListenersExceededwarning. The listeners are now tracked and removed inresetGlobalState()/resetParentStreamMonitoring().
Related Pull Request: #171
[Rust] 0.11.1 (Rust)
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 bothProcessRunnerandPipeline.
[Rust] 0.11.0 (Rust)
Changed
- Make the built-in
cdcommand fullysh/bash compatible so shell scripts translate directly to Rust (issue #50):cd -switches to the previous directory and prints it, likesh~and~/pathtilde expansion- a successful
cdupdates thePWDandOLDPWDenvironment variables - relative targets resolve against the
cwdoption for consistency
[Rust] 0.10.0 (Rust)
Added
StreamingRunner::kill_signalto configure the signal used to stop a process
(defaultSIGTERM), mirroring the JavaScriptkillSignaloption.StreamingRunner::exit_pump_grace_msto configure the post-exit pipe drain
grace period (default 100ms).OutputStream::kill/OutputStream::kill_withto stop a streaming process
from inside the consumption loop; abandoning the stream (drop/break) now
stops the process too.
Fixed
OutputStreamno 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).
JavaScript 0.12.0
Fix stream() async iterator to yield exit chunks and never hang on open pipes (issue #155)
stream()now yields a final{ type: 'exit', code }chunk when the process
exits, so the documentedchunk.type === 'exit'handling is no longer dead
code. Consumers that touchchunk.datamust guard onchunk.typefirst.- Both
stream()and awaiting a command no longer hang forever when the process
has exited but a grandchild keeps the stdio pipes open (e.g.
sh -c 'long-task & echo done'). The command resolves as soon as the process
exits; remaining buffered output is drained within a short grace period before
the lingering reads are aborted. - The grace period is configurable via the
exitPumpGraceoption (milliseconds,
default100). For ordinary commands the pumps drain immediately, so the grace
adds no latency — it only bounds the wait in the grandchild-holds-pipe case. - A long-running command can be stopped from inside the
stream()loop, either by
callingkill()(the loop then ends with a terminatingexitchunk) or by
breaking out of the loop (which kills the process as the iterator unwinds). - The stop signal is configurable via the new
killSignaloption (default
SIGTERM). An argument-lesskill(), abreak, and an externalAbortSignal
all use it; an explicitkill(signal)argument still overrides it. Exit codes
follow the conventional128 + signalmapping (e.g.SIGINT=> 130). - Awaiting a command while an external
AbortSignalfires no longer hangs: the
abort listener is now registered on the await/then path too, so the command
resolves promptly with the configured signal's exit code.
Related Pull Request: #169
[Rust] 0.9.6 (Rust)
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.
JavaScript 0.11.1
Fix false-positive releases and the "failed to deploy" restart in the JavaScript
release pipeline (issue #166):
publish-to-npm.mjs: only report success when output-scan, exit code, and an
npm viewregistry check all agree — a failedchangeset publish(e.g. npm
E404) can no longer create a GitHub release for a version that never reached
npm.check-release-needed.mjs: always probe npm and emitcurrent_unpublished,
so a version that was committed tomainbut never published self-heals on the
next push regardless of changeset state (closes the restart that "failed to do
any deploy").- New
wait-for-npm.mjsstep verifies, after publish, that the exact version is
actually installable from npm — turning any "tagged but not on npm" divergence
into a hard CI failure. setup-npm.mjsnow asserts npm ≥ 11.5.1 (required for OIDC trusted
publishing) andcreate-github-release.mjscaps the release-notes body and is
idempotent on an already-existing release.
Related Pull Request: #168
JavaScript 0.11.0
Add literal() function to preserve apostrophes in shell arguments
When passing text containing apostrophes to programs that store it literally (like API calls via CLI tools), apostrophes would appear corrupted as triple quotes ('''). The new literal() function uses double-quote escaping which preserves apostrophes while still escaping shell-dangerous characters.
New features:
literal(value)- Mark text for double-quote escaping, preserving apostrophesquoteLiteral(value)- Low-level function for manual command building
Usage:
import { $, literal } from 'command-stream';
// Apostrophes now preserved for API storage
const notes = "Dependencies didn't exist";
await $`gh release create --notes ${literal(notes)}`;Related Pull Request: #148