command-stream 0.9.0 → 0.9.2
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/js/src/$.ansi.mjs +147 -0
- package/js/src/$.mjs +49 -6382
- package/js/src/$.process-runner-base.mjs +563 -0
- package/js/src/$.process-runner-execution.mjs +1497 -0
- package/js/src/$.process-runner-orchestration.mjs +250 -0
- package/js/src/$.process-runner-pipeline.mjs +1162 -0
- package/js/src/$.process-runner-stream-kill.mjs +312 -0
- package/js/src/$.process-runner-virtual.mjs +297 -0
- package/js/src/$.quote.mjs +161 -0
- package/js/src/$.result.mjs +23 -0
- package/js/src/$.shell-settings.mjs +84 -0
- package/js/src/$.shell.mjs +157 -0
- package/js/src/$.state.mjs +401 -0
- package/js/src/$.stream-emitter.mjs +111 -0
- package/js/src/$.stream-utils.mjs +390 -0
- package/js/src/$.trace.mjs +36 -0
- package/js/src/$.utils.mjs +2 -23
- package/js/src/$.virtual-commands.mjs +113 -0
- package/js/src/commands/$.which.mjs +3 -1
- package/js/src/commands/index.mjs +24 -0
- package/js/src/shell-parser.mjs +125 -83
- package/js/tests/resource-cleanup-internals.test.mjs +22 -24
- package/js/tests/sigint-cleanup.test.mjs +3 -0
- package/package.json +1 -1
- package/rust/src/ansi.rs +194 -0
- package/rust/src/events.rs +305 -0
- package/rust/src/lib.rs +71 -60
- package/rust/src/macros.rs +165 -0
- package/rust/src/pipeline.rs +411 -0
- package/rust/src/quote.rs +161 -0
- package/rust/src/state.rs +333 -0
- package/rust/src/stream.rs +369 -0
- package/rust/src/trace.rs +152 -0
- package/rust/src/utils.rs +53 -158
- package/rust/tests/events.rs +207 -0
- package/rust/tests/macros.rs +77 -0
- package/rust/tests/pipeline.rs +93 -0
- package/rust/tests/state.rs +207 -0
- package/rust/tests/stream.rs +102 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
// ProcessRunner base class - core constructor, properties, and lifecycle methods
|
|
2
|
+
// Part of the modular ProcessRunner architecture
|
|
3
|
+
|
|
4
|
+
import { trace } from './$.trace.mjs';
|
|
5
|
+
import {
|
|
6
|
+
activeProcessRunners,
|
|
7
|
+
virtualCommands,
|
|
8
|
+
installSignalHandlers,
|
|
9
|
+
monitorParentStreams,
|
|
10
|
+
uninstallSignalHandlers,
|
|
11
|
+
} from './$.state.mjs';
|
|
12
|
+
import { StreamEmitter } from './$.stream-emitter.mjs';
|
|
13
|
+
import { processOutput } from './$.ansi.mjs';
|
|
14
|
+
|
|
15
|
+
const isBun = typeof globalThis.Bun !== 'undefined';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Wait for child stream to become available
|
|
19
|
+
* @param {object} self - ProcessRunner instance
|
|
20
|
+
* @param {string} streamName - Name of stream (stdin, stdout, stderr)
|
|
21
|
+
* @returns {Promise<object|null>}
|
|
22
|
+
*/
|
|
23
|
+
function waitForChildStream(self, streamName) {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const checkForChild = () => {
|
|
26
|
+
if (self.child && self.child[streamName]) {
|
|
27
|
+
resolve(self.child[streamName]);
|
|
28
|
+
} else if (self.finished || self._virtualGenerator) {
|
|
29
|
+
resolve(null);
|
|
30
|
+
} else {
|
|
31
|
+
setImmediate(checkForChild);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
setImmediate(checkForChild);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if command is a virtual command
|
|
40
|
+
* @param {object} self - ProcessRunner instance
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isVirtualCommand(self) {
|
|
44
|
+
return (
|
|
45
|
+
self._virtualGenerator ||
|
|
46
|
+
(self.spec &&
|
|
47
|
+
self.spec.command &&
|
|
48
|
+
virtualCommands.has(self.spec.command.split(' ')[0]))
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get stream from child or wait for it
|
|
54
|
+
* @param {object} self - ProcessRunner instance
|
|
55
|
+
* @param {string} streamName - Name of stream
|
|
56
|
+
* @param {boolean} checkVirtual - Whether to check for virtual commands
|
|
57
|
+
* @returns {object|Promise|null}
|
|
58
|
+
*/
|
|
59
|
+
function getOrWaitForStream(self, streamName, checkVirtual = true) {
|
|
60
|
+
self._autoStartIfNeeded(`streams.${streamName} access`);
|
|
61
|
+
|
|
62
|
+
if (self.child && self.child[streamName]) {
|
|
63
|
+
return self.child[streamName];
|
|
64
|
+
}
|
|
65
|
+
if (self.finished) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (checkVirtual && isVirtualCommand(self)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!self.started) {
|
|
72
|
+
self._startAsync();
|
|
73
|
+
return waitForChildStream(self, streamName);
|
|
74
|
+
}
|
|
75
|
+
if (self.promise && !self.child) {
|
|
76
|
+
return waitForChildStream(self, streamName);
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get stdin stream with special handling for pipe mode
|
|
83
|
+
* @param {object} self - ProcessRunner instance
|
|
84
|
+
* @returns {object|Promise|null}
|
|
85
|
+
*/
|
|
86
|
+
function getStdinStream(self) {
|
|
87
|
+
self._autoStartIfNeeded('streams.stdin access');
|
|
88
|
+
|
|
89
|
+
if (self.child && self.child.stdin) {
|
|
90
|
+
return self.child.stdin;
|
|
91
|
+
}
|
|
92
|
+
if (self.finished) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isVirtual = isVirtualCommand(self);
|
|
97
|
+
const willFallbackToReal = isVirtual && self.options.stdin === 'pipe';
|
|
98
|
+
|
|
99
|
+
if (isVirtual && !willFallbackToReal) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (!self.started) {
|
|
103
|
+
self._startAsync();
|
|
104
|
+
return waitForChildStream(self, 'stdin');
|
|
105
|
+
}
|
|
106
|
+
if (self.promise && !self.child) {
|
|
107
|
+
return waitForChildStream(self, 'stdin');
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Cleanup abort controller
|
|
114
|
+
* @param {object} runner - ProcessRunner instance
|
|
115
|
+
*/
|
|
116
|
+
function cleanupAbortController(runner) {
|
|
117
|
+
if (!runner._abortController) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
trace('ProcessRunner', () => 'Cleaning up abort controller');
|
|
121
|
+
try {
|
|
122
|
+
runner._abortController.abort();
|
|
123
|
+
} catch (e) {
|
|
124
|
+
trace('ProcessRunner', () => `Error aborting controller: ${e.message}`);
|
|
125
|
+
}
|
|
126
|
+
runner._abortController = null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Cleanup child process reference
|
|
131
|
+
* @param {object} runner - ProcessRunner instance
|
|
132
|
+
*/
|
|
133
|
+
function cleanupChildProcess(runner) {
|
|
134
|
+
if (!runner.child) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
trace('ProcessRunner', () => `Cleaning up child process ${runner.child.pid}`);
|
|
138
|
+
try {
|
|
139
|
+
runner.child.removeAllListeners?.();
|
|
140
|
+
} catch (e) {
|
|
141
|
+
trace('ProcessRunner', () => `Error removing listeners: ${e.message}`);
|
|
142
|
+
}
|
|
143
|
+
runner.child = null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Cleanup virtual generator
|
|
148
|
+
* @param {object} runner - ProcessRunner instance
|
|
149
|
+
*/
|
|
150
|
+
function cleanupGenerator(runner) {
|
|
151
|
+
if (!runner._virtualGenerator) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
trace('ProcessRunner', () => 'Cleaning up virtual generator');
|
|
155
|
+
try {
|
|
156
|
+
if (runner._virtualGenerator.return) {
|
|
157
|
+
runner._virtualGenerator.return();
|
|
158
|
+
}
|
|
159
|
+
} catch (e) {
|
|
160
|
+
trace('ProcessRunner', () => `Error closing generator: ${e.message}`);
|
|
161
|
+
}
|
|
162
|
+
runner._virtualGenerator = null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Cleanup pipeline components
|
|
167
|
+
* @param {object} runner - ProcessRunner instance
|
|
168
|
+
*/
|
|
169
|
+
function cleanupPipeline(runner) {
|
|
170
|
+
if (runner.spec?.mode !== 'pipeline') {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
trace('ProcessRunner', () => 'Cleaning up pipeline components');
|
|
174
|
+
if (runner.spec.source && typeof runner.spec.source._cleanup === 'function') {
|
|
175
|
+
runner.spec.source._cleanup();
|
|
176
|
+
}
|
|
177
|
+
if (
|
|
178
|
+
runner.spec.destination &&
|
|
179
|
+
typeof runner.spec.destination._cleanup === 'function'
|
|
180
|
+
) {
|
|
181
|
+
runner.spec.destination._cleanup();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* ProcessRunner - Enhanced process runner with streaming capabilities
|
|
187
|
+
* Extends StreamEmitter for event-based output handling
|
|
188
|
+
*/
|
|
189
|
+
class ProcessRunner extends StreamEmitter {
|
|
190
|
+
constructor(spec, options = {}) {
|
|
191
|
+
super();
|
|
192
|
+
|
|
193
|
+
trace(
|
|
194
|
+
'ProcessRunner',
|
|
195
|
+
() =>
|
|
196
|
+
`constructor ENTER | ${JSON.stringify(
|
|
197
|
+
{
|
|
198
|
+
spec:
|
|
199
|
+
typeof spec === 'object'
|
|
200
|
+
? { ...spec, command: spec.command?.slice(0, 100) }
|
|
201
|
+
: spec,
|
|
202
|
+
options,
|
|
203
|
+
},
|
|
204
|
+
null,
|
|
205
|
+
2
|
|
206
|
+
)}`
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
this.spec = spec;
|
|
210
|
+
this.options = {
|
|
211
|
+
mirror: true,
|
|
212
|
+
capture: true,
|
|
213
|
+
stdin: 'inherit',
|
|
214
|
+
cwd: undefined,
|
|
215
|
+
env: undefined,
|
|
216
|
+
interactive: false,
|
|
217
|
+
shellOperators: true,
|
|
218
|
+
...options,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
this.outChunks = this.options.capture ? [] : null;
|
|
222
|
+
this.errChunks = this.options.capture ? [] : null;
|
|
223
|
+
this.inChunks =
|
|
224
|
+
this.options.capture && this.options.stdin === 'inherit'
|
|
225
|
+
? []
|
|
226
|
+
: this.options.capture &&
|
|
227
|
+
(typeof this.options.stdin === 'string' ||
|
|
228
|
+
Buffer.isBuffer(this.options.stdin))
|
|
229
|
+
? [Buffer.from(this.options.stdin)]
|
|
230
|
+
: [];
|
|
231
|
+
|
|
232
|
+
this.result = null;
|
|
233
|
+
this.child = null;
|
|
234
|
+
this.started = false;
|
|
235
|
+
this.finished = false;
|
|
236
|
+
|
|
237
|
+
this.promise = null;
|
|
238
|
+
this._mode = null;
|
|
239
|
+
|
|
240
|
+
this._cancelled = false;
|
|
241
|
+
this._cancellationSignal = null;
|
|
242
|
+
this._virtualGenerator = null;
|
|
243
|
+
this._abortController = new AbortController();
|
|
244
|
+
|
|
245
|
+
activeProcessRunners.add(this);
|
|
246
|
+
monitorParentStreams();
|
|
247
|
+
|
|
248
|
+
trace(
|
|
249
|
+
'ProcessRunner',
|
|
250
|
+
() =>
|
|
251
|
+
`Added to activeProcessRunners | ${JSON.stringify(
|
|
252
|
+
{
|
|
253
|
+
command: this.spec?.command || 'unknown',
|
|
254
|
+
totalActive: activeProcessRunners.size,
|
|
255
|
+
},
|
|
256
|
+
null,
|
|
257
|
+
2
|
|
258
|
+
)}`
|
|
259
|
+
);
|
|
260
|
+
installSignalHandlers();
|
|
261
|
+
|
|
262
|
+
this.finished = false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Stream property getters
|
|
266
|
+
get stdout() {
|
|
267
|
+
trace(
|
|
268
|
+
'ProcessRunner',
|
|
269
|
+
() =>
|
|
270
|
+
`stdout getter accessed | ${JSON.stringify(
|
|
271
|
+
{
|
|
272
|
+
hasChild: !!this.child,
|
|
273
|
+
hasStdout: !!(this.child && this.child.stdout),
|
|
274
|
+
},
|
|
275
|
+
null,
|
|
276
|
+
2
|
|
277
|
+
)}`
|
|
278
|
+
);
|
|
279
|
+
return this.child ? this.child.stdout : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get stderr() {
|
|
283
|
+
trace(
|
|
284
|
+
'ProcessRunner',
|
|
285
|
+
() =>
|
|
286
|
+
`stderr getter accessed | ${JSON.stringify(
|
|
287
|
+
{
|
|
288
|
+
hasChild: !!this.child,
|
|
289
|
+
hasStderr: !!(this.child && this.child.stderr),
|
|
290
|
+
},
|
|
291
|
+
null,
|
|
292
|
+
2
|
|
293
|
+
)}`
|
|
294
|
+
);
|
|
295
|
+
return this.child ? this.child.stderr : null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
get stdin() {
|
|
299
|
+
trace(
|
|
300
|
+
'ProcessRunner',
|
|
301
|
+
() =>
|
|
302
|
+
`stdin getter accessed | ${JSON.stringify(
|
|
303
|
+
{
|
|
304
|
+
hasChild: !!this.child,
|
|
305
|
+
hasStdin: !!(this.child && this.child.stdin),
|
|
306
|
+
},
|
|
307
|
+
null,
|
|
308
|
+
2
|
|
309
|
+
)}`
|
|
310
|
+
);
|
|
311
|
+
return this.child ? this.child.stdin : null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_autoStartIfNeeded(reason) {
|
|
315
|
+
if (!this.started && !this.finished) {
|
|
316
|
+
trace('ProcessRunner', () => `Auto-starting process due to ${reason}`);
|
|
317
|
+
this.start({
|
|
318
|
+
mode: 'async',
|
|
319
|
+
stdin: 'pipe',
|
|
320
|
+
stdout: 'pipe',
|
|
321
|
+
stderr: 'pipe',
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
get streams() {
|
|
327
|
+
const self = this;
|
|
328
|
+
return {
|
|
329
|
+
get stdin() {
|
|
330
|
+
trace('ProcessRunner.streams', () => `stdin access`);
|
|
331
|
+
return getStdinStream(self);
|
|
332
|
+
},
|
|
333
|
+
get stdout() {
|
|
334
|
+
trace('ProcessRunner.streams', () => `stdout access`);
|
|
335
|
+
return getOrWaitForStream(self, 'stdout');
|
|
336
|
+
},
|
|
337
|
+
get stderr() {
|
|
338
|
+
trace('ProcessRunner.streams', () => `stderr access`);
|
|
339
|
+
return getOrWaitForStream(self, 'stderr');
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
get buffers() {
|
|
345
|
+
const self = this;
|
|
346
|
+
return {
|
|
347
|
+
get stdin() {
|
|
348
|
+
self._autoStartIfNeeded('buffers.stdin access');
|
|
349
|
+
if (self.finished && self.result) {
|
|
350
|
+
return Buffer.from(self.result.stdin || '', 'utf8');
|
|
351
|
+
}
|
|
352
|
+
return self.then
|
|
353
|
+
? self.then((result) => Buffer.from(result.stdin || '', 'utf8'))
|
|
354
|
+
: Promise.resolve(Buffer.alloc(0));
|
|
355
|
+
},
|
|
356
|
+
get stdout() {
|
|
357
|
+
self._autoStartIfNeeded('buffers.stdout access');
|
|
358
|
+
if (self.finished && self.result) {
|
|
359
|
+
return Buffer.from(self.result.stdout || '', 'utf8');
|
|
360
|
+
}
|
|
361
|
+
return self.then
|
|
362
|
+
? self.then((result) => Buffer.from(result.stdout || '', 'utf8'))
|
|
363
|
+
: Promise.resolve(Buffer.alloc(0));
|
|
364
|
+
},
|
|
365
|
+
get stderr() {
|
|
366
|
+
self._autoStartIfNeeded('buffers.stderr access');
|
|
367
|
+
if (self.finished && self.result) {
|
|
368
|
+
return Buffer.from(self.result.stderr || '', 'utf8');
|
|
369
|
+
}
|
|
370
|
+
return self.then
|
|
371
|
+
? self.then((result) => Buffer.from(result.stderr || '', 'utf8'))
|
|
372
|
+
: Promise.resolve(Buffer.alloc(0));
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
get strings() {
|
|
378
|
+
const self = this;
|
|
379
|
+
return {
|
|
380
|
+
get stdin() {
|
|
381
|
+
self._autoStartIfNeeded('strings.stdin access');
|
|
382
|
+
if (self.finished && self.result) {
|
|
383
|
+
return self.result.stdin || '';
|
|
384
|
+
}
|
|
385
|
+
return self.then
|
|
386
|
+
? self.then((result) => result.stdin || '')
|
|
387
|
+
: Promise.resolve('');
|
|
388
|
+
},
|
|
389
|
+
get stdout() {
|
|
390
|
+
self._autoStartIfNeeded('strings.stdout access');
|
|
391
|
+
if (self.finished && self.result) {
|
|
392
|
+
return self.result.stdout || '';
|
|
393
|
+
}
|
|
394
|
+
return self.then
|
|
395
|
+
? self.then((result) => result.stdout || '')
|
|
396
|
+
: Promise.resolve('');
|
|
397
|
+
},
|
|
398
|
+
get stderr() {
|
|
399
|
+
self._autoStartIfNeeded('strings.stderr access');
|
|
400
|
+
if (self.finished && self.result) {
|
|
401
|
+
return self.result.stderr || '';
|
|
402
|
+
}
|
|
403
|
+
return self.then
|
|
404
|
+
? self.then((result) => result.stderr || '')
|
|
405
|
+
: Promise.resolve('');
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Centralized method to properly finish a process with correct event emission order
|
|
411
|
+
finish(result) {
|
|
412
|
+
trace(
|
|
413
|
+
'ProcessRunner',
|
|
414
|
+
() =>
|
|
415
|
+
`finish() called | ${JSON.stringify(
|
|
416
|
+
{
|
|
417
|
+
alreadyFinished: this.finished,
|
|
418
|
+
resultCode: result?.code,
|
|
419
|
+
hasStdout: !!result?.stdout,
|
|
420
|
+
hasStderr: !!result?.stderr,
|
|
421
|
+
command: this.spec?.command?.slice(0, 50),
|
|
422
|
+
},
|
|
423
|
+
null,
|
|
424
|
+
2
|
|
425
|
+
)}`
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
if (this.finished) {
|
|
429
|
+
trace(
|
|
430
|
+
'ProcessRunner',
|
|
431
|
+
() => `Already finished, returning existing result`
|
|
432
|
+
);
|
|
433
|
+
return this.result || result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
this.result = result;
|
|
437
|
+
trace('ProcessRunner', () => `Result stored, about to emit events`);
|
|
438
|
+
|
|
439
|
+
this.emit('end', result);
|
|
440
|
+
trace('ProcessRunner', () => `'end' event emitted`);
|
|
441
|
+
this.emit('exit', result.code);
|
|
442
|
+
trace(
|
|
443
|
+
'ProcessRunner',
|
|
444
|
+
() => `'exit' event emitted with code ${result.code}`
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
this.finished = true;
|
|
448
|
+
trace('ProcessRunner', () => `Marked as finished, calling cleanup`);
|
|
449
|
+
|
|
450
|
+
this._cleanup();
|
|
451
|
+
trace('ProcessRunner', () => `Cleanup completed`);
|
|
452
|
+
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
_emitProcessedData(type, buf) {
|
|
457
|
+
if (this._cancelled) {
|
|
458
|
+
trace(
|
|
459
|
+
'ProcessRunner',
|
|
460
|
+
() => 'Skipping data emission - process cancelled'
|
|
461
|
+
);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const processedBuf = processOutput(buf, this.options.ansi);
|
|
465
|
+
this.emit(type, processedBuf);
|
|
466
|
+
this.emit('data', { type, data: processedBuf });
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
_handleParentStreamClosure() {
|
|
470
|
+
if (this.finished || this._cancelled) {
|
|
471
|
+
trace(
|
|
472
|
+
'ProcessRunner',
|
|
473
|
+
() =>
|
|
474
|
+
`Parent stream closure ignored | ${JSON.stringify({
|
|
475
|
+
finished: this.finished,
|
|
476
|
+
cancelled: this._cancelled,
|
|
477
|
+
})}`
|
|
478
|
+
);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
trace(
|
|
483
|
+
'ProcessRunner',
|
|
484
|
+
() =>
|
|
485
|
+
`Handling parent stream closure | ${JSON.stringify(
|
|
486
|
+
{
|
|
487
|
+
started: this.started,
|
|
488
|
+
hasChild: !!this.child,
|
|
489
|
+
command: this.spec.command?.slice(0, 50) || this.spec.file,
|
|
490
|
+
},
|
|
491
|
+
null,
|
|
492
|
+
2
|
|
493
|
+
)}`
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
this._cancelled = true;
|
|
497
|
+
|
|
498
|
+
if (this._abortController) {
|
|
499
|
+
this._abortController.abort();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (this.child) {
|
|
503
|
+
try {
|
|
504
|
+
if (this.child.stdin && typeof this.child.stdin.end === 'function') {
|
|
505
|
+
this.child.stdin.end();
|
|
506
|
+
} else if (
|
|
507
|
+
isBun &&
|
|
508
|
+
this.child.stdin &&
|
|
509
|
+
typeof this.child.stdin.getWriter === 'function'
|
|
510
|
+
) {
|
|
511
|
+
const writer = this.child.stdin.getWriter();
|
|
512
|
+
writer.close().catch(() => {});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
setImmediate(() => {
|
|
516
|
+
if (this.child && !this.finished) {
|
|
517
|
+
trace(
|
|
518
|
+
'ProcessRunner',
|
|
519
|
+
() => 'Terminating child process after parent stream closure'
|
|
520
|
+
);
|
|
521
|
+
if (typeof this.child.kill === 'function') {
|
|
522
|
+
this.child.kill('SIGTERM');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
} catch (error) {
|
|
527
|
+
trace(
|
|
528
|
+
'ProcessRunner',
|
|
529
|
+
() =>
|
|
530
|
+
`Error during graceful shutdown | ${JSON.stringify({ error: error.message }, null, 2)}`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
this._cleanup();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
_cleanup() {
|
|
539
|
+
trace(
|
|
540
|
+
'ProcessRunner',
|
|
541
|
+
() => `_cleanup() | active=${activeProcessRunners.size}`
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
activeProcessRunners.delete(this);
|
|
545
|
+
cleanupPipeline(this);
|
|
546
|
+
|
|
547
|
+
if (activeProcessRunners.size === 0) {
|
|
548
|
+
uninstallSignalHandlers();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (this.listeners) {
|
|
552
|
+
this.listeners.clear();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
cleanupAbortController(this);
|
|
556
|
+
cleanupChildProcess(this);
|
|
557
|
+
cleanupGenerator(this);
|
|
558
|
+
|
|
559
|
+
trace('ProcessRunner', () => `_cleanup() completed`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export { ProcessRunner, isBun };
|