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,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
|
+
}
|