command-stream 0.7.0 → 0.8.1

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.
@@ -3,12 +3,14 @@ import { trace, VirtualUtils } from '../$.utils.mjs';
3
3
 
4
4
  export default async function rm({ args, stdin, cwd }) {
5
5
  const argError = VirtualUtils.validateArgs(args, 1, 'rm');
6
- if (argError) return argError;
6
+ if (argError) {
7
+ return argError;
8
+ }
7
9
 
8
10
  // Parse flags and paths
9
11
  const flags = new Set();
10
12
  const paths = [];
11
-
13
+
12
14
  for (const arg of args) {
13
15
  if (arg.startsWith('-')) {
14
16
  for (const flag of arg.slice(1)) {
@@ -25,18 +27,24 @@ export default async function rm({ args, stdin, cwd }) {
25
27
 
26
28
  const recursive = flags.has('r') || flags.has('R');
27
29
  const force = flags.has('f');
28
-
30
+
29
31
  try {
30
32
  for (const file of paths) {
31
33
  const resolvedPath = VirtualUtils.resolvePath(file, cwd);
32
- trace('VirtualCommand', () => `rm: removing | ${JSON.stringify({ file: resolvedPath, recursive, force }, null, 2)}`);
33
-
34
+ trace(
35
+ 'VirtualCommand',
36
+ () =>
37
+ `rm: removing | ${JSON.stringify({ file: resolvedPath, recursive, force }, null, 2)}`
38
+ );
39
+
34
40
  try {
35
41
  const stats = fs.statSync(resolvedPath);
36
42
  if (stats.isDirectory() && !recursive) {
37
- return VirtualUtils.error(`rm: cannot remove '${file}': Is a directory`);
43
+ return VirtualUtils.error(
44
+ `rm: cannot remove '${file}': Is a directory`
45
+ );
38
46
  }
39
-
47
+
40
48
  if (stats.isDirectory()) {
41
49
  fs.rmSync(resolvedPath, { recursive: true, force });
42
50
  } else {
@@ -44,17 +52,26 @@ export default async function rm({ args, stdin, cwd }) {
44
52
  }
45
53
  } catch (error) {
46
54
  if (error.code === 'ENOENT' && !force) {
47
- return VirtualUtils.error(`rm: cannot remove '${file}': No such file or directory`);
55
+ return VirtualUtils.error(
56
+ `rm: cannot remove '${file}': No such file or directory`
57
+ );
48
58
  } else if (error.code !== 'ENOENT') {
49
59
  throw error;
50
60
  }
51
61
  }
52
62
  }
53
-
54
- trace('VirtualCommand', () => `rm: success | ${JSON.stringify({ filesRemoved: paths.length }, null, 2)}`);
63
+
64
+ trace(
65
+ 'VirtualCommand',
66
+ () =>
67
+ `rm: success | ${JSON.stringify({ filesRemoved: paths.length }, null, 2)}`
68
+ );
55
69
  return VirtualUtils.success();
56
70
  } catch (error) {
57
- trace('VirtualCommand', () => `rm: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
71
+ trace(
72
+ 'VirtualCommand',
73
+ () => `rm: error | ${JSON.stringify({ error: error.message }, null, 2)}`
74
+ );
58
75
  return VirtualUtils.error(`rm: ${error.message}`);
59
76
  }
60
- }
77
+ }
@@ -2,10 +2,12 @@ import { VirtualUtils } from '../$.utils.mjs';
2
2
 
3
3
  export default async function seq({ args }) {
4
4
  const argError = VirtualUtils.validateArgs(args, 1, 'seq');
5
- if (argError) return argError;
5
+ if (argError) {
6
+ return argError;
7
+ }
6
8
 
7
9
  let start, step, end;
8
-
10
+
9
11
  if (args.length === 1) {
10
12
  // seq END
11
13
  start = 1;
@@ -22,18 +24,18 @@ export default async function seq({ args }) {
22
24
  step = parseFloat(args[1]);
23
25
  end = parseFloat(args[2]);
24
26
  }
25
-
27
+
26
28
  // Validate numbers
27
29
  if (isNaN(start) || isNaN(step) || isNaN(end)) {
28
30
  return VirtualUtils.error('seq: invalid number');
29
31
  }
30
-
32
+
31
33
  if (step === 0) {
32
34
  return VirtualUtils.error('seq: step cannot be zero');
33
35
  }
34
-
36
+
35
37
  const output = [];
36
-
38
+
37
39
  if (step > 0) {
38
40
  for (let i = start; i <= end; i += step) {
39
41
  output.push(i.toString());
@@ -43,6 +45,8 @@ export default async function seq({ args }) {
43
45
  output.push(i.toString());
44
46
  }
45
47
  }
46
-
47
- return VirtualUtils.success(output.join('\n') + (output.length > 0 ? '\n' : ''));
48
- }
48
+
49
+ return VirtualUtils.success(
50
+ output.join('\n') + (output.length > 0 ? '\n' : '')
51
+ );
52
+ }
@@ -2,54 +2,94 @@ import { trace } from '../$.utils.mjs';
2
2
 
3
3
  export default async function sleep({ args, abortSignal, isCancelled }) {
4
4
  const seconds = parseFloat(args[0] || 0);
5
- trace('VirtualCommand', () => `sleep: starting | ${JSON.stringify({
6
- seconds,
7
- hasSignal: !!abortSignal,
8
- signalAborted: abortSignal?.aborted,
9
- hasIsCancelled: !!isCancelled
10
- }, null, 2)}`);
11
-
5
+ trace(
6
+ 'VirtualCommand',
7
+ () =>
8
+ `sleep: starting | ${JSON.stringify(
9
+ {
10
+ seconds,
11
+ hasSignal: !!abortSignal,
12
+ signalAborted: abortSignal?.aborted,
13
+ hasIsCancelled: !!isCancelled,
14
+ },
15
+ null,
16
+ 2
17
+ )}`
18
+ );
19
+
12
20
  if (isNaN(seconds) || seconds < 0) {
13
21
  return { stderr: `sleep: invalid time interval '${args[0]}'`, code: 1 };
14
22
  }
15
-
23
+
16
24
  // Use abort signal if available, otherwise use setTimeout
17
25
  try {
18
26
  await new Promise((resolve, reject) => {
19
27
  const timeoutId = setTimeout(resolve, seconds * 1000);
20
-
28
+
21
29
  // Handle cancellation via abort signal
22
30
  if (abortSignal) {
23
- trace('VirtualCommand', () => `sleep: setting up abort signal listener | ${JSON.stringify({
24
- signalAborted: abortSignal.aborted
25
- }, null, 2)}`);
26
-
31
+ trace(
32
+ 'VirtualCommand',
33
+ () =>
34
+ `sleep: setting up abort signal listener | ${JSON.stringify(
35
+ {
36
+ signalAborted: abortSignal.aborted,
37
+ },
38
+ null,
39
+ 2
40
+ )}`
41
+ );
42
+
27
43
  abortSignal.addEventListener('abort', () => {
28
- trace('VirtualCommand', () => `sleep: abort signal received | ${JSON.stringify({
29
- seconds,
30
- signalAborted: abortSignal.aborted
31
- }, null, 2)}`);
44
+ trace(
45
+ 'VirtualCommand',
46
+ () =>
47
+ `sleep: abort signal received | ${JSON.stringify(
48
+ {
49
+ seconds,
50
+ signalAborted: abortSignal.aborted,
51
+ },
52
+ null,
53
+ 2
54
+ )}`
55
+ );
32
56
  clearTimeout(timeoutId);
33
57
  reject(new Error('Sleep cancelled'));
34
58
  });
35
-
59
+
36
60
  // Check if already aborted
37
61
  if (abortSignal.aborted) {
38
- trace('VirtualCommand', () => `sleep: signal already aborted | ${JSON.stringify({ seconds }, null, 2)}`);
62
+ trace(
63
+ 'VirtualCommand',
64
+ () =>
65
+ `sleep: signal already aborted | ${JSON.stringify({ seconds }, null, 2)}`
66
+ );
39
67
  clearTimeout(timeoutId);
40
68
  reject(new Error('Sleep cancelled'));
41
69
  return;
42
70
  }
43
71
  } else {
44
- trace('VirtualCommand', () => `sleep: no abort signal provided | ${JSON.stringify({ seconds }, null, 2)}`);
72
+ trace(
73
+ 'VirtualCommand',
74
+ () =>
75
+ `sleep: no abort signal provided | ${JSON.stringify({ seconds }, null, 2)}`
76
+ );
45
77
  }
46
-
78
+
47
79
  // Also check isCancelled periodically for quicker response
48
80
  if (isCancelled) {
49
- trace('VirtualCommand', () => `sleep: setting up isCancelled polling | ${JSON.stringify({ seconds }, null, 2)}`);
81
+ trace(
82
+ 'VirtualCommand',
83
+ () =>
84
+ `sleep: setting up isCancelled polling | ${JSON.stringify({ seconds }, null, 2)}`
85
+ );
50
86
  const checkInterval = setInterval(() => {
51
87
  if (isCancelled()) {
52
- trace('VirtualCommand', () => `sleep: isCancelled returned true | ${JSON.stringify({ seconds }, null, 2)}`);
88
+ trace(
89
+ 'VirtualCommand',
90
+ () =>
91
+ `sleep: isCancelled returned true | ${JSON.stringify({ seconds }, null, 2)}`
92
+ );
53
93
  clearTimeout(timeoutId);
54
94
  clearInterval(checkInterval);
55
95
  reject(new Error('Sleep cancelled'));
@@ -57,16 +97,28 @@ export default async function sleep({ args, abortSignal, isCancelled }) {
57
97
  }, 100);
58
98
  }
59
99
  });
60
-
61
- trace('VirtualCommand', () => `sleep: completed naturally | ${JSON.stringify({ seconds }, null, 2)}`);
100
+
101
+ trace(
102
+ 'VirtualCommand',
103
+ () =>
104
+ `sleep: completed naturally | ${JSON.stringify({ seconds }, null, 2)}`
105
+ );
62
106
  return { stdout: '', code: 0 };
63
107
  } catch (err) {
64
- trace('VirtualCommand', () => `sleep: interrupted | ${JSON.stringify({
65
- seconds,
66
- error: err.message,
67
- errorName: err.name
68
- }, null, 2)}`);
108
+ trace(
109
+ 'VirtualCommand',
110
+ () =>
111
+ `sleep: interrupted | ${JSON.stringify(
112
+ {
113
+ seconds,
114
+ error: err.message,
115
+ errorName: err.name,
116
+ },
117
+ null,
118
+ 2
119
+ )}`
120
+ );
69
121
  // Let the ProcessRunner determine the appropriate exit code based on the cancellation signal
70
122
  throw err;
71
123
  }
72
- }
124
+ }
@@ -44,16 +44,16 @@ export default async function test({ args }) {
44
44
  }
45
45
 
46
46
  case '-z': // String is empty
47
- return { stdout: '', code: (!operand || operand.length === 0) ? 0 : 1 };
47
+ return { stdout: '', code: !operand || operand.length === 0 ? 0 : 1 };
48
48
 
49
49
  case '-n': // String is not empty
50
- return { stdout: '', code: (operand && operand.length > 0) ? 0 : 1 };
50
+ return { stdout: '', code: operand && operand.length > 0 ? 0 : 1 };
51
51
 
52
52
  default:
53
53
  // Simple string test (non-empty)
54
- return { stdout: '', code: (operator && operator.length > 0) ? 0 : 1 };
54
+ return { stdout: '', code: operator && operator.length > 0 ? 0 : 1 };
55
55
  }
56
56
  } catch (error) {
57
57
  return VirtualUtils.error(`test: ${error.message}`);
58
58
  }
59
- }
59
+ }
@@ -3,34 +3,59 @@ import { trace, VirtualUtils } from '../$.utils.mjs';
3
3
 
4
4
  export default async function touch({ args, stdin, cwd }) {
5
5
  const argError = VirtualUtils.validateArgs(args, 1, 'touch');
6
- if (argError) return VirtualUtils.missingOperandError('touch', 'touch: missing file operand');
6
+ if (argError) {
7
+ return VirtualUtils.missingOperandError(
8
+ 'touch',
9
+ 'touch: missing file operand'
10
+ );
11
+ }
7
12
 
8
13
  try {
9
14
  for (const file of args) {
10
15
  const resolvedPath = VirtualUtils.resolvePath(file, cwd);
11
- trace('VirtualCommand', () => `touch: processing | ${JSON.stringify({ file: resolvedPath }, null, 2)}`);
12
-
16
+ trace(
17
+ 'VirtualCommand',
18
+ () =>
19
+ `touch: processing | ${JSON.stringify({ file: resolvedPath }, null, 2)}`
20
+ );
21
+
13
22
  const now = new Date();
14
-
23
+
15
24
  try {
16
25
  // Try to update existing file's timestamp
17
26
  fs.utimesSync(resolvedPath, now, now);
18
- trace('VirtualCommand', () => `touch: updated timestamp | ${JSON.stringify({ file: resolvedPath }, null, 2)}`);
27
+ trace(
28
+ 'VirtualCommand',
29
+ () =>
30
+ `touch: updated timestamp | ${JSON.stringify({ file: resolvedPath }, null, 2)}`
31
+ );
19
32
  } catch (error) {
20
33
  if (error.code === 'ENOENT') {
21
34
  // File doesn't exist, create it
22
35
  fs.writeFileSync(resolvedPath, '');
23
- trace('VirtualCommand', () => `touch: created file | ${JSON.stringify({ file: resolvedPath }, null, 2)}`);
36
+ trace(
37
+ 'VirtualCommand',
38
+ () =>
39
+ `touch: created file | ${JSON.stringify({ file: resolvedPath }, null, 2)}`
40
+ );
24
41
  } else {
25
42
  throw error;
26
43
  }
27
44
  }
28
45
  }
29
-
30
- trace('VirtualCommand', () => `touch: success | ${JSON.stringify({ filesTouched: args.length }, null, 2)}`);
46
+
47
+ trace(
48
+ 'VirtualCommand',
49
+ () =>
50
+ `touch: success | ${JSON.stringify({ filesTouched: args.length }, null, 2)}`
51
+ );
31
52
  return VirtualUtils.success();
32
53
  } catch (error) {
33
- trace('VirtualCommand', () => `touch: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
54
+ trace(
55
+ 'VirtualCommand',
56
+ () =>
57
+ `touch: error | ${JSON.stringify({ error: error.message }, null, 2)}`
58
+ );
34
59
  return VirtualUtils.error(`touch: ${error.message}`);
35
60
  }
36
- }
61
+ }
@@ -2,4 +2,4 @@ import { VirtualUtils } from '../$.utils.mjs';
2
2
 
3
3
  export default async function trueCommand() {
4
4
  return VirtualUtils.success();
5
- }
5
+ }
@@ -5,7 +5,9 @@ import { VirtualUtils } from '../$.utils.mjs';
5
5
  export default function createWhichCommand(virtualCommands) {
6
6
  return async function which({ args }) {
7
7
  const argError = VirtualUtils.validateArgs(args, 1, 'which');
8
- if (argError) return argError;
8
+ if (argError) {
9
+ return argError;
10
+ }
9
11
 
10
12
  const cmd = args[0];
11
13
 
@@ -13,20 +15,23 @@ export default function createWhichCommand(virtualCommands) {
13
15
  return VirtualUtils.success(`${cmd}: shell builtin\n`);
14
16
  }
15
17
 
16
- const paths = (process.env.PATH || '').split(process.platform === 'win32' ? ';' : ':');
17
- const extensions = process.platform === 'win32' ? ['', '.exe', '.cmd', '.bat'] : [''];
18
+ const paths = (process.env.PATH || '').split(
19
+ process.platform === 'win32' ? ';' : ':'
20
+ );
21
+ const extensions =
22
+ process.platform === 'win32' ? ['', '.exe', '.cmd', '.bat'] : [''];
18
23
 
19
24
  for (const pathDir of paths) {
20
25
  for (const ext of extensions) {
21
26
  const fullPath = path.join(pathDir, cmd + ext);
22
27
  try {
23
28
  if (fs.statSync(fullPath).isFile()) {
24
- return VirtualUtils.success(fullPath + '\n');
29
+ return VirtualUtils.success(`${fullPath}\n`);
25
30
  }
26
- } catch { }
31
+ } catch {}
27
32
  }
28
33
  }
29
34
 
30
35
  return VirtualUtils.error(`which: no ${cmd} in PATH`);
31
36
  };
32
- }
37
+ }
@@ -1,48 +1,92 @@
1
1
  import { trace } from '../$.utils.mjs';
2
2
 
3
- export default async function* yes({ args, stdin, isCancelled, abortSignal, ...rest }) {
3
+ export default async function* yes({
4
+ args,
5
+ stdin,
6
+ isCancelled,
7
+ abortSignal,
8
+ ...rest
9
+ }) {
4
10
  const output = args.length > 0 ? args.join(' ') : 'y';
5
- trace('VirtualCommand', () => `yes: starting infinite generator | ${JSON.stringify({
6
- output,
7
- hasIsCancelled: !!isCancelled,
8
- hasAbortSignal: !!abortSignal
9
- }, null, 2)}`);
11
+ trace(
12
+ 'VirtualCommand',
13
+ () =>
14
+ `yes: starting infinite generator | ${JSON.stringify(
15
+ {
16
+ output,
17
+ hasIsCancelled: !!isCancelled,
18
+ hasAbortSignal: !!abortSignal,
19
+ },
20
+ null,
21
+ 2
22
+ )}`
23
+ );
10
24
 
11
25
  let iteration = 0;
12
26
  const MAX_ITERATIONS = 1000000; // Safety limit
13
27
 
14
28
  while (!isCancelled?.() && iteration < MAX_ITERATIONS) {
15
- trace('VirtualCommand', () => `yes: iteration ${iteration} starting | ${JSON.stringify({
16
- isCancelled: isCancelled?.(),
17
- abortSignalAborted: abortSignal?.aborted
18
- }, null, 2)}`);
19
-
29
+ trace(
30
+ 'VirtualCommand',
31
+ () =>
32
+ `yes: iteration ${iteration} starting | ${JSON.stringify(
33
+ {
34
+ isCancelled: isCancelled?.(),
35
+ abortSignalAborted: abortSignal?.aborted,
36
+ },
37
+ null,
38
+ 2
39
+ )}`
40
+ );
41
+
20
42
  // Check for abort signal
21
43
  if (abortSignal?.aborted) {
22
- trace('VirtualCommand', () => `yes: aborted via abort signal | ${JSON.stringify({ iteration }, null, 2)}`);
44
+ trace(
45
+ 'VirtualCommand',
46
+ () =>
47
+ `yes: aborted via abort signal | ${JSON.stringify({ iteration }, null, 2)}`
48
+ );
23
49
  break;
24
50
  }
25
51
 
26
52
  // Also check rest properties for various cancellation methods
27
53
  if (rest.aborted || rest.cancelled || rest.stop) {
28
- trace('VirtualCommand', () => `yes: stopped via property | ${JSON.stringify({ iteration }, null, 2)}`);
54
+ trace(
55
+ 'VirtualCommand',
56
+ () =>
57
+ `yes: stopped via property | ${JSON.stringify({ iteration }, null, 2)}`
58
+ );
29
59
  break;
30
60
  }
31
61
 
32
- trace('VirtualCommand', () => `yes: yielding output for iteration ${iteration}`);
33
- yield output + '\n';
62
+ trace(
63
+ 'VirtualCommand',
64
+ () => `yes: yielding output for iteration ${iteration}`
65
+ );
66
+ yield `${output}\n`;
34
67
 
35
68
  iteration++;
36
69
 
37
70
  // Yield control after every iteration to allow cancellation
38
71
  // This ensures the consumer can break cleanly
39
- trace('VirtualCommand', () => `yes: yielding control after iteration ${iteration - 1}`);
40
- await new Promise(resolve => setImmediate(resolve));
72
+ trace(
73
+ 'VirtualCommand',
74
+ () => `yes: yielding control after iteration ${iteration - 1}`
75
+ );
76
+ await new Promise((resolve) => setImmediate(resolve));
41
77
  }
42
78
 
43
- trace('VirtualCommand', () => `yes: generator completed | ${JSON.stringify({
44
- iteration,
45
- wasCancelled: isCancelled?.(),
46
- wasAborted: abortSignal?.aborted
47
- }, null, 2)}`);
48
- }
79
+ trace(
80
+ 'VirtualCommand',
81
+ () =>
82
+ `yes: generator completed | ${JSON.stringify(
83
+ {
84
+ iteration,
85
+ wasCancelled: isCancelled?.(),
86
+ wasAborted: abortSignal?.aborted,
87
+ },
88
+ null,
89
+ 2
90
+ )}`
91
+ );
92
+ }