command-stream 0.0.4 → 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 +209 -37
- package/package.json +1 -1
package/$.mjs
CHANGED
|
@@ -311,12 +311,12 @@ class ProcessRunner extends StreamEmitter {
|
|
|
311
311
|
|
|
312
312
|
const cmd = parts[0];
|
|
313
313
|
const args = parts.slice(1).map(arg => {
|
|
314
|
-
//
|
|
314
|
+
// Keep track of whether the arg was quoted
|
|
315
315
|
if ((arg.startsWith('"') && arg.endsWith('"')) ||
|
|
316
316
|
(arg.startsWith("'") && arg.endsWith("'"))) {
|
|
317
|
-
return arg.slice(1, -1);
|
|
317
|
+
return { value: arg.slice(1, -1), quoted: true, quoteChar: arg[0] };
|
|
318
318
|
}
|
|
319
|
-
return arg;
|
|
319
|
+
return { value: arg, quoted: false };
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
return { cmd, args, type: 'simple' };
|
|
@@ -359,11 +359,13 @@ class ProcessRunner extends StreamEmitter {
|
|
|
359
359
|
|
|
360
360
|
const cmd = parts[0];
|
|
361
361
|
const args = parts.slice(1).map(arg => {
|
|
362
|
+
// Keep track of whether the arg was quoted
|
|
362
363
|
if ((arg.startsWith('"') && arg.endsWith('"')) ||
|
|
363
364
|
(arg.startsWith("'") && arg.endsWith("'"))) {
|
|
364
|
-
|
|
365
|
+
// Store the original with quotes for system commands
|
|
366
|
+
return { value: arg.slice(1, -1), quoted: true, quoteChar: arg[0] };
|
|
365
367
|
}
|
|
366
|
-
return arg;
|
|
368
|
+
return { value: arg, quoted: false };
|
|
367
369
|
});
|
|
368
370
|
|
|
369
371
|
return { cmd, args };
|
|
@@ -387,12 +389,15 @@ class ProcessRunner extends StreamEmitter {
|
|
|
387
389
|
stdinData = this.options.stdin.toString('utf8');
|
|
388
390
|
}
|
|
389
391
|
|
|
392
|
+
// Extract actual values for virtual command
|
|
393
|
+
const argValues = args.map(arg => arg.value !== undefined ? arg.value : arg);
|
|
394
|
+
|
|
390
395
|
// Shell tracing for virtual commands
|
|
391
396
|
if (globalShellSettings.xtrace) {
|
|
392
|
-
console.log(`+ ${cmd} ${
|
|
397
|
+
console.log(`+ ${cmd} ${argValues.join(' ')}`);
|
|
393
398
|
}
|
|
394
399
|
if (globalShellSettings.verbose) {
|
|
395
|
-
console.log(`${cmd} ${
|
|
400
|
+
console.log(`${cmd} ${argValues.join(' ')}`);
|
|
396
401
|
}
|
|
397
402
|
|
|
398
403
|
// Execute the virtual command
|
|
@@ -402,7 +407,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
402
407
|
if (handler.constructor.name === 'AsyncGeneratorFunction') {
|
|
403
408
|
// Handle streaming virtual command
|
|
404
409
|
const chunks = [];
|
|
405
|
-
for await (const chunk of handler(
|
|
410
|
+
for await (const chunk of handler(argValues, stdinData, this.options)) {
|
|
406
411
|
const buf = Buffer.from(chunk);
|
|
407
412
|
chunks.push(buf);
|
|
408
413
|
|
|
@@ -422,7 +427,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
422
427
|
};
|
|
423
428
|
} else {
|
|
424
429
|
// Regular async function
|
|
425
|
-
result = await handler(
|
|
430
|
+
result = await handler(argValues, stdinData, this.options);
|
|
426
431
|
|
|
427
432
|
// Ensure result has required fields, respecting capture option
|
|
428
433
|
result = {
|
|
@@ -524,18 +529,21 @@ class ProcessRunner extends StreamEmitter {
|
|
|
524
529
|
const command = commands[i];
|
|
525
530
|
const { cmd, args } = command;
|
|
526
531
|
|
|
527
|
-
// Check if this is a virtual command
|
|
532
|
+
// Check if this is a virtual command (only if virtual commands are enabled)
|
|
528
533
|
if (virtualCommandsEnabled && virtualCommands.has(cmd)) {
|
|
529
534
|
// Run virtual command with current input
|
|
530
535
|
const handler = virtualCommands.get(cmd);
|
|
531
536
|
|
|
532
537
|
try {
|
|
538
|
+
// Extract actual values for virtual command
|
|
539
|
+
const argValues = args.map(arg => arg.value !== undefined ? arg.value : arg);
|
|
540
|
+
|
|
533
541
|
// Shell tracing for virtual commands
|
|
534
542
|
if (globalShellSettings.xtrace) {
|
|
535
|
-
console.log(`+ ${cmd} ${
|
|
543
|
+
console.log(`+ ${cmd} ${argValues.join(' ')}`);
|
|
536
544
|
}
|
|
537
545
|
if (globalShellSettings.verbose) {
|
|
538
|
-
console.log(`${cmd} ${
|
|
546
|
+
console.log(`${cmd} ${argValues.join(' ')}`);
|
|
539
547
|
}
|
|
540
548
|
|
|
541
549
|
let result;
|
|
@@ -543,7 +551,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
543
551
|
// Check if handler is async generator (streaming)
|
|
544
552
|
if (handler.constructor.name === 'AsyncGeneratorFunction') {
|
|
545
553
|
const chunks = [];
|
|
546
|
-
for await (const chunk of handler(
|
|
554
|
+
for await (const chunk of handler(argValues, currentInput, this.options)) {
|
|
547
555
|
chunks.push(Buffer.from(chunk));
|
|
548
556
|
}
|
|
549
557
|
result = {
|
|
@@ -554,7 +562,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
554
562
|
};
|
|
555
563
|
} else {
|
|
556
564
|
// Regular async function
|
|
557
|
-
result = await handler(
|
|
565
|
+
result = await handler(argValues, currentInput, this.options);
|
|
558
566
|
result = {
|
|
559
567
|
code: result.code ?? 0,
|
|
560
568
|
stdout: this.options.capture ? (result.stdout ?? '') : undefined,
|
|
@@ -660,30 +668,194 @@ class ProcessRunner extends StreamEmitter {
|
|
|
660
668
|
return result;
|
|
661
669
|
}
|
|
662
670
|
} else {
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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;
|
|
679
858
|
}
|
|
680
|
-
this.emit('stderr', buf);
|
|
681
|
-
this.emit('data', { type: 'stderr', data: buf });
|
|
682
|
-
|
|
683
|
-
this.emit('end', result);
|
|
684
|
-
this.emit('exit', result.code);
|
|
685
|
-
|
|
686
|
-
return result;
|
|
687
859
|
}
|
|
688
860
|
}
|
|
689
861
|
}
|
package/package.json
CHANGED