bunosh 0.4.14 → 0.5.6
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 +92 -565
- package/bunosh.js +32 -193
- package/index.js +4 -3
- package/package.json +18 -2
- package/src/error-formatter.js +80 -0
- package/src/formatters/console.js +5 -1
- package/src/io.js +0 -5
- package/src/printer.js +29 -9
- package/src/program.js +131 -343
- package/src/task.js +8 -1
- package/src/tasks/exec.js +4 -248
- package/src/tasks/fetch.js +2 -1
- package/src/tasks/shell.js +194 -119
- package/src/upgrade.js +135 -30
- package/src/mcp-server.js +0 -575
package/src/task.js
CHANGED
|
@@ -198,6 +198,8 @@ export async function task(name, fn, isSilent = false) {
|
|
|
198
198
|
|
|
199
199
|
// Check if result is a TaskResult instance
|
|
200
200
|
if (result && result.constructor && result.constructor.name === 'TaskResult') {
|
|
201
|
+
printer.cancel();
|
|
202
|
+
runningTasks.delete(taskInfo.id);
|
|
201
203
|
return result;
|
|
202
204
|
}
|
|
203
205
|
|
|
@@ -246,8 +248,13 @@ export async function task(name, fn, isSilent = false) {
|
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
// Add
|
|
251
|
+
// Add methods to task function
|
|
250
252
|
task.try = tryTask;
|
|
253
|
+
task.stopOnFailures = stopOnFailures;
|
|
254
|
+
task.ignoreFailures = ignoreFailures;
|
|
255
|
+
task.silence = silence;
|
|
256
|
+
task.prints = prints;
|
|
257
|
+
task.silent = silent;
|
|
251
258
|
|
|
252
259
|
|
|
253
260
|
export class SilentTaskWrapper {
|
package/src/tasks/exec.js
CHANGED
|
@@ -1,251 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const isBun = typeof Bun !== 'undefined';
|
|
1
|
+
// Deprecated: `exec` is now an alias for `shell`.
|
|
2
|
+
// Use `shell` (or `$`) instead. This module is kept for backward compatibility.
|
|
3
|
+
import shell from './shell.js';
|
|
5
4
|
|
|
6
5
|
export default function exec(strings, ...values) {
|
|
7
|
-
|
|
8
|
-
if (!Array.isArray(strings)) {
|
|
9
|
-
// If first argument is a string, treat it as the command
|
|
10
|
-
if (typeof strings === 'string') {
|
|
11
|
-
strings = [strings];
|
|
12
|
-
values = [];
|
|
13
|
-
} else {
|
|
14
|
-
throw new Error('exec() must be called as a template literal: exec`command` or exec("command")');
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const cmd = strings.reduce((accumulator, str, i) => {
|
|
19
|
-
return accumulator + str + (values[i] || '');
|
|
20
|
-
}, '');
|
|
21
|
-
|
|
22
|
-
let envs = null;
|
|
23
|
-
let cwd = null;
|
|
24
|
-
|
|
25
|
-
const cmdPromise = new Promise(async (resolve, reject) => {
|
|
26
|
-
// Wait for the next event loop tick to ensure .env() and .cwd() have been called
|
|
27
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
28
|
-
|
|
29
|
-
const currentTaskId = getCurrentTaskId();
|
|
30
|
-
|
|
31
|
-
// Check if parent task is silent
|
|
32
|
-
let isParentSilent = false;
|
|
33
|
-
if (currentTaskId) {
|
|
34
|
-
const parentTask = runningTasks.get(currentTaskId);
|
|
35
|
-
if (parentTask && parentTask.isSilent) {
|
|
36
|
-
isParentSilent = true;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const extraInfo = {};
|
|
41
|
-
if (cwd) extraInfo.cwd = cwd;
|
|
42
|
-
if (envs) extraInfo.env = envs;
|
|
43
|
-
|
|
44
|
-
const taskInfo = createTaskInfo(cmd, currentTaskId, isParentSilent);
|
|
45
|
-
const printer = new Printer('exec', taskInfo.id);
|
|
46
|
-
printer.start(cmd, extraInfo);
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
if (global.disableBunForTesting || !isBun) {
|
|
50
|
-
const result = await nodeExec(cmd, extraInfo, printer, taskInfo);
|
|
51
|
-
if (result.status === 'success') {
|
|
52
|
-
finishTaskInfo(taskInfo, true, null, result.output);
|
|
53
|
-
resolve(result);
|
|
54
|
-
} else {
|
|
55
|
-
finishTaskInfo(taskInfo, false, new Error(result.output), result.output);
|
|
56
|
-
resolve(result);
|
|
57
|
-
}
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Bun implementation with real-time streaming
|
|
62
|
-
const needsShell = cmd.includes('|') || cmd.includes('>') || cmd.includes('<') || cmd.includes('&&') || cmd.includes('||') || cmd.includes("'") || cmd.includes('"') || cmd.includes(';');
|
|
63
|
-
|
|
64
|
-
const { spawn } = Bun;
|
|
65
|
-
const proc = spawn({
|
|
66
|
-
cmd: needsShell ? ['/bin/sh', '-c', cmd] : cmd.trim().split(/\s+/),
|
|
67
|
-
cwd: cwd || process.cwd(),
|
|
68
|
-
env: {
|
|
69
|
-
...(envs ? { ...process.env, ...envs } : process.env),
|
|
70
|
-
},
|
|
71
|
-
stdout: "pipe",
|
|
72
|
-
stderr: "pipe",
|
|
73
|
-
stdin: "ignore"
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const decoder = new TextDecoder();
|
|
77
|
-
let output = '';
|
|
78
|
-
let stdout = '';
|
|
79
|
-
let stderr = '';
|
|
80
|
-
let finished = false;
|
|
81
|
-
|
|
82
|
-
// Process stdout
|
|
83
|
-
const readStdout = async () => {
|
|
84
|
-
const reader = proc.stdout.getReader();
|
|
85
|
-
let buffer = '';
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
while (!finished) {
|
|
89
|
-
const { done, value } = await reader.read();
|
|
90
|
-
if (done) break;
|
|
91
|
-
|
|
92
|
-
const text = decoder.decode(value, { stream: true });
|
|
93
|
-
buffer += text;
|
|
94
|
-
|
|
95
|
-
const lines = buffer.split('\n');
|
|
96
|
-
buffer = lines.pop();
|
|
97
|
-
|
|
98
|
-
for (const line of lines) {
|
|
99
|
-
if (line.trim()) {
|
|
100
|
-
printer.output(line);
|
|
101
|
-
output += line + '\n';
|
|
102
|
-
stdout += line + '\n';
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (buffer.trim()) {
|
|
108
|
-
printer.output(buffer);
|
|
109
|
-
output += buffer + '\n';
|
|
110
|
-
stdout += buffer + '\n';
|
|
111
|
-
}
|
|
112
|
-
} finally {
|
|
113
|
-
reader.releaseLock();
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Process stderr
|
|
118
|
-
const readStderr = async () => {
|
|
119
|
-
const reader = proc.stderr.getReader();
|
|
120
|
-
let buffer = '';
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
while (!finished) {
|
|
124
|
-
const { done, value } = await reader.read();
|
|
125
|
-
if (done) break;
|
|
126
|
-
|
|
127
|
-
const text = decoder.decode(value, { stream: true });
|
|
128
|
-
buffer += text;
|
|
129
|
-
|
|
130
|
-
const lines = buffer.split('\n');
|
|
131
|
-
buffer = lines.pop();
|
|
132
|
-
|
|
133
|
-
for (const line of lines) {
|
|
134
|
-
if (line.trim()) {
|
|
135
|
-
printer.output(line, true);
|
|
136
|
-
output += line + '\n';
|
|
137
|
-
stderr += line + '\n';
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (buffer.trim()) {
|
|
143
|
-
printer.output(buffer, true);
|
|
144
|
-
output += buffer + '\n';
|
|
145
|
-
stderr += buffer + '\n';
|
|
146
|
-
}
|
|
147
|
-
} finally {
|
|
148
|
-
reader.releaseLock();
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// Start reading both streams
|
|
153
|
-
const [, , exitResult] = await Promise.all([
|
|
154
|
-
readStdout(),
|
|
155
|
-
readStderr(),
|
|
156
|
-
proc.exited
|
|
157
|
-
]);
|
|
158
|
-
|
|
159
|
-
finished = true;
|
|
160
|
-
const exitCode = parseInt(exitResult, 10);
|
|
161
|
-
|
|
162
|
-
const metadata = {
|
|
163
|
-
taskType: 'exec',
|
|
164
|
-
exitCode,
|
|
165
|
-
stdout: stdout.trim(),
|
|
166
|
-
stderr: stderr.trim()
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
if (exitCode === 0) {
|
|
170
|
-
printer.finish(cmd);
|
|
171
|
-
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
172
|
-
resolve(TaskResult.success(output.trim(), metadata));
|
|
173
|
-
} else {
|
|
174
|
-
const error = new Error(`Exit code: ${exitCode}`);
|
|
175
|
-
printer.error(cmd, null, { exitCode });
|
|
176
|
-
finishTaskInfo(taskInfo, false, error, output.trim());
|
|
177
|
-
resolve(TaskResult.fail(output.trim(), metadata));
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
printer.error(cmd, error);
|
|
181
|
-
finishTaskInfo(taskInfo, false, error, error.message);
|
|
182
|
-
resolve(TaskResult.fail(error.message, { taskType: 'exec' }));
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
cmdPromise.env = (newEnvs) => {
|
|
187
|
-
envs = newEnvs;
|
|
188
|
-
return cmdPromise;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
cmdPromise.cwd = (newCwd) => {
|
|
192
|
-
cwd = newCwd;
|
|
193
|
-
return cmdPromise;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
return cmdPromise;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async function nodeExec(cmd, extraInfo, printer, taskInfo) {
|
|
200
|
-
// Node.js fallback - simple execution without real-time output
|
|
201
|
-
const { spawn } = await import('child_process');
|
|
202
|
-
|
|
203
|
-
return new Promise((resolve) => {
|
|
204
|
-
const proc = spawn('sh', ['-c', cmd], {
|
|
205
|
-
cwd: extraInfo.cwd || process.cwd(),
|
|
206
|
-
env: extraInfo.env ? { ...process.env, ...extraInfo.env } : process.env,
|
|
207
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
let output = '';
|
|
211
|
-
let stdout = '';
|
|
212
|
-
let stderr = '';
|
|
213
|
-
|
|
214
|
-
proc.stdout.on('data', (data) => {
|
|
215
|
-
const text = data.toString();
|
|
216
|
-
printer.output(text.trim());
|
|
217
|
-
output += text;
|
|
218
|
-
stdout += text;
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
proc.stderr.on('data', (data) => {
|
|
222
|
-
const text = data.toString();
|
|
223
|
-
printer.output(text.trim(), true);
|
|
224
|
-
output += text;
|
|
225
|
-
stderr += text;
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
proc.on('close', (code) => {
|
|
229
|
-
const combinedOutput = (output).trim();
|
|
230
|
-
const metadata = {
|
|
231
|
-
taskType: 'exec',
|
|
232
|
-
exitCode: code,
|
|
233
|
-
stdout: stdout.trim(),
|
|
234
|
-
stderr: stderr.trim()
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
if (code === 0) {
|
|
238
|
-
printer.finish(cmd);
|
|
239
|
-
resolve(TaskResult.success(combinedOutput, metadata));
|
|
240
|
-
} else {
|
|
241
|
-
printer.error(cmd, new Error(`Exit code: ${code}`));
|
|
242
|
-
resolve(TaskResult.fail(combinedOutput, metadata));
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
proc.on('error', (error) => {
|
|
247
|
-
printer.error(cmd, error);
|
|
248
|
-
resolve(TaskResult.fail(error.message, { taskType: 'exec' }));
|
|
249
|
-
});
|
|
250
|
-
});
|
|
6
|
+
return shell(strings, ...values);
|
|
251
7
|
}
|
package/src/tasks/fetch.js
CHANGED
|
@@ -23,6 +23,7 @@ export default async function httpFetch() {
|
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
25
|
const response = await fetch(...arguments);
|
|
26
|
+
const responseForBody = response.clone(); // Clone before streaming consumes the body
|
|
26
27
|
const textDecoder = new TextDecoder();
|
|
27
28
|
let output = '';
|
|
28
29
|
|
|
@@ -40,7 +41,7 @@ export default async function httpFetch() {
|
|
|
40
41
|
|
|
41
42
|
const metadata = {
|
|
42
43
|
taskType: 'fetch',
|
|
43
|
-
response:
|
|
44
|
+
response: responseForBody, // Unconsumed clone so json()/text() work later
|
|
44
45
|
status: response.status,
|
|
45
46
|
statusText: response.statusText,
|
|
46
47
|
headers: Object.fromEntries(response.headers.entries())
|
package/src/tasks/shell.js
CHANGED
|
@@ -1,66 +1,34 @@
|
|
|
1
|
-
import { TaskResult, createTaskInfo, finishTaskInfo, getCurrentTaskId, runningTasks } from
|
|
2
|
-
import Printer from
|
|
1
|
+
import { TaskResult, createTaskInfo, finishTaskInfo, getCurrentTaskId, runningTasks } from '../task.js';
|
|
2
|
+
import Printer from '../printer.js';
|
|
3
3
|
|
|
4
4
|
const isBun = typeof Bun !== 'undefined' && typeof Bun.spawn === 'function';
|
|
5
5
|
|
|
6
6
|
export default function shell(strings, ...values) {
|
|
7
|
-
let envs = null;
|
|
8
|
-
let cwd = null;
|
|
9
|
-
|
|
10
7
|
// Check if called as regular function instead of template literal
|
|
11
8
|
if (!Array.isArray(strings)) {
|
|
12
|
-
// If first argument is a string, treat it as the command
|
|
13
9
|
if (typeof strings === 'string') {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
console.log('Note: shell() with string argument falls back to exec()');
|
|
17
|
-
const cmdPromise = (async () => {
|
|
18
|
-
const { default: exec } = await import("./exec.js");
|
|
19
|
-
let execPromise = exec(strings);
|
|
20
|
-
if (envs) execPromise = execPromise.env(envs);
|
|
21
|
-
if (cwd) execPromise = execPromise.cwd(cwd);
|
|
22
|
-
return execPromise;
|
|
23
|
-
})();
|
|
24
|
-
|
|
25
|
-
// Add .env and .cwd methods
|
|
26
|
-
cmdPromise.env = (newEnvs) => {
|
|
27
|
-
envs = newEnvs;
|
|
28
|
-
return cmdPromise;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
cmdPromise.cwd = (newCwd) => {
|
|
32
|
-
cwd = newCwd;
|
|
33
|
-
return cmdPromise;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return cmdPromise;
|
|
10
|
+
strings = [strings];
|
|
11
|
+
values = [];
|
|
37
12
|
} else {
|
|
38
|
-
throw new Error('shell() must be called as a template literal: shell`command`');
|
|
13
|
+
throw new Error('shell() must be called as a template literal: shell`command` or shell("command")');
|
|
39
14
|
}
|
|
40
15
|
}
|
|
41
|
-
|
|
16
|
+
|
|
42
17
|
const cmd = strings.reduce((accumulator, str, i) => {
|
|
43
|
-
return accumulator + str + (values[i] ||
|
|
44
|
-
},
|
|
18
|
+
return accumulator + str + (values[i] || '');
|
|
19
|
+
}, '');
|
|
45
20
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (cwd) extraInfo.cwd = cwd;
|
|
49
|
-
if (envs) extraInfo.env = envs;
|
|
21
|
+
let envs = null;
|
|
22
|
+
let cwd = null;
|
|
50
23
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const result = await execPromise;
|
|
57
|
-
resolve(result);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
24
|
+
const cmdPromise = new Promise(async (resolve) => {
|
|
25
|
+
// Wait for the next event loop tick to ensure .env() and .cwd() have been called
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
27
|
+
|
|
28
|
+
const currentTaskId = getCurrentTaskId();
|
|
60
29
|
|
|
61
30
|
// Check if parent task is silent
|
|
62
31
|
let isParentSilent = false;
|
|
63
|
-
const currentTaskId = getCurrentTaskId();
|
|
64
32
|
if (currentTaskId) {
|
|
65
33
|
const parentTask = runningTasks.get(currentTaskId);
|
|
66
34
|
if (parentTask && parentTask.isSilent) {
|
|
@@ -68,91 +36,144 @@ export default function shell(strings, ...values) {
|
|
|
68
36
|
}
|
|
69
37
|
}
|
|
70
38
|
|
|
71
|
-
const
|
|
72
|
-
|
|
39
|
+
const extraInfo = {};
|
|
40
|
+
if (cwd) extraInfo.cwd = cwd;
|
|
41
|
+
if (envs) extraInfo.env = envs;
|
|
42
|
+
|
|
43
|
+
const taskInfo = createTaskInfo(cmd, currentTaskId, isParentSilent);
|
|
44
|
+
const printer = new Printer('shell', taskInfo.id);
|
|
73
45
|
printer.start(cmd, extraInfo);
|
|
74
46
|
|
|
75
47
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
48
|
+
if (global.disableBunForTesting || !isBun) {
|
|
49
|
+
const result = await nodeShell(cmd, extraInfo, printer);
|
|
50
|
+
if (result.status === 'success') {
|
|
51
|
+
finishTaskInfo(taskInfo, true, null, result.output);
|
|
52
|
+
resolve(result);
|
|
53
|
+
} else {
|
|
54
|
+
finishTaskInfo(taskInfo, false, new Error(result.output), result.output);
|
|
55
|
+
resolve(result);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
86
58
|
}
|
|
87
59
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
60
|
+
// Bun implementation with real-time streaming
|
|
61
|
+
const needsShell = cmd.includes('|') || cmd.includes('>') || cmd.includes('<') || cmd.includes('&&') || cmd.includes('||') || cmd.includes("'") || cmd.includes('"') || cmd.includes(';') || cmd.includes('$') || cmd.includes('`') || cmd.includes('\n');
|
|
62
|
+
|
|
63
|
+
const { spawn } = Bun;
|
|
64
|
+
const proc = spawn({
|
|
65
|
+
cmd: needsShell ? ['/bin/sh', '-c', cmd] : cmd.trim().split(/\s+/),
|
|
66
|
+
cwd: cwd || process.cwd(),
|
|
67
|
+
env: {
|
|
68
|
+
...(envs ? { ...process.env, ...envs } : process.env),
|
|
69
|
+
},
|
|
70
|
+
stdout: "pipe",
|
|
71
|
+
stderr: "pipe",
|
|
72
|
+
stdin: "ignore"
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const decoder = new TextDecoder();
|
|
76
|
+
let output = '';
|
|
77
|
+
let stdout = '';
|
|
78
|
+
let stderr = '';
|
|
79
|
+
let finished = false;
|
|
80
|
+
|
|
81
|
+
// Process stdout
|
|
82
|
+
const readStdout = async () => {
|
|
83
|
+
const reader = proc.stdout.getReader();
|
|
84
|
+
let buffer = '';
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
while (!finished) {
|
|
88
|
+
const { done, value } = await reader.read();
|
|
89
|
+
if (done) break;
|
|
90
|
+
|
|
91
|
+
const text = decoder.decode(value, { stream: true });
|
|
92
|
+
buffer += text;
|
|
93
|
+
|
|
94
|
+
const lines = buffer.split('\n');
|
|
95
|
+
buffer = lines.pop();
|
|
96
|
+
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
if (line.trim()) {
|
|
99
|
+
printer.output(line);
|
|
100
|
+
output += line + '\n';
|
|
101
|
+
stdout += line + '\n';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (buffer.trim()) {
|
|
107
|
+
printer.output(buffer);
|
|
108
|
+
output += buffer + '\n';
|
|
109
|
+
stdout += buffer + '\n';
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
reader.releaseLock();
|
|
122
113
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Process stderr
|
|
117
|
+
const readStderr = async () => {
|
|
118
|
+
const reader = proc.stderr.getReader();
|
|
119
|
+
let buffer = '';
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
while (!finished) {
|
|
123
|
+
const { done, value } = await reader.read();
|
|
124
|
+
if (done) break;
|
|
125
|
+
|
|
126
|
+
const text = decoder.decode(value, { stream: true });
|
|
127
|
+
buffer += text;
|
|
128
|
+
|
|
129
|
+
const lines = buffer.split('\n');
|
|
130
|
+
buffer = lines.pop();
|
|
131
|
+
|
|
131
132
|
for (const line of lines) {
|
|
132
133
|
if (line.trim()) {
|
|
133
134
|
printer.output(line, true);
|
|
135
|
+
output += line + '\n';
|
|
136
|
+
stderr += line + '\n';
|
|
134
137
|
}
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const error = new Error(`Exit code: ${shellError.exitCode}`);
|
|
146
|
-
printer.error(cmd, null, { exitCode: shellError.exitCode });
|
|
147
|
-
finishTaskInfo(taskInfo, false, error, errorOutput);
|
|
148
|
-
resolve(TaskResult.fail(errorOutput, metadata));
|
|
149
|
-
return;
|
|
150
|
-
} else {
|
|
151
|
-
const errorMessage = shellError.message || shellError.toString();
|
|
152
|
-
printer.error(cmd, shellError);
|
|
153
|
-
finishTaskInfo(taskInfo, false, shellError, errorMessage);
|
|
154
|
-
resolve(TaskResult.fail(errorMessage, { taskType: 'shell' }));
|
|
140
|
+
|
|
141
|
+
if (buffer.trim()) {
|
|
142
|
+
printer.output(buffer, true);
|
|
143
|
+
output += buffer + '\n';
|
|
144
|
+
stderr += buffer + '\n';
|
|
145
|
+
}
|
|
146
|
+
} finally {
|
|
147
|
+
reader.releaseLock();
|
|
155
148
|
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Start reading both streams
|
|
152
|
+
const [, , exitResult] = await Promise.all([
|
|
153
|
+
readStdout(),
|
|
154
|
+
readStderr(),
|
|
155
|
+
proc.exited
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
finished = true;
|
|
159
|
+
const exitCode = parseInt(exitResult, 10);
|
|
160
|
+
|
|
161
|
+
const metadata = {
|
|
162
|
+
taskType: 'shell',
|
|
163
|
+
exitCode,
|
|
164
|
+
stdout: stdout.trim(),
|
|
165
|
+
stderr: stderr.trim()
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (exitCode === 0) {
|
|
169
|
+
printer.finish(cmd);
|
|
170
|
+
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
171
|
+
resolve(TaskResult.success(output.trim(), metadata));
|
|
172
|
+
} else {
|
|
173
|
+
const error = new Error(`Exit code: ${exitCode}`);
|
|
174
|
+
printer.error(cmd, null, { exitCode });
|
|
175
|
+
finishTaskInfo(taskInfo, false, error, output.trim());
|
|
176
|
+
resolve(TaskResult.fail(output.trim(), metadata));
|
|
156
177
|
}
|
|
157
178
|
} catch (error) {
|
|
158
179
|
printer.error(cmd, error);
|
|
@@ -173,3 +194,57 @@ export default function shell(strings, ...values) {
|
|
|
173
194
|
|
|
174
195
|
return cmdPromise;
|
|
175
196
|
}
|
|
197
|
+
|
|
198
|
+
async function nodeShell(cmd, extraInfo, printer) {
|
|
199
|
+
// Node.js fallback - simple execution without real-time output
|
|
200
|
+
const { spawn } = await import('child_process');
|
|
201
|
+
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
const proc = spawn('sh', ['-c', cmd], {
|
|
204
|
+
cwd: extraInfo.cwd || process.cwd(),
|
|
205
|
+
env: extraInfo.env ? { ...process.env, ...extraInfo.env } : process.env,
|
|
206
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
let output = '';
|
|
210
|
+
let stdout = '';
|
|
211
|
+
let stderr = '';
|
|
212
|
+
|
|
213
|
+
proc.stdout.on('data', (data) => {
|
|
214
|
+
const text = data.toString();
|
|
215
|
+
printer.output(text.trim());
|
|
216
|
+
output += text;
|
|
217
|
+
stdout += text;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
proc.stderr.on('data', (data) => {
|
|
221
|
+
const text = data.toString();
|
|
222
|
+
printer.output(text.trim(), true);
|
|
223
|
+
output += text;
|
|
224
|
+
stderr += text;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
proc.on('close', (code) => {
|
|
228
|
+
const combinedOutput = output.trim();
|
|
229
|
+
const metadata = {
|
|
230
|
+
taskType: 'shell',
|
|
231
|
+
exitCode: code,
|
|
232
|
+
stdout: stdout.trim(),
|
|
233
|
+
stderr: stderr.trim()
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (code === 0) {
|
|
237
|
+
printer.finish(cmd);
|
|
238
|
+
resolve(TaskResult.success(combinedOutput, metadata));
|
|
239
|
+
} else {
|
|
240
|
+
printer.error(cmd, new Error(`Exit code: ${code}`));
|
|
241
|
+
resolve(TaskResult.fail(combinedOutput, metadata));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
proc.on('error', (error) => {
|
|
246
|
+
printer.error(cmd, error);
|
|
247
|
+
resolve(TaskResult.fail(error.message, { taskType: 'shell' }));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|