bunosh 0.3.1 → 0.3.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/README.md +616 -839
- package/bunosh.js +24 -0
- package/index.js +8 -2
- package/package.json +1 -1
- package/src/formatters/console.js +1 -0
- package/src/formatters/github-actions.js +4 -0
- package/src/printer.js +11 -0
- package/src/task.js +131 -8
package/bunosh.js
CHANGED
|
@@ -60,6 +60,30 @@ import(tasksFile).then((tasks) => {
|
|
|
60
60
|
handleBunoshfileError(error, tasksFile);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
// Handle unhandled promise rejections
|
|
64
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
65
|
+
if (!process.env.BUNOSH_COMMAND_STARTED) return;
|
|
66
|
+
|
|
67
|
+
console.error('\n❌ Unhandled Promise Rejection:');
|
|
68
|
+
console.error(reason instanceof Error ? reason.message : reason);
|
|
69
|
+
if (reason instanceof Error && reason.stack && process.env.BUNOSH_DEBUG) {
|
|
70
|
+
console.error(reason.stack);
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Handle uncaught exceptions
|
|
76
|
+
process.on('uncaughtException', (error) => {
|
|
77
|
+
if (!process.env.BUNOSH_COMMAND_STARTED) return;
|
|
78
|
+
|
|
79
|
+
console.error('\n❌ Uncaught Exception:');
|
|
80
|
+
console.error(error.message);
|
|
81
|
+
if (error.stack && process.env.BUNOSH_DEBUG) {
|
|
82
|
+
console.error(error.stack);
|
|
83
|
+
}
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
63
87
|
function handleBunoshfileError(error, filePath) {
|
|
64
88
|
banner();
|
|
65
89
|
console.log();
|
package/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import writeToFile from "./src/tasks/writeToFile.js";
|
|
|
5
5
|
import copyFile from "./src/tasks/copyFile.js";
|
|
6
6
|
import ai from "./src/tasks/ai.js";
|
|
7
7
|
import { ask, yell, say } from "./src/io.js";
|
|
8
|
-
import { task, stopOnFail, ignoreFail } from "./src/task.js";
|
|
8
|
+
import { task, tryTask, stopOnFail, ignoreFail, stopOnFailures, ignoreFailures, silence, prints, silent } from "./src/task.js";
|
|
9
9
|
|
|
10
|
-
export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
|
|
10
|
+
export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, tryTask, stopOnFail, ignoreFail, stopOnFailures, ignoreFailures, silence, prints, silent };
|
|
11
11
|
|
|
12
12
|
export function buildCmd(cmd) {
|
|
13
13
|
return function (args) {
|
|
@@ -28,6 +28,12 @@ global.bunosh = {
|
|
|
28
28
|
stopOnFail,
|
|
29
29
|
ignoreFail,
|
|
30
30
|
task,
|
|
31
|
+
try: tryTask,
|
|
32
|
+
stopOnFailures,
|
|
33
|
+
ignoreFailures,
|
|
34
|
+
silence,
|
|
35
|
+
prints,
|
|
36
|
+
silent,
|
|
31
37
|
buildCmd,
|
|
32
38
|
$: exec,
|
|
33
39
|
};
|
package/package.json
CHANGED
|
@@ -22,6 +22,10 @@ export class GitHubActionsFormatter extends BaseFormatter {
|
|
|
22
22
|
const errorDetails = extra.error ? ` - ${extra.error}` : '';
|
|
23
23
|
return `::endgroup::\n::error::❌ ${fullTaskName}${errorDetails}`;
|
|
24
24
|
|
|
25
|
+
case 'warning':
|
|
26
|
+
const warningDetails = extra.error ? ` - ${extra.error}` : '';
|
|
27
|
+
return `::endgroup::\n::warning::⚠️ ${fullTaskName}${warningDetails}`;
|
|
28
|
+
|
|
25
29
|
case 'output':
|
|
26
30
|
return taskName;
|
|
27
31
|
|
package/src/printer.js
CHANGED
|
@@ -65,6 +65,17 @@ export class Printer {
|
|
|
65
65
|
this.print(taskName, 'error', extra);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
warning(taskName, error = null, extra = {}) {
|
|
69
|
+
if (this.startTimeout) {
|
|
70
|
+
clearTimeout(this.startTimeout);
|
|
71
|
+
this.startTimeout = null;
|
|
72
|
+
}
|
|
73
|
+
if (error) {
|
|
74
|
+
extra.error = typeof error === 'string' ? error : error.message;
|
|
75
|
+
}
|
|
76
|
+
this.print(taskName, 'warning', extra);
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
output(line, isError = false) {
|
|
69
80
|
if (!line.trim()) return;
|
|
70
81
|
|
package/src/task.js
CHANGED
|
@@ -4,7 +4,8 @@ import Printer from './printer.js';
|
|
|
4
4
|
export const TaskStatus = {
|
|
5
5
|
RUNNING: 'running',
|
|
6
6
|
FAIL: 'fail',
|
|
7
|
-
SUCCESS: 'success'
|
|
7
|
+
SUCCESS: 'success',
|
|
8
|
+
WARNING: 'warning'
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
export const tasksExecuted = [];
|
|
@@ -12,6 +13,7 @@ export const runningTasks = new Map();
|
|
|
12
13
|
|
|
13
14
|
let taskCounter = 0;
|
|
14
15
|
let stopFailToggle = true;
|
|
16
|
+
let globalSilenceMode = false;
|
|
15
17
|
const asyncLocalStorage = new AsyncLocalStorage();
|
|
16
18
|
|
|
17
19
|
|
|
@@ -23,16 +25,50 @@ export function ignoreFail(enable = true) {
|
|
|
23
25
|
stopFailToggle = !enable;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
// Global failure mode control
|
|
29
|
+
// true = stop on failures (exit with code 1), false = continue on failures
|
|
30
|
+
let stopOnFailuresMode = false;
|
|
31
|
+
|
|
32
|
+
export function ignoreFailures() {
|
|
33
|
+
stopOnFailuresMode = false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function stopOnFailures() {
|
|
37
|
+
stopOnFailuresMode = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function silence() {
|
|
41
|
+
globalSilenceMode = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function prints() {
|
|
45
|
+
globalSilenceMode = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
const startTime = Date.now();
|
|
27
49
|
|
|
28
50
|
process.on('exit', (code) => {
|
|
29
51
|
if (!process.env.BUNOSH_COMMAND_STARTED) return;
|
|
30
52
|
|
|
31
53
|
const totalTime = Date.now() - startTime;
|
|
32
|
-
const success = code === 0;
|
|
33
54
|
const tasksFailed = tasksExecuted.filter(ti => ti.result?.status === TaskStatus.FAIL).length;
|
|
55
|
+
const tasksWarning = tasksExecuted.filter(ti => ti.result?.status === TaskStatus.WARNING).length;
|
|
56
|
+
|
|
57
|
+
// Check if we're in test environment
|
|
58
|
+
const isTestEnvironment = process.env.NODE_ENV === 'test' ||
|
|
59
|
+
typeof Bun?.jest !== 'undefined' ||
|
|
60
|
+
process.argv.some(arg => arg.includes('vitest') || arg.includes('jest') || arg.includes('--test') || arg.includes('test:'));
|
|
61
|
+
|
|
62
|
+
// Set exit code to 1 if any tasks failed AND we're not in ignoreFailures mode AND not in test environment
|
|
63
|
+
// Note: if stopOnFailuresMode is true, we would have already exited immediately
|
|
64
|
+
if (tasksFailed > 0 && !stopOnFailuresMode && !isTestEnvironment) {
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const finalExitCode = (tasksFailed > 0 && !stopOnFailuresMode && !isTestEnvironment) ? 1 : code;
|
|
69
|
+
const success = finalExitCode === 0;
|
|
34
70
|
|
|
35
|
-
console.log(`\n🍲 ${success ? '' : 'FAIL '}Exit Code: ${
|
|
71
|
+
console.log(`\n🍲 ${success ? '' : 'FAIL '}Exit Code: ${finalExitCode} | Tasks: ${tasksExecuted.length}${tasksFailed ? ` | Failed: ${tasksFailed}` : ''}${tasksWarning ? ` | Warnings: ${tasksWarning}` : ''} | Time: ${totalTime}ms`);
|
|
36
72
|
});
|
|
37
73
|
|
|
38
74
|
export function getRunningTaskCount() {
|
|
@@ -78,7 +114,53 @@ export class TaskInfo {
|
|
|
78
114
|
}
|
|
79
115
|
}
|
|
80
116
|
|
|
81
|
-
export async function
|
|
117
|
+
export async function tryTask(name, fn, isSilent = false) {
|
|
118
|
+
if (!fn) {
|
|
119
|
+
fn = name;
|
|
120
|
+
name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const taskInfo = new TaskInfo(name, Date.now(), TaskStatus.RUNNING);
|
|
124
|
+
|
|
125
|
+
tasksExecuted.push(taskInfo);
|
|
126
|
+
runningTasks.set(taskInfo.id, taskInfo);
|
|
127
|
+
|
|
128
|
+
const shouldPrint = !globalSilenceMode && !isSilent;
|
|
129
|
+
const printer = new Printer('task', taskInfo.id);
|
|
130
|
+
if (shouldPrint) printer.start(name);
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const result = await asyncLocalStorage.run(taskInfo.id, async () => {
|
|
134
|
+
return await Promise.resolve(fn());
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const endTime = Date.now();
|
|
138
|
+
const duration = endTime - taskInfo.startTime;
|
|
139
|
+
|
|
140
|
+
taskInfo.status = TaskStatus.SUCCESS;
|
|
141
|
+
taskInfo.duration = duration;
|
|
142
|
+
taskInfo.result = { status: TaskStatus.SUCCESS, output: result };
|
|
143
|
+
|
|
144
|
+
if (shouldPrint) printer.finish(name);
|
|
145
|
+
runningTasks.delete(taskInfo.id);
|
|
146
|
+
|
|
147
|
+
return true;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const endTime = Date.now();
|
|
150
|
+
const duration = endTime - taskInfo.startTime;
|
|
151
|
+
|
|
152
|
+
taskInfo.status = TaskStatus.WARNING;
|
|
153
|
+
taskInfo.duration = duration;
|
|
154
|
+
taskInfo.result = { status: TaskStatus.WARNING, output: err.message };
|
|
155
|
+
|
|
156
|
+
if (shouldPrint) printer.warning(name, err);
|
|
157
|
+
runningTasks.delete(taskInfo.id);
|
|
158
|
+
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function task(name, fn, isSilent = false) {
|
|
82
164
|
if (!fn) {
|
|
83
165
|
fn = name;
|
|
84
166
|
name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
|
|
@@ -89,8 +171,9 @@ export async function task(name, fn) {
|
|
|
89
171
|
tasksExecuted.push(taskInfo);
|
|
90
172
|
runningTasks.set(taskInfo.id, taskInfo);
|
|
91
173
|
|
|
174
|
+
const shouldPrint = !globalSilenceMode && !isSilent;
|
|
92
175
|
const printer = new Printer('task', taskInfo.id);
|
|
93
|
-
printer.start(name);
|
|
176
|
+
if (shouldPrint) printer.start(name);
|
|
94
177
|
|
|
95
178
|
try {
|
|
96
179
|
const result = await asyncLocalStorage.run(taskInfo.id, async () => {
|
|
@@ -104,7 +187,7 @@ export async function task(name, fn) {
|
|
|
104
187
|
taskInfo.duration = duration;
|
|
105
188
|
taskInfo.result = { status: TaskStatus.SUCCESS, output: result };
|
|
106
189
|
|
|
107
|
-
printer.finish(name);
|
|
190
|
+
if (shouldPrint) printer.finish(name);
|
|
108
191
|
runningTasks.delete(taskInfo.id);
|
|
109
192
|
|
|
110
193
|
return result;
|
|
@@ -116,14 +199,20 @@ export async function task(name, fn) {
|
|
|
116
199
|
taskInfo.duration = duration;
|
|
117
200
|
taskInfo.result = { status: TaskStatus.FAIL, output: err.message };
|
|
118
201
|
|
|
119
|
-
printer.error(name, err);
|
|
202
|
+
if (shouldPrint) printer.error(name, err);
|
|
120
203
|
runningTasks.delete(taskInfo.id);
|
|
121
204
|
|
|
122
205
|
// Don't exit during testing
|
|
123
206
|
const isTestEnvironment = process.env.NODE_ENV === 'test' ||
|
|
124
207
|
typeof Bun?.jest !== 'undefined' ||
|
|
125
|
-
process.argv.some(arg => arg.includes('test'));
|
|
208
|
+
process.argv.some(arg => arg.includes('vitest') || arg.includes('jest') || arg.includes('--test') || arg.includes('test:'));
|
|
126
209
|
|
|
210
|
+
// Exit immediately if stopOnFailures mode is enabled
|
|
211
|
+
if (stopOnFailuresMode && !isTestEnvironment) {
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Also exit if stopFailToggle is enabled (legacy behavior)
|
|
127
216
|
if (stopFailToggle && !isTestEnvironment) {
|
|
128
217
|
process.exit(1);
|
|
129
218
|
}
|
|
@@ -132,6 +221,32 @@ export async function task(name, fn) {
|
|
|
132
221
|
}
|
|
133
222
|
}
|
|
134
223
|
|
|
224
|
+
export class SilentTaskWrapper {
|
|
225
|
+
constructor() {
|
|
226
|
+
this.silent = true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async try(name, fn) {
|
|
230
|
+
if (!fn) {
|
|
231
|
+
fn = name;
|
|
232
|
+
name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
|
|
233
|
+
}
|
|
234
|
+
return await tryTask(name, fn, true);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async task(name, fn) {
|
|
238
|
+
if (!fn) {
|
|
239
|
+
fn = name;
|
|
240
|
+
name = fn.toString().slice(0, 50).replace(/\s+/g, ' ').trim();
|
|
241
|
+
}
|
|
242
|
+
return await task(name, fn, true);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function silent() {
|
|
247
|
+
return new SilentTaskWrapper();
|
|
248
|
+
}
|
|
249
|
+
|
|
135
250
|
export class TaskResult {
|
|
136
251
|
constructor({ status, output }) {
|
|
137
252
|
this.status = status;
|
|
@@ -146,6 +261,10 @@ export class TaskResult {
|
|
|
146
261
|
return this.status === TaskStatus.SUCCESS;
|
|
147
262
|
}
|
|
148
263
|
|
|
264
|
+
get hasWarning() {
|
|
265
|
+
return this.status === TaskStatus.WARNING;
|
|
266
|
+
}
|
|
267
|
+
|
|
149
268
|
static fail(output = null) {
|
|
150
269
|
return new TaskResult({ status: TaskStatus.FAIL, output });
|
|
151
270
|
}
|
|
@@ -153,4 +272,8 @@ export class TaskResult {
|
|
|
153
272
|
static success(output = null) {
|
|
154
273
|
return new TaskResult({ status: TaskStatus.SUCCESS, output });
|
|
155
274
|
}
|
|
275
|
+
|
|
276
|
+
static warning(output = null) {
|
|
277
|
+
return new TaskResult({ status: TaskStatus.WARNING, output });
|
|
278
|
+
}
|
|
156
279
|
}
|