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/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 try method to task function
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
- import { TaskResult, createTaskInfo, finishTaskInfo, getCurrentTaskId, runningTasks } from '../task.js';
2
- import Printer from '../printer.js';
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
- // Check if called as regular function instead of template literal
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
  }
@@ -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: response.clone(), // Clone to allow json() to be called later
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())
@@ -1,66 +1,34 @@
1
- import { TaskResult, createTaskInfo, finishTaskInfo, getCurrentTaskId, runningTasks } from "../task.js";
2
- import Printer from "../printer.js";
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
- // For Bun shell, we need to create a template literal-like call
15
- // But since we can't, fall back to exec
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
- const cmdPromise = new Promise(async (resolve, reject) => {
47
- const extraInfo = {};
48
- if (cwd) extraInfo.cwd = cwd;
49
- if (envs) extraInfo.env = envs;
21
+ let envs = null;
22
+ let cwd = null;
50
23
 
51
- if (!isBun) {
52
- const { default: exec } = await import("./exec.js");
53
- let execPromise = exec([cmd]);
54
- if (envs) execPromise = execPromise.env(envs);
55
- if (cwd) execPromise = execPromise.cwd(cwd);
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 taskInfo = createTaskInfo(cmd, null, isParentSilent);
72
- const printer = new Printer("shell", taskInfo.id);
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
- const { $ } = await import("bun");
77
-
78
- let shell = $;
79
-
80
- if (cwd) {
81
- shell = shell.cwd(cwd);
82
- }
83
-
84
- if (envs) {
85
- shell = shell.env(envs);
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
- let result;
89
- try {
90
- result = await shell(strings, ...values);
91
-
92
- const output = await result.text();
93
-
94
- const metadata = {
95
- taskType: 'shell',
96
- exitCode: 0,
97
- stdout: output.trim(),
98
- stderr: ''
99
- };
100
-
101
- printer.finish(cmd);
102
- finishTaskInfo(taskInfo, true, null, output.trim());
103
- resolve(TaskResult.success(output.trim(), metadata));
104
- return;
105
-
106
- } catch (shellError) {
107
- const isCommandNotFound = shellError.stderr &&
108
- (shellError.stderr.includes('command not found') ||
109
- shellError.stderr.includes('bun: command not found'));
110
-
111
- if (isCommandNotFound) {
112
- printer.finish(cmd);
113
- finishTaskInfo(taskInfo, true, null, "fallback to exec");
114
-
115
- const { default: exec } = await import("./exec.js");
116
- let execPromise = exec([cmd]);
117
- if (envs) execPromise = execPromise.env(envs);
118
- if (cwd) execPromise = execPromise.cwd(cwd);
119
- const result = await execPromise;
120
- resolve(result);
121
- return;
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
- if (shellError.exitCode !== undefined) {
125
- const stderr = shellError.stderr ? Buffer.isBuffer(shellError.stderr) ? shellError.stderr.toString() : shellError.stderr : "";
126
- const stdout = shellError.stdout ? Buffer.isBuffer(shellError.stdout) ? shellError.stdout.toString() : shellError.stdout : "";
127
- const errorOutput = (stderr + stdout).trim() || `Command failed with exit code ${shellError.exitCode}`;
128
-
129
- if (errorOutput) {
130
- const lines = errorOutput.split('\n');
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
- const metadata = {
139
- taskType: 'shell',
140
- exitCode: shellError.exitCode,
141
- stdout: stdout.trim(),
142
- stderr: stderr.trim()
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
+ }