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.
Files changed (39) hide show
  1. package/js/src/$.ansi.mjs +147 -0
  2. package/js/src/$.mjs +49 -6382
  3. package/js/src/$.process-runner-base.mjs +563 -0
  4. package/js/src/$.process-runner-execution.mjs +1497 -0
  5. package/js/src/$.process-runner-orchestration.mjs +250 -0
  6. package/js/src/$.process-runner-pipeline.mjs +1162 -0
  7. package/js/src/$.process-runner-stream-kill.mjs +312 -0
  8. package/js/src/$.process-runner-virtual.mjs +297 -0
  9. package/js/src/$.quote.mjs +161 -0
  10. package/js/src/$.result.mjs +23 -0
  11. package/js/src/$.shell-settings.mjs +84 -0
  12. package/js/src/$.shell.mjs +157 -0
  13. package/js/src/$.state.mjs +401 -0
  14. package/js/src/$.stream-emitter.mjs +111 -0
  15. package/js/src/$.stream-utils.mjs +390 -0
  16. package/js/src/$.trace.mjs +36 -0
  17. package/js/src/$.utils.mjs +2 -23
  18. package/js/src/$.virtual-commands.mjs +113 -0
  19. package/js/src/commands/$.which.mjs +3 -1
  20. package/js/src/commands/index.mjs +24 -0
  21. package/js/src/shell-parser.mjs +125 -83
  22. package/js/tests/resource-cleanup-internals.test.mjs +22 -24
  23. package/js/tests/sigint-cleanup.test.mjs +3 -0
  24. package/package.json +1 -1
  25. package/rust/src/ansi.rs +194 -0
  26. package/rust/src/events.rs +305 -0
  27. package/rust/src/lib.rs +71 -60
  28. package/rust/src/macros.rs +165 -0
  29. package/rust/src/pipeline.rs +411 -0
  30. package/rust/src/quote.rs +161 -0
  31. package/rust/src/state.rs +333 -0
  32. package/rust/src/stream.rs +369 -0
  33. package/rust/src/trace.rs +152 -0
  34. package/rust/src/utils.rs +53 -158
  35. package/rust/tests/events.rs +207 -0
  36. package/rust/tests/macros.rs +77 -0
  37. package/rust/tests/pipeline.rs +93 -0
  38. package/rust/tests/state.rs +207 -0
  39. package/rust/tests/stream.rs +102 -0
@@ -0,0 +1,250 @@
1
+ // ProcessRunner orchestration methods - sequence, subshell, simple command, and pipe
2
+ // Part of the modular ProcessRunner architecture
3
+
4
+ import { trace } from './$.trace.mjs';
5
+
6
+ /**
7
+ * Execute a command based on its type
8
+ * @param {object} runner - The ProcessRunner instance
9
+ * @param {object} command - Command to execute
10
+ * @returns {Promise<object>} Command result
11
+ */
12
+ function executeCommand(runner, command) {
13
+ if (command.type === 'subshell') {
14
+ return runner._runSubshell(command);
15
+ } else if (command.type === 'pipeline') {
16
+ return runner._runPipeline(command.commands);
17
+ } else if (command.type === 'sequence') {
18
+ return runner._runSequence(command);
19
+ } else if (command.type === 'simple') {
20
+ return runner._runSimpleCommand(command);
21
+ }
22
+ return Promise.resolve({ code: 0, stdout: '', stderr: '' });
23
+ }
24
+
25
+ /**
26
+ * Restore working directory after subshell execution
27
+ * @param {string} savedCwd - Directory to restore
28
+ */
29
+ async function restoreCwd(savedCwd) {
30
+ trace(
31
+ 'ProcessRunner',
32
+ () => `Restoring cwd from ${process.cwd()} to ${savedCwd}`
33
+ );
34
+ const fs = await import('fs');
35
+ if (fs.existsSync(savedCwd)) {
36
+ process.chdir(savedCwd);
37
+ } else {
38
+ const fallbackDir = process.env.HOME || process.env.USERPROFILE || '/';
39
+ trace(
40
+ 'ProcessRunner',
41
+ () =>
42
+ `Saved directory ${savedCwd} no longer exists, falling back to ${fallbackDir}`
43
+ );
44
+ try {
45
+ process.chdir(fallbackDir);
46
+ } catch (e) {
47
+ trace('ProcessRunner', () => `Failed to restore directory: ${e.message}`);
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Handle file redirections for virtual command output
54
+ * @param {object} result - Command result
55
+ * @param {Array} redirects - Redirect specifications
56
+ */
57
+ async function handleRedirects(result, redirects) {
58
+ if (!redirects || redirects.length === 0) {
59
+ return;
60
+ }
61
+ for (const redirect of redirects) {
62
+ if (redirect.type === '>' || redirect.type === '>>') {
63
+ const fs = await import('fs');
64
+ if (redirect.type === '>') {
65
+ fs.writeFileSync(redirect.target, result.stdout);
66
+ } else {
67
+ fs.appendFileSync(redirect.target, result.stdout);
68
+ }
69
+ result.stdout = '';
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Build command string from parsed command parts
76
+ * @param {string} cmd - Command name
77
+ * @param {Array} args - Command arguments
78
+ * @param {Array} redirects - Redirect specifications
79
+ * @returns {string} Assembled command string
80
+ */
81
+ function buildCommandString(cmd, args, redirects) {
82
+ let commandStr = cmd;
83
+ for (const arg of args) {
84
+ if (arg.quoted && arg.quoteChar) {
85
+ commandStr += ` ${arg.quoteChar}${arg.value}${arg.quoteChar}`;
86
+ } else if (arg.value !== undefined) {
87
+ commandStr += ` ${arg.value}`;
88
+ } else {
89
+ commandStr += ` ${arg}`;
90
+ }
91
+ }
92
+ if (redirects) {
93
+ for (const redirect of redirects) {
94
+ commandStr += ` ${redirect.type} ${redirect.target}`;
95
+ }
96
+ }
97
+ return commandStr;
98
+ }
99
+
100
+ /**
101
+ * Attach orchestration methods to ProcessRunner prototype
102
+ * @param {Function} ProcessRunner - The ProcessRunner class
103
+ * @param {Object} deps - Dependencies
104
+ */
105
+ export function attachOrchestrationMethods(ProcessRunner, deps) {
106
+ const { virtualCommands, isVirtualCommandsEnabled } = deps;
107
+
108
+ ProcessRunner.prototype._runSequence = async function (sequence) {
109
+ trace(
110
+ 'ProcessRunner',
111
+ () =>
112
+ `_runSequence ENTER | ${JSON.stringify({ commandCount: sequence.commands.length, operators: sequence.operators }, null, 2)}`
113
+ );
114
+
115
+ let lastResult = { code: 0, stdout: '', stderr: '' };
116
+ let combinedStdout = '';
117
+ let combinedStderr = '';
118
+
119
+ for (let i = 0; i < sequence.commands.length; i++) {
120
+ const command = sequence.commands[i];
121
+ const operator = i > 0 ? sequence.operators[i - 1] : null;
122
+
123
+ trace(
124
+ 'ProcessRunner',
125
+ () =>
126
+ `Executing command ${i} | ${JSON.stringify({ command: command.type, operator, lastCode: lastResult.code }, null, 2)}`
127
+ );
128
+
129
+ if (operator === '&&' && lastResult.code !== 0) {
130
+ trace(
131
+ 'ProcessRunner',
132
+ () => `Skipping due to && with exit code ${lastResult.code}`
133
+ );
134
+ continue;
135
+ }
136
+ if (operator === '||' && lastResult.code === 0) {
137
+ trace(
138
+ 'ProcessRunner',
139
+ () => `Skipping due to || with exit code ${lastResult.code}`
140
+ );
141
+ continue;
142
+ }
143
+
144
+ lastResult = await executeCommand(this, command);
145
+ combinedStdout += lastResult.stdout;
146
+ combinedStderr += lastResult.stderr;
147
+ }
148
+
149
+ return {
150
+ code: lastResult.code,
151
+ stdout: combinedStdout,
152
+ stderr: combinedStderr,
153
+ text() {
154
+ return Promise.resolve(combinedStdout);
155
+ },
156
+ };
157
+ };
158
+
159
+ ProcessRunner.prototype._runSubshell = async function (subshell) {
160
+ trace(
161
+ 'ProcessRunner',
162
+ () =>
163
+ `_runSubshell ENTER | ${JSON.stringify({ commandType: subshell.command.type }, null, 2)}`
164
+ );
165
+ const savedCwd = process.cwd();
166
+ try {
167
+ return await executeCommand(this, subshell.command);
168
+ } finally {
169
+ await restoreCwd(savedCwd);
170
+ }
171
+ };
172
+
173
+ ProcessRunner.prototype._runSimpleCommand = async function (command) {
174
+ trace(
175
+ 'ProcessRunner',
176
+ () =>
177
+ `_runSimpleCommand ENTER | ${JSON.stringify({ cmd: command.cmd, argsCount: command.args?.length || 0, hasRedirects: !!command.redirects }, null, 2)}`
178
+ );
179
+
180
+ const { cmd, args, redirects } = command;
181
+
182
+ if (isVirtualCommandsEnabled() && virtualCommands.has(cmd)) {
183
+ trace('ProcessRunner', () => `Using virtual command: ${cmd}`);
184
+ const argValues = args.map((a) => a.value || a);
185
+ const result = await this._runVirtual(cmd, argValues);
186
+ await handleRedirects(result, redirects);
187
+ return result;
188
+ }
189
+
190
+ const commandStr = buildCommandString(cmd, args, redirects);
191
+ trace('ProcessRunner', () => `Executing real command: ${commandStr}`);
192
+
193
+ const ProcessRunnerRef = this.constructor;
194
+ const runner = new ProcessRunnerRef(
195
+ { mode: 'shell', command: commandStr },
196
+ { ...this.options, cwd: process.cwd(), _bypassVirtual: true }
197
+ );
198
+
199
+ return await runner;
200
+ };
201
+
202
+ ProcessRunner.prototype.pipe = function (destination) {
203
+ trace(
204
+ 'ProcessRunner',
205
+ () =>
206
+ `pipe ENTER | ${JSON.stringify({ hasDestination: !!destination, destinationType: destination?.constructor?.name }, null, 2)}`
207
+ );
208
+
209
+ const ProcessRunnerRef = this.constructor;
210
+
211
+ if (destination instanceof ProcessRunnerRef) {
212
+ trace(
213
+ 'ProcessRunner',
214
+ () =>
215
+ `BRANCH: pipe => PROCESS_RUNNER_DEST | ${JSON.stringify({}, null, 2)}`
216
+ );
217
+ const pipeSpec = { mode: 'pipeline', source: this, destination };
218
+ const pipeRunner = new ProcessRunnerRef(pipeSpec, {
219
+ ...this.options,
220
+ capture: destination.options.capture ?? true,
221
+ });
222
+ trace(
223
+ 'ProcessRunner',
224
+ () => `pipe EXIT | ${JSON.stringify({ mode: 'pipeline' }, null, 2)}`
225
+ );
226
+ return pipeRunner;
227
+ }
228
+
229
+ if (destination && destination.spec) {
230
+ trace(
231
+ 'ProcessRunner',
232
+ () =>
233
+ `BRANCH: pipe => TEMPLATE_LITERAL_DEST | ${JSON.stringify({}, null, 2)}`
234
+ );
235
+ const destRunner = new ProcessRunnerRef(
236
+ destination.spec,
237
+ destination.options
238
+ );
239
+ return this.pipe(destRunner);
240
+ }
241
+
242
+ trace(
243
+ 'ProcessRunner',
244
+ () => `BRANCH: pipe => INVALID_DEST | ${JSON.stringify({}, null, 2)}`
245
+ );
246
+ throw new Error(
247
+ 'pipe() destination must be a ProcessRunner or $`command` result'
248
+ );
249
+ };
250
+ }