command-stream 0.0.2 → 0.0.4
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 +47 -21
- package/README.md +30 -2
- package/package.json +1 -1
package/$.mjs
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
// 3. EventEmitter: $`command`.on('data', chunk => ...).on('end', result => ...)
|
|
6
6
|
// 4. Stream access: $`command`.stdout, $`command`.stderr
|
|
7
7
|
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
8
11
|
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
9
12
|
|
|
10
13
|
// Global shell settings (like bash set -e / set +e)
|
|
@@ -16,6 +19,20 @@ let globalShellSettings = {
|
|
|
16
19
|
nounset: false // set -u equivalent: error on undefined variables
|
|
17
20
|
};
|
|
18
21
|
|
|
22
|
+
// Helper function to create result objects with Bun.$ compatibility
|
|
23
|
+
function createResult({ code, stdout = '', stderr = '', stdin = '' }) {
|
|
24
|
+
return {
|
|
25
|
+
code,
|
|
26
|
+
stdout,
|
|
27
|
+
stderr,
|
|
28
|
+
stdin,
|
|
29
|
+
// Bun.$ compatibility method
|
|
30
|
+
async text() {
|
|
31
|
+
return stdout;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
// Virtual command registry - unified system for all commands
|
|
20
37
|
const virtualCommands = new Map();
|
|
21
38
|
|
|
@@ -219,13 +236,21 @@ class ProcessRunner extends StreamEmitter {
|
|
|
219
236
|
const code = await exited;
|
|
220
237
|
await Promise.all([outPump, errPump, stdinPumpPromise]);
|
|
221
238
|
|
|
222
|
-
|
|
239
|
+
const resultData = {
|
|
223
240
|
code,
|
|
224
241
|
stdout: this.options.capture ? Buffer.concat(this.outChunks).toString('utf8') : undefined,
|
|
225
242
|
stderr: this.options.capture ? Buffer.concat(this.errChunks).toString('utf8') : undefined,
|
|
226
243
|
stdin: this.options.capture && this.inChunks ? Buffer.concat(this.inChunks).toString('utf8') : undefined,
|
|
227
244
|
child: this.child
|
|
228
245
|
};
|
|
246
|
+
|
|
247
|
+
this.result = {
|
|
248
|
+
...resultData,
|
|
249
|
+
// Bun.$ compatibility method
|
|
250
|
+
async text() {
|
|
251
|
+
return resultData.stdout || '';
|
|
252
|
+
}
|
|
253
|
+
};
|
|
229
254
|
|
|
230
255
|
this.finished = true;
|
|
231
256
|
this.emit('end', this.result);
|
|
@@ -481,7 +506,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
481
506
|
|
|
482
507
|
async _runPipeline(commands) {
|
|
483
508
|
if (commands.length === 0) {
|
|
484
|
-
return { code: 1, stdout: '', stderr: 'No commands in pipeline', stdin: '' };
|
|
509
|
+
return createResult({ code: 1, stdout: '', stderr: 'No commands in pipeline', stdin: '' });
|
|
485
510
|
}
|
|
486
511
|
|
|
487
512
|
let currentOutput = '';
|
|
@@ -565,14 +590,14 @@ class ProcessRunner extends StreamEmitter {
|
|
|
565
590
|
this.emit('data', { type: 'stderr', data: buf });
|
|
566
591
|
}
|
|
567
592
|
|
|
568
|
-
// Store final result
|
|
569
|
-
const finalResult = {
|
|
593
|
+
// Store final result using createResult helper for .text() method compatibility
|
|
594
|
+
const finalResult = createResult({
|
|
570
595
|
code: result.code,
|
|
571
596
|
stdout: currentOutput,
|
|
572
597
|
stderr: result.stderr,
|
|
573
598
|
stdin: this.options.stdin && typeof this.options.stdin === 'string' ? this.options.stdin :
|
|
574
599
|
this.options.stdin && Buffer.isBuffer(this.options.stdin) ? this.options.stdin.toString('utf8') : ''
|
|
575
|
-
};
|
|
600
|
+
});
|
|
576
601
|
|
|
577
602
|
this.result = finalResult;
|
|
578
603
|
this.finished = true;
|
|
@@ -605,13 +630,13 @@ class ProcessRunner extends StreamEmitter {
|
|
|
605
630
|
}
|
|
606
631
|
} catch (error) {
|
|
607
632
|
// Handle errors from virtual commands in pipeline
|
|
608
|
-
const result = {
|
|
633
|
+
const result = createResult({
|
|
609
634
|
code: error.code ?? 1,
|
|
610
635
|
stdout: currentOutput,
|
|
611
636
|
stderr: error.stderr ?? error.message,
|
|
612
637
|
stdin: this.options.stdin && typeof this.options.stdin === 'string' ? this.options.stdin :
|
|
613
638
|
this.options.stdin && Buffer.isBuffer(this.options.stdin) ? this.options.stdin.toString('utf8') : ''
|
|
614
|
-
};
|
|
639
|
+
});
|
|
615
640
|
|
|
616
641
|
this.result = result;
|
|
617
642
|
this.finished = true;
|
|
@@ -637,13 +662,13 @@ class ProcessRunner extends StreamEmitter {
|
|
|
637
662
|
} else {
|
|
638
663
|
// For system commands in pipeline, we would need to spawn processes
|
|
639
664
|
// For now, return an error indicating this isn't supported
|
|
640
|
-
const result = {
|
|
665
|
+
const result = createResult({
|
|
641
666
|
code: 1,
|
|
642
667
|
stdout: currentOutput,
|
|
643
668
|
stderr: `Pipeline with system command '${cmd}' not yet supported`,
|
|
644
669
|
stdin: this.options.stdin && typeof this.options.stdin === 'string' ? this.options.stdin :
|
|
645
670
|
this.options.stdin && Buffer.isBuffer(this.options.stdin) ? this.options.stdin.toString('utf8') : ''
|
|
646
|
-
};
|
|
671
|
+
});
|
|
647
672
|
|
|
648
673
|
this.result = result;
|
|
649
674
|
this.finished = true;
|
|
@@ -684,21 +709,21 @@ class ProcessRunner extends StreamEmitter {
|
|
|
684
709
|
const destResult = await destination;
|
|
685
710
|
|
|
686
711
|
// Return the final result with combined information
|
|
687
|
-
return {
|
|
712
|
+
return createResult({
|
|
688
713
|
code: destResult.code,
|
|
689
714
|
stdout: destResult.stdout,
|
|
690
715
|
stderr: sourceResult.stderr + destResult.stderr,
|
|
691
716
|
stdin: sourceResult.stdin
|
|
692
|
-
};
|
|
717
|
+
});
|
|
693
718
|
|
|
694
719
|
} catch (error) {
|
|
695
|
-
const result = {
|
|
720
|
+
const result = createResult({
|
|
696
721
|
code: error.code ?? 1,
|
|
697
722
|
stdout: '',
|
|
698
723
|
stderr: error.message || 'Pipeline execution failed',
|
|
699
724
|
stdin: this.options.stdin && typeof this.options.stdin === 'string' ? this.options.stdin :
|
|
700
725
|
this.options.stdin && Buffer.isBuffer(this.options.stdin) ? this.options.stdin.toString('utf8') : ''
|
|
701
|
-
};
|
|
726
|
+
});
|
|
702
727
|
|
|
703
728
|
this.result = result;
|
|
704
729
|
this.finished = true;
|
|
@@ -846,16 +871,17 @@ class ProcessRunner extends StreamEmitter {
|
|
|
846
871
|
stderr: 'pipe'
|
|
847
872
|
});
|
|
848
873
|
|
|
849
|
-
result = {
|
|
874
|
+
result = createResult({
|
|
850
875
|
code: proc.exitCode || 0,
|
|
851
876
|
stdout: proc.stdout?.toString('utf8') || '',
|
|
852
877
|
stderr: proc.stderr?.toString('utf8') || '',
|
|
853
878
|
stdin: typeof stdin === 'string' ? stdin :
|
|
854
|
-
Buffer.isBuffer(stdin) ? stdin.toString('utf8') : ''
|
|
855
|
-
|
|
856
|
-
|
|
879
|
+
Buffer.isBuffer(stdin) ? stdin.toString('utf8') : ''
|
|
880
|
+
});
|
|
881
|
+
result.child = proc;
|
|
857
882
|
} else {
|
|
858
883
|
// Use Node's synchronous spawn
|
|
884
|
+
const require = createRequire(import.meta.url);
|
|
859
885
|
const cp = require('child_process');
|
|
860
886
|
const proc = cp.spawnSync(argv[0], argv.slice(1), {
|
|
861
887
|
cwd,
|
|
@@ -866,14 +892,14 @@ class ProcessRunner extends StreamEmitter {
|
|
|
866
892
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
867
893
|
});
|
|
868
894
|
|
|
869
|
-
result = {
|
|
895
|
+
result = createResult({
|
|
870
896
|
code: proc.status || 0,
|
|
871
897
|
stdout: proc.stdout || '',
|
|
872
898
|
stderr: proc.stderr || '',
|
|
873
899
|
stdin: typeof stdin === 'string' ? stdin :
|
|
874
|
-
Buffer.isBuffer(stdin) ? stdin.toString('utf8') : ''
|
|
875
|
-
|
|
876
|
-
|
|
900
|
+
Buffer.isBuffer(stdin) ? stdin.toString('utf8') : ''
|
|
901
|
+
});
|
|
902
|
+
result.child = proc;
|
|
877
903
|
}
|
|
878
904
|
|
|
879
905
|
// Mirror output if requested (but always capture for result)
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
14
14
|
- 📡 **Real-time Streaming**: Process command output as it arrives, not after completion
|
|
15
15
|
- 🔄 **Bun Optimized**: Designed for Bun runtime with Node.js compatibility
|
|
16
16
|
- ⚡ **Performance**: Memory-efficient streaming prevents large buffer accumulation
|
|
17
|
-
- 🎯 **Backward Compatible**: Existing `await $` syntax continues to work
|
|
17
|
+
- 🎯 **Backward Compatible**: Existing `await $` syntax continues to work + Bun.$ `.text()` method
|
|
18
18
|
- 🛡️ **Type Safe**: Full TypeScript support (coming soon)
|
|
19
19
|
- 🔧 **Built-in Commands**: 18 essential commands work identically across platforms
|
|
20
20
|
|
|
@@ -29,6 +29,7 @@ A modern $ shell utility library with streaming, async iteration, and EventEmitt
|
|
|
29
29
|
| **Async Iteration** | ✅ `for await (chunk of $.stream())` | ❌ No | ❌ No | ❌ No |
|
|
30
30
|
| **EventEmitter Pattern** | ✅ `.on('data', ...)` | ❌ No | 🟡 Limited events | ❌ No |
|
|
31
31
|
| **Mixed Patterns** | ✅ Events + await/sync | ❌ No | ❌ No | ❌ No |
|
|
32
|
+
| **Bun.$ Compatibility** | ✅ `.text()` method support | ✅ Native API | ❌ No | ❌ No |
|
|
32
33
|
| **Shell Injection Protection** | ✅ Auto-quoting | ✅ Built-in | ✅ Safe by default | ✅ Safe by default |
|
|
33
34
|
| **Cross-platform** | ✅ macOS/Linux/Windows | ✅ Yes | ✅ Yes | ✅ Yes |
|
|
34
35
|
| **Performance** | ⚡ Fast (Bun optimized) | ⚡ Very fast | 🐌 Moderate | 🐌 Slow |
|
|
@@ -695,10 +696,37 @@ All built-in commands support:
|
|
|
695
696
|
stdout: string, // Complete stdout output
|
|
696
697
|
stderr: string, // Complete stderr output
|
|
697
698
|
stdin: string, // Input sent to process
|
|
698
|
-
child: ChildProcess
|
|
699
|
+
child: ChildProcess, // Original child process object
|
|
700
|
+
async text() // Bun.$ compatibility method - returns stdout as string
|
|
699
701
|
}
|
|
700
702
|
```
|
|
701
703
|
|
|
704
|
+
#### `.text()` Method (Bun.$ Compatibility)
|
|
705
|
+
|
|
706
|
+
For compatibility with Bun.$, all result objects include an async `.text()` method:
|
|
707
|
+
|
|
708
|
+
```javascript
|
|
709
|
+
import { $ } from 'command-stream';
|
|
710
|
+
|
|
711
|
+
// Both sync and async execution support .text()
|
|
712
|
+
const result1 = await $`echo "hello world"`;
|
|
713
|
+
const text1 = await result1.text(); // "hello world\n"
|
|
714
|
+
|
|
715
|
+
const result2 = $`echo "sync example"`.sync();
|
|
716
|
+
const text2 = await result2.text(); // "sync example\n"
|
|
717
|
+
|
|
718
|
+
// .text() is equivalent to accessing .stdout
|
|
719
|
+
expect(await result.text()).toBe(result.stdout);
|
|
720
|
+
|
|
721
|
+
// Works with built-in commands
|
|
722
|
+
const result3 = await $`seq 1 3`;
|
|
723
|
+
const text3 = await result3.text(); // "1\n2\n3\n"
|
|
724
|
+
|
|
725
|
+
// Works with .pipe() method
|
|
726
|
+
const result4 = await $`echo "pipe test"`.pipe($`cat`);
|
|
727
|
+
const text4 = await result4.text(); // "pipe test\n"
|
|
728
|
+
```
|
|
729
|
+
|
|
702
730
|
## Testing
|
|
703
731
|
|
|
704
732
|
```bash
|
package/package.json
CHANGED