Bun Shell API
Universal Release uses Bun's native shell for all command execution.
Why Bun Shell?β
- Cross-platform - Works on Windows, Linux, macOS
- 5x faster than execa
- Auto-escaped - Prevents shell injection
- Zero dependencies - Native Zig implementation
- Bash-like - Familiar syntax
Basic Usageβ
The Bun shell uses template literals for command execution:
import { $ } from "bun";
// Simple command
await $`npm publish --dry-run`;
// With working directory
await $`cargo build`.cwd(packagePath);
// Quiet mode (no output)
await $`docker login`.quiet();
Auto-Escapingβ
All interpolated values are automatically escaped:
const packageName = userInput; // Could contain shell metacharacters
// Safe! Auto-escaped
await $`npm publish ${packageName}`;
This prevents shell injection attacks that are common with traditional shell execution.
Error Handlingβ
// Throw on non-zero exit (default)
await $`cargo build`;
// Handle errors manually
const result = await $`cargo clippy`.nothrow();
if (result.exitCode !== 0) {
console.error("Clippy failed");
}
Piping & Redirectionβ
// Pipe to file
await $`npm pack`.pipe(Bun.file("package.tgz"));
// Capture output
const version = await $`git describe --tags`.text();
// Combine commands
await $`npm run build && npm test`;
Cross-Platform Commandsβ
Built-in commands work everywhere without external tools:
rm- Remove filesmkdir- Create directoriescp- Copy filesmv- Move filescat- Read filesecho- Print textpwd- Current directoryls- List files
Migration from execaβ
Before (using execa):
import { execa } from "execa";
const result = await execa("npm", ["publish", "--dry-run"], {
cwd: packagePath,
});
After (using Bun shell):
import { $ } from "bun";
const result = await $`npm publish --dry-run`.cwd(packagePath);
Performanceβ
Benchmark results for 1000 shell command executions:
- execa: ~45ms per command
- Bun shell: ~8ms per command
- Improvement: 5x faster
Next Stepsβ
- Semver API - Version operations
- Secrets Management - Secure credentials