command-stream 0.1.0 → 0.2.0
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 +352 -86
- package/package.json +1 -1
package/$.mjs
CHANGED
|
@@ -36,6 +36,75 @@ function traceFunc(category, funcName, phase, data = {}) {
|
|
|
36
36
|
trace(category, `${funcName} ${phase}`, data);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Track parent stream state for graceful shutdown
|
|
40
|
+
let parentStreamsMonitored = false;
|
|
41
|
+
const activeProcessRunners = new Set();
|
|
42
|
+
|
|
43
|
+
function monitorParentStreams() {
|
|
44
|
+
if (parentStreamsMonitored) return;
|
|
45
|
+
parentStreamsMonitored = true;
|
|
46
|
+
|
|
47
|
+
// Monitor parent stdout/stderr for closure
|
|
48
|
+
const checkParentStream = (stream, name) => {
|
|
49
|
+
if (stream && typeof stream.on === 'function') {
|
|
50
|
+
stream.on('close', () => {
|
|
51
|
+
trace('ProcessRunner', `Parent ${name} closed - triggering graceful shutdown`, {
|
|
52
|
+
activeProcesses: activeProcessRunners.size
|
|
53
|
+
});
|
|
54
|
+
// Signal all active ProcessRunners to gracefully shutdown
|
|
55
|
+
for (const runner of activeProcessRunners) {
|
|
56
|
+
runner._handleParentStreamClosure();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
checkParentStream(process.stdout, 'stdout');
|
|
63
|
+
checkParentStream(process.stderr, 'stderr');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Safe write function that checks stream state and handles parent closure
|
|
67
|
+
function safeWrite(stream, data, processRunner = null) {
|
|
68
|
+
// Ensure parent stream monitoring is active
|
|
69
|
+
monitorParentStreams();
|
|
70
|
+
|
|
71
|
+
// Check if stream is writable and not destroyed/closed
|
|
72
|
+
if (!stream || !stream.writable || stream.destroyed || stream.closed) {
|
|
73
|
+
trace('ProcessRunner', 'safeWrite skipped - stream not writable', {
|
|
74
|
+
hasStream: !!stream,
|
|
75
|
+
writable: stream?.writable,
|
|
76
|
+
destroyed: stream?.destroyed,
|
|
77
|
+
closed: stream?.closed
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// If this is a parent stream closure, signal graceful shutdown
|
|
81
|
+
if (processRunner && (stream === process.stdout || stream === process.stderr)) {
|
|
82
|
+
processRunner._handleParentStreamClosure();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
return stream.write(data);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
trace('ProcessRunner', 'safeWrite error', {
|
|
92
|
+
error: error.message,
|
|
93
|
+
code: error.code,
|
|
94
|
+
writable: stream.writable,
|
|
95
|
+
destroyed: stream.destroyed
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// If this is an EPIPE on parent streams, signal graceful shutdown
|
|
99
|
+
if (error.code === 'EPIPE' && processRunner &&
|
|
100
|
+
(stream === process.stdout || stream === process.stderr)) {
|
|
101
|
+
processRunner._handleParentStreamClosure();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
39
108
|
// Global shell settings (like bash set -e / set +e)
|
|
40
109
|
let globalShellSettings = {
|
|
41
110
|
errexit: false, // set -e equivalent: exit on error
|
|
@@ -192,6 +261,79 @@ class ProcessRunner extends StreamEmitter {
|
|
|
192
261
|
this._cancelled = false;
|
|
193
262
|
this._virtualGenerator = null;
|
|
194
263
|
this._abortController = new AbortController();
|
|
264
|
+
|
|
265
|
+
// Register this ProcessRunner for parent stream monitoring
|
|
266
|
+
activeProcessRunners.add(this);
|
|
267
|
+
|
|
268
|
+
// Track finished state changes to trigger cleanup
|
|
269
|
+
this._finished = false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Override finished property to trigger cleanup when set to true
|
|
273
|
+
get finished() {
|
|
274
|
+
return this._finished;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
set finished(value) {
|
|
278
|
+
if (value === true && this._finished === false) {
|
|
279
|
+
this._finished = true;
|
|
280
|
+
this._cleanup(); // Trigger cleanup when process finishes
|
|
281
|
+
} else {
|
|
282
|
+
this._finished = value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Handle parent stream closure by gracefully shutting down child processes
|
|
287
|
+
_handleParentStreamClosure() {
|
|
288
|
+
if (this.finished || this._cancelled) return;
|
|
289
|
+
|
|
290
|
+
trace('ProcessRunner', 'Handling parent stream closure', {
|
|
291
|
+
started: this.started,
|
|
292
|
+
hasChild: !!this.child,
|
|
293
|
+
command: this.spec.command?.slice(0, 50) || this.spec.file
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Mark as cancelled to prevent further operations
|
|
297
|
+
this._cancelled = true;
|
|
298
|
+
|
|
299
|
+
// Cancel abort controller for virtual commands
|
|
300
|
+
if (this._abortController) {
|
|
301
|
+
this._abortController.abort();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Gracefully close child process if it exists
|
|
305
|
+
if (this.child) {
|
|
306
|
+
try {
|
|
307
|
+
// Close stdin first to signal completion
|
|
308
|
+
if (this.child.stdin && typeof this.child.stdin.end === 'function') {
|
|
309
|
+
this.child.stdin.end();
|
|
310
|
+
} else if (isBun && this.child.stdin && typeof this.child.stdin.getWriter === 'function') {
|
|
311
|
+
const writer = this.child.stdin.getWriter();
|
|
312
|
+
writer.close().catch(() => {}); // Ignore close errors
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Give the process a moment to exit gracefully, then terminate
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
if (this.child && !this.finished) {
|
|
318
|
+
trace('ProcessRunner', 'Terminating child process after parent stream closure', {});
|
|
319
|
+
if (typeof this.child.kill === 'function') {
|
|
320
|
+
this.child.kill('SIGTERM');
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}, 100);
|
|
324
|
+
|
|
325
|
+
} catch (error) {
|
|
326
|
+
trace('ProcessRunner', 'Error during graceful shutdown', { error: error.message });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Remove from active set
|
|
331
|
+
activeProcessRunners.delete(this);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Cleanup method to remove from active set when process completes normally
|
|
335
|
+
_cleanup() {
|
|
336
|
+
activeProcessRunners.delete(this);
|
|
195
337
|
}
|
|
196
338
|
|
|
197
339
|
// Unified start method that can work in both async and sync modes
|
|
@@ -304,7 +446,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
304
446
|
// Setup stdout streaming
|
|
305
447
|
const outPump = pumpReadable(this.child.stdout, async (buf) => {
|
|
306
448
|
if (this.options.capture) this.outChunks.push(buf);
|
|
307
|
-
if (this.options.mirror) process.stdout
|
|
449
|
+
if (this.options.mirror) safeWrite(process.stdout, buf);
|
|
308
450
|
|
|
309
451
|
// Emit chunk events
|
|
310
452
|
this.emit('stdout', buf);
|
|
@@ -314,7 +456,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
314
456
|
// Setup stderr streaming
|
|
315
457
|
const errPump = pumpReadable(this.child.stderr, async (buf) => {
|
|
316
458
|
if (this.options.capture) this.errChunks.push(buf);
|
|
317
|
-
if (this.options.mirror) process.stderr
|
|
459
|
+
if (this.options.mirror) safeWrite(process.stderr, buf);
|
|
318
460
|
|
|
319
461
|
// Emit chunk events
|
|
320
462
|
this.emit('stderr', buf);
|
|
@@ -394,7 +536,27 @@ class ProcessRunner extends StreamEmitter {
|
|
|
394
536
|
const buf = asBuffer(chunk);
|
|
395
537
|
captureChunks && captureChunks.push(buf);
|
|
396
538
|
if (bunWriter) await bunWriter.write(buf);
|
|
397
|
-
else if (typeof child.stdin.write === 'function')
|
|
539
|
+
else if (typeof child.stdin.write === 'function') {
|
|
540
|
+
// Add error handler to prevent unhandled error events
|
|
541
|
+
if (child.stdin && typeof child.stdin.on === 'function') {
|
|
542
|
+
child.stdin.on('error', (error) => {
|
|
543
|
+
if (error.code !== 'EPIPE') {
|
|
544
|
+
trace('ProcessRunner', 'child stdin buffer error event', { error: error.message, code: error.code });
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Safe write to handle EPIPE errors
|
|
550
|
+
if (child.stdin && child.stdin.writable && !child.stdin.destroyed && !child.stdin.closed) {
|
|
551
|
+
try {
|
|
552
|
+
child.stdin.write(buf);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (error.code !== 'EPIPE') {
|
|
555
|
+
trace('ProcessRunner', 'Error writing stdin buffer', { error: error.message, code: error.code });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
398
560
|
else if (isBun && typeof Bun.write === 'function') await Bun.write(child.stdin, buf);
|
|
399
561
|
}
|
|
400
562
|
if (bunWriter) await bunWriter.close();
|
|
@@ -405,8 +567,14 @@ class ProcessRunner extends StreamEmitter {
|
|
|
405
567
|
if (isBun && this.child.stdin && typeof this.child.stdin.getWriter === 'function') {
|
|
406
568
|
const w = this.child.stdin.getWriter();
|
|
407
569
|
const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf.buffer, buf.byteOffset ?? 0, buf.byteLength);
|
|
408
|
-
|
|
409
|
-
|
|
570
|
+
try {
|
|
571
|
+
await w.write(bytes);
|
|
572
|
+
await w.close();
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (error.code !== 'EPIPE') {
|
|
575
|
+
trace('ProcessRunner', 'Error writing to Bun writer', { error: error.message, code: error.code });
|
|
576
|
+
}
|
|
577
|
+
}
|
|
410
578
|
} else if (this.child.stdin && typeof this.child.stdin.write === 'function') {
|
|
411
579
|
this.child.stdin.end(buf);
|
|
412
580
|
} else if (isBun && typeof Bun.write === 'function') {
|
|
@@ -541,7 +709,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
541
709
|
signal: this._abortController.signal
|
|
542
710
|
};
|
|
543
711
|
|
|
544
|
-
const generator = handler(argValues, stdinData, commandOptions);
|
|
712
|
+
const generator = handler({ args: argValues, stdin: stdinData, ...commandOptions });
|
|
545
713
|
this._virtualGenerator = generator;
|
|
546
714
|
|
|
547
715
|
// Create a promise that resolves when cancelled
|
|
@@ -578,7 +746,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
578
746
|
// Only output if not cancelled
|
|
579
747
|
if (!this._cancelled) {
|
|
580
748
|
if (this.options.mirror) {
|
|
581
|
-
process.stdout
|
|
749
|
+
safeWrite(process.stdout, buf);
|
|
582
750
|
}
|
|
583
751
|
|
|
584
752
|
this.emit('stdout', buf);
|
|
@@ -600,7 +768,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
600
768
|
};
|
|
601
769
|
} else {
|
|
602
770
|
// Regular async function
|
|
603
|
-
result = await handler(argValues, stdinData, this.options);
|
|
771
|
+
result = await handler({ args: argValues, stdin: stdinData, ...this.options });
|
|
604
772
|
|
|
605
773
|
// Ensure result has required fields, respecting capture option
|
|
606
774
|
result = {
|
|
@@ -615,7 +783,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
615
783
|
if (result.stdout) {
|
|
616
784
|
const buf = Buffer.from(result.stdout);
|
|
617
785
|
if (this.options.mirror) {
|
|
618
|
-
process.stdout
|
|
786
|
+
safeWrite(process.stdout, buf);
|
|
619
787
|
}
|
|
620
788
|
this.emit('stdout', buf);
|
|
621
789
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -624,7 +792,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
624
792
|
if (result.stderr) {
|
|
625
793
|
const buf = Buffer.from(result.stderr);
|
|
626
794
|
if (this.options.mirror) {
|
|
627
|
-
process.stderr
|
|
795
|
+
safeWrite(process.stderr, buf);
|
|
628
796
|
}
|
|
629
797
|
this.emit('stderr', buf);
|
|
630
798
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -665,7 +833,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
665
833
|
if (result.stderr) {
|
|
666
834
|
const buf = Buffer.from(result.stderr);
|
|
667
835
|
if (this.options.mirror) {
|
|
668
|
-
process.stderr
|
|
836
|
+
safeWrite(process.stderr, buf);
|
|
669
837
|
}
|
|
670
838
|
this.emit('stderr', buf);
|
|
671
839
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -794,13 +962,26 @@ class ProcessRunner extends StreamEmitter {
|
|
|
794
962
|
|
|
795
963
|
// Write stdin data if needed for first process
|
|
796
964
|
if (needsManualStdin && stdinData && proc.stdin) {
|
|
965
|
+
// Add error handler for Bun stdin
|
|
966
|
+
if (proc.stdin && typeof proc.stdin.on === 'function') {
|
|
967
|
+
proc.stdin.on('error', (error) => {
|
|
968
|
+
if (error.code !== 'EPIPE') {
|
|
969
|
+
trace('ProcessRunner', 'Bun stdin error event', { error: error.message, code: error.code });
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
797
974
|
(async () => {
|
|
798
975
|
try {
|
|
799
976
|
// Bun's FileSink has write and end methods
|
|
800
|
-
|
|
801
|
-
|
|
977
|
+
if (proc.stdin && proc.stdin.writable && !proc.stdin.destroyed && !proc.stdin.closed) {
|
|
978
|
+
await proc.stdin.write(stdinData);
|
|
979
|
+
await proc.stdin.end();
|
|
980
|
+
}
|
|
802
981
|
} catch (e) {
|
|
803
|
-
|
|
982
|
+
if (e.code !== 'EPIPE') {
|
|
983
|
+
trace('ProcessRunner', 'Error writing stdin (Bun)', { error: e.message, code: e.code });
|
|
984
|
+
}
|
|
804
985
|
}
|
|
805
986
|
})();
|
|
806
987
|
}
|
|
@@ -815,7 +996,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
815
996
|
// Only emit stderr for the last command
|
|
816
997
|
if (i === commands.length - 1) {
|
|
817
998
|
if (this.options.mirror) {
|
|
818
|
-
process.stderr
|
|
999
|
+
safeWrite(process.stderr, buf);
|
|
819
1000
|
}
|
|
820
1001
|
this.emit('stderr', buf);
|
|
821
1002
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -833,7 +1014,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
833
1014
|
const buf = Buffer.from(chunk);
|
|
834
1015
|
finalOutput += buf.toString();
|
|
835
1016
|
if (this.options.mirror) {
|
|
836
|
-
process.stdout
|
|
1017
|
+
safeWrite(process.stdout, buf);
|
|
837
1018
|
}
|
|
838
1019
|
this.emit('stdout', buf);
|
|
839
1020
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -959,11 +1140,24 @@ class ProcessRunner extends StreamEmitter {
|
|
|
959
1140
|
|
|
960
1141
|
// Write stdin data if needed for first process
|
|
961
1142
|
if (needsManualStdin && stdinData && proc.stdin) {
|
|
1143
|
+
// Add error handler for Node stdin
|
|
1144
|
+
if (proc.stdin && typeof proc.stdin.on === 'function') {
|
|
1145
|
+
proc.stdin.on('error', (error) => {
|
|
1146
|
+
if (error.code !== 'EPIPE') {
|
|
1147
|
+
trace('ProcessRunner', 'Node stdin error event', { error: error.message, code: error.code });
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
962
1152
|
try {
|
|
963
|
-
|
|
964
|
-
|
|
1153
|
+
if (proc.stdin && proc.stdin.writable && !proc.stdin.destroyed && !proc.stdin.closed) {
|
|
1154
|
+
await proc.stdin.write(stdinData);
|
|
1155
|
+
await proc.stdin.end();
|
|
1156
|
+
}
|
|
965
1157
|
} catch (e) {
|
|
966
|
-
|
|
1158
|
+
if (e.code !== 'EPIPE') {
|
|
1159
|
+
trace('ProcessRunner', 'Error writing stdin (Node stream)', { error: e.message, code: e.code });
|
|
1160
|
+
}
|
|
967
1161
|
}
|
|
968
1162
|
}
|
|
969
1163
|
|
|
@@ -982,7 +1176,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
982
1176
|
// Emit from the first process for real-time updates
|
|
983
1177
|
const buf = Buffer.from(chunk);
|
|
984
1178
|
if (this.options.mirror) {
|
|
985
|
-
process.stdout
|
|
1179
|
+
safeWrite(process.stdout, buf);
|
|
986
1180
|
}
|
|
987
1181
|
this.emit('stdout', buf);
|
|
988
1182
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -1007,7 +1201,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1007
1201
|
allStderr += buf.toString();
|
|
1008
1202
|
if (i === commands.length - 1) {
|
|
1009
1203
|
if (this.options.mirror) {
|
|
1010
|
-
process.stderr
|
|
1204
|
+
safeWrite(process.stderr, buf);
|
|
1011
1205
|
}
|
|
1012
1206
|
this.emit('stderr', buf);
|
|
1013
1207
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -1028,7 +1222,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1028
1222
|
finalOutput += buf.toString();
|
|
1029
1223
|
if (shouldEmitFromLast) {
|
|
1030
1224
|
if (this.options.mirror) {
|
|
1031
|
-
process.stdout
|
|
1225
|
+
safeWrite(process.stdout, buf);
|
|
1032
1226
|
}
|
|
1033
1227
|
this.emit('stdout', buf);
|
|
1034
1228
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -1139,7 +1333,8 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1139
1333
|
const self = this; // Capture this context
|
|
1140
1334
|
currentInputStream = new ReadableStream({
|
|
1141
1335
|
async start(controller) {
|
|
1142
|
-
|
|
1336
|
+
const { stdin: _, ...optionsWithoutStdin } = self.options;
|
|
1337
|
+
for await (const chunk of handler({ args: argValues, stdin: inputData, ...optionsWithoutStdin })) {
|
|
1143
1338
|
const data = Buffer.from(chunk);
|
|
1144
1339
|
controller.enqueue(data);
|
|
1145
1340
|
|
|
@@ -1147,7 +1342,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1147
1342
|
if (isLastCommand) {
|
|
1148
1343
|
chunks.push(data);
|
|
1149
1344
|
if (self.options.mirror) {
|
|
1150
|
-
process.stdout
|
|
1345
|
+
safeWrite(process.stdout, data);
|
|
1151
1346
|
}
|
|
1152
1347
|
self.emit('stdout', data);
|
|
1153
1348
|
self.emit('data', { type: 'stdout', data });
|
|
@@ -1162,14 +1357,15 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1162
1357
|
});
|
|
1163
1358
|
} else {
|
|
1164
1359
|
// Regular async function
|
|
1165
|
-
const
|
|
1360
|
+
const { stdin: _, ...optionsWithoutStdin } = this.options;
|
|
1361
|
+
const result = await handler({ args: argValues, stdin: inputData, ...optionsWithoutStdin });
|
|
1166
1362
|
const outputData = result.stdout || '';
|
|
1167
1363
|
|
|
1168
1364
|
if (isLastCommand) {
|
|
1169
1365
|
finalOutput = outputData;
|
|
1170
1366
|
const buf = Buffer.from(outputData);
|
|
1171
1367
|
if (this.options.mirror) {
|
|
1172
|
-
process.stdout
|
|
1368
|
+
safeWrite(process.stdout, buf);
|
|
1173
1369
|
}
|
|
1174
1370
|
this.emit('stdout', buf);
|
|
1175
1371
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -1229,11 +1425,25 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1229
1425
|
const { done, value } = await reader.read();
|
|
1230
1426
|
if (done) break;
|
|
1231
1427
|
if (writer.write) {
|
|
1232
|
-
|
|
1428
|
+
try {
|
|
1429
|
+
await writer.write(value);
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
if (error.code !== 'EPIPE') {
|
|
1432
|
+
trace('ProcessRunner', 'Error writing to stream writer', { error: error.message, code: error.code });
|
|
1433
|
+
}
|
|
1434
|
+
break; // Stop streaming if write fails
|
|
1435
|
+
}
|
|
1233
1436
|
} else if (writer.getWriter) {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1437
|
+
try {
|
|
1438
|
+
const w = writer.getWriter();
|
|
1439
|
+
await w.write(value);
|
|
1440
|
+
w.releaseLock();
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
if (error.code !== 'EPIPE') {
|
|
1443
|
+
trace('ProcessRunner', 'Error writing to stream writer (getWriter)', { error: error.message, code: error.code });
|
|
1444
|
+
}
|
|
1445
|
+
break; // Stop streaming if write fails
|
|
1446
|
+
}
|
|
1237
1447
|
}
|
|
1238
1448
|
}
|
|
1239
1449
|
} finally {
|
|
@@ -1254,7 +1464,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1254
1464
|
allStderr += buf.toString();
|
|
1255
1465
|
if (isLastCommand) {
|
|
1256
1466
|
if (this.options.mirror) {
|
|
1257
|
-
process.stderr
|
|
1467
|
+
safeWrite(process.stderr, buf);
|
|
1258
1468
|
}
|
|
1259
1469
|
this.emit('stderr', buf);
|
|
1260
1470
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -1269,7 +1479,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1269
1479
|
const buf = Buffer.from(chunk);
|
|
1270
1480
|
chunks.push(buf);
|
|
1271
1481
|
if (this.options.mirror) {
|
|
1272
|
-
process.stdout
|
|
1482
|
+
safeWrite(process.stdout, buf);
|
|
1273
1483
|
}
|
|
1274
1484
|
this.emit('stdout', buf);
|
|
1275
1485
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -1346,7 +1556,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1346
1556
|
if (handler.constructor.name === 'AsyncGeneratorFunction') {
|
|
1347
1557
|
traceBranch('ProcessRunner', '_runPipelineNonStreaming', 'ASYNC_GENERATOR', { cmd });
|
|
1348
1558
|
const chunks = [];
|
|
1349
|
-
for await (const chunk of handler(argValues, currentInput, this.options)) {
|
|
1559
|
+
for await (const chunk of handler({ args: argValues, stdin: currentInput, ...this.options })) {
|
|
1350
1560
|
chunks.push(Buffer.from(chunk));
|
|
1351
1561
|
}
|
|
1352
1562
|
result = {
|
|
@@ -1357,7 +1567,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1357
1567
|
};
|
|
1358
1568
|
} else {
|
|
1359
1569
|
// Regular async function
|
|
1360
|
-
result = await handler(argValues, currentInput, this.options);
|
|
1570
|
+
result = await handler({ args: argValues, stdin: currentInput, ...this.options });
|
|
1361
1571
|
result = {
|
|
1362
1572
|
code: result.code ?? 0,
|
|
1363
1573
|
stdout: this.options.capture ? (result.stdout ?? '') : undefined,
|
|
@@ -1378,7 +1588,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1378
1588
|
if (result.stdout) {
|
|
1379
1589
|
const buf = Buffer.from(result.stdout);
|
|
1380
1590
|
if (this.options.mirror) {
|
|
1381
|
-
process.stdout
|
|
1591
|
+
safeWrite(process.stdout, buf);
|
|
1382
1592
|
}
|
|
1383
1593
|
this.emit('stdout', buf);
|
|
1384
1594
|
this.emit('data', { type: 'stdout', data: buf });
|
|
@@ -1387,7 +1597,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1387
1597
|
if (result.stderr) {
|
|
1388
1598
|
const buf = Buffer.from(result.stderr);
|
|
1389
1599
|
if (this.options.mirror) {
|
|
1390
|
-
process.stderr
|
|
1600
|
+
safeWrite(process.stderr, buf);
|
|
1391
1601
|
}
|
|
1392
1602
|
this.emit('stderr', buf);
|
|
1393
1603
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -1447,7 +1657,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1447
1657
|
if (result.stderr) {
|
|
1448
1658
|
const buf = Buffer.from(result.stderr);
|
|
1449
1659
|
if (this.options.mirror) {
|
|
1450
|
-
process.stderr
|
|
1660
|
+
safeWrite(process.stderr, buf);
|
|
1451
1661
|
}
|
|
1452
1662
|
this.emit('stderr', buf);
|
|
1453
1663
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -1518,7 +1728,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1518
1728
|
// If this is the last command, emit streaming data
|
|
1519
1729
|
if (isLastCommand) {
|
|
1520
1730
|
if (this.options.mirror) {
|
|
1521
|
-
process.stdout
|
|
1731
|
+
safeWrite(process.stdout, chunk);
|
|
1522
1732
|
}
|
|
1523
1733
|
this.emit('stdout', chunk);
|
|
1524
1734
|
this.emit('data', { type: 'stdout', data: chunk });
|
|
@@ -1530,7 +1740,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1530
1740
|
// If this is the last command, emit streaming data
|
|
1531
1741
|
if (isLastCommand) {
|
|
1532
1742
|
if (this.options.mirror) {
|
|
1533
|
-
process.stderr
|
|
1743
|
+
safeWrite(process.stderr, chunk);
|
|
1534
1744
|
}
|
|
1535
1745
|
this.emit('stderr', chunk);
|
|
1536
1746
|
this.emit('data', { type: 'stderr', data: chunk });
|
|
@@ -1547,10 +1757,68 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1547
1757
|
|
|
1548
1758
|
proc.on('error', reject);
|
|
1549
1759
|
|
|
1760
|
+
// Add error handler to stdin to prevent unhandled error events
|
|
1761
|
+
if (proc.stdin) {
|
|
1762
|
+
proc.stdin.on('error', (error) => {
|
|
1763
|
+
trace('ProcessRunner', 'stdin error event', {
|
|
1764
|
+
error: error.message,
|
|
1765
|
+
code: error.code,
|
|
1766
|
+
isEPIPE: error.code === 'EPIPE'
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1769
|
+
// Only reject on non-EPIPE errors
|
|
1770
|
+
if (error.code !== 'EPIPE') {
|
|
1771
|
+
reject(error);
|
|
1772
|
+
}
|
|
1773
|
+
// EPIPE errors are expected when pipe is closed, so we ignore them
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1550
1777
|
if (stdin) {
|
|
1551
|
-
|
|
1778
|
+
// Use safe write to handle potential EPIPE errors
|
|
1779
|
+
trace('ProcessRunner', 'Attempting to write stdin', {
|
|
1780
|
+
hasStdin: !!proc.stdin,
|
|
1781
|
+
writable: proc.stdin?.writable,
|
|
1782
|
+
destroyed: proc.stdin?.destroyed,
|
|
1783
|
+
closed: proc.stdin?.closed,
|
|
1784
|
+
stdinLength: stdin.length
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
if (proc.stdin && proc.stdin.writable && !proc.stdin.destroyed && !proc.stdin.closed) {
|
|
1788
|
+
try {
|
|
1789
|
+
proc.stdin.write(stdin);
|
|
1790
|
+
trace('ProcessRunner', 'Successfully wrote to stdin', { stdinLength: stdin.length });
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
trace('ProcessRunner', 'Error writing to stdin', {
|
|
1793
|
+
error: error.message,
|
|
1794
|
+
code: error.code,
|
|
1795
|
+
isEPIPE: error.code === 'EPIPE'
|
|
1796
|
+
});
|
|
1797
|
+
if (error.code !== 'EPIPE') {
|
|
1798
|
+
throw error; // Re-throw non-EPIPE errors
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
} else {
|
|
1802
|
+
trace('ProcessRunner', 'Skipped writing to stdin - stream not writable', {
|
|
1803
|
+
hasStdin: !!proc.stdin,
|
|
1804
|
+
writable: proc.stdin?.writable,
|
|
1805
|
+
destroyed: proc.stdin?.destroyed,
|
|
1806
|
+
closed: proc.stdin?.closed
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// Safely end the stdin stream
|
|
1812
|
+
if (proc.stdin && typeof proc.stdin.end === 'function' &&
|
|
1813
|
+
proc.stdin.writable && !proc.stdin.destroyed && !proc.stdin.closed) {
|
|
1814
|
+
try {
|
|
1815
|
+
proc.stdin.end();
|
|
1816
|
+
} catch (error) {
|
|
1817
|
+
if (error.code !== 'EPIPE') {
|
|
1818
|
+
trace('ProcessRunner', 'Error ending stdin', { error: error.message });
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1552
1821
|
}
|
|
1553
|
-
proc.stdin.end();
|
|
1554
1822
|
});
|
|
1555
1823
|
};
|
|
1556
1824
|
|
|
@@ -1641,7 +1909,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1641
1909
|
if (result.stderr) {
|
|
1642
1910
|
const buf = Buffer.from(result.stderr);
|
|
1643
1911
|
if (this.options.mirror) {
|
|
1644
|
-
process.stderr
|
|
1912
|
+
safeWrite(process.stderr, buf);
|
|
1645
1913
|
}
|
|
1646
1914
|
this.emit('stderr', buf);
|
|
1647
1915
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -1741,7 +2009,7 @@ class ProcessRunner extends StreamEmitter {
|
|
|
1741
2009
|
|
|
1742
2010
|
const buf = Buffer.from(result.stderr);
|
|
1743
2011
|
if (this.options.mirror) {
|
|
1744
|
-
process.stderr
|
|
2012
|
+
safeWrite(process.stderr, buf);
|
|
1745
2013
|
}
|
|
1746
2014
|
this.emit('stderr', buf);
|
|
1747
2015
|
this.emit('data', { type: 'stderr', data: buf });
|
|
@@ -2016,8 +2284,8 @@ class ProcessRunner extends StreamEmitter {
|
|
|
2016
2284
|
|
|
2017
2285
|
// Mirror output if requested (but always capture for result)
|
|
2018
2286
|
if (this.options.mirror) {
|
|
2019
|
-
if (result.stdout) process.stdout
|
|
2020
|
-
if (result.stderr) process.stderr
|
|
2287
|
+
if (result.stdout) safeWrite(process.stdout, result.stdout);
|
|
2288
|
+
if (result.stderr) safeWrite(process.stderr, result.stderr);
|
|
2021
2289
|
}
|
|
2022
2290
|
|
|
2023
2291
|
// Store chunks for events (batched after completion)
|
|
@@ -2249,7 +2517,7 @@ function disableVirtualCommands() {
|
|
|
2249
2517
|
// Built-in commands that match Bun.$ functionality
|
|
2250
2518
|
function registerBuiltins() {
|
|
2251
2519
|
// cd - change directory
|
|
2252
|
-
register('cd', async (args) => {
|
|
2520
|
+
register('cd', async ({ args }) => {
|
|
2253
2521
|
const target = args[0] || process.env.HOME || process.env.USERPROFILE || '/';
|
|
2254
2522
|
trace('VirtualCommand', 'cd: changing directory', { target });
|
|
2255
2523
|
|
|
@@ -2265,15 +2533,15 @@ function registerBuiltins() {
|
|
|
2265
2533
|
});
|
|
2266
2534
|
|
|
2267
2535
|
// pwd - print working directory
|
|
2268
|
-
register('pwd', async (args, stdin,
|
|
2536
|
+
register('pwd', async ({ args, stdin, cwd }) => {
|
|
2269
2537
|
// If cwd option is provided, return that instead of process.cwd()
|
|
2270
|
-
const dir =
|
|
2538
|
+
const dir = cwd || process.cwd();
|
|
2271
2539
|
trace('VirtualCommand', 'pwd: getting directory', { dir });
|
|
2272
2540
|
return { stdout: dir, code: 0 };
|
|
2273
2541
|
});
|
|
2274
2542
|
|
|
2275
2543
|
// echo - print arguments
|
|
2276
|
-
register('echo', async (args) => {
|
|
2544
|
+
register('echo', async ({ args }) => {
|
|
2277
2545
|
trace('VirtualCommand', 'echo: processing', { argsCount: args.length });
|
|
2278
2546
|
|
|
2279
2547
|
let output = args.join(' ');
|
|
@@ -2288,7 +2556,7 @@ function registerBuiltins() {
|
|
|
2288
2556
|
});
|
|
2289
2557
|
|
|
2290
2558
|
// sleep - wait for specified time
|
|
2291
|
-
register('sleep', async (args) => {
|
|
2559
|
+
register('sleep', async ({ args }) => {
|
|
2292
2560
|
const seconds = parseFloat(args[0] || 0);
|
|
2293
2561
|
trace('VirtualCommand', 'sleep: starting', { seconds });
|
|
2294
2562
|
|
|
@@ -2313,7 +2581,7 @@ function registerBuiltins() {
|
|
|
2313
2581
|
});
|
|
2314
2582
|
|
|
2315
2583
|
// which - locate command
|
|
2316
|
-
register('which', async (args) => {
|
|
2584
|
+
register('which', async ({ args }) => {
|
|
2317
2585
|
if (args.length === 0) {
|
|
2318
2586
|
return { stderr: 'which: missing operand', code: 1 };
|
|
2319
2587
|
}
|
|
@@ -2344,7 +2612,7 @@ function registerBuiltins() {
|
|
|
2344
2612
|
});
|
|
2345
2613
|
|
|
2346
2614
|
// exit - exit with code
|
|
2347
|
-
register('exit', async (args) => {
|
|
2615
|
+
register('exit', async ({ args }) => {
|
|
2348
2616
|
const code = parseInt(args[0] || 0);
|
|
2349
2617
|
if (globalShellSettings.errexit || code !== 0) {
|
|
2350
2618
|
// For virtual commands, we simulate exit by returning the code
|
|
@@ -2354,11 +2622,11 @@ function registerBuiltins() {
|
|
|
2354
2622
|
});
|
|
2355
2623
|
|
|
2356
2624
|
// env - print environment variables
|
|
2357
|
-
register('env', async (args, stdin,
|
|
2625
|
+
register('env', async ({ args, stdin, env }) => {
|
|
2358
2626
|
if (args.length === 0) {
|
|
2359
2627
|
// Use custom env if provided, otherwise use process.env
|
|
2360
|
-
const
|
|
2361
|
-
const output = Object.entries(
|
|
2628
|
+
const envVars = env || process.env;
|
|
2629
|
+
const output = Object.entries(envVars)
|
|
2362
2630
|
.map(([key, value]) => `${key}=${value}`)
|
|
2363
2631
|
.join('\n') + '\n';
|
|
2364
2632
|
return { stdout: output, code: 0 };
|
|
@@ -2369,7 +2637,7 @@ function registerBuiltins() {
|
|
|
2369
2637
|
});
|
|
2370
2638
|
|
|
2371
2639
|
// cat - read and display file contents
|
|
2372
|
-
register('cat', async (args, stdin,
|
|
2640
|
+
register('cat', async ({ args, stdin, cwd }) => {
|
|
2373
2641
|
if (args.length === 0) {
|
|
2374
2642
|
// Read from stdin if no files specified
|
|
2375
2643
|
return { stdout: stdin || '', code: 0 };
|
|
@@ -2386,7 +2654,7 @@ function registerBuiltins() {
|
|
|
2386
2654
|
|
|
2387
2655
|
try {
|
|
2388
2656
|
// Resolve path relative to cwd if provided
|
|
2389
|
-
const basePath =
|
|
2657
|
+
const basePath = cwd || process.cwd();
|
|
2390
2658
|
const fullPath = path.isAbsolute(filename) ? filename : path.join(basePath, filename);
|
|
2391
2659
|
|
|
2392
2660
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
@@ -2409,7 +2677,7 @@ function registerBuiltins() {
|
|
|
2409
2677
|
});
|
|
2410
2678
|
|
|
2411
2679
|
// ls - list directory contents
|
|
2412
|
-
register('ls', async (args, stdin,
|
|
2680
|
+
register('ls', async ({ args, stdin, cwd }) => {
|
|
2413
2681
|
try {
|
|
2414
2682
|
const fs = await import('fs');
|
|
2415
2683
|
const path = await import('path');
|
|
@@ -2428,7 +2696,7 @@ function registerBuiltins() {
|
|
|
2428
2696
|
|
|
2429
2697
|
for (const targetPath of targetPaths) {
|
|
2430
2698
|
// Resolve path relative to cwd if provided
|
|
2431
|
-
const basePath =
|
|
2699
|
+
const basePath = cwd || process.cwd();
|
|
2432
2700
|
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.join(basePath, targetPath);
|
|
2433
2701
|
|
|
2434
2702
|
try {
|
|
@@ -2483,7 +2751,7 @@ function registerBuiltins() {
|
|
|
2483
2751
|
});
|
|
2484
2752
|
|
|
2485
2753
|
// mkdir - create directories
|
|
2486
|
-
register('mkdir', async (args, stdin,
|
|
2754
|
+
register('mkdir', async ({ args, stdin, cwd }) => {
|
|
2487
2755
|
if (args.length === 0) {
|
|
2488
2756
|
return { stderr: 'mkdir: missing operand', code: 1 };
|
|
2489
2757
|
}
|
|
@@ -2498,7 +2766,7 @@ function registerBuiltins() {
|
|
|
2498
2766
|
|
|
2499
2767
|
for (const dir of dirs) {
|
|
2500
2768
|
try {
|
|
2501
|
-
const basePath =
|
|
2769
|
+
const basePath = cwd || process.cwd();
|
|
2502
2770
|
const fullPath = path.isAbsolute(dir) ? dir : path.join(basePath, dir);
|
|
2503
2771
|
|
|
2504
2772
|
if (recursive) {
|
|
@@ -2521,7 +2789,7 @@ function registerBuiltins() {
|
|
|
2521
2789
|
});
|
|
2522
2790
|
|
|
2523
2791
|
// rm - remove files and directories
|
|
2524
|
-
register('rm', async (args, stdin,
|
|
2792
|
+
register('rm', async ({ args, stdin, cwd }) => {
|
|
2525
2793
|
if (args.length === 0) {
|
|
2526
2794
|
return { stderr: 'rm: missing operand', code: 1 };
|
|
2527
2795
|
}
|
|
@@ -2537,7 +2805,7 @@ function registerBuiltins() {
|
|
|
2537
2805
|
|
|
2538
2806
|
for (const target of targets) {
|
|
2539
2807
|
try {
|
|
2540
|
-
const basePath =
|
|
2808
|
+
const basePath = cwd || process.cwd();
|
|
2541
2809
|
const fullPath = path.isAbsolute(target) ? target : path.join(basePath, target);
|
|
2542
2810
|
|
|
2543
2811
|
const stat = fs.statSync(fullPath);
|
|
@@ -2570,7 +2838,7 @@ function registerBuiltins() {
|
|
|
2570
2838
|
});
|
|
2571
2839
|
|
|
2572
2840
|
// mv - move/rename files and directories
|
|
2573
|
-
register('mv', async (args, stdin,
|
|
2841
|
+
register('mv', async ({ args, stdin, cwd }) => {
|
|
2574
2842
|
if (args.length < 2) {
|
|
2575
2843
|
return { stderr: 'mv: missing destination file operand', code: 1 };
|
|
2576
2844
|
}
|
|
@@ -2579,7 +2847,7 @@ function registerBuiltins() {
|
|
|
2579
2847
|
const fs = await import('fs');
|
|
2580
2848
|
const path = await import('path');
|
|
2581
2849
|
|
|
2582
|
-
const basePath =
|
|
2850
|
+
const basePath = cwd || process.cwd();
|
|
2583
2851
|
|
|
2584
2852
|
if (args.length === 2) {
|
|
2585
2853
|
// Simple rename/move
|
|
@@ -2651,7 +2919,7 @@ function registerBuiltins() {
|
|
|
2651
2919
|
});
|
|
2652
2920
|
|
|
2653
2921
|
// cp - copy files and directories
|
|
2654
|
-
register('cp', async (args, stdin,
|
|
2922
|
+
register('cp', async ({ args, stdin, cwd }) => {
|
|
2655
2923
|
if (args.length < 2) {
|
|
2656
2924
|
return { stderr: 'cp: missing destination file operand', code: 1 };
|
|
2657
2925
|
}
|
|
@@ -2664,7 +2932,7 @@ function registerBuiltins() {
|
|
|
2664
2932
|
const paths = args.filter(arg => !arg.startsWith('-'));
|
|
2665
2933
|
const recursive = flags.includes('-r') || flags.includes('-R');
|
|
2666
2934
|
|
|
2667
|
-
const basePath =
|
|
2935
|
+
const basePath = cwd || process.cwd();
|
|
2668
2936
|
|
|
2669
2937
|
if (paths.length === 2) {
|
|
2670
2938
|
// Simple copy
|
|
@@ -2748,7 +3016,7 @@ function registerBuiltins() {
|
|
|
2748
3016
|
});
|
|
2749
3017
|
|
|
2750
3018
|
// touch - create or update file timestamps
|
|
2751
|
-
register('touch', async (args, stdin,
|
|
3019
|
+
register('touch', async ({ args, stdin, cwd }) => {
|
|
2752
3020
|
if (args.length === 0) {
|
|
2753
3021
|
return { stderr: 'touch: missing file operand', code: 1 };
|
|
2754
3022
|
}
|
|
@@ -2757,7 +3025,7 @@ function registerBuiltins() {
|
|
|
2757
3025
|
const fs = await import('fs');
|
|
2758
3026
|
const path = await import('path');
|
|
2759
3027
|
|
|
2760
|
-
const basePath =
|
|
3028
|
+
const basePath = cwd || process.cwd();
|
|
2761
3029
|
|
|
2762
3030
|
for (const file of args) {
|
|
2763
3031
|
try {
|
|
@@ -2786,7 +3054,7 @@ function registerBuiltins() {
|
|
|
2786
3054
|
});
|
|
2787
3055
|
|
|
2788
3056
|
// basename - extract filename from path
|
|
2789
|
-
register('basename', async (args) => {
|
|
3057
|
+
register('basename', async ({ args }) => {
|
|
2790
3058
|
if (args.length === 0) {
|
|
2791
3059
|
return { stderr: 'basename: missing operand', code: 1 };
|
|
2792
3060
|
}
|
|
@@ -2811,7 +3079,7 @@ function registerBuiltins() {
|
|
|
2811
3079
|
});
|
|
2812
3080
|
|
|
2813
3081
|
// dirname - extract directory from path
|
|
2814
|
-
register('dirname', async (args) => {
|
|
3082
|
+
register('dirname', async ({ args }) => {
|
|
2815
3083
|
if (args.length === 0) {
|
|
2816
3084
|
return { stderr: 'dirname: missing operand', code: 1 };
|
|
2817
3085
|
}
|
|
@@ -2829,22 +3097,20 @@ function registerBuiltins() {
|
|
|
2829
3097
|
});
|
|
2830
3098
|
|
|
2831
3099
|
// yes - output a string repeatedly
|
|
2832
|
-
register('yes', async function* (args, stdin,
|
|
3100
|
+
register('yes', async function* ({ args, stdin, isCancelled, signal, ...rest }) {
|
|
2833
3101
|
const output = args.length > 0 ? args.join(' ') : 'y';
|
|
2834
3102
|
trace('VirtualCommand', 'yes: starting infinite generator', { output });
|
|
2835
3103
|
|
|
2836
3104
|
// Generate infinite stream of the output
|
|
2837
3105
|
while (true) {
|
|
2838
3106
|
// Check if cancelled via function or abort signal
|
|
2839
|
-
if (
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
return;
|
|
2847
|
-
}
|
|
3107
|
+
if (isCancelled && isCancelled()) {
|
|
3108
|
+
trace('VirtualCommand', 'yes: cancelled via function', {});
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
if (signal && signal.aborted) {
|
|
3112
|
+
trace('VirtualCommand', 'yes: cancelled via abort signal', {});
|
|
3113
|
+
return;
|
|
2848
3114
|
}
|
|
2849
3115
|
|
|
2850
3116
|
yield output + '\n';
|
|
@@ -2855,16 +3121,16 @@ function registerBuiltins() {
|
|
|
2855
3121
|
const timeout = setTimeout(resolve, 0);
|
|
2856
3122
|
|
|
2857
3123
|
// Listen for abort signal if available
|
|
2858
|
-
if (
|
|
3124
|
+
if (signal) {
|
|
2859
3125
|
const abortHandler = () => {
|
|
2860
3126
|
clearTimeout(timeout);
|
|
2861
3127
|
reject(new Error('Aborted'));
|
|
2862
3128
|
};
|
|
2863
3129
|
|
|
2864
|
-
if (
|
|
3130
|
+
if (signal.aborted) {
|
|
2865
3131
|
abortHandler();
|
|
2866
3132
|
} else {
|
|
2867
|
-
|
|
3133
|
+
signal.addEventListener('abort', abortHandler, { once: true });
|
|
2868
3134
|
}
|
|
2869
3135
|
}
|
|
2870
3136
|
});
|
|
@@ -2876,7 +3142,7 @@ function registerBuiltins() {
|
|
|
2876
3142
|
});
|
|
2877
3143
|
|
|
2878
3144
|
// seq - generate sequence of numbers
|
|
2879
|
-
register('seq', async (args) => {
|
|
3145
|
+
register('seq', async ({ args }) => {
|
|
2880
3146
|
if (args.length === 0) {
|
|
2881
3147
|
return { stderr: 'seq: missing operand', code: 1 };
|
|
2882
3148
|
}
|
|
@@ -2924,7 +3190,7 @@ function registerBuiltins() {
|
|
|
2924
3190
|
});
|
|
2925
3191
|
|
|
2926
3192
|
// test - test file conditions (basic implementation)
|
|
2927
|
-
register('test', async (args) => {
|
|
3193
|
+
register('test', async ({ args }) => {
|
|
2928
3194
|
if (args.length === 0) {
|
|
2929
3195
|
return { stdout: '', code: 1 };
|
|
2930
3196
|
}
|
package/package.json
CHANGED