command-stream 0.0.3 → 0.0.5
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 +213 -37
- 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)
|
|
@@ -308,12 +311,12 @@ class ProcessRunner extends StreamEmitter {
|
|
|
308
311
|
|
|
309
312
|
const cmd = parts[0];
|
|
310
313
|
const args = parts.slice(1).map(arg => {
|
|
311
|
-
//
|
|
314
|
+
// Keep track of whether the arg was quoted
|
|
312
315
|
if ((arg.startsWith('"') && arg.endsWith('"')) ||
|
|
313
316
|
(arg.startsWith("'") && arg.endsWith("'"))) {
|
|
314
|
-
return arg.slice(1, -1);
|
|
317
|
+
return { value: arg.slice(1, -1), quoted: true, quoteChar: arg[0] };
|
|
315
318
|
}
|
|
316
|
-
return arg;
|
|
319
|
+
return { value: arg, quoted: false };
|
|
317
320
|
});
|
|
318
321
|
|
|
319
322
|
return { cmd, args, type: 'simple' };
|
|
@@ -356,11 +359,13 @@ class ProcessRunner extends StreamEmitter {
|
|
|
356
359
|
|
|
357
360
|
const cmd = parts[0];
|
|
358
361
|
const args = parts.slice(1).map(arg => {
|
|
362
|
+
// Keep track of whether the arg was quoted
|
|
359
363
|
if ((arg.startsWith('"') && arg.endsWith('"')) ||
|
|
360
364
|
(arg.startsWith("'") && arg.endsWith("'"))) {
|
|
361
|
-
|
|
365
|
+
// Store the original with quotes for system commands
|
|
366
|
+
return { value: arg.slice(1, -1), quoted: true, quoteChar: arg[0] };
|
|
362
367
|
}
|
|
363
|
-
return arg;
|
|
368
|
+
return { value: arg, quoted: false };
|
|
364
369
|
});
|
|
365
370
|
|
|
366
371
|
return { cmd, args };
|
|
@@ -384,12 +389,15 @@ class ProcessRunner extends StreamEmitter {
|
|
|
384
389
|
stdinData = this.options.stdin.toString('utf8');
|
|
385
390
|
}
|
|
386
391
|
|
|
392
|
+
// Extract actual values for virtual command
|
|
393
|
+
const argValues = args.map(arg => arg.value !== undefined ? arg.value : arg);
|
|
394
|
+
|
|
387
395
|
// Shell tracing for virtual commands
|
|
388
396
|
if (globalShellSettings.xtrace) {
|
|
389
|
-
console.log(`+ ${cmd} ${
|
|
397
|
+
console.log(`+ ${cmd} ${argValues.join(' ')}`);
|
|
390
398
|
}
|
|
391
399
|
if (globalShellSettings.verbose) {
|
|
392
|
-
console.log(`${cmd} ${
|
|
400
|
+
console.log(`${cmd} ${argValues.join(' ')}`);
|
|
393
401
|
}
|
|
394
402
|
|
|
395
403
|
// Execute the virtual command
|
|
@@ -399,7 +407,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
399
407
|
if (handler.constructor.name === 'AsyncGeneratorFunction') {
|
|
400
408
|
// Handle streaming virtual command
|
|
401
409
|
const chunks = [];
|
|
402
|
-
for await (const chunk of handler(
|
|
410
|
+
for await (const chunk of handler(argValues, stdinData, this.options)) {
|
|
403
411
|
const buf = Buffer.from(chunk);
|
|
404
412
|
chunks.push(buf);
|
|
405
413
|
|
|
@@ -419,7 +427,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
419
427
|
};
|
|
420
428
|
} else {
|
|
421
429
|
// Regular async function
|
|
422
|
-
result = await handler(
|
|
430
|
+
result = await handler(argValues, stdinData, this.options);
|
|
423
431
|
|
|
424
432
|
// Ensure result has required fields, respecting capture option
|
|
425
433
|
result = {
|
|
@@ -521,18 +529,21 @@ class ProcessRunner extends StreamEmitter {
|
|
|
521
529
|
const command = commands[i];
|
|
522
530
|
const { cmd, args } = command;
|
|
523
531
|
|
|
524
|
-
// Check if this is a virtual command
|
|
532
|
+
// Check if this is a virtual command (only if virtual commands are enabled)
|
|
525
533
|
if (virtualCommandsEnabled && virtualCommands.has(cmd)) {
|
|
526
534
|
// Run virtual command with current input
|
|
527
535
|
const handler = virtualCommands.get(cmd);
|
|
528
536
|
|
|
529
537
|
try {
|
|
538
|
+
// Extract actual values for virtual command
|
|
539
|
+
const argValues = args.map(arg => arg.value !== undefined ? arg.value : arg);
|
|
540
|
+
|
|
530
541
|
// Shell tracing for virtual commands
|
|
531
542
|
if (globalShellSettings.xtrace) {
|
|
532
|
-
console.log(`+ ${cmd} ${
|
|
543
|
+
console.log(`+ ${cmd} ${argValues.join(' ')}`);
|
|
533
544
|
}
|
|
534
545
|
if (globalShellSettings.verbose) {
|
|
535
|
-
console.log(`${cmd} ${
|
|
546
|
+
console.log(`${cmd} ${argValues.join(' ')}`);
|
|
536
547
|
}
|
|
537
548
|
|
|
538
549
|
let result;
|
|
@@ -540,7 +551,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
540
551
|
// Check if handler is async generator (streaming)
|
|
541
552
|
if (handler.constructor.name === 'AsyncGeneratorFunction') {
|
|
542
553
|
const chunks = [];
|
|
543
|
-
for await (const chunk of handler(
|
|
554
|
+
for await (const chunk of handler(argValues, currentInput, this.options)) {
|
|
544
555
|
chunks.push(Buffer.from(chunk));
|
|
545
556
|
}
|
|
546
557
|
result = {
|
|
@@ -551,7 +562,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
551
562
|
};
|
|
552
563
|
} else {
|
|
553
564
|
// Regular async function
|
|
554
|
-
result = await handler(
|
|
565
|
+
result = await handler(argValues, currentInput, this.options);
|
|
555
566
|
result = {
|
|
556
567
|
code: result.code ?? 0,
|
|
557
568
|
stdout: this.options.capture ? (result.stdout ?? '') : undefined,
|
|
@@ -657,30 +668,194 @@ class ProcessRunner extends StreamEmitter {
|
|
|
657
668
|
return result;
|
|
658
669
|
}
|
|
659
670
|
} else {
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
671
|
+
// Execute system command in pipeline
|
|
672
|
+
try {
|
|
673
|
+
// Build command string for this part of the pipeline
|
|
674
|
+
const commandParts = [cmd];
|
|
675
|
+
for (const arg of args) {
|
|
676
|
+
if (arg.value !== undefined) {
|
|
677
|
+
// Handle our parsed arg structure
|
|
678
|
+
if (arg.quoted) {
|
|
679
|
+
// Preserve original quotes
|
|
680
|
+
commandParts.push(`${arg.quoteChar}${arg.value}${arg.quoteChar}`);
|
|
681
|
+
} else if (arg.value.includes(' ')) {
|
|
682
|
+
// Quote if contains spaces
|
|
683
|
+
commandParts.push(`"${arg.value}"`);
|
|
684
|
+
} else {
|
|
685
|
+
commandParts.push(arg.value);
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
// Handle plain string args (backward compatibility)
|
|
689
|
+
if (typeof arg === 'string' && arg.includes(' ') && !arg.startsWith('"') && !arg.startsWith("'")) {
|
|
690
|
+
commandParts.push(`"${arg}"`);
|
|
691
|
+
} else {
|
|
692
|
+
commandParts.push(arg);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const commandStr = commandParts.join(' ');
|
|
697
|
+
|
|
698
|
+
// Shell tracing for system commands
|
|
699
|
+
if (globalShellSettings.xtrace) {
|
|
700
|
+
console.log(`+ ${commandStr}`);
|
|
701
|
+
}
|
|
702
|
+
if (globalShellSettings.verbose) {
|
|
703
|
+
console.log(commandStr);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Execute the system command with current input as stdin
|
|
707
|
+
const spawnBun = (argv, stdin) => {
|
|
708
|
+
return Bun.spawnSync(argv, {
|
|
709
|
+
cwd: this.options.cwd,
|
|
710
|
+
env: this.options.env,
|
|
711
|
+
stdin: stdin ? Buffer.from(stdin) : undefined,
|
|
712
|
+
stdout: 'pipe',
|
|
713
|
+
stderr: 'pipe'
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const spawnNode = (argv, stdin) => {
|
|
718
|
+
const require = createRequire(import.meta.url);
|
|
719
|
+
const cp = require('child_process');
|
|
720
|
+
return cp.spawnSync(argv[0], argv.slice(1), {
|
|
721
|
+
cwd: this.options.cwd,
|
|
722
|
+
env: this.options.env,
|
|
723
|
+
input: stdin || undefined,
|
|
724
|
+
encoding: 'utf8',
|
|
725
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
726
|
+
});
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// Execute using shell to handle complex commands
|
|
730
|
+
const argv = ['sh', '-c', commandStr];
|
|
731
|
+
const proc = isBun ? spawnBun(argv, currentInput) : spawnNode(argv, currentInput);
|
|
732
|
+
|
|
733
|
+
let result;
|
|
734
|
+
if (isBun) {
|
|
735
|
+
result = {
|
|
736
|
+
code: proc.exitCode || 0,
|
|
737
|
+
stdout: proc.stdout?.toString('utf8') || '',
|
|
738
|
+
stderr: proc.stderr?.toString('utf8') || '',
|
|
739
|
+
stdin: currentInput
|
|
740
|
+
};
|
|
741
|
+
} else {
|
|
742
|
+
result = {
|
|
743
|
+
code: proc.status || 0,
|
|
744
|
+
stdout: proc.stdout || '',
|
|
745
|
+
stderr: proc.stderr || '',
|
|
746
|
+
stdin: currentInput
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// If command failed and pipefail is set, fail the entire pipeline
|
|
751
|
+
if (globalShellSettings.pipefail && result.code !== 0) {
|
|
752
|
+
const error = new Error(`Pipeline command '${commandStr}' failed with exit code ${result.code}`);
|
|
753
|
+
error.code = result.code;
|
|
754
|
+
error.stdout = result.stdout;
|
|
755
|
+
error.stderr = result.stderr;
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// If this isn't the last command, pass stdout as stdin to next command
|
|
760
|
+
if (i < commands.length - 1) {
|
|
761
|
+
currentInput = result.stdout;
|
|
762
|
+
// Accumulate stderr from all commands
|
|
763
|
+
if (result.stderr && this.options.capture) {
|
|
764
|
+
this.errChunks = this.errChunks || [];
|
|
765
|
+
this.errChunks.push(Buffer.from(result.stderr));
|
|
766
|
+
}
|
|
767
|
+
} else {
|
|
768
|
+
// This is the last command - emit output and store final result
|
|
769
|
+
currentOutput = result.stdout;
|
|
770
|
+
|
|
771
|
+
// Collect all accumulated stderr
|
|
772
|
+
let allStderr = '';
|
|
773
|
+
if (this.errChunks && this.errChunks.length > 0) {
|
|
774
|
+
allStderr = Buffer.concat(this.errChunks).toString('utf8');
|
|
775
|
+
}
|
|
776
|
+
if (result.stderr) {
|
|
777
|
+
allStderr += result.stderr;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Mirror and emit output for final command
|
|
781
|
+
if (result.stdout) {
|
|
782
|
+
const buf = Buffer.from(result.stdout);
|
|
783
|
+
if (this.options.mirror) {
|
|
784
|
+
process.stdout.write(buf);
|
|
785
|
+
}
|
|
786
|
+
this.emit('stdout', buf);
|
|
787
|
+
this.emit('data', { type: 'stdout', data: buf });
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (allStderr) {
|
|
791
|
+
const buf = Buffer.from(allStderr);
|
|
792
|
+
if (this.options.mirror) {
|
|
793
|
+
process.stderr.write(buf);
|
|
794
|
+
}
|
|
795
|
+
this.emit('stderr', buf);
|
|
796
|
+
this.emit('data', { type: 'stderr', data: buf });
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Store final result using createResult helper for .text() method compatibility
|
|
800
|
+
const finalResult = createResult({
|
|
801
|
+
code: result.code,
|
|
802
|
+
stdout: currentOutput,
|
|
803
|
+
stderr: allStderr,
|
|
804
|
+
stdin: this.options.stdin && typeof this.options.stdin === 'string' ? this.options.stdin :
|
|
805
|
+
this.options.stdin && Buffer.isBuffer(this.options.stdin) ? this.options.stdin.toString('utf8') : ''
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
this.result = finalResult;
|
|
809
|
+
this.finished = true;
|
|
810
|
+
|
|
811
|
+
// Emit completion events
|
|
812
|
+
this.emit('end', finalResult);
|
|
813
|
+
this.emit('exit', finalResult.code);
|
|
814
|
+
|
|
815
|
+
// Handle shell settings
|
|
816
|
+
if (globalShellSettings.errexit && finalResult.code !== 0) {
|
|
817
|
+
const error = new Error(`Pipeline failed with exit code ${finalResult.code}`);
|
|
818
|
+
error.code = finalResult.code;
|
|
819
|
+
error.stdout = finalResult.stdout;
|
|
820
|
+
error.stderr = finalResult.stderr;
|
|
821
|
+
error.result = finalResult;
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return finalResult;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
} catch (error) {
|
|
829
|
+
// Handle errors from system commands in pipeline
|
|
830
|
+
const result = createResult({
|
|
831
|
+
code: error.code ?? 1,
|
|
832
|
+
stdout: currentOutput,
|
|
833
|
+
stderr: error.stderr ?? error.message,
|
|
834
|
+
stdin: this.options.stdin && typeof this.options.stdin === 'string' ? this.options.stdin :
|
|
835
|
+
this.options.stdin && Buffer.isBuffer(this.options.stdin) ? this.options.stdin.toString('utf8') : ''
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
this.result = result;
|
|
839
|
+
this.finished = true;
|
|
840
|
+
|
|
841
|
+
if (result.stderr) {
|
|
842
|
+
const buf = Buffer.from(result.stderr);
|
|
843
|
+
if (this.options.mirror) {
|
|
844
|
+
process.stderr.write(buf);
|
|
845
|
+
}
|
|
846
|
+
this.emit('stderr', buf);
|
|
847
|
+
this.emit('data', { type: 'stderr', data: buf });
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
this.emit('end', result);
|
|
851
|
+
this.emit('exit', result.code);
|
|
852
|
+
|
|
853
|
+
if (globalShellSettings.errexit) {
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return result;
|
|
676
858
|
}
|
|
677
|
-
this.emit('stderr', buf);
|
|
678
|
-
this.emit('data', { type: 'stderr', data: buf });
|
|
679
|
-
|
|
680
|
-
this.emit('end', result);
|
|
681
|
-
this.emit('exit', result.code);
|
|
682
|
-
|
|
683
|
-
return result;
|
|
684
859
|
}
|
|
685
860
|
}
|
|
686
861
|
}
|
|
@@ -878,6 +1053,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
878
1053
|
result.child = proc;
|
|
879
1054
|
} else {
|
|
880
1055
|
// Use Node's synchronous spawn
|
|
1056
|
+
const require = createRequire(import.meta.url);
|
|
881
1057
|
const cp = require('child_process');
|
|
882
1058
|
const proc = cp.spawnSync(argv[0], argv.slice(1), {
|
|
883
1059
|
cwd,
|
package/package.json
CHANGED