bunosh 0.5.8 → 0.5.9

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 CHANGED
@@ -448,6 +448,8 @@ export async function checkServices() {
448
448
  }
449
449
  ```
450
450
 
451
+ `task.try` fully isolates failures from the exit code. Any `shell`, `fetch`, `task`, or `assert` that fails inside the callback is recorded as a warning (yellow), never as a failed task — so the run still exits with code `0` if the rest succeeded. `task.stopOnFailures()` is also suppressed inside `task.try`: an inner failure will never call `process.exit(1)`. The return value (`true`/`false`) is the only signal you act on.
452
+
451
453
  ## Documentation
452
454
 
453
455
  - **[Examples](docs/examples.md)** — Real-world examples and workflows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunosh",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Task runner that turns JavaScript functions into CLI commands. Runs on Bun and Node.js.",
5
5
  "type": "module",
6
6
  "module": "index.js",
package/src/task.js CHANGED
@@ -99,10 +99,21 @@ export function getTaskPrefix(taskId) {
99
99
 
100
100
  export function createTaskInfo(name, parentId = null, isSilent = false) {
101
101
  const taskInfo = new TaskInfo(name, Date.now(), TaskStatus.RUNNING, parentId, isSilent);
102
+
103
+ let p = parentId;
104
+ while (p) {
105
+ const parent = runningTasks.get(p);
106
+ if (!parent) break;
107
+ if (parent.isTry || parent.isInsideTry) {
108
+ taskInfo.isInsideTry = true;
109
+ break;
110
+ }
111
+ p = parent.parentId;
112
+ }
113
+
102
114
  runningTasks.set(taskInfo.id, taskInfo);
103
115
  tasksExecuted.push(taskInfo);
104
116
 
105
- // Also add to global array for exit handler
106
117
  if (globalThis._bunoshGlobalTasksExecuted) {
107
118
  globalThis._bunoshGlobalTasksExecuted.push(taskInfo);
108
119
  }
@@ -114,10 +125,19 @@ export function finishTaskInfo(taskInfo, success = true, error = null, output =
114
125
  const endTime = Date.now();
115
126
  const duration = endTime - taskInfo.startTime;
116
127
 
117
- taskInfo.status = success ? TaskStatus.SUCCESS : TaskStatus.FAIL;
128
+ let status;
129
+ if (success) {
130
+ status = TaskStatus.SUCCESS;
131
+ } else if (taskInfo.isInsideTry) {
132
+ status = TaskStatus.WARNING;
133
+ } else {
134
+ status = TaskStatus.FAIL;
135
+ }
136
+
137
+ taskInfo.status = status;
118
138
  taskInfo.duration = duration;
119
139
  taskInfo.result = {
120
- status: success ? TaskStatus.SUCCESS : TaskStatus.FAIL,
140
+ status,
121
141
  output: error?.message || output || null
122
142
  };
123
143
 
@@ -132,6 +152,8 @@ export class TaskInfo {
132
152
  this.status = status;
133
153
  this.parentId = parentId;
134
154
  this.isSilent = isSilent;
155
+ this.isTry = false;
156
+ this.isInsideTry = false;
135
157
  }
136
158
  }
137
159
 
@@ -141,55 +163,60 @@ export async function tryTask(name, fn, isSilent = true) {
141
163
  name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
142
164
  }
143
165
 
144
- const taskInfo = createTaskInfo(name, null, isSilent);
166
+ const taskInfo = createTaskInfo(name, getCurrentTaskId() || null, isSilent);
167
+ taskInfo.isTry = true;
168
+ const startIndex = tasksExecuted.length;
145
169
 
146
170
  const shouldPrint = !globalSilenceMode && !isSilent;
147
171
  const printer = new Printer('task', taskInfo.id);
148
172
  if (shouldPrint) printer.start(name);
149
173
 
174
+ let result;
175
+ let caughtError = null;
150
176
  try {
151
- const result = await asyncLocalStorage.run(taskInfo.id, async () => {
177
+ result = await asyncLocalStorage.run(taskInfo.id, async () => {
152
178
  return await Promise.resolve(fn());
153
179
  });
180
+ } catch (err) {
181
+ caughtError = err;
182
+ }
154
183
 
155
- const endTime = Date.now();
156
- const duration = endTime - taskInfo.startTime;
184
+ const endTime = Date.now();
185
+ const duration = endTime - taskInfo.startTime;
157
186
 
158
- // Check if result is a TaskResult and if it has failed
159
- if (result && typeof result === 'object' && result.constructor && result.constructor.name === 'TaskResult') {
160
- if (result.hasFailed || result.hasWarning) {
161
- taskInfo.status = TaskStatus.WARNING;
162
- taskInfo.duration = duration;
163
- taskInfo.result = { status: TaskStatus.WARNING, output: result.output };
187
+ let failed = caughtError !== null;
164
188
 
165
- if (shouldPrint) printer.warning(name);
166
- runningTasks.delete(taskInfo.id);
189
+ if (!failed && result && typeof result === 'object' && result.constructor && result.constructor.name === 'TaskResult') {
190
+ if (result.hasFailed || result.hasWarning) failed = true;
191
+ }
167
192
 
168
- return false;
193
+ if (!failed) {
194
+ for (let i = startIndex; i < tasksExecuted.length; i++) {
195
+ const t = tasksExecuted[i];
196
+ if (t === taskInfo) continue;
197
+ const s = t.result?.status;
198
+ if (s === TaskStatus.FAIL || s === TaskStatus.WARNING) {
199
+ failed = true;
200
+ break;
169
201
  }
170
202
  }
203
+ }
171
204
 
172
- taskInfo.status = TaskStatus.SUCCESS;
173
- taskInfo.duration = duration;
174
- taskInfo.result = { status: TaskStatus.SUCCESS, output: result };
175
-
176
- if (shouldPrint) printer.finish(name);
177
- runningTasks.delete(taskInfo.id);
178
-
179
- return true;
180
- } catch (err) {
181
- const endTime = Date.now();
182
- const duration = endTime - taskInfo.startTime;
183
-
205
+ if (failed) {
184
206
  taskInfo.status = TaskStatus.WARNING;
185
207
  taskInfo.duration = duration;
186
- taskInfo.result = { status: TaskStatus.WARNING, output: err.message };
187
-
188
- if (shouldPrint) printer.warning(name, err);
208
+ taskInfo.result = { status: TaskStatus.WARNING, output: caughtError?.message || result?.output || null };
209
+ if (shouldPrint) printer.warning(name, caughtError);
189
210
  runningTasks.delete(taskInfo.id);
190
-
191
211
  return false;
192
212
  }
213
+
214
+ taskInfo.status = TaskStatus.SUCCESS;
215
+ taskInfo.duration = duration;
216
+ taskInfo.result = { status: TaskStatus.SUCCESS, output: result };
217
+ if (shouldPrint) printer.finish(name);
218
+ runningTasks.delete(taskInfo.id);
219
+ return true;
193
220
  }
194
221
 
195
222
  export async function task(name, fn, isSilent = false) {
@@ -198,7 +225,7 @@ export async function task(name, fn, isSilent = false) {
198
225
  name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
199
226
  }
200
227
 
201
- const taskInfo = createTaskInfo(name, null, isSilent);
228
+ const taskInfo = createTaskInfo(name, getCurrentTaskId() || null, isSilent);
202
229
 
203
230
  const shouldPrint = !globalSilenceMode && !isSilent;
204
231
  const printer = new Printer('task', taskInfo.id);
@@ -231,6 +258,15 @@ export async function task(name, fn, isSilent = false) {
231
258
  const endTime = Date.now();
232
259
  const duration = endTime - taskInfo.startTime;
233
260
 
261
+ if (taskInfo.isInsideTry) {
262
+ taskInfo.status = TaskStatus.WARNING;
263
+ taskInfo.duration = duration;
264
+ taskInfo.result = { status: TaskStatus.WARNING, output: err.message };
265
+ printer.warning(name, err);
266
+ runningTasks.delete(taskInfo.id);
267
+ return TaskResult.fail(err.message, { taskType: 'task', error: err });
268
+ }
269
+
234
270
  taskInfo.status = TaskStatus.FAIL;
235
271
  taskInfo.duration = duration;
236
272
  taskInfo.result = { status: TaskStatus.FAIL, output: err.message };
@@ -247,7 +283,7 @@ export async function task(name, fn, isSilent = false) {
247
283
  if (stopFailToggle && !isTestEnvironment) {
248
284
  process.exit(1);
249
285
  }
250
-
286
+
251
287
  return TaskResult.fail(err.message, { taskType: 'task', error: err });
252
288
  }
253
289
  }
@@ -16,6 +16,13 @@ export default function assert(condition, message = 'Assertion failed') {
16
16
  }
17
17
 
18
18
  const error = new Error(message);
19
+
20
+ if (taskInfo.isInsideTry) {
21
+ printer.warning(message, error);
22
+ finishTaskInfo(taskInfo, false, error, message);
23
+ return;
24
+ }
25
+
19
26
  printer.error(message, error);
20
27
  finishTaskInfo(taskInfo, false, error, message);
21
28