command-stream 0.3.2 → 0.4.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/README.md +322 -29
- package/package.json +1 -1
- package/src/$.mjs +1202 -213
- package/src/commands/$.cat.mjs +7 -1
- package/src/commands/$.sleep.mjs +63 -6
- package/src/commands/$.yes.mjs +20 -6
package/src/commands/$.cat.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
3
3
|
|
|
4
|
-
export default async function cat({ args, stdin, cwd }) {
|
|
4
|
+
export default async function cat({ args, stdin, cwd, isCancelled, signal }) {
|
|
5
5
|
if (args.length === 0) {
|
|
6
6
|
// Read from stdin if no files specified
|
|
7
7
|
if (stdin !== undefined && stdin !== '') {
|
|
@@ -13,6 +13,12 @@ export default async function cat({ args, stdin, cwd }) {
|
|
|
13
13
|
try {
|
|
14
14
|
const outputs = [];
|
|
15
15
|
for (const file of args) {
|
|
16
|
+
// Check for cancellation before processing each file
|
|
17
|
+
if (isCancelled?.() || signal?.aborted) {
|
|
18
|
+
trace('VirtualCommand', () => `cat: cancelled while processing files`);
|
|
19
|
+
return { code: 130, stdout: '', stderr: '' }; // SIGINT exit code
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
trace('VirtualCommand', () => `cat: reading file | ${JSON.stringify({ file }, null, 2)}`);
|
|
17
23
|
const resolvedPath = VirtualUtils.resolvePath(file, cwd);
|
|
18
24
|
try {
|
package/src/commands/$.sleep.mjs
CHANGED
|
@@ -1,15 +1,72 @@
|
|
|
1
1
|
import { trace } from '../$.utils.mjs';
|
|
2
2
|
|
|
3
|
-
export default async function sleep({ args }) {
|
|
3
|
+
export default async function sleep({ args, signal, isCancelled }) {
|
|
4
4
|
const seconds = parseFloat(args[0] || 0);
|
|
5
|
-
trace('VirtualCommand', () => `sleep: starting | ${JSON.stringify({
|
|
5
|
+
trace('VirtualCommand', () => `sleep: starting | ${JSON.stringify({
|
|
6
|
+
seconds,
|
|
7
|
+
hasSignal: !!signal,
|
|
8
|
+
signalAborted: signal?.aborted,
|
|
9
|
+
hasIsCancelled: !!isCancelled
|
|
10
|
+
}, null, 2)}`);
|
|
6
11
|
|
|
7
12
|
if (isNaN(seconds) || seconds < 0) {
|
|
8
13
|
return { stderr: `sleep: invalid time interval '${args[0]}'`, code: 1 };
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
// Use abort signal if available, otherwise use setTimeout
|
|
17
|
+
try {
|
|
18
|
+
await new Promise((resolve, reject) => {
|
|
19
|
+
const timeoutId = setTimeout(resolve, seconds * 1000);
|
|
20
|
+
|
|
21
|
+
// Handle cancellation via signal
|
|
22
|
+
if (signal) {
|
|
23
|
+
trace('VirtualCommand', () => `sleep: setting up abort signal listener | ${JSON.stringify({
|
|
24
|
+
signalAborted: signal.aborted
|
|
25
|
+
}, null, 2)}`);
|
|
26
|
+
|
|
27
|
+
signal.addEventListener('abort', () => {
|
|
28
|
+
trace('VirtualCommand', () => `sleep: abort signal received | ${JSON.stringify({
|
|
29
|
+
seconds,
|
|
30
|
+
signalAborted: signal.aborted
|
|
31
|
+
}, null, 2)}`);
|
|
32
|
+
clearTimeout(timeoutId);
|
|
33
|
+
reject(new Error('Sleep cancelled'));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Check if already aborted
|
|
37
|
+
if (signal.aborted) {
|
|
38
|
+
trace('VirtualCommand', () => `sleep: signal already aborted | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
reject(new Error('Sleep cancelled'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
trace('VirtualCommand', () => `sleep: no signal provided | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Also check isCancelled periodically for quicker response
|
|
48
|
+
if (isCancelled) {
|
|
49
|
+
trace('VirtualCommand', () => `sleep: setting up isCancelled polling | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
50
|
+
const checkInterval = setInterval(() => {
|
|
51
|
+
if (isCancelled()) {
|
|
52
|
+
trace('VirtualCommand', () => `sleep: isCancelled returned true | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
clearInterval(checkInterval);
|
|
55
|
+
reject(new Error('Sleep cancelled'));
|
|
56
|
+
}
|
|
57
|
+
}, 100);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
trace('VirtualCommand', () => `sleep: completed naturally | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
62
|
+
return { stdout: '', code: 0 };
|
|
63
|
+
} catch (err) {
|
|
64
|
+
trace('VirtualCommand', () => `sleep: interrupted | ${JSON.stringify({
|
|
65
|
+
seconds,
|
|
66
|
+
error: err.message,
|
|
67
|
+
errorName: err.name
|
|
68
|
+
}, null, 2)}`);
|
|
69
|
+
// Let the ProcessRunner determine the appropriate exit code based on the cancellation signal
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
15
72
|
}
|
package/src/commands/$.yes.mjs
CHANGED
|
@@ -2,12 +2,21 @@ import { trace } from '../$.utils.mjs';
|
|
|
2
2
|
|
|
3
3
|
export default async function* yes({ args, stdin, isCancelled, signal, ...rest }) {
|
|
4
4
|
const output = args.length > 0 ? args.join(' ') : 'y';
|
|
5
|
-
trace('VirtualCommand', () => `yes: starting infinite generator | ${JSON.stringify({
|
|
5
|
+
trace('VirtualCommand', () => `yes: starting infinite generator | ${JSON.stringify({
|
|
6
|
+
output,
|
|
7
|
+
hasIsCancelled: !!isCancelled,
|
|
8
|
+
hasSignal: !!signal
|
|
9
|
+
}, null, 2)}`);
|
|
6
10
|
|
|
7
11
|
let iteration = 0;
|
|
8
12
|
const MAX_ITERATIONS = 1000000; // Safety limit
|
|
9
13
|
|
|
10
14
|
while (!isCancelled?.() && iteration < MAX_ITERATIONS) {
|
|
15
|
+
trace('VirtualCommand', () => `yes: iteration ${iteration} starting | ${JSON.stringify({
|
|
16
|
+
isCancelled: isCancelled?.(),
|
|
17
|
+
signalAborted: signal?.aborted
|
|
18
|
+
}, null, 2)}`);
|
|
19
|
+
|
|
11
20
|
// Check for abort signal
|
|
12
21
|
if (signal?.aborted) {
|
|
13
22
|
trace('VirtualCommand', () => `yes: aborted via signal | ${JSON.stringify({ iteration }, null, 2)}`);
|
|
@@ -20,15 +29,20 @@ export default async function* yes({ args, stdin, isCancelled, signal, ...rest }
|
|
|
20
29
|
break;
|
|
21
30
|
}
|
|
22
31
|
|
|
32
|
+
trace('VirtualCommand', () => `yes: yielding output for iteration ${iteration}`);
|
|
23
33
|
yield output + '\n';
|
|
24
34
|
|
|
25
35
|
iteration++;
|
|
26
36
|
|
|
27
|
-
// Yield control
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
// Yield control after every iteration to allow cancellation
|
|
38
|
+
// This ensures the consumer can break cleanly
|
|
39
|
+
trace('VirtualCommand', () => `yes: yielding control after iteration ${iteration - 1}`);
|
|
40
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
trace('VirtualCommand', () => `yes: generator completed | ${JSON.stringify({
|
|
43
|
+
trace('VirtualCommand', () => `yes: generator completed | ${JSON.stringify({
|
|
44
|
+
iteration,
|
|
45
|
+
wasCancelled: isCancelled?.(),
|
|
46
|
+
wasAborted: signal?.aborted
|
|
47
|
+
}, null, 2)}`);
|
|
34
48
|
}
|