@optique/testing 1.3.0-dev.2554 → 1.3.0-dev.2559
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/dist/cli.cjs +277 -140
- package/dist/cli.js +278 -141
- package/package.json +4 -4
package/dist/cli.cjs
CHANGED
|
@@ -1,11 +1,263 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
-
const node_child_process = require_chunk.__toESM(require("node:child_process"));
|
|
3
2
|
const node_path = require_chunk.__toESM(require("node:path"));
|
|
4
3
|
const node_process = require_chunk.__toESM(require("node:process"));
|
|
5
4
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
5
|
+
const node_child_process = require_chunk.__toESM(require("node:child_process"));
|
|
6
6
|
const node_events = require_chunk.__toESM(require("node:events"));
|
|
7
7
|
const node_stream = require_chunk.__toESM(require("node:stream"));
|
|
8
8
|
|
|
9
|
+
//#region src/cli-cleanup.ts
|
|
10
|
+
const systemClock = {
|
|
11
|
+
now: () => performance.now(),
|
|
12
|
+
schedule(callback, delay) {
|
|
13
|
+
const timer = setTimeout(callback, delay);
|
|
14
|
+
return () => clearTimeout(timer);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Creates deadline waits driven by a monotonic clock and lifecycle notifications.
|
|
19
|
+
* @param clock The clock and cancellable timer scheduler.
|
|
20
|
+
* @returns A waiter that owns and disposes its outstanding timers.
|
|
21
|
+
*/
|
|
22
|
+
function createCleanupWaiter(clock = systemClock) {
|
|
23
|
+
const pending = /* @__PURE__ */ new Set();
|
|
24
|
+
let disposed = false;
|
|
25
|
+
return {
|
|
26
|
+
now: () => clock.now(),
|
|
27
|
+
notify() {
|
|
28
|
+
for (const wait of [...pending]) wait.wake();
|
|
29
|
+
},
|
|
30
|
+
waitFor(predicate, deadline) {
|
|
31
|
+
if (predicate()) return Promise.resolve(true);
|
|
32
|
+
if (disposed || deadline <= clock.now()) return Promise.resolve(false);
|
|
33
|
+
return new Promise((resolve$1) => {
|
|
34
|
+
let cancel = () => {};
|
|
35
|
+
let finished = false;
|
|
36
|
+
const wait = {
|
|
37
|
+
wake() {
|
|
38
|
+
if (predicate()) wait.finish(true);
|
|
39
|
+
},
|
|
40
|
+
finish(value) {
|
|
41
|
+
if (finished) return;
|
|
42
|
+
finished = true;
|
|
43
|
+
cancel();
|
|
44
|
+
pending.delete(wait);
|
|
45
|
+
resolve$1(value);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
pending.add(wait);
|
|
49
|
+
cancel = clock.schedule(() => wait.finish(false), Math.max(0, deadline - clock.now()));
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
async observeClose() {
|
|
53
|
+
await Promise.resolve();
|
|
54
|
+
if (disposed) return;
|
|
55
|
+
await new Promise((resolve$1) => {
|
|
56
|
+
let cancel = () => {};
|
|
57
|
+
const wait = {
|
|
58
|
+
wake() {},
|
|
59
|
+
finish() {
|
|
60
|
+
cancel();
|
|
61
|
+
pending.delete(wait);
|
|
62
|
+
resolve$1();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
pending.add(wait);
|
|
66
|
+
cancel = clock.schedule(() => wait.finish(), 0);
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
dispose() {
|
|
70
|
+
disposed = true;
|
|
71
|
+
for (const wait of [...pending]) wait.finish(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function errorCode$1(error) {
|
|
76
|
+
return error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Terminates a failed invocation and closes its owned process streams.
|
|
80
|
+
* @param context Live invocation state and cleanup error sink.
|
|
81
|
+
* @param dependencies Internal platform operations and measurement overrides.
|
|
82
|
+
* @returns Completion after bounded cleanup attempts.
|
|
83
|
+
*/
|
|
84
|
+
async function cleanupCliProcess(context, dependencies = {}) {
|
|
85
|
+
const { child, getState, waiter } = context;
|
|
86
|
+
if (child === void 0) return;
|
|
87
|
+
const platform = dependencies.platform ?? node_process.default.platform;
|
|
88
|
+
const deadline = waiter.now() + (dependencies.budget ?? 2e3);
|
|
89
|
+
let active = true;
|
|
90
|
+
let helper;
|
|
91
|
+
let helperClosed = false;
|
|
92
|
+
let helperExited = false;
|
|
93
|
+
let helperFailed = false;
|
|
94
|
+
let helperSpawned = false;
|
|
95
|
+
let helperCode = null;
|
|
96
|
+
let diagnostic = "";
|
|
97
|
+
function record(stage, cause) {
|
|
98
|
+
if (!active) return;
|
|
99
|
+
context.recordError(new Error(stage, { cause }));
|
|
100
|
+
}
|
|
101
|
+
function attempt(stage, action) {
|
|
102
|
+
try {
|
|
103
|
+
action();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
record(stage, error);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function killChild(signal) {
|
|
109
|
+
if (child?.pid === void 0 || getState().exited || getState().closed) return;
|
|
110
|
+
attempt("The CLI process could not be signaled during cleanup.", () => {
|
|
111
|
+
child.kill(signal);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function groupSignal(signal) {
|
|
115
|
+
if (child?.pid === void 0) return false;
|
|
116
|
+
try {
|
|
117
|
+
if (dependencies.signalGroup !== void 0) dependencies.signalGroup(-child.pid, signal);
|
|
118
|
+
else node_process.default.kill(-child.pid, signal);
|
|
119
|
+
return true;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (errorCode$1(error) !== "ESRCH") record("The CLI process group could not be signaled during cleanup.", error);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function destroyStreams(target, name) {
|
|
126
|
+
for (const stream of [
|
|
127
|
+
"stdin",
|
|
128
|
+
"stdout",
|
|
129
|
+
"stderr"
|
|
130
|
+
]) attempt(`${name} ${stream} could not be closed during cleanup.`, () => {
|
|
131
|
+
target[stream].destroy();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function retireHelper() {
|
|
135
|
+
const target = helper;
|
|
136
|
+
if (target === void 0) return;
|
|
137
|
+
if (!helperExited && !helperClosed) attempt("The tree cleanup command could not be terminated.", () => {
|
|
138
|
+
target.kill("SIGKILL");
|
|
139
|
+
});
|
|
140
|
+
destroyStreams(target, "Tree cleanup command");
|
|
141
|
+
}
|
|
142
|
+
async function taskkill(pid) {
|
|
143
|
+
const root = dependencies.systemRoot?.() ?? (dependencies.systemRoot === void 0 ? node_process.default.env.SystemRoot ?? node_process.default.env.windir : void 0);
|
|
144
|
+
if (root === void 0 || !node_path.win32.isAbsolute(root)) {
|
|
145
|
+
record("Windows system directory is unavailable for tree cleanup.");
|
|
146
|
+
killChild("SIGKILL");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const command = node_path.win32.join(root, "System32", "taskkill.exe");
|
|
151
|
+
const args = [
|
|
152
|
+
"/PID",
|
|
153
|
+
String(pid),
|
|
154
|
+
"/T",
|
|
155
|
+
"/F"
|
|
156
|
+
];
|
|
157
|
+
helper = dependencies.spawnHelper?.(command, args) ?? (0, node_child_process.spawn)(command, args, {
|
|
158
|
+
shell: false,
|
|
159
|
+
windowsHide: true,
|
|
160
|
+
stdio: "pipe"
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
record("The tree cleanup command could not be launched.", error);
|
|
164
|
+
killChild("SIGKILL");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
helper.on("spawn", () => {
|
|
168
|
+
helperSpawned = true;
|
|
169
|
+
});
|
|
170
|
+
helper.on("exit", () => {
|
|
171
|
+
helperExited = true;
|
|
172
|
+
waiter.notify();
|
|
173
|
+
});
|
|
174
|
+
helper.on("close", (code) => {
|
|
175
|
+
helperClosed = true;
|
|
176
|
+
helperCode = code;
|
|
177
|
+
waiter.notify();
|
|
178
|
+
});
|
|
179
|
+
helper.on("error", (error) => {
|
|
180
|
+
helperFailed = true;
|
|
181
|
+
record(helperSpawned ? "The tree cleanup command failed." : "The tree cleanup command could not be launched.", error);
|
|
182
|
+
waiter.notify();
|
|
183
|
+
});
|
|
184
|
+
helper.stdin.on("error", () => {});
|
|
185
|
+
helper.stdout.on("error", (error) => {
|
|
186
|
+
record("Tree cleanup command stdout could not be read.", error);
|
|
187
|
+
});
|
|
188
|
+
helper.stderr.on("error", (error) => {
|
|
189
|
+
record("Tree cleanup command stderr could not be read.", error);
|
|
190
|
+
});
|
|
191
|
+
helper.stderr.setEncoding("utf8");
|
|
192
|
+
helper.stderr.on("data", (text) => {
|
|
193
|
+
if (active) diagnostic += text;
|
|
194
|
+
});
|
|
195
|
+
helper.stdout.resume();
|
|
196
|
+
helper.stdin.end();
|
|
197
|
+
const completed = await waiter.waitFor(() => helperClosed || helperFailed, deadline);
|
|
198
|
+
if (!completed) {
|
|
199
|
+
record("The tree cleanup command timed out.");
|
|
200
|
+
retireHelper();
|
|
201
|
+
killChild("SIGKILL");
|
|
202
|
+
} else if (helperFailed) {
|
|
203
|
+
retireHelper();
|
|
204
|
+
killChild("SIGKILL");
|
|
205
|
+
} else if (helperCode !== 0) {
|
|
206
|
+
record(`Tree cleanup command exited with code ${helperCode}: ${diagnostic}`);
|
|
207
|
+
killChild("SIGKILL");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const exited = () => getState().exited || getState().closed;
|
|
211
|
+
const targetClosed = () => {
|
|
212
|
+
const state = getState();
|
|
213
|
+
return state.closed && state.stdinClosed && state.stdoutClosed && state.stderrClosed;
|
|
214
|
+
};
|
|
215
|
+
const allClosed = () => targetClosed() && (helper === void 0 || helperClosed);
|
|
216
|
+
try {
|
|
217
|
+
attempt("CLI stdin could not be closed during cleanup.", () => {
|
|
218
|
+
child.stdin.destroy();
|
|
219
|
+
});
|
|
220
|
+
if (child.pid !== void 0) if (platform === "win32") if (context.mode === "tree") if (exited()) record("Cannot trace a Windows process tree after its root exits.");
|
|
221
|
+
else await taskkill(child.pid);
|
|
222
|
+
else killChild("SIGKILL");
|
|
223
|
+
else if (context.mode === "tree") {
|
|
224
|
+
const found = groupSignal("SIGTERM");
|
|
225
|
+
if (!found && !exited()) record("The child process group is unavailable.");
|
|
226
|
+
killChild("SIGTERM");
|
|
227
|
+
if (found && !await waiter.waitFor(() => exited() && !groupSignal(0), Math.min(deadline, waiter.now() + 1e3))) groupSignal("SIGKILL");
|
|
228
|
+
killChild("SIGKILL");
|
|
229
|
+
} else {
|
|
230
|
+
killChild("SIGTERM");
|
|
231
|
+
if (!await waiter.waitFor(exited, Math.min(deadline, waiter.now() + 1e3))) killChild("SIGKILL");
|
|
232
|
+
}
|
|
233
|
+
if (!await waiter.waitFor(exited, deadline)) {
|
|
234
|
+
record("The CLI process did not exit during cleanup.");
|
|
235
|
+
killChild("SIGKILL");
|
|
236
|
+
}
|
|
237
|
+
await waiter.waitFor(() => getState().closed, Math.min(deadline, waiter.now() + 250));
|
|
238
|
+
} catch (error) {
|
|
239
|
+
record("Process termination failed during cleanup.", error);
|
|
240
|
+
retireHelper();
|
|
241
|
+
killChild("SIGKILL");
|
|
242
|
+
}
|
|
243
|
+
destroyStreams(child, "CLI process");
|
|
244
|
+
if (!await waiter.waitFor(allClosed, deadline)) await waiter.observeClose();
|
|
245
|
+
if (helper !== void 0 && !helperClosed) {
|
|
246
|
+
record("The tree cleanup command did not close.");
|
|
247
|
+
attempt("The tree cleanup command could not be unreferenced.", () => {
|
|
248
|
+
helper?.unref();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (!targetClosed()) {
|
|
252
|
+
record("The CLI process streams did not close during cleanup.");
|
|
253
|
+
attempt("The CLI process could not be unreferenced during cleanup.", () => {
|
|
254
|
+
child.unref();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
active = false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
//#endregion
|
|
9
261
|
//#region src/cli-process.ts
|
|
10
262
|
function isDenoRuntime(value) {
|
|
11
263
|
return typeof value === "object" && value !== null && "Command" in value && typeof value.Command === "function";
|
|
@@ -274,7 +526,7 @@ function runProcess(command, args, options) {
|
|
|
274
526
|
let failure;
|
|
275
527
|
const cleanupErrors = [];
|
|
276
528
|
let timer;
|
|
277
|
-
const
|
|
529
|
+
const waiter = createCleanupWaiter();
|
|
278
530
|
const snapshot = () => ({
|
|
279
531
|
stdout: stdout.join(""),
|
|
280
532
|
stderr: stderr.join(""),
|
|
@@ -288,6 +540,7 @@ function runProcess(command, args, options) {
|
|
|
288
540
|
};
|
|
289
541
|
function release() {
|
|
290
542
|
clearInvocation();
|
|
543
|
+
if (settled) waiter.dispose();
|
|
291
544
|
child?.stdout.removeListener("data", onOut);
|
|
292
545
|
child?.stderr.removeListener("data", onErr);
|
|
293
546
|
if (closed) child?.removeListener("error", onError);
|
|
@@ -296,7 +549,7 @@ function runProcess(command, args, options) {
|
|
|
296
549
|
if (inClose) child?.stdin.removeListener("error", onInputError);
|
|
297
550
|
}
|
|
298
551
|
function update() {
|
|
299
|
-
|
|
552
|
+
waiter.notify();
|
|
300
553
|
if (settled) {
|
|
301
554
|
release();
|
|
302
555
|
return;
|
|
@@ -307,21 +560,6 @@ function runProcess(command, args, options) {
|
|
|
307
560
|
resolveResult(snapshot());
|
|
308
561
|
}
|
|
309
562
|
}
|
|
310
|
-
function waitFor(predicate, milliseconds) {
|
|
311
|
-
if (predicate()) return Promise.resolve(true);
|
|
312
|
-
return new Promise((done) => {
|
|
313
|
-
const finish = (value) => {
|
|
314
|
-
clearTimeout(timeout);
|
|
315
|
-
waiters.delete(wake);
|
|
316
|
-
done(value);
|
|
317
|
-
};
|
|
318
|
-
const wake = () => {
|
|
319
|
-
if (predicate()) finish(true);
|
|
320
|
-
};
|
|
321
|
-
const timeout = setTimeout(() => finish(false), Math.max(0, milliseconds));
|
|
322
|
-
waiters.add(wake);
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
563
|
function fail(reason, message, cause) {
|
|
326
564
|
if (failure || settled) return;
|
|
327
565
|
failure = {
|
|
@@ -334,129 +572,22 @@ function runProcess(command, args, options) {
|
|
|
334
572
|
cleanup();
|
|
335
573
|
});
|
|
336
574
|
}
|
|
337
|
-
function killChild(kind) {
|
|
338
|
-
if (!child?.pid || exited || closed) return;
|
|
339
|
-
try {
|
|
340
|
-
child.kill(kind);
|
|
341
|
-
} catch (error) {
|
|
342
|
-
cleanupErrors.push(error);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
function groupSignal(kind) {
|
|
346
|
-
if (!child?.pid) return false;
|
|
347
|
-
try {
|
|
348
|
-
node_process.default.kill(-child.pid, kind);
|
|
349
|
-
return true;
|
|
350
|
-
} catch (error) {
|
|
351
|
-
if (errorCode(error) !== "ESRCH") cleanupErrors.push(error);
|
|
352
|
-
return false;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
async function taskkill(deadline, pid) {
|
|
356
|
-
let helper;
|
|
357
|
-
const root = node_process.default.env.SystemRoot ?? node_process.default.env.windir;
|
|
358
|
-
if (root === void 0 || !(0, node_path.isAbsolute)(root)) {
|
|
359
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("Windows system directory is unavailable."));
|
|
360
|
-
killChild("SIGKILL");
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
try {
|
|
364
|
-
helper = (0, node_child_process.spawn)((0, node_path.join)(root, "System32", "taskkill.exe"), [
|
|
365
|
-
"/PID",
|
|
366
|
-
String(pid),
|
|
367
|
-
"/T",
|
|
368
|
-
"/F"
|
|
369
|
-
], {
|
|
370
|
-
shell: false,
|
|
371
|
-
windowsHide: true,
|
|
372
|
-
stdio: "pipe"
|
|
373
|
-
});
|
|
374
|
-
} catch (error) {
|
|
375
|
-
cleanupErrors.push(error);
|
|
376
|
-
killChild("SIGKILL");
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
let helperClosed = false;
|
|
380
|
-
let helperError;
|
|
381
|
-
let helperCode = null;
|
|
382
|
-
let diagnostic = "";
|
|
383
|
-
helper.stdout.resume();
|
|
384
|
-
helper.stderr.setEncoding("utf8");
|
|
385
|
-
helper.stderr.on("data", (text) => {
|
|
386
|
-
diagnostic += text;
|
|
387
|
-
});
|
|
388
|
-
helper.stdin.on("error", () => {});
|
|
389
|
-
helper.stdout.on("error", (error) => {
|
|
390
|
-
helperError = error;
|
|
391
|
-
});
|
|
392
|
-
helper.stderr.on("error", (error) => {
|
|
393
|
-
helperError = error;
|
|
394
|
-
});
|
|
395
|
-
helper.on("error", (error) => {
|
|
396
|
-
helperError = error;
|
|
397
|
-
killChild("SIGKILL");
|
|
398
|
-
});
|
|
399
|
-
helper.on("close", (code) => {
|
|
400
|
-
helperClosed = true;
|
|
401
|
-
helperCode = code;
|
|
402
|
-
update();
|
|
403
|
-
});
|
|
404
|
-
helper.stdin.end();
|
|
405
|
-
if (!await waitFor(() => helperClosed, Math.min(1e3, deadline - Date.now()))) {
|
|
406
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("Tree cleanup command timed out."));
|
|
407
|
-
try {
|
|
408
|
-
helper.kill("SIGKILL");
|
|
409
|
-
} catch (error) {
|
|
410
|
-
cleanupErrors.push(error);
|
|
411
|
-
}
|
|
412
|
-
killChild("SIGKILL");
|
|
413
|
-
helper.stdin.destroy();
|
|
414
|
-
helper.stdout.destroy();
|
|
415
|
-
helper.stderr.destroy();
|
|
416
|
-
if (!await waitFor(() => helperClosed, deadline - Date.now())) {
|
|
417
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("Tree cleanup command did not close."));
|
|
418
|
-
helper.unref();
|
|
419
|
-
}
|
|
420
|
-
} else if (helperError !== void 0 || helperCode !== 0) {
|
|
421
|
-
cleanupErrors.push(helperError ?? /* @__PURE__ */ new Error(`Tree cleanup exited with code ${helperCode}: ${diagnostic}`));
|
|
422
|
-
killChild("SIGKILL");
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
575
|
async function cleanup() {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
} else {
|
|
440
|
-
killChild("SIGTERM");
|
|
441
|
-
if (!await waitFor(() => exited || closed, 1e3)) killChild("SIGKILL");
|
|
442
|
-
}
|
|
443
|
-
if (!await waitFor(() => exited || closed, deadline - Date.now())) cleanupErrors.push(/* @__PURE__ */ new Error("The CLI process did not exit during cleanup."));
|
|
444
|
-
await waitFor(() => closed, Math.min(250, deadline - Date.now()));
|
|
445
|
-
child.stdout.destroy();
|
|
446
|
-
child.stderr.destroy();
|
|
447
|
-
if (!await waitFor(() => closed && outClose && errClose && inClose, deadline - Date.now())) {
|
|
448
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("The CLI process streams did not close during cleanup."));
|
|
449
|
-
child.unref();
|
|
450
|
-
}
|
|
576
|
+
await cleanupCliProcess({
|
|
577
|
+
child,
|
|
578
|
+
mode: options.cleanup,
|
|
579
|
+
getState: () => ({
|
|
580
|
+
exited,
|
|
581
|
+
closed,
|
|
582
|
+
stdinClosed: inClose,
|
|
583
|
+
stdoutClosed: outClose,
|
|
584
|
+
stderrClosed: errClose
|
|
585
|
+
}),
|
|
586
|
+
waiter,
|
|
587
|
+
recordError: (error) => {
|
|
588
|
+
if (!settled) cleanupErrors.push(error);
|
|
451
589
|
}
|
|
452
|
-
}
|
|
453
|
-
cleanupErrors.push(error);
|
|
454
|
-
killChild("SIGKILL");
|
|
455
|
-
child?.stdin.destroy();
|
|
456
|
-
child?.stdout.destroy();
|
|
457
|
-
child?.stderr.destroy();
|
|
458
|
-
child?.unref();
|
|
459
|
-
}
|
|
590
|
+
});
|
|
460
591
|
if (failure === void 0 || settled) return;
|
|
461
592
|
const result = snapshot();
|
|
462
593
|
const primary = new CliInvocationError(failure.reason, failure.message, result, { cause: failure.cause });
|
|
@@ -471,10 +602,16 @@ function runProcess(command, args, options) {
|
|
|
471
602
|
stderr.push(text);
|
|
472
603
|
}
|
|
473
604
|
function onError(error) {
|
|
474
|
-
if (
|
|
605
|
+
if (settled) return;
|
|
606
|
+
if (failure) cleanupErrors.push(new Error("The CLI process failed during cleanup.", { cause: error }));
|
|
475
607
|
else fail(spawned ? "io" : "spawn", "Could not execute the CLI.", error);
|
|
476
608
|
}
|
|
477
609
|
function onIoError(error) {
|
|
610
|
+
if (settled) return;
|
|
611
|
+
if (failure) {
|
|
612
|
+
cleanupErrors.push(new Error("CLI output could not be read during cleanup.", { cause: error }));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
478
615
|
fail("io", "Could not capture CLI output.", error);
|
|
479
616
|
}
|
|
480
617
|
function onInputError(error) {
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,262 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
1
|
+
import { resolve, win32 } from "node:path";
|
|
3
2
|
import process from "node:process";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
5
|
import { EventEmitter } from "node:events";
|
|
6
6
|
import { Readable, Writable } from "node:stream";
|
|
7
7
|
|
|
8
|
+
//#region src/cli-cleanup.ts
|
|
9
|
+
const systemClock = {
|
|
10
|
+
now: () => performance.now(),
|
|
11
|
+
schedule(callback, delay) {
|
|
12
|
+
const timer = setTimeout(callback, delay);
|
|
13
|
+
return () => clearTimeout(timer);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Creates deadline waits driven by a monotonic clock and lifecycle notifications.
|
|
18
|
+
* @param clock The clock and cancellable timer scheduler.
|
|
19
|
+
* @returns A waiter that owns and disposes its outstanding timers.
|
|
20
|
+
*/
|
|
21
|
+
function createCleanupWaiter(clock = systemClock) {
|
|
22
|
+
const pending = /* @__PURE__ */ new Set();
|
|
23
|
+
let disposed = false;
|
|
24
|
+
return {
|
|
25
|
+
now: () => clock.now(),
|
|
26
|
+
notify() {
|
|
27
|
+
for (const wait of [...pending]) wait.wake();
|
|
28
|
+
},
|
|
29
|
+
waitFor(predicate, deadline) {
|
|
30
|
+
if (predicate()) return Promise.resolve(true);
|
|
31
|
+
if (disposed || deadline <= clock.now()) return Promise.resolve(false);
|
|
32
|
+
return new Promise((resolve$1) => {
|
|
33
|
+
let cancel = () => {};
|
|
34
|
+
let finished = false;
|
|
35
|
+
const wait = {
|
|
36
|
+
wake() {
|
|
37
|
+
if (predicate()) wait.finish(true);
|
|
38
|
+
},
|
|
39
|
+
finish(value) {
|
|
40
|
+
if (finished) return;
|
|
41
|
+
finished = true;
|
|
42
|
+
cancel();
|
|
43
|
+
pending.delete(wait);
|
|
44
|
+
resolve$1(value);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
pending.add(wait);
|
|
48
|
+
cancel = clock.schedule(() => wait.finish(false), Math.max(0, deadline - clock.now()));
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
async observeClose() {
|
|
52
|
+
await Promise.resolve();
|
|
53
|
+
if (disposed) return;
|
|
54
|
+
await new Promise((resolve$1) => {
|
|
55
|
+
let cancel = () => {};
|
|
56
|
+
const wait = {
|
|
57
|
+
wake() {},
|
|
58
|
+
finish() {
|
|
59
|
+
cancel();
|
|
60
|
+
pending.delete(wait);
|
|
61
|
+
resolve$1();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
pending.add(wait);
|
|
65
|
+
cancel = clock.schedule(() => wait.finish(), 0);
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
dispose() {
|
|
69
|
+
disposed = true;
|
|
70
|
+
for (const wait of [...pending]) wait.finish(false);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function errorCode$1(error) {
|
|
75
|
+
return error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Terminates a failed invocation and closes its owned process streams.
|
|
79
|
+
* @param context Live invocation state and cleanup error sink.
|
|
80
|
+
* @param dependencies Internal platform operations and measurement overrides.
|
|
81
|
+
* @returns Completion after bounded cleanup attempts.
|
|
82
|
+
*/
|
|
83
|
+
async function cleanupCliProcess(context, dependencies = {}) {
|
|
84
|
+
const { child, getState, waiter } = context;
|
|
85
|
+
if (child === void 0) return;
|
|
86
|
+
const platform = dependencies.platform ?? process.platform;
|
|
87
|
+
const deadline = waiter.now() + (dependencies.budget ?? 2e3);
|
|
88
|
+
let active = true;
|
|
89
|
+
let helper;
|
|
90
|
+
let helperClosed = false;
|
|
91
|
+
let helperExited = false;
|
|
92
|
+
let helperFailed = false;
|
|
93
|
+
let helperSpawned = false;
|
|
94
|
+
let helperCode = null;
|
|
95
|
+
let diagnostic = "";
|
|
96
|
+
function record(stage, cause) {
|
|
97
|
+
if (!active) return;
|
|
98
|
+
context.recordError(new Error(stage, { cause }));
|
|
99
|
+
}
|
|
100
|
+
function attempt(stage, action) {
|
|
101
|
+
try {
|
|
102
|
+
action();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
record(stage, error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function killChild(signal) {
|
|
108
|
+
if (child?.pid === void 0 || getState().exited || getState().closed) return;
|
|
109
|
+
attempt("The CLI process could not be signaled during cleanup.", () => {
|
|
110
|
+
child.kill(signal);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function groupSignal(signal) {
|
|
114
|
+
if (child?.pid === void 0) return false;
|
|
115
|
+
try {
|
|
116
|
+
if (dependencies.signalGroup !== void 0) dependencies.signalGroup(-child.pid, signal);
|
|
117
|
+
else process.kill(-child.pid, signal);
|
|
118
|
+
return true;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (errorCode$1(error) !== "ESRCH") record("The CLI process group could not be signaled during cleanup.", error);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function destroyStreams(target, name) {
|
|
125
|
+
for (const stream of [
|
|
126
|
+
"stdin",
|
|
127
|
+
"stdout",
|
|
128
|
+
"stderr"
|
|
129
|
+
]) attempt(`${name} ${stream} could not be closed during cleanup.`, () => {
|
|
130
|
+
target[stream].destroy();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function retireHelper() {
|
|
134
|
+
const target = helper;
|
|
135
|
+
if (target === void 0) return;
|
|
136
|
+
if (!helperExited && !helperClosed) attempt("The tree cleanup command could not be terminated.", () => {
|
|
137
|
+
target.kill("SIGKILL");
|
|
138
|
+
});
|
|
139
|
+
destroyStreams(target, "Tree cleanup command");
|
|
140
|
+
}
|
|
141
|
+
async function taskkill(pid) {
|
|
142
|
+
const root = dependencies.systemRoot?.() ?? (dependencies.systemRoot === void 0 ? process.env.SystemRoot ?? process.env.windir : void 0);
|
|
143
|
+
if (root === void 0 || !win32.isAbsolute(root)) {
|
|
144
|
+
record("Windows system directory is unavailable for tree cleanup.");
|
|
145
|
+
killChild("SIGKILL");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const command = win32.join(root, "System32", "taskkill.exe");
|
|
150
|
+
const args = [
|
|
151
|
+
"/PID",
|
|
152
|
+
String(pid),
|
|
153
|
+
"/T",
|
|
154
|
+
"/F"
|
|
155
|
+
];
|
|
156
|
+
helper = dependencies.spawnHelper?.(command, args) ?? spawn(command, args, {
|
|
157
|
+
shell: false,
|
|
158
|
+
windowsHide: true,
|
|
159
|
+
stdio: "pipe"
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
record("The tree cleanup command could not be launched.", error);
|
|
163
|
+
killChild("SIGKILL");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
helper.on("spawn", () => {
|
|
167
|
+
helperSpawned = true;
|
|
168
|
+
});
|
|
169
|
+
helper.on("exit", () => {
|
|
170
|
+
helperExited = true;
|
|
171
|
+
waiter.notify();
|
|
172
|
+
});
|
|
173
|
+
helper.on("close", (code) => {
|
|
174
|
+
helperClosed = true;
|
|
175
|
+
helperCode = code;
|
|
176
|
+
waiter.notify();
|
|
177
|
+
});
|
|
178
|
+
helper.on("error", (error) => {
|
|
179
|
+
helperFailed = true;
|
|
180
|
+
record(helperSpawned ? "The tree cleanup command failed." : "The tree cleanup command could not be launched.", error);
|
|
181
|
+
waiter.notify();
|
|
182
|
+
});
|
|
183
|
+
helper.stdin.on("error", () => {});
|
|
184
|
+
helper.stdout.on("error", (error) => {
|
|
185
|
+
record("Tree cleanup command stdout could not be read.", error);
|
|
186
|
+
});
|
|
187
|
+
helper.stderr.on("error", (error) => {
|
|
188
|
+
record("Tree cleanup command stderr could not be read.", error);
|
|
189
|
+
});
|
|
190
|
+
helper.stderr.setEncoding("utf8");
|
|
191
|
+
helper.stderr.on("data", (text) => {
|
|
192
|
+
if (active) diagnostic += text;
|
|
193
|
+
});
|
|
194
|
+
helper.stdout.resume();
|
|
195
|
+
helper.stdin.end();
|
|
196
|
+
const completed = await waiter.waitFor(() => helperClosed || helperFailed, deadline);
|
|
197
|
+
if (!completed) {
|
|
198
|
+
record("The tree cleanup command timed out.");
|
|
199
|
+
retireHelper();
|
|
200
|
+
killChild("SIGKILL");
|
|
201
|
+
} else if (helperFailed) {
|
|
202
|
+
retireHelper();
|
|
203
|
+
killChild("SIGKILL");
|
|
204
|
+
} else if (helperCode !== 0) {
|
|
205
|
+
record(`Tree cleanup command exited with code ${helperCode}: ${diagnostic}`);
|
|
206
|
+
killChild("SIGKILL");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const exited = () => getState().exited || getState().closed;
|
|
210
|
+
const targetClosed = () => {
|
|
211
|
+
const state = getState();
|
|
212
|
+
return state.closed && state.stdinClosed && state.stdoutClosed && state.stderrClosed;
|
|
213
|
+
};
|
|
214
|
+
const allClosed = () => targetClosed() && (helper === void 0 || helperClosed);
|
|
215
|
+
try {
|
|
216
|
+
attempt("CLI stdin could not be closed during cleanup.", () => {
|
|
217
|
+
child.stdin.destroy();
|
|
218
|
+
});
|
|
219
|
+
if (child.pid !== void 0) if (platform === "win32") if (context.mode === "tree") if (exited()) record("Cannot trace a Windows process tree after its root exits.");
|
|
220
|
+
else await taskkill(child.pid);
|
|
221
|
+
else killChild("SIGKILL");
|
|
222
|
+
else if (context.mode === "tree") {
|
|
223
|
+
const found = groupSignal("SIGTERM");
|
|
224
|
+
if (!found && !exited()) record("The child process group is unavailable.");
|
|
225
|
+
killChild("SIGTERM");
|
|
226
|
+
if (found && !await waiter.waitFor(() => exited() && !groupSignal(0), Math.min(deadline, waiter.now() + 1e3))) groupSignal("SIGKILL");
|
|
227
|
+
killChild("SIGKILL");
|
|
228
|
+
} else {
|
|
229
|
+
killChild("SIGTERM");
|
|
230
|
+
if (!await waiter.waitFor(exited, Math.min(deadline, waiter.now() + 1e3))) killChild("SIGKILL");
|
|
231
|
+
}
|
|
232
|
+
if (!await waiter.waitFor(exited, deadline)) {
|
|
233
|
+
record("The CLI process did not exit during cleanup.");
|
|
234
|
+
killChild("SIGKILL");
|
|
235
|
+
}
|
|
236
|
+
await waiter.waitFor(() => getState().closed, Math.min(deadline, waiter.now() + 250));
|
|
237
|
+
} catch (error) {
|
|
238
|
+
record("Process termination failed during cleanup.", error);
|
|
239
|
+
retireHelper();
|
|
240
|
+
killChild("SIGKILL");
|
|
241
|
+
}
|
|
242
|
+
destroyStreams(child, "CLI process");
|
|
243
|
+
if (!await waiter.waitFor(allClosed, deadline)) await waiter.observeClose();
|
|
244
|
+
if (helper !== void 0 && !helperClosed) {
|
|
245
|
+
record("The tree cleanup command did not close.");
|
|
246
|
+
attempt("The tree cleanup command could not be unreferenced.", () => {
|
|
247
|
+
helper?.unref();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (!targetClosed()) {
|
|
251
|
+
record("The CLI process streams did not close during cleanup.");
|
|
252
|
+
attempt("The CLI process could not be unreferenced during cleanup.", () => {
|
|
253
|
+
child.unref();
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
active = false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
8
260
|
//#region src/cli-process.ts
|
|
9
261
|
function isDenoRuntime(value) {
|
|
10
262
|
return typeof value === "object" && value !== null && "Command" in value && typeof value.Command === "function";
|
|
@@ -273,7 +525,7 @@ function runProcess(command, args, options) {
|
|
|
273
525
|
let failure;
|
|
274
526
|
const cleanupErrors = [];
|
|
275
527
|
let timer;
|
|
276
|
-
const
|
|
528
|
+
const waiter = createCleanupWaiter();
|
|
277
529
|
const snapshot = () => ({
|
|
278
530
|
stdout: stdout.join(""),
|
|
279
531
|
stderr: stderr.join(""),
|
|
@@ -287,6 +539,7 @@ function runProcess(command, args, options) {
|
|
|
287
539
|
};
|
|
288
540
|
function release() {
|
|
289
541
|
clearInvocation();
|
|
542
|
+
if (settled) waiter.dispose();
|
|
290
543
|
child?.stdout.removeListener("data", onOut);
|
|
291
544
|
child?.stderr.removeListener("data", onErr);
|
|
292
545
|
if (closed) child?.removeListener("error", onError);
|
|
@@ -295,7 +548,7 @@ function runProcess(command, args, options) {
|
|
|
295
548
|
if (inClose) child?.stdin.removeListener("error", onInputError);
|
|
296
549
|
}
|
|
297
550
|
function update() {
|
|
298
|
-
|
|
551
|
+
waiter.notify();
|
|
299
552
|
if (settled) {
|
|
300
553
|
release();
|
|
301
554
|
return;
|
|
@@ -306,21 +559,6 @@ function runProcess(command, args, options) {
|
|
|
306
559
|
resolveResult(snapshot());
|
|
307
560
|
}
|
|
308
561
|
}
|
|
309
|
-
function waitFor(predicate, milliseconds) {
|
|
310
|
-
if (predicate()) return Promise.resolve(true);
|
|
311
|
-
return new Promise((done) => {
|
|
312
|
-
const finish = (value) => {
|
|
313
|
-
clearTimeout(timeout);
|
|
314
|
-
waiters.delete(wake);
|
|
315
|
-
done(value);
|
|
316
|
-
};
|
|
317
|
-
const wake = () => {
|
|
318
|
-
if (predicate()) finish(true);
|
|
319
|
-
};
|
|
320
|
-
const timeout = setTimeout(() => finish(false), Math.max(0, milliseconds));
|
|
321
|
-
waiters.add(wake);
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
562
|
function fail(reason, message, cause) {
|
|
325
563
|
if (failure || settled) return;
|
|
326
564
|
failure = {
|
|
@@ -333,129 +571,22 @@ function runProcess(command, args, options) {
|
|
|
333
571
|
cleanup();
|
|
334
572
|
});
|
|
335
573
|
}
|
|
336
|
-
function killChild(kind) {
|
|
337
|
-
if (!child?.pid || exited || closed) return;
|
|
338
|
-
try {
|
|
339
|
-
child.kill(kind);
|
|
340
|
-
} catch (error) {
|
|
341
|
-
cleanupErrors.push(error);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
function groupSignal(kind) {
|
|
345
|
-
if (!child?.pid) return false;
|
|
346
|
-
try {
|
|
347
|
-
process.kill(-child.pid, kind);
|
|
348
|
-
return true;
|
|
349
|
-
} catch (error) {
|
|
350
|
-
if (errorCode(error) !== "ESRCH") cleanupErrors.push(error);
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
async function taskkill(deadline, pid) {
|
|
355
|
-
let helper;
|
|
356
|
-
const root = process.env.SystemRoot ?? process.env.windir;
|
|
357
|
-
if (root === void 0 || !isAbsolute(root)) {
|
|
358
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("Windows system directory is unavailable."));
|
|
359
|
-
killChild("SIGKILL");
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
try {
|
|
363
|
-
helper = spawn(join(root, "System32", "taskkill.exe"), [
|
|
364
|
-
"/PID",
|
|
365
|
-
String(pid),
|
|
366
|
-
"/T",
|
|
367
|
-
"/F"
|
|
368
|
-
], {
|
|
369
|
-
shell: false,
|
|
370
|
-
windowsHide: true,
|
|
371
|
-
stdio: "pipe"
|
|
372
|
-
});
|
|
373
|
-
} catch (error) {
|
|
374
|
-
cleanupErrors.push(error);
|
|
375
|
-
killChild("SIGKILL");
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
let helperClosed = false;
|
|
379
|
-
let helperError;
|
|
380
|
-
let helperCode = null;
|
|
381
|
-
let diagnostic = "";
|
|
382
|
-
helper.stdout.resume();
|
|
383
|
-
helper.stderr.setEncoding("utf8");
|
|
384
|
-
helper.stderr.on("data", (text) => {
|
|
385
|
-
diagnostic += text;
|
|
386
|
-
});
|
|
387
|
-
helper.stdin.on("error", () => {});
|
|
388
|
-
helper.stdout.on("error", (error) => {
|
|
389
|
-
helperError = error;
|
|
390
|
-
});
|
|
391
|
-
helper.stderr.on("error", (error) => {
|
|
392
|
-
helperError = error;
|
|
393
|
-
});
|
|
394
|
-
helper.on("error", (error) => {
|
|
395
|
-
helperError = error;
|
|
396
|
-
killChild("SIGKILL");
|
|
397
|
-
});
|
|
398
|
-
helper.on("close", (code) => {
|
|
399
|
-
helperClosed = true;
|
|
400
|
-
helperCode = code;
|
|
401
|
-
update();
|
|
402
|
-
});
|
|
403
|
-
helper.stdin.end();
|
|
404
|
-
if (!await waitFor(() => helperClosed, Math.min(1e3, deadline - Date.now()))) {
|
|
405
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("Tree cleanup command timed out."));
|
|
406
|
-
try {
|
|
407
|
-
helper.kill("SIGKILL");
|
|
408
|
-
} catch (error) {
|
|
409
|
-
cleanupErrors.push(error);
|
|
410
|
-
}
|
|
411
|
-
killChild("SIGKILL");
|
|
412
|
-
helper.stdin.destroy();
|
|
413
|
-
helper.stdout.destroy();
|
|
414
|
-
helper.stderr.destroy();
|
|
415
|
-
if (!await waitFor(() => helperClosed, deadline - Date.now())) {
|
|
416
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("Tree cleanup command did not close."));
|
|
417
|
-
helper.unref();
|
|
418
|
-
}
|
|
419
|
-
} else if (helperError !== void 0 || helperCode !== 0) {
|
|
420
|
-
cleanupErrors.push(helperError ?? /* @__PURE__ */ new Error(`Tree cleanup exited with code ${helperCode}: ${diagnostic}`));
|
|
421
|
-
killChild("SIGKILL");
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
574
|
async function cleanup() {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
} else {
|
|
439
|
-
killChild("SIGTERM");
|
|
440
|
-
if (!await waitFor(() => exited || closed, 1e3)) killChild("SIGKILL");
|
|
441
|
-
}
|
|
442
|
-
if (!await waitFor(() => exited || closed, deadline - Date.now())) cleanupErrors.push(/* @__PURE__ */ new Error("The CLI process did not exit during cleanup."));
|
|
443
|
-
await waitFor(() => closed, Math.min(250, deadline - Date.now()));
|
|
444
|
-
child.stdout.destroy();
|
|
445
|
-
child.stderr.destroy();
|
|
446
|
-
if (!await waitFor(() => closed && outClose && errClose && inClose, deadline - Date.now())) {
|
|
447
|
-
cleanupErrors.push(/* @__PURE__ */ new Error("The CLI process streams did not close during cleanup."));
|
|
448
|
-
child.unref();
|
|
449
|
-
}
|
|
575
|
+
await cleanupCliProcess({
|
|
576
|
+
child,
|
|
577
|
+
mode: options.cleanup,
|
|
578
|
+
getState: () => ({
|
|
579
|
+
exited,
|
|
580
|
+
closed,
|
|
581
|
+
stdinClosed: inClose,
|
|
582
|
+
stdoutClosed: outClose,
|
|
583
|
+
stderrClosed: errClose
|
|
584
|
+
}),
|
|
585
|
+
waiter,
|
|
586
|
+
recordError: (error) => {
|
|
587
|
+
if (!settled) cleanupErrors.push(error);
|
|
450
588
|
}
|
|
451
|
-
}
|
|
452
|
-
cleanupErrors.push(error);
|
|
453
|
-
killChild("SIGKILL");
|
|
454
|
-
child?.stdin.destroy();
|
|
455
|
-
child?.stdout.destroy();
|
|
456
|
-
child?.stderr.destroy();
|
|
457
|
-
child?.unref();
|
|
458
|
-
}
|
|
589
|
+
});
|
|
459
590
|
if (failure === void 0 || settled) return;
|
|
460
591
|
const result = snapshot();
|
|
461
592
|
const primary = new CliInvocationError(failure.reason, failure.message, result, { cause: failure.cause });
|
|
@@ -470,10 +601,16 @@ function runProcess(command, args, options) {
|
|
|
470
601
|
stderr.push(text);
|
|
471
602
|
}
|
|
472
603
|
function onError(error) {
|
|
473
|
-
if (
|
|
604
|
+
if (settled) return;
|
|
605
|
+
if (failure) cleanupErrors.push(new Error("The CLI process failed during cleanup.", { cause: error }));
|
|
474
606
|
else fail(spawned ? "io" : "spawn", "Could not execute the CLI.", error);
|
|
475
607
|
}
|
|
476
608
|
function onIoError(error) {
|
|
609
|
+
if (settled) return;
|
|
610
|
+
if (failure) {
|
|
611
|
+
cleanupErrors.push(new Error("CLI output could not be read during cleanup.", { cause: error }));
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
477
614
|
fail("io", "Could not capture CLI output.", error);
|
|
478
615
|
}
|
|
479
616
|
function onInputError(error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/testing",
|
|
3
|
-
"version": "1.3.0-dev.
|
|
3
|
+
"version": "1.3.0-dev.2559",
|
|
4
4
|
"description": "Testing support for Optique command-line interfaces",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -86,9 +86,9 @@
|
|
|
86
86
|
},
|
|
87
87
|
"sideEffects": false,
|
|
88
88
|
"dependencies": {
|
|
89
|
-
"@optique/core": "1.3.0-dev.
|
|
90
|
-
"@optique/discover": "1.3.0-dev.
|
|
91
|
-
"@optique/run": "1.3.0-dev.
|
|
89
|
+
"@optique/core": "1.3.0-dev.2559+02079a47",
|
|
90
|
+
"@optique/discover": "1.3.0-dev.2559+02079a47",
|
|
91
|
+
"@optique/run": "1.3.0-dev.2559+02079a47"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"@types/node": "^24.0.0",
|