command-stream 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/$.mjs +1720 -0
- package/LICENSE +24 -0
- package/README.md +776 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
# command-$tream
|
|
2
|
+
|
|
3
|
+
$treamable commands executor
|
|
4
|
+
|
|
5
|
+
A modern $ shell utility library with streaming, async iteration, and EventEmitter support, optimized for Bun runtime.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- π **Shell-like by Default**: Commands behave exactly like running in terminal (stdoutβstdout, stderrβstderr, stdinβstdin)
|
|
10
|
+
- ποΈ **Fully Controllable**: Override default behavior with options (`mirror`, `capture`, `stdin`)
|
|
11
|
+
- π **Multiple Usage Patterns**: Classic await, async iteration, EventEmitter, .pipe() method, and mixed patterns
|
|
12
|
+
- π‘ **Real-time Streaming**: Process command output as it arrives, not after completion
|
|
13
|
+
- π **Bun Optimized**: Designed for Bun runtime with Node.js compatibility
|
|
14
|
+
- β‘ **Performance**: Memory-efficient streaming prevents large buffer accumulation
|
|
15
|
+
- π― **Backward Compatible**: Existing `await $` syntax continues to work
|
|
16
|
+
- π‘οΈ **Type Safe**: Full TypeScript support (coming soon)
|
|
17
|
+
- π§ **Built-in Commands**: 18 essential commands work identically across platforms
|
|
18
|
+
|
|
19
|
+
## Built-in Commands (π NEW!)
|
|
20
|
+
|
|
21
|
+
command-stream now includes **18 built-in commands** that work identically to their bash/sh counterparts, providing true cross-platform shell scripting without system dependencies:
|
|
22
|
+
|
|
23
|
+
### π **File System Commands**
|
|
24
|
+
- `cat` - Read and display file contents
|
|
25
|
+
- `ls` - List directory contents (supports `-l`, `-a`, `-A`)
|
|
26
|
+
- `mkdir` - Create directories (supports `-p` recursive)
|
|
27
|
+
- `rm` - Remove files/directories (supports `-r`, `-f`)
|
|
28
|
+
- `mv` - Move/rename files and directories
|
|
29
|
+
- `cp` - Copy files/directories (supports `-r` recursive)
|
|
30
|
+
- `touch` - Create files or update timestamps
|
|
31
|
+
|
|
32
|
+
### π§ **Utility Commands**
|
|
33
|
+
- `basename` - Extract filename from path
|
|
34
|
+
- `dirname` - Extract directory from path
|
|
35
|
+
- `seq` - Generate number sequences
|
|
36
|
+
- `yes` - Output string repeatedly (streaming)
|
|
37
|
+
|
|
38
|
+
### β‘ **System Commands**
|
|
39
|
+
- `cd` - Change directory
|
|
40
|
+
- `pwd` - Print working directory
|
|
41
|
+
- `echo` - Print arguments (supports `-n`)
|
|
42
|
+
- `sleep` - Wait for specified time
|
|
43
|
+
- `true`/`false` - Success/failure commands
|
|
44
|
+
- `which` - Locate commands
|
|
45
|
+
- `exit` - Exit with code
|
|
46
|
+
- `env` - Print environment variables
|
|
47
|
+
- `test` - File condition testing
|
|
48
|
+
|
|
49
|
+
### β¨ **Key Advantages**
|
|
50
|
+
|
|
51
|
+
- **π Cross-Platform**: Works identically on Windows, macOS, and Linux
|
|
52
|
+
- **π Performance**: No system calls - pure JavaScript execution
|
|
53
|
+
- **π Pipeline Support**: All commands work in pipelines and virtual command chains
|
|
54
|
+
- **βοΈ Option Aware**: Commands respect `cwd`, `env`, and other options
|
|
55
|
+
- **π‘οΈ Safe by Default**: Proper error handling and safety checks (e.g., `rm` requires `-r` for directories)
|
|
56
|
+
- **π Bash Compatible**: Error messages and behavior match bash/sh exactly
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import { $ } from 'command-stream';
|
|
60
|
+
|
|
61
|
+
// All these work without any system dependencies!
|
|
62
|
+
await $`mkdir -p project/src`;
|
|
63
|
+
await $`touch project/src/index.js`;
|
|
64
|
+
await $`echo "console.log('Hello!');" > project/src/index.js`;
|
|
65
|
+
await $`ls -la project/src`;
|
|
66
|
+
await $`cat project/src/index.js`;
|
|
67
|
+
await $`cp -r project project-backup`;
|
|
68
|
+
await $`rm -r project-backup`;
|
|
69
|
+
|
|
70
|
+
// Mix built-ins with pipelines and virtual commands
|
|
71
|
+
await $`seq 1 5 | cat > numbers.txt`;
|
|
72
|
+
await $`basename /path/to/file.txt .txt`; // β "file"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Comparison with Other Libraries
|
|
76
|
+
|
|
77
|
+
| Feature | [command-stream](https://github.com/link-foundation/command-stream) | [Bun.$](https://bun.sh/docs/runtime/shell) | [execa](https://github.com/sindresorhus/execa) | [zx](https://github.com/google/zx) |
|
|
78
|
+
|---------|----------------|-------|-------|-----|
|
|
79
|
+
| **Runtime Support** | β
Bun + Node.js | π‘ Bun only | β
Node.js | β
Node.js |
|
|
80
|
+
| **Template Literals** | β
`` $`cmd` `` | β
`` $`cmd` `` | β
`` $`cmd` `` | β
`` $`cmd` `` |
|
|
81
|
+
| **Real-time Streaming** | β
Live output | β Buffer only | π‘ Limited | β Buffer only |
|
|
82
|
+
| **Synchronous Execution** | β
`.sync()` with events | β No | β
`execaSync` | β No |
|
|
83
|
+
| **Async Iteration** | β
`for await (chunk of $.stream())` | β No | β No | β No |
|
|
84
|
+
| **EventEmitter Pattern** | β
`.on('data', ...)` | β No | π‘ Limited events | β No |
|
|
85
|
+
| **Mixed Patterns** | β
Events + await/sync | β No | β No | β No |
|
|
86
|
+
| **Shell Injection Protection** | β
Auto-quoting | β
Built-in | β
Safe by default | β
Safe by default |
|
|
87
|
+
| **Cross-platform** | β
macOS/Linux/Windows | β
Yes | β
Yes | β
Yes |
|
|
88
|
+
| **Performance** | β‘ Fast (Bun optimized) | β‘ Very fast | π Moderate | π Slow |
|
|
89
|
+
| **Memory Efficiency** | β
Streaming prevents buildup | π‘ Buffers in memory | π‘ Buffers in memory | π‘ Buffers in memory |
|
|
90
|
+
| **Error Handling** | β
Configurable (`set -e`/`set +e`, non-zero OK by default) | β
Throws on error | β
Throws on error | β
Throws on error |
|
|
91
|
+
| **Shell Settings** | β
`set -e`/`set +e` equivalent | β No | β No | β No |
|
|
92
|
+
| **Stdout Support** | β
Real-time streaming + events | β
Shell redirection + buffered | β
Node.js streams + interleaved | β
Readable streams + `.pipe.stdout` |
|
|
93
|
+
| **Stderr Support** | β
Real-time streaming + events | β
Redirection + `.quiet()` access | β
Streams + interleaved output | β
Readable streams + `.pipe.stderr` |
|
|
94
|
+
| **Stdin Support** | β
string/Buffer/inherit/ignore | β
Pipe operations | β
Input/output streams | β
Basic stdin |
|
|
95
|
+
| **Built-in Commands** | β
**18 commands**: cat, ls, mkdir, rm, mv, cp, touch, basename, dirname, seq, yes + all Bun.$ commands | β
echo, cd, etc. | β Uses system | β Uses system |
|
|
96
|
+
| **Virtual Commands Engine** | β
**Revolutionary**: Register JavaScript functions as shell commands with full pipeline support | β No extensibility | β No custom commands | β No custom commands |
|
|
97
|
+
| **Pipeline/Piping Support** | β
**Advanced**: System + Built-ins + Virtual + Mixed + `.pipe()` method | β
Standard shell piping | β
Programmatic `.pipe()` + multi-destination | β
Shell piping + `.pipe()` method |
|
|
98
|
+
| **Bundle Size** | π¦ ~15KB | π― 0KB (built-in) | π¦ ~25KB | π¦ ~50KB |
|
|
99
|
+
| **TypeScript** | π Coming soon | β
Built-in | β
Full support | β
Full support |
|
|
100
|
+
| **License** | β
**Unlicense (Public Domain)** | π‘ MIT (+ LGPL dependencies) | π‘ MIT | π‘ Apache 2.0 |
|
|
101
|
+
|
|
102
|
+
### Why Choose command-stream?
|
|
103
|
+
|
|
104
|
+
- **π Truly Free**: **Unlicense (Public Domain)** - No restrictions, no attribution required, use however you want
|
|
105
|
+
- **π Revolutionary Virtual Commands**: **World's first** fully customizable virtual commands engine - register JavaScript functions as shell commands!
|
|
106
|
+
- **π Advanced Pipeline System**: **Only library** where virtual commands work seamlessly in pipelines with built-ins and system commands
|
|
107
|
+
- **π§ Built-in Commands**: **18 essential commands** work identically across all platforms - no system dependencies!
|
|
108
|
+
- **π‘ Real-time Processing**: Only library with true streaming and async iteration
|
|
109
|
+
- **π Flexible Patterns**: Multiple usage patterns (await, events, iteration, mixed)
|
|
110
|
+
- **π Shell Replacement**: Dynamic error handling with `set -e`/`set +e` equivalents for .sh file replacement
|
|
111
|
+
- **β‘ Bun Optimized**: Designed for Bun with Node.js fallback compatibility
|
|
112
|
+
- **πΎ Memory Efficient**: Streaming prevents large buffer accumulation
|
|
113
|
+
- **π‘οΈ Production Ready**: 266+ tests with comprehensive coverage
|
|
114
|
+
|
|
115
|
+
## Installation
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Using npm
|
|
119
|
+
npm install command-stream
|
|
120
|
+
|
|
121
|
+
# Using bun
|
|
122
|
+
bun add command-stream
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Usage Patterns
|
|
126
|
+
|
|
127
|
+
### Classic Await (Backward Compatible)
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
import { $ } from 'command-stream';
|
|
131
|
+
|
|
132
|
+
const result = await $`ls -la`;
|
|
133
|
+
console.log(result.stdout);
|
|
134
|
+
console.log(result.code); // exit code
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Synchronous Execution
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
import { $ } from 'command-stream';
|
|
141
|
+
|
|
142
|
+
// Use .sync() for blocking execution
|
|
143
|
+
const result = $`echo "hello"`.sync();
|
|
144
|
+
console.log(result.stdout); // "hello\n"
|
|
145
|
+
|
|
146
|
+
// Events still work but are batched after completion
|
|
147
|
+
$`echo "world"`
|
|
148
|
+
.on('end', result => console.log('Done:', result))
|
|
149
|
+
.sync();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Async Iteration (Real-time Streaming)
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
import { $ } from 'command-stream';
|
|
156
|
+
|
|
157
|
+
for await (const chunk of $`long-running-command`.stream()) {
|
|
158
|
+
if (chunk.type === 'stdout') {
|
|
159
|
+
console.log('Real-time output:', chunk.data.toString());
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### EventEmitter Pattern (Event-driven)
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
import { $ } from 'command-stream';
|
|
168
|
+
|
|
169
|
+
$`command`
|
|
170
|
+
.on('data', chunk => {
|
|
171
|
+
if (chunk.type === 'stdout') {
|
|
172
|
+
console.log('Stdout:', chunk.data.toString());
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
.on('stderr', chunk => console.log('Stderr:', chunk))
|
|
176
|
+
.on('end', result => console.log('Done:', result))
|
|
177
|
+
.on('exit', code => console.log('Exit code:', code));
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Mixed Pattern (Best of Both Worlds)
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
import { $ } from 'command-stream';
|
|
184
|
+
|
|
185
|
+
// Async mode - events fire in real-time
|
|
186
|
+
const process = $`streaming-command`;
|
|
187
|
+
process.on('data', chunk => {
|
|
188
|
+
processRealTimeData(chunk);
|
|
189
|
+
});
|
|
190
|
+
const result = await process;
|
|
191
|
+
console.log('Final output:', result.stdout);
|
|
192
|
+
|
|
193
|
+
// Sync mode - events fire after completion (batched)
|
|
194
|
+
const syncCmd = $`another-command`;
|
|
195
|
+
syncCmd.on('end', result => {
|
|
196
|
+
console.log('Completed with:', result.stdout);
|
|
197
|
+
});
|
|
198
|
+
const syncResult = syncCmd.sync();
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Shell Replacement (.sh β .mjs)
|
|
202
|
+
|
|
203
|
+
Replace bash scripts with JavaScript while keeping shell semantics:
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
import { $, shell, set, unset } from 'command-stream';
|
|
207
|
+
|
|
208
|
+
// set -e equivalent: exit on any error
|
|
209
|
+
shell.errexit(true);
|
|
210
|
+
|
|
211
|
+
await $`mkdir -p build`;
|
|
212
|
+
await $`npm run build`;
|
|
213
|
+
|
|
214
|
+
// set +e equivalent: allow errors (like bash)
|
|
215
|
+
shell.errexit(false);
|
|
216
|
+
const cleanup = await $`rm -rf temp`; // Won't throw if fails
|
|
217
|
+
|
|
218
|
+
// set -e again for critical operations
|
|
219
|
+
shell.errexit(true);
|
|
220
|
+
await $`cp -r build/* deploy/`;
|
|
221
|
+
|
|
222
|
+
// Other bash-like settings
|
|
223
|
+
shell.verbose(true); // set -v: print commands
|
|
224
|
+
shell.xtrace(true); // set -x: trace execution
|
|
225
|
+
|
|
226
|
+
// Or use the bash-style API
|
|
227
|
+
set('e'); // set -e
|
|
228
|
+
unset('e'); // set +e
|
|
229
|
+
set('x'); // set -x
|
|
230
|
+
set('verbose'); // Long form also supported
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Cross-Platform File Operations (Built-in Commands)
|
|
234
|
+
|
|
235
|
+
Replace system-dependent operations with built-in commands that work identically everywhere:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
import { $ } from 'command-stream';
|
|
239
|
+
|
|
240
|
+
// File system operations work on Windows, macOS, and Linux identically
|
|
241
|
+
await $`mkdir -p project/src project/tests`;
|
|
242
|
+
await $`touch project/src/index.js project/tests/test.js`;
|
|
243
|
+
|
|
244
|
+
// List files with details
|
|
245
|
+
const files = await $`ls -la project/src`;
|
|
246
|
+
console.log(files.stdout);
|
|
247
|
+
|
|
248
|
+
// Copy and move operations
|
|
249
|
+
await $`cp project/src/index.js project/src/backup.js`;
|
|
250
|
+
await $`mv project/src/backup.js project/backup.js`;
|
|
251
|
+
|
|
252
|
+
// File content operations
|
|
253
|
+
await $`echo "export default 'Hello World';" > project/src/index.js`;
|
|
254
|
+
const content = await $`cat project/src/index.js`;
|
|
255
|
+
console.log(content.stdout);
|
|
256
|
+
|
|
257
|
+
// Path operations
|
|
258
|
+
const filename = await $`basename project/src/index.js .js`; // β "index"
|
|
259
|
+
const directory = await $`dirname project/src/index.js`; // β "project/src"
|
|
260
|
+
|
|
261
|
+
// Generate sequences and process them
|
|
262
|
+
await $`seq 1 10 | cat > numbers.txt`;
|
|
263
|
+
const numbers = await $`cat numbers.txt`;
|
|
264
|
+
|
|
265
|
+
// Cleanup
|
|
266
|
+
await $`rm -r project numbers.txt`;
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Virtual Commands (Extensible Shell)
|
|
270
|
+
|
|
271
|
+
Create custom commands that work seamlessly alongside built-ins:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
import { $, register, unregister, listCommands } from 'command-stream';
|
|
275
|
+
|
|
276
|
+
// Register a custom command
|
|
277
|
+
register('greet', async (args, stdin) => {
|
|
278
|
+
const name = args[0] || 'World';
|
|
279
|
+
return { stdout: `Hello, ${name}!\n`, code: 0 };
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Use it like any other command
|
|
283
|
+
await $`greet Alice`; // β "Hello, Alice!"
|
|
284
|
+
await $`echo "Bob" | greet`; // β "Hello, Bob!"
|
|
285
|
+
|
|
286
|
+
// Streaming virtual commands with async generators
|
|
287
|
+
register('countdown', async function* (args) {
|
|
288
|
+
const start = parseInt(args[0] || 5);
|
|
289
|
+
for (let i = start; i >= 0; i--) {
|
|
290
|
+
yield `${i}\n`;
|
|
291
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Use in pipelines with built-ins
|
|
296
|
+
await $`countdown 3 | cat > countdown.txt`;
|
|
297
|
+
|
|
298
|
+
// Virtual commands work in all patterns
|
|
299
|
+
for await (const chunk of $`countdown 3`.stream()) {
|
|
300
|
+
console.log('Countdown:', chunk.data.toString().trim());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Management functions
|
|
304
|
+
console.log(listCommands()); // List all registered commands
|
|
305
|
+
unregister('greet'); // Remove custom commands
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### π₯ **Why Virtual Commands Are Revolutionary**
|
|
309
|
+
|
|
310
|
+
**No other shell library offers this level of extensibility:**
|
|
311
|
+
|
|
312
|
+
- **π« Bun.$**: Fixed set of built-in commands, no extensibility API
|
|
313
|
+
- **π« execa**: Transform/pipeline system, but no custom commands
|
|
314
|
+
- **π« zx**: JavaScript functions only, no shell command integration
|
|
315
|
+
|
|
316
|
+
**command-stream breaks the barrier** between JavaScript functions and shell commands:
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
// β Other libraries: Choose JavaScript OR shell
|
|
320
|
+
await execa('node', ['script.js']); // execa: separate processes
|
|
321
|
+
await $`node script.js`; // zx: shell commands only
|
|
322
|
+
|
|
323
|
+
// β
command-stream: JavaScript functions AS shell commands
|
|
324
|
+
register('deploy', async (args) => {
|
|
325
|
+
const env = args[0] || 'staging';
|
|
326
|
+
await deployToEnvironment(env);
|
|
327
|
+
return { stdout: `Deployed to ${env}!\n`, code: 0 };
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await $`deploy production`; // JavaScript function as shell command!
|
|
331
|
+
await $`deploy staging | tee log.txt`; // Works in pipelines!
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Unique capabilities:**
|
|
335
|
+
- **Seamless Integration**: Virtual commands work exactly like built-ins
|
|
336
|
+
- **Pipeline Support**: Full stdin/stdout passing between virtual and system commands
|
|
337
|
+
- **Streaming**: Async generators for real-time output
|
|
338
|
+
- **Dynamic Registration**: Add/remove commands at runtime
|
|
339
|
+
- **Option Awareness**: Virtual commands respect `cwd`, `env`, etc.
|
|
340
|
+
|
|
341
|
+
### π **Advanced Pipeline Support**
|
|
342
|
+
|
|
343
|
+
**command-stream offers the most advanced piping system in the JavaScript ecosystem:**
|
|
344
|
+
|
|
345
|
+
#### **Shell-Style Piping (Traditional)**
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
import { $, register } from 'command-stream';
|
|
349
|
+
|
|
350
|
+
// β
Standard shell piping (like all libraries)
|
|
351
|
+
await $`echo "hello world" | wc -w`; // β "2"
|
|
352
|
+
|
|
353
|
+
// β
Built-in to built-in piping
|
|
354
|
+
await $`seq 1 5 | cat > numbers.txt`;
|
|
355
|
+
|
|
356
|
+
// β
System to built-in piping
|
|
357
|
+
await $`git log --oneline | head -n 5`;
|
|
358
|
+
|
|
359
|
+
// π UNIQUE: Virtual command piping
|
|
360
|
+
register('uppercase', async (args, stdin) => {
|
|
361
|
+
return { stdout: stdin.toUpperCase(), code: 0 };
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
register('reverse', async (args, stdin) => {
|
|
365
|
+
return { stdout: stdin.split('').reverse().join(''), code: 0 };
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// β
Built-in to virtual piping
|
|
369
|
+
await $`echo "hello" | uppercase`; // β "HELLO"
|
|
370
|
+
|
|
371
|
+
// β
Virtual to virtual piping
|
|
372
|
+
await $`echo "hello" | uppercase | reverse`; // β "OLLEH"
|
|
373
|
+
|
|
374
|
+
// β
Mixed pipelines (system + built-in + virtual)
|
|
375
|
+
await $`git log --oneline | head -n 3 | uppercase | cat > LOG.txt`;
|
|
376
|
+
|
|
377
|
+
// β
Complex multi-stage pipelines
|
|
378
|
+
await $`find . -name "*.js" | head -n 10 | basename | sort | uniq`;
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### **π Programmatic .pipe() Method (NEW!)**
|
|
382
|
+
|
|
383
|
+
**World's first shell library with full `.pipe()` method support for virtual commands:**
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
import { $, register } from 'command-stream';
|
|
387
|
+
|
|
388
|
+
// β
Basic programmatic piping
|
|
389
|
+
const result = await $`echo "hello"`.pipe($`echo "World: $(cat)"`);
|
|
390
|
+
|
|
391
|
+
// π Virtual command chaining
|
|
392
|
+
register('add-prefix', async (args, stdin) => {
|
|
393
|
+
const prefix = args[0] || 'PREFIX:';
|
|
394
|
+
return { stdout: `${prefix} ${stdin.trim()}\n`, code: 0 };
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
register('add-suffix', async (args, stdin) => {
|
|
398
|
+
const suffix = args[0] || 'SUFFIX';
|
|
399
|
+
return { stdout: `${stdin.trim()} ${suffix}\n`, code: 0 };
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// β
Chain virtual commands with .pipe()
|
|
403
|
+
const result = await $`echo "Hello"`
|
|
404
|
+
.pipe($`add-prefix "[PROCESSED]"`)
|
|
405
|
+
.pipe($`add-suffix "!!!"`);
|
|
406
|
+
// β "[PROCESSED] Hello !!!"
|
|
407
|
+
|
|
408
|
+
// β
Mix with built-in commands
|
|
409
|
+
const fileData = await $`cat large-file.txt`
|
|
410
|
+
.pipe($`head -n 100`)
|
|
411
|
+
.pipe($`add-prefix "Line:"`);
|
|
412
|
+
|
|
413
|
+
// β
Error handling in pipelines
|
|
414
|
+
try {
|
|
415
|
+
const result = await $`cat nonexistent.txt`.pipe($`add-prefix "Data:"`);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
// Source error propagates - destination never executes
|
|
418
|
+
console.log('File not found, pipeline stopped');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// β
Complex data processing
|
|
422
|
+
register('json-parse', async (args, stdin) => {
|
|
423
|
+
try {
|
|
424
|
+
const data = JSON.parse(stdin);
|
|
425
|
+
return { stdout: JSON.stringify(data, null, 2), code: 0 };
|
|
426
|
+
} catch (error) {
|
|
427
|
+
return { stdout: '', stderr: `JSON Error: ${error.message}`, code: 1 };
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
register('extract-field', async (args, stdin) => {
|
|
432
|
+
const field = args[0];
|
|
433
|
+
try {
|
|
434
|
+
const data = JSON.parse(stdin);
|
|
435
|
+
const value = data[field] || 'null';
|
|
436
|
+
return { stdout: `${value}\n`, code: 0 };
|
|
437
|
+
} catch (error) {
|
|
438
|
+
return { stdout: '', stderr: `Extract Error: ${error.message}`, code: 1 };
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Real-world API processing pipeline
|
|
443
|
+
const userName = await $`curl -s https://api.github.com/users/octocat`
|
|
444
|
+
.pipe($`json-parse`)
|
|
445
|
+
.pipe($`extract-field name`);
|
|
446
|
+
// β "The Octocat"
|
|
447
|
+
|
|
448
|
+
// Cleanup
|
|
449
|
+
unregister('add-prefix');
|
|
450
|
+
unregister('add-suffix');
|
|
451
|
+
unregister('json-parse');
|
|
452
|
+
unregister('extract-field');
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### **π How We Compare**
|
|
456
|
+
|
|
457
|
+
| Library | Pipeline Types | Custom Commands in Pipes | `.pipe()` Method | Real-time Streaming |
|
|
458
|
+
|---------|----------------|---------------------------|------------------|---------------------|
|
|
459
|
+
| **command-stream** | β
System + Built-ins + Virtual + Mixed | β
**Full support** | β
**Full virtual command support** | β
**Yes** |
|
|
460
|
+
| **Bun.$** | β
System + Built-ins | β No custom commands | β No `.pipe()` method | β No |
|
|
461
|
+
| **execa** | β
Programmatic `.pipe()` | β No shell integration | β
Basic process piping | π‘ Limited |
|
|
462
|
+
| **zx** | β
Shell piping + `.pipe()` | β No custom commands | β
Stream piping only | β No |
|
|
463
|
+
|
|
464
|
+
**π― Unique Advantages:**
|
|
465
|
+
- **Virtual commands work seamlessly in both shell pipes AND `.pipe()` method** - no other library can do this
|
|
466
|
+
- **Mixed pipeline types** - combine system, built-in, and virtual commands freely in both syntaxes
|
|
467
|
+
- **Real-time streaming** through virtual command pipelines
|
|
468
|
+
- **Full stdin/stdout passing** between all command types
|
|
469
|
+
- **Dual piping syntax** - use shell `|` OR programmatic `.pipe()` interchangeably
|
|
470
|
+
|
|
471
|
+
## Default Behavior: Shell-like with Programmatic Control
|
|
472
|
+
|
|
473
|
+
**command-stream behaves exactly like running commands in your shell by default:**
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
import { $ } from 'command-stream';
|
|
477
|
+
|
|
478
|
+
// This command will:
|
|
479
|
+
// 1. Print "Hello" to your terminal (stdoutβstdout)
|
|
480
|
+
// 2. Print "Error!" to your terminal (stderrβstderr)
|
|
481
|
+
// 3. Capture both outputs for programmatic access
|
|
482
|
+
const result = await $`sh -c "echo 'Hello'; echo 'Error!' >&2"`;
|
|
483
|
+
|
|
484
|
+
console.log('Captured stdout:', result.stdout); // "Hello\n"
|
|
485
|
+
console.log('Captured stderr:', result.stderr); // "Error!\n"
|
|
486
|
+
console.log('Exit code:', result.code); // 0
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Key Default Options:**
|
|
490
|
+
- `mirror: true` - Live output to terminal (like shell)
|
|
491
|
+
- `capture: true` - Capture output for later use (unlike shell)
|
|
492
|
+
- `stdin: 'inherit'` - Inherit stdin from parent process
|
|
493
|
+
|
|
494
|
+
**Fully Controllable:**
|
|
495
|
+
```javascript
|
|
496
|
+
import { $, create, sh } from 'command-stream';
|
|
497
|
+
|
|
498
|
+
// Disable terminal output but still capture
|
|
499
|
+
const result = await sh('echo "silent"', { mirror: false });
|
|
500
|
+
|
|
501
|
+
// Custom stdin input
|
|
502
|
+
const custom = await sh('cat', { stdin: "custom input" });
|
|
503
|
+
|
|
504
|
+
// Create custom $ with different defaults
|
|
505
|
+
const quiet$ = create({ mirror: false });
|
|
506
|
+
await quiet$`echo "silent"`; // Won't print to terminal
|
|
507
|
+
|
|
508
|
+
// Disable both mirroring and capturing for performance
|
|
509
|
+
await sh('make build', { mirror: false, capture: false });
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**This gives you the best of both worlds:** shell-like behavior by default, but with full programmatic control and real-time streaming capabilities.
|
|
513
|
+
|
|
514
|
+
## Real-world Examples
|
|
515
|
+
|
|
516
|
+
### Log File Streaming with Session ID Extraction
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
import { $ } from 'command-stream';
|
|
520
|
+
import { appendFileSync, writeFileSync } from 'fs';
|
|
521
|
+
|
|
522
|
+
let sessionId = null;
|
|
523
|
+
let logFile = null;
|
|
524
|
+
|
|
525
|
+
for await (const chunk of $`your-streaming-command`.stream()) {
|
|
526
|
+
if (chunk.type === 'stdout') {
|
|
527
|
+
const data = chunk.data.toString();
|
|
528
|
+
|
|
529
|
+
// Extract session ID from output
|
|
530
|
+
if (!sessionId && data.includes('session_id')) {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(data);
|
|
533
|
+
sessionId = parsed.session_id;
|
|
534
|
+
logFile = `${sessionId}.log`;
|
|
535
|
+
console.log(`Session ID: ${sessionId}`);
|
|
536
|
+
} catch (e) {
|
|
537
|
+
// Handle JSON parse errors
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Write to log file in real-time
|
|
542
|
+
if (logFile) {
|
|
543
|
+
appendFileSync(logFile, data);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Progress Monitoring
|
|
550
|
+
|
|
551
|
+
```javascript
|
|
552
|
+
import { $ } from 'command-stream';
|
|
553
|
+
|
|
554
|
+
let progress = 0;
|
|
555
|
+
|
|
556
|
+
$`download-large-file`
|
|
557
|
+
.on('stdout', (chunk) => {
|
|
558
|
+
const output = chunk.toString();
|
|
559
|
+
if (output.includes('Progress:')) {
|
|
560
|
+
progress = parseProgress(output);
|
|
561
|
+
updateProgressBar(progress);
|
|
562
|
+
}
|
|
563
|
+
})
|
|
564
|
+
.on('end', (result) => {
|
|
565
|
+
console.log('Download completed!');
|
|
566
|
+
});
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## API Reference
|
|
570
|
+
|
|
571
|
+
### ProcessRunner Class
|
|
572
|
+
|
|
573
|
+
The enhanced `$` function returns a `ProcessRunner` instance that extends `EventEmitter`.
|
|
574
|
+
|
|
575
|
+
#### Events
|
|
576
|
+
|
|
577
|
+
- `data`: Emitted for each chunk with `{type: 'stdout'|'stderr', data: Buffer}`
|
|
578
|
+
- `stdout`: Emitted for stdout chunks (Buffer)
|
|
579
|
+
- `stderr`: Emitted for stderr chunks (Buffer)
|
|
580
|
+
- `end`: Emitted when process completes with final result object
|
|
581
|
+
- `exit`: Emitted with exit code
|
|
582
|
+
|
|
583
|
+
#### Methods
|
|
584
|
+
|
|
585
|
+
- `stream()`: Returns an async iterator for real-time chunk processing
|
|
586
|
+
- `sync()`: Execute command synchronously (blocks until completion, events batched)
|
|
587
|
+
- `pipe(destination)`: Programmatically pipe output to another command (returns new ProcessRunner)
|
|
588
|
+
- `then()`, `catch()`, `finally()`: Promise interface for await support
|
|
589
|
+
|
|
590
|
+
#### Properties
|
|
591
|
+
|
|
592
|
+
- `stdout`: Direct access to child process stdout stream
|
|
593
|
+
- `stderr`: Direct access to child process stderr stream
|
|
594
|
+
- `stdin`: Direct access to child process stdin stream
|
|
595
|
+
|
|
596
|
+
### Default Options
|
|
597
|
+
|
|
598
|
+
**By default, command-stream behaves like running commands in the shell:**
|
|
599
|
+
|
|
600
|
+
```javascript
|
|
601
|
+
{
|
|
602
|
+
mirror: true, // Live output to terminal (stdoutβstdout, stderrβstderr)
|
|
603
|
+
capture: true, // Capture output for programmatic access
|
|
604
|
+
stdin: 'inherit' // Inherit stdin from parent process
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Option Details:**
|
|
609
|
+
- `mirror: boolean` - Whether to pipe output to terminal in real-time
|
|
610
|
+
- `capture: boolean` - Whether to capture output in result object
|
|
611
|
+
- `stdin: 'inherit' | 'ignore' | string | Buffer` - How to handle stdin
|
|
612
|
+
- `cwd: string` - Working directory for command
|
|
613
|
+
- `env: object` - Environment variables
|
|
614
|
+
|
|
615
|
+
**Override defaults:**
|
|
616
|
+
- Use `sh(command, options)` for one-off overrides
|
|
617
|
+
- Use `create(defaultOptions)` to create custom `$` with different defaults
|
|
618
|
+
|
|
619
|
+
### Shell Settings API
|
|
620
|
+
|
|
621
|
+
Control shell behavior like bash `set`/`unset` commands:
|
|
622
|
+
|
|
623
|
+
#### Functions
|
|
624
|
+
|
|
625
|
+
- `shell.errexit(boolean)`: Enable/disable exit-on-error (like `set Β±e`)
|
|
626
|
+
- `shell.verbose(boolean)`: Enable/disable command printing (like `set Β±v`)
|
|
627
|
+
- `shell.xtrace(boolean)`: Enable/disable execution tracing (like `set Β±x`)
|
|
628
|
+
- `set(option)`: Enable shell option (`'e'`, `'v'`, `'x'`, or long names)
|
|
629
|
+
- `unset(option)`: Disable shell option
|
|
630
|
+
- `shell.settings()`: Get current settings object
|
|
631
|
+
|
|
632
|
+
### Virtual Commands API
|
|
633
|
+
|
|
634
|
+
Control and extend the command system with custom JavaScript functions:
|
|
635
|
+
|
|
636
|
+
#### Functions
|
|
637
|
+
|
|
638
|
+
- `register(name, handler)`: Register a virtual command
|
|
639
|
+
- `name`: Command name (string)
|
|
640
|
+
- `handler`: Function or async generator `(args, stdin, options) => result`
|
|
641
|
+
- `unregister(name)`: Remove a virtual command
|
|
642
|
+
- `listCommands()`: Get array of all registered command names
|
|
643
|
+
- `enableVirtualCommands()`: Enable virtual command processing
|
|
644
|
+
- `disableVirtualCommands()`: Disable virtual commands (use system commands only)
|
|
645
|
+
|
|
646
|
+
#### Handler Function Signature
|
|
647
|
+
|
|
648
|
+
```javascript
|
|
649
|
+
// Regular async function
|
|
650
|
+
async function handler(args, stdin, options) {
|
|
651
|
+
return {
|
|
652
|
+
code: 0, // Exit code (number)
|
|
653
|
+
stdout: "output", // Standard output (string)
|
|
654
|
+
stderr: "", // Standard error (string)
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Async generator for streaming
|
|
659
|
+
async function* streamingHandler(args, stdin, options) {
|
|
660
|
+
yield "chunk1\n";
|
|
661
|
+
yield "chunk2\n";
|
|
662
|
+
// Each yield sends a chunk in real-time
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Built-in Commands
|
|
667
|
+
|
|
668
|
+
18 cross-platform commands that work identically everywhere:
|
|
669
|
+
|
|
670
|
+
**File System**: `cat`, `ls`, `mkdir`, `rm`, `mv`, `cp`, `touch`
|
|
671
|
+
**Utilities**: `basename`, `dirname`, `seq`, `yes`
|
|
672
|
+
**System**: `cd`, `pwd`, `echo`, `sleep`, `true`, `false`, `which`, `exit`, `env`, `test`
|
|
673
|
+
|
|
674
|
+
All built-in commands support:
|
|
675
|
+
- Standard flags (e.g., `ls -la`, `mkdir -p`, `rm -rf`)
|
|
676
|
+
- Pipeline operations
|
|
677
|
+
- Option awareness (`cwd`, `env`, etc.)
|
|
678
|
+
- Bash-compatible error messages and exit codes
|
|
679
|
+
|
|
680
|
+
#### Supported Options
|
|
681
|
+
|
|
682
|
+
- `'e'` / `'errexit'`: Exit on command failure
|
|
683
|
+
- `'v'` / `'verbose'`: Print commands before execution
|
|
684
|
+
- `'x'` / `'xtrace'`: Trace command execution with `+` prefix
|
|
685
|
+
- `'u'` / `'nounset'`: Error on undefined variables (planned)
|
|
686
|
+
- `'pipefail'`: Pipe failure detection (planned)
|
|
687
|
+
|
|
688
|
+
### Result Object
|
|
689
|
+
|
|
690
|
+
```javascript
|
|
691
|
+
{
|
|
692
|
+
code: number, // Exit code
|
|
693
|
+
stdout: string, // Complete stdout output
|
|
694
|
+
stderr: string, // Complete stderr output
|
|
695
|
+
stdin: string, // Input sent to process
|
|
696
|
+
child: ChildProcess // Original child process object
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## Testing
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
# Run comprehensive test suite (266 tests)
|
|
704
|
+
bun test
|
|
705
|
+
|
|
706
|
+
# Run tests with coverage report
|
|
707
|
+
bun test --coverage
|
|
708
|
+
|
|
709
|
+
# Run specific test categories
|
|
710
|
+
npm run test:features # Feature comparison tests
|
|
711
|
+
npm run test:builtin # Built-in commands tests
|
|
712
|
+
npm run test:pipe # .pipe() method tests
|
|
713
|
+
npm run test:sync # Synchronous execution tests
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
## Requirements
|
|
717
|
+
|
|
718
|
+
- **Bun**: >= 1.0.0 (primary runtime)
|
|
719
|
+
- **Node.js**: >= 20.0.0 (compatibility support)
|
|
720
|
+
|
|
721
|
+
## Roadmap
|
|
722
|
+
|
|
723
|
+
### π **Coming Soon**
|
|
724
|
+
- **TypeScript Support**: Full .d.ts definitions and type safety
|
|
725
|
+
- **Enhanced Shell Options**: `set -u` (nounset) and `set -o pipefail` support
|
|
726
|
+
- **More Built-in Commands**: Additional cross-platform utilities
|
|
727
|
+
|
|
728
|
+
### π‘ **Planned Features**
|
|
729
|
+
- **Performance Optimizations**: Further Bun runtime optimizations
|
|
730
|
+
- **Advanced Error Handling**: Enhanced error context and debugging
|
|
731
|
+
- **Plugin System**: Extensible architecture for custom integrations
|
|
732
|
+
|
|
733
|
+
## Contributing
|
|
734
|
+
|
|
735
|
+
We welcome contributions! Since command-stream is **public domain software**, your contributions will also be released into the public domain.
|
|
736
|
+
|
|
737
|
+
### π **Getting Started**
|
|
738
|
+
```bash
|
|
739
|
+
git clone https://github.com/link-foundation/command-stream.git
|
|
740
|
+
cd command-stream
|
|
741
|
+
bun install
|
|
742
|
+
bun test # Run the full test suite
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### π **Development Guidelines**
|
|
746
|
+
- All features must have comprehensive tests
|
|
747
|
+
- Built-in commands should match bash/sh behavior exactly
|
|
748
|
+
- Maintain cross-platform compatibility (Windows, macOS, Linux)
|
|
749
|
+
- Follow the existing code style and patterns
|
|
750
|
+
|
|
751
|
+
### π§ͺ **Running Tests**
|
|
752
|
+
```bash
|
|
753
|
+
bun test # All 266 tests
|
|
754
|
+
bun test tests/pipe.test.mjs # Specific test file
|
|
755
|
+
npm run test:builtin # Built-in commands only
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
## License - Our Biggest Advantage
|
|
759
|
+
|
|
760
|
+
**The Unlicense (Public Domain)**
|
|
761
|
+
|
|
762
|
+
Unlike other shell utilities that require attribution (MIT, Apache 2.0), command-stream is released into the **public domain**. This means:
|
|
763
|
+
|
|
764
|
+
- β
**No attribution required** - Use it without crediting anyone
|
|
765
|
+
- β
**No license files to include** - Simplify your distribution
|
|
766
|
+
- β
**No restrictions** - Modify, sell, embed, whatever you want
|
|
767
|
+
- β
**No legal concerns** - It's as free as code can be
|
|
768
|
+
- β
**Corporate friendly** - No license compliance overhead
|
|
769
|
+
|
|
770
|
+
This makes command-stream ideal for:
|
|
771
|
+
- **Commercial products** where license attribution is inconvenient
|
|
772
|
+
- **Embedded systems** where every byte counts
|
|
773
|
+
- **Educational materials** that can be freely shared
|
|
774
|
+
- **Internal tools** without legal review requirements
|
|
775
|
+
|
|
776
|
+
> "This is free and unencumbered software released into the public domain."
|