@nutgaard/bun-recording-shell
v0.0.2
Published
Record and replay Bun shell invocations for deterministic tests.
Maintainers
Readme
@nutgaard/bun-recording-shell
Record and replay Bun shell invocations for deterministic tests.
This package wraps Bun's $ shell API and gives you three modes:
passthrough: run commands normallyrecord: run commands and capture their outputsreplay: return previously recorded outputs without re-running commands
It is useful when you want to test code that shells out, but you do not want your test suite to depend on the real system state, network, filesystem tools, or command timing.
Requirements
- Bun runtime
- Node compatibility is for package consumers, but the runtime behavior depends on
bun
Install
bun add @nutgaard/bun-recording-shellBasic usage
import { createShell } from '@nutgaard/bun-recording-shell';
const $ = createShell({ mode: 'passthrough' });
const result = await $`printf hello`;
console.log(result.text()); // helloRecord and replay
import { createShell } from '@nutgaard/bun-recording-shell';
const recordShell = createShell({ mode: 'record' });
await recordShell`printf first`.quiet();
await recordShell`printf second`.quiet();
const replayShell = createShell({
mode: 'replay',
recording: recordShell.getRecording(),
});
console.log((await replayShell`printf first`).text()); // first
console.log((await replayShell`printf second`).text()); // secondPersist recordings to disk
In record mode, you can write command events to an NDJSON log file and later replay from that file.
import { createShell } from '@nutgaard/bun-recording-shell';
const recordShell = createShell({
mode: 'record',
recordingLogPath: './fixtures/commands.ndjson',
});
await recordShell`printf hello`.quiet();
const replayShell = createShell({
mode: 'replay',
recordingLogPath: './fixtures/commands.ndjson',
});
console.log((await replayShell`printf hello`).text()); // helloIf you only need the finished command entries, you can also parse the file directly:
import { readReplayRecording } from '@nutgaard/bun-recording-shell';
const recording = readReplayRecording('./fixtures/commands.ndjson');Command behavior
Replay mode is strict by design:
- commands must be replayed in the same order they were recorded
- the rendered command string must match exactly
- non-zero exits throw by default, just like live shell execution
If you want to inspect a non-zero result without throwing, use .nothrow():
const result = await replayShell`false`.nothrow();
console.log(result.exitCode);Errors
The package exports specific error types for replay and recorded failures:
RecordedShellError: command failed with a non-zero exit codeReplayMismatchError: replayed command did not match the next recorded commandReplayExhaustedError: replay ran out of recorded entries
RecordedShellError also exposes:
commandstdoutstderrexitCode
API
createShell(options)
Creates a Bun shell wrapper.
Modes:
{ mode: 'passthrough' }{ mode: 'record', recordingLogPath?: string }{ mode: 'replay', recording: ShellRecordingEntry[] }{ mode: 'replay', recordingLogPath: string }
Shared options:
cwdenvthrows
ShellRecordingEntry
type ShellRecordingEntry = {
command: string;
stdout: string;
stderr: string;
exitCode: number;
};readReplayRecording(logPath)
Reads a recording log file and returns the finished command entries as ShellRecordingEntry[].
Notes
- This package is Bun-specific because it builds on Bun's shell API.
- Unsupported stream-like interpolations are rejected in replay mode.
- Published output is ESM-only.
