@j-o-r/sh
Version:
Execute shell commands on Linux-based systems from javascript
161 lines (118 loc) • 4.76 kB
Markdown
# @j-o-r/sh [](https://www.npmjs.com/package/@j-o-r/sh) [](https://codeberg.org/duin/sh/src/branch/main/LICENSE) [](https://codeberg.org/duin/sh)
Execute shell commands from JavaScript on Linux.
## Introduction
`@j-o-r/sh` is a lightweight Node.js module for running shell commands with a clean, zx-inspired API. It fixes namespace pollution, adds **rolling timeouts** (reset on stdout/stderr), **no-shell mode** (`/usr/bin/env -S`), process tree kills, buffer limits (1MB default), and a built-in **Test** framework + **AsyncTracker** for leaks.
**Key Features**:
- Template tag `SH`cmd`` → `SHDispatch` for `.options().run()`.
- Global options: `SH.timeout = '5s'; SH.cwd = '/tmp';`.
- Sync/async `run()`/`runSync()`; stdin payload; detached mode.
- Utils: `sleep`, `retry`, `expBackoff`, `cd`, `parseArgs`, `userIn`, `readIn`.
- Testing: `new Test().add('name', () => assert(...)).run()`.
- Async leak detection: `AsyncTracker` via `async_hooks`.
- Safe interpolation; `bashEscape`; full JSDoc.
No runtime deps. ESM-only (ES2020+).
## Quick Install
```bash
npm i @j-o-r/sh
```
## Usage
### Basics
```js
import { SH, cd, sleep } from '@j-o-r/sh';
cd('/tmp');
const out = await SH`ls -la`.run();
console.log(out); // Captured stdout (trimmed)
```
### Chaining & Options
```js
SH`curl -s ip.js.org`
.options({ timeout: '2s', shell: false }) // No-shell: /usr/bin/env -S
.run()
.catch(e => console.error(e.message)); // "Command failed with code 1: ..."
```
### Parallel & Context
```js
import { within } from '@j-o-r/sh';
const results = await within(async () => Promise.all([
SH`sleep 1; echo ok`.run(),
sleep('500ms'),
SH`uname`.run()
]));
```
### Retry & Backoff
```js
import { retry, expBackoff } from '@j-o-r/sh';
try {
const res = await retry(3, expBackoff('10s'), () =>
SH`curl -s unreachable`.run()
);
} catch (e) {
// Last error
}
```
### Interactive / Stdin
```js
import { userIn, readIn } from '@j-o-r/sh';
// User prompt (abortable)
const { input, abort } = userIn('Password: ');
const pw = await input;
// Piped stdin
const out = await SH`grep secret`.run(await readIn());
```
### Vim / TTY (Sync)
```js
SH`vim`.options({ stdio: 'inherit' }).runSync();
```
## Testing
Full-featured tester with async support, error reporting, unresolved Promise detection.
```js
import { Test, assert, jsType } from '@j-o-r/sh';
const t = new Test(true); // quiet: no console
t.add('sync assert', () => assert.strictEqual(1 + 1, 2));
t.add('async', async () => {
await sleep('100ms');
assert.strictEqual(jsType([]), 'Array');
});
const report = await t.run();
console.log(report); // { tests: 2, executed: 2, duration: 150, errors: 0 }
t.unresolved(); // Logs leaks if any
```
## Advanced
- **SSH Example** (interactive; use keys/sshpass for automation):
```js
// TTY/inherit for prompts
SH`ssh user@host`.options({ stdio: 'inherit' }).runSync();
// Or sshpass: SH`sshpass -p pw ssh user@host`.run()
```
See `scenarios/` for full demos.
- **CLI Args**: `parseArgs()` → `{ port: '8080', _: ['file'] }`.
- **Global Defaults**: Set global options on the `SH` object to apply to all subsequent commands. These can be overridden per-command via `.options()`. Examples:
- `SH.timeout = '5s';` – Default timeout for all runs.
- `SH.cwd = '/tmp';` – Default working directory.
- `SH.shell = false;` – Disable shell mode (uses `/usr/bin/env -S`).
- `SH.maxBuffer = 1 * 1024 * 1024;` – Override default buffer limit (500kb) to 1MB per stream (stdout/stderr). Buffering captures output up to this limit; excess is truncated with markers.
- **Kill Tree**: `dispatch.kill('SIGKILL')` → children via `pgrep -P`.
## API
Full JSDoc in `lib/*.js`. Key exports:
| Utility | Description |
|---------|-------------|
| `SH`cmd`` | Template → {@link SHDispatch} |
| `cd(dir)` | `process.chdir()` |
| `sleep('1s')` | Promise delay |
| `retry(3, '1s', fn)` | Retry w/ delay/gen |
| `userIn(prompt)` | `{ input: Promise, abort() }` |
| `Test` | Test runner |
| `AsyncTracker` | Async leak detector |
| `parseArgs(argv)` | CLI parser |
| `bashEscape(str)` | Shell-safe string |
See [types/index.d.ts](types/index.d.ts) for TS defs.
## Development
```bash
npm run types # Generate types/
npm test # Run scenarios/sh.js
npm run release # Pack for publish
npm run publish # npm publish
```
Repo: [Codeberg](https://codeberg.org/duin/sh) | Issues: [Codeberg Issues](https://codeberg.org/duin/sh/issues)
## License
Apache-2.0 © [Jorrit Duin](mailto:jorrit.duin+sh@gmail.com)