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/bunosh.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
process.removeAllListeners('warning');
|
|
4
|
+
process.on('warning', () => {});
|
|
2
5
|
|
|
3
|
-
// Set up global variables BEFORE any imports
|
|
4
6
|
globalThis._bunoshStartTime = Date.now();
|
|
5
7
|
globalThis._bunoshCommandCompleted = false;
|
|
6
8
|
globalThis._bunoshGlobalTasksExecuted = [];
|
|
@@ -12,35 +14,28 @@ globalThis._bunoshGlobalTaskStatus = {
|
|
|
12
14
|
WARNING: 'warning'
|
|
13
15
|
};
|
|
14
16
|
|
|
15
|
-
// Now import modules
|
|
16
|
-
|
|
17
17
|
import bunosh, { BUNOSHFILE, banner } from "./src/program.js";
|
|
18
18
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
19
19
|
import init from "./src/init.js";
|
|
20
20
|
import path from "path";
|
|
21
21
|
import { config } from "dotenv";
|
|
22
|
+
import { formatError } from "./src/error-formatter.js";
|
|
23
|
+
import color from "chalk";
|
|
22
24
|
|
|
23
|
-
/**
|
|
24
|
-
* Load environment variables from .env files following Bun's loading order
|
|
25
|
-
* @param {string} bunoshfileDir - Directory containing the Bunoshfile
|
|
26
|
-
* @param {string} customEnvFile - Optional custom env file path from --env-file option
|
|
27
|
-
*/
|
|
28
25
|
function loadEnvFiles(bunoshfileDir, customEnvFile = null) {
|
|
29
26
|
if (customEnvFile) {
|
|
30
|
-
// Load the specified env file
|
|
31
27
|
const customEnvPath = path.isAbsolute(customEnvFile)
|
|
32
28
|
? customEnvFile
|
|
33
29
|
: path.resolve(bunoshfileDir, customEnvFile);
|
|
34
30
|
|
|
35
31
|
if (existsSync(customEnvPath)) {
|
|
36
|
-
config({ path: customEnvPath });
|
|
32
|
+
config({ path: customEnvPath, quiet: true });
|
|
37
33
|
} else {
|
|
38
34
|
console.warn(`Warning: Specified env file not found: ${customEnvPath}`);
|
|
39
35
|
}
|
|
40
36
|
return;
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
// Follow Bun's automatic loading order
|
|
44
39
|
const envFiles = [
|
|
45
40
|
'.env',
|
|
46
41
|
'.env.production',
|
|
@@ -52,7 +47,7 @@ function loadEnvFiles(bunoshfileDir, customEnvFile = null) {
|
|
|
52
47
|
envFiles.forEach(envFile => {
|
|
53
48
|
const envPath = path.join(bunoshfileDir, envFile);
|
|
54
49
|
if (existsSync(envPath)) {
|
|
55
|
-
config({ path: envPath });
|
|
50
|
+
config({ path: envPath, quiet: true });
|
|
56
51
|
}
|
|
57
52
|
});
|
|
58
53
|
}
|
|
@@ -60,38 +55,32 @@ function loadEnvFiles(bunoshfileDir, customEnvFile = null) {
|
|
|
60
55
|
async function loadBunoshfiles(tasksFile) {
|
|
61
56
|
const path = await import('path');
|
|
62
57
|
const fs = await import('fs');
|
|
63
|
-
|
|
64
|
-
// Get the directory and base name of the tasks file
|
|
58
|
+
|
|
65
59
|
const dir = path.dirname(tasksFile);
|
|
66
60
|
const baseName = path.basename(tasksFile, '.js');
|
|
67
|
-
|
|
68
|
-
// Find all matching Bunoshfile variants
|
|
61
|
+
|
|
69
62
|
const files = fs.readdirSync(dir);
|
|
70
63
|
const bunoshFiles = files
|
|
71
64
|
.filter(file => {
|
|
72
|
-
// Match Bunoshfile.js, Bunoshfile.*.js
|
|
73
65
|
const regex = new RegExp(`^${baseName}(\\.\\w+)?\\.js$`);
|
|
74
66
|
return regex.test(file);
|
|
75
67
|
})
|
|
76
|
-
.sort();
|
|
77
|
-
|
|
68
|
+
.sort();
|
|
69
|
+
|
|
78
70
|
const allTasks = {};
|
|
79
71
|
const allSources = {};
|
|
80
|
-
|
|
72
|
+
|
|
81
73
|
for (const file of bunoshFiles) {
|
|
82
74
|
const filePath = path.join(dir, file);
|
|
83
75
|
try {
|
|
84
|
-
// Extract namespace from filename
|
|
85
76
|
let namespace = '';
|
|
86
77
|
if (file !== `${baseName}.js`) {
|
|
87
|
-
// Remove baseName and .js, then remove the leading dot
|
|
88
78
|
namespace = file.slice(baseName.length, -3).substring(1);
|
|
89
79
|
}
|
|
90
|
-
|
|
80
|
+
|
|
91
81
|
const tasks = await import(filePath);
|
|
92
82
|
const source = fs.readFileSync(filePath, 'utf-8');
|
|
93
|
-
|
|
94
|
-
// Add namespace prefix to tasks if namespace exists
|
|
83
|
+
|
|
95
84
|
if (namespace) {
|
|
96
85
|
Object.keys(tasks).forEach(key => {
|
|
97
86
|
if (typeof tasks[key] === 'function') {
|
|
@@ -100,7 +89,6 @@ async function loadBunoshfiles(tasksFile) {
|
|
|
100
89
|
}
|
|
101
90
|
});
|
|
102
91
|
} else {
|
|
103
|
-
// No namespace for the main Bunoshfile
|
|
104
92
|
Object.keys(tasks).forEach(key => {
|
|
105
93
|
if (typeof tasks[key] === 'function') {
|
|
106
94
|
allTasks[key] = tasks[key];
|
|
@@ -112,95 +100,41 @@ async function loadBunoshfiles(tasksFile) {
|
|
|
112
100
|
console.warn(`Warning: Could not load ${file}:`, error.message);
|
|
113
101
|
}
|
|
114
102
|
}
|
|
115
|
-
|
|
103
|
+
|
|
116
104
|
return { tasks: allTasks, sources: allSources };
|
|
117
105
|
}
|
|
118
106
|
|
|
119
107
|
async function main() {
|
|
120
108
|
|
|
121
|
-
// Parse --bunoshfile flag or BUNOSHFILE env var before importing tasks
|
|
122
109
|
const bunoshfileIndex = process.argv.indexOf('--bunoshfile');
|
|
123
110
|
let customBunoshfile = null;
|
|
124
111
|
|
|
125
112
|
if (bunoshfileIndex !== -1 && bunoshfileIndex + 1 < process.argv.length) {
|
|
126
113
|
customBunoshfile = process.argv[bunoshfileIndex + 1];
|
|
127
|
-
// Remove the flag and its value from process.argv so it doesn't interfere with command parsing
|
|
128
114
|
process.argv.splice(bunoshfileIndex, 2);
|
|
129
115
|
} else if (process.env.BUNOSHFILE) {
|
|
130
116
|
customBunoshfile = process.env.BUNOSHFILE;
|
|
131
117
|
}
|
|
132
118
|
|
|
133
|
-
// Parse --env-file flag
|
|
134
119
|
const envFileIndex = process.argv.findIndex(arg => arg.startsWith('--env-file='));
|
|
135
120
|
let customEnvFile = null;
|
|
136
121
|
|
|
137
122
|
if (envFileIndex !== -1) {
|
|
138
123
|
customEnvFile = process.argv[envFileIndex].split('=')[1];
|
|
139
|
-
// Remove the flag from process.argv so it doesn't interfere with command parsing
|
|
140
124
|
process.argv.splice(envFileIndex, 1);
|
|
141
125
|
}
|
|
142
126
|
|
|
143
|
-
// Check for -mcp flag first
|
|
144
|
-
const mcpFlagIndex = process.argv.indexOf('-mcp');
|
|
145
|
-
if (mcpFlagIndex !== -1) {
|
|
146
|
-
// Remove the flag from process.argv
|
|
147
|
-
process.argv.splice(mcpFlagIndex, 1);
|
|
148
|
-
|
|
149
|
-
// Set environment variable to indicate MCP mode
|
|
150
|
-
process.env.BUNOSH_MCP_MODE = 'true';
|
|
151
|
-
|
|
152
|
-
// Import MCP server and start it
|
|
153
|
-
const { createMcpServer, startMcpServer } = await import('./src/mcp-server.js');
|
|
154
|
-
|
|
155
|
-
let tasksFile;
|
|
156
|
-
let bunoshfileDir;
|
|
157
|
-
if (customBunoshfile) {
|
|
158
|
-
const resolvedPath = path.isAbsolute(customBunoshfile) ? customBunoshfile : path.resolve(customBunoshfile);
|
|
159
|
-
// If it's a directory, append the default BUNOSHFILE
|
|
160
|
-
if (existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
161
|
-
tasksFile = path.join(resolvedPath, BUNOSHFILE);
|
|
162
|
-
bunoshfileDir = resolvedPath;
|
|
163
|
-
} else {
|
|
164
|
-
tasksFile = resolvedPath;
|
|
165
|
-
bunoshfileDir = path.dirname(resolvedPath);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
tasksFile = path.join(process.cwd(), BUNOSHFILE);
|
|
169
|
-
bunoshfileDir = process.cwd();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (!existsSync(tasksFile)) {
|
|
173
|
-
console.error('Bunoshfile not found for MCP mode');
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Load environment files from the Bunoshfile directory
|
|
178
|
-
loadEnvFiles(bunoshfileDir, customEnvFile);
|
|
179
|
-
|
|
180
|
-
// Load tasks and sources
|
|
181
|
-
const { tasks, sources } = await loadBunoshfiles(tasksFile);
|
|
182
|
-
|
|
183
|
-
// Create and start MCP server
|
|
184
|
-
const server = createMcpServer(tasks, sources);
|
|
185
|
-
await startMcpServer(server);
|
|
186
|
-
|
|
187
|
-
return; // Exit early for MCP mode
|
|
188
|
-
}
|
|
189
|
-
|
|
190
127
|
let tasksFile;
|
|
191
128
|
let bunoshfileDir;
|
|
192
129
|
if (customBunoshfile) {
|
|
193
130
|
const resolvedPath = path.isAbsolute(customBunoshfile) ? customBunoshfile : path.resolve(customBunoshfile);
|
|
194
|
-
// If it's a directory, append the default BUNOSHFILE
|
|
195
131
|
if (existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
|
|
196
132
|
tasksFile = path.join(resolvedPath, BUNOSHFILE);
|
|
197
133
|
bunoshfileDir = resolvedPath;
|
|
198
|
-
// Change working directory to the bunoshfile directory
|
|
199
134
|
process.chdir(resolvedPath);
|
|
200
135
|
} else {
|
|
201
136
|
tasksFile = resolvedPath;
|
|
202
137
|
bunoshfileDir = path.dirname(resolvedPath);
|
|
203
|
-
// Change working directory to the bunoshfile's directory
|
|
204
138
|
process.chdir(path.dirname(resolvedPath));
|
|
205
139
|
}
|
|
206
140
|
} else {
|
|
@@ -208,44 +142,37 @@ async function main() {
|
|
|
208
142
|
bunoshfileDir = process.cwd();
|
|
209
143
|
}
|
|
210
144
|
|
|
211
|
-
// Load environment files from the Bunoshfile directory
|
|
212
145
|
loadEnvFiles(bunoshfileDir, customEnvFile);
|
|
213
146
|
|
|
214
|
-
// Handle -e flag for executing JavaScript code
|
|
215
147
|
const eFlagIndex = process.argv.indexOf('-e');
|
|
216
148
|
if (eFlagIndex !== -1) {
|
|
217
149
|
(async () => {
|
|
218
150
|
let jsCode = '';
|
|
219
|
-
|
|
220
|
-
// Check if code is provided as argument
|
|
151
|
+
|
|
221
152
|
if (eFlagIndex + 1 < process.argv.length && !process.argv[eFlagIndex + 1].startsWith('-')) {
|
|
222
153
|
jsCode = process.argv[eFlagIndex + 1];
|
|
223
154
|
} else if (!process.stdin.isTTY) {
|
|
224
|
-
// Read from stdin only if it's not a TTY (i.e., it's being piped)
|
|
225
155
|
const chunks = [];
|
|
226
156
|
for await (const chunk of process.stdin) {
|
|
227
157
|
chunks.push(chunk);
|
|
228
158
|
}
|
|
229
159
|
jsCode = Buffer.concat(chunks).toString('utf8').trim();
|
|
230
160
|
}
|
|
231
|
-
|
|
161
|
+
|
|
232
162
|
if (!jsCode) {
|
|
233
163
|
console.error('No JavaScript code provided');
|
|
234
164
|
process.exit(1);
|
|
235
165
|
}
|
|
236
|
-
|
|
166
|
+
|
|
237
167
|
try {
|
|
238
|
-
// Import bunosh globals before executing JavaScript
|
|
239
168
|
await import('./index.js');
|
|
240
|
-
|
|
241
|
-
// Make bunosh globals available to the function
|
|
169
|
+
|
|
242
170
|
for (const [key, value] of Object.entries(global.bunosh)) {
|
|
243
171
|
if (typeof value === 'function') {
|
|
244
172
|
globalThis[key] = value;
|
|
245
173
|
}
|
|
246
174
|
}
|
|
247
|
-
|
|
248
|
-
// Execute the JavaScript code with bunosh globals available
|
|
175
|
+
|
|
249
176
|
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
250
177
|
const func = new AsyncFunction(jsCode);
|
|
251
178
|
await func();
|
|
@@ -268,7 +195,7 @@ async function main() {
|
|
|
268
195
|
|
|
269
196
|
console.log();
|
|
270
197
|
console.error(`Bunoshfile not found: ${tasksFile}`);
|
|
271
|
-
console.log(customBunoshfile ?
|
|
198
|
+
console.log(customBunoshfile ?
|
|
272
199
|
`Run \`bunosh init\` in the directory or specify a valid --bunoshfile path` :
|
|
273
200
|
"Run `bunosh init` to create a new Bunoshfile here")
|
|
274
201
|
console.log();
|
|
@@ -277,9 +204,8 @@ async function main() {
|
|
|
277
204
|
|
|
278
205
|
await import('./index.js');
|
|
279
206
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
await bunosh(tasks, sources);
|
|
207
|
+
const { tasks, sources } = await loadBunoshfiles(tasksFile);
|
|
208
|
+
await bunosh(tasks, sources);
|
|
283
209
|
}
|
|
284
210
|
|
|
285
211
|
main().catch((error) => {
|
|
@@ -287,27 +213,22 @@ main().catch((error) => {
|
|
|
287
213
|
process.exit(1);
|
|
288
214
|
});
|
|
289
215
|
|
|
290
|
-
// Handle unhandled promise rejections
|
|
291
216
|
process.on('unhandledRejection', (reason, promise) => {
|
|
292
217
|
if (!process.env.BUNOSH_COMMAND_STARTED) return;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
console.error(
|
|
218
|
+
|
|
219
|
+
if (reason instanceof Error) {
|
|
220
|
+
console.error('\n' + formatError(reason));
|
|
221
|
+
} else {
|
|
222
|
+
console.error('\n' + color.red.bold('Unhandled Promise Rejection:'));
|
|
223
|
+
console.error(reason);
|
|
298
224
|
}
|
|
299
225
|
process.exit(1);
|
|
300
226
|
});
|
|
301
227
|
|
|
302
|
-
// Handle uncaught exceptions
|
|
303
228
|
process.on('uncaughtException', (error) => {
|
|
304
229
|
if (!process.env.BUNOSH_COMMAND_STARTED) return;
|
|
305
|
-
|
|
306
|
-
console.error('\n
|
|
307
|
-
console.error(error.message);
|
|
308
|
-
if (error.stack) {
|
|
309
|
-
console.error(error.stack);
|
|
310
|
-
}
|
|
230
|
+
|
|
231
|
+
console.error('\n' + formatError(error));
|
|
311
232
|
process.exit(1);
|
|
312
233
|
});
|
|
313
234
|
|
|
@@ -327,7 +248,6 @@ process.on('exit', (code) => {
|
|
|
327
248
|
|
|
328
249
|
const commandArgs = process.argv.slice(2);
|
|
329
250
|
|
|
330
|
-
// Test environment detection
|
|
331
251
|
const isTestEnvironment = process.env.NODE_ENV === 'test' ||
|
|
332
252
|
(typeof jest !== 'undefined' && jest.isRunning) ||
|
|
333
253
|
(process.env.VITEST_WORKER_ID !== undefined) ||
|
|
@@ -349,90 +269,9 @@ process.on('exit', (code) => {
|
|
|
349
269
|
const success = finalExitCode === 0;
|
|
350
270
|
|
|
351
271
|
if (globalThis._bunoshExitHandlerCalled) {
|
|
352
|
-
console.log('\n[DEBUG] Exit handler already called, skipping duplicate');
|
|
353
272
|
return;
|
|
354
273
|
}
|
|
355
274
|
globalThis._bunoshExitHandlerCalled = true;
|
|
356
275
|
|
|
357
276
|
console.log(`\n🍲 ${success ? '' : 'FAIL '}Exit Code: ${finalExitCode} | Tasks: ${tasksExecuted.length}${tasksFailed ? ` | Failed: ${tasksFailed}` : ''}${tasksWarning ? ` | Warnings: ${tasksWarning}` : ''} | Time: ${totalTime}ms`);
|
|
358
277
|
});
|
|
359
|
-
|
|
360
|
-
function handleBunoshfileError(error, filePath) {
|
|
361
|
-
// Don't show banner for errors - it interferes with error visibility
|
|
362
|
-
|
|
363
|
-
// Check for Babel parser syntax errors
|
|
364
|
-
if (error.code === 'BABEL_PARSER_SYNTAX_ERROR' ||
|
|
365
|
-
(error.reasonCode && error.loc) ||
|
|
366
|
-
error.constructor.name === 'SyntaxError') {
|
|
367
|
-
|
|
368
|
-
console.error(`❌ Syntax Error in ${path.basename(filePath)}:`);
|
|
369
|
-
console.log();
|
|
370
|
-
|
|
371
|
-
if (error.loc) {
|
|
372
|
-
console.error(` Line ${error.loc.line}, Column ${error.loc.column}:`);
|
|
373
|
-
|
|
374
|
-
// Provide specific error messages based on reasonCode
|
|
375
|
-
if (error.reasonCode === 'VarRedeclaration') {
|
|
376
|
-
console.error(` Variable redeclaration - '${error.message}' is already declared`);
|
|
377
|
-
} else if (error.reasonCode && error.reasonCode.includes('Unexpected')) {
|
|
378
|
-
console.error(` ${error.reasonCode}: ${error.message || 'Unexpected token'}`);
|
|
379
|
-
} else {
|
|
380
|
-
console.error(` ${error.message || error.reasonCode || 'Invalid syntax'}`);
|
|
381
|
-
}
|
|
382
|
-
} else {
|
|
383
|
-
console.error(` ${error.message || 'Invalid JavaScript syntax'}`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
console.log();
|
|
387
|
-
console.log('💡 Common issues:');
|
|
388
|
-
console.log(' • Missing semicolons or commas');
|
|
389
|
-
console.log(' • Unclosed brackets, parentheses, or quotes');
|
|
390
|
-
console.log(' • Invalid variable declarations');
|
|
391
|
-
console.log(' • Mixing import/export with require/module.exports');
|
|
392
|
-
console.log();
|
|
393
|
-
console.log(`📝 Edit your Bunoshfile: ${color.blue('bunosh edit')}`);
|
|
394
|
-
console.log(`🔧 Validate syntax: ${color.blue(`bun --check ${path.basename(filePath)}`)}`);
|
|
395
|
-
|
|
396
|
-
} else if (error.message && error.message.includes('SyntaxError')) {
|
|
397
|
-
console.error(`❌ JavaScript Syntax Error in ${path.basename(filePath)}:`);
|
|
398
|
-
console.log();
|
|
399
|
-
console.error(` ${error.message}`);
|
|
400
|
-
console.log();
|
|
401
|
-
console.log(`💡 Try running: ${color.blue('bun --check Bunoshfile.js')}`);
|
|
402
|
-
console.log(`📝 Edit your Bunoshfile: ${color.blue('bunosh edit')}`);
|
|
403
|
-
|
|
404
|
-
} else if (error.code === 'MODULE_NOT_FOUND' ||
|
|
405
|
-
error.message?.includes('Cannot resolve') ||
|
|
406
|
-
error.message?.includes('Could not resolve')) {
|
|
407
|
-
console.error(`❌ Module Import Error in ${path.basename(filePath)}:`);
|
|
408
|
-
console.log();
|
|
409
|
-
console.error(` ${error.message}`);
|
|
410
|
-
console.log();
|
|
411
|
-
console.log('💡 Common solutions:');
|
|
412
|
-
console.log(` • Run: ${color.blue('bun install')}`);
|
|
413
|
-
console.log(' • Check import paths are correct');
|
|
414
|
-
console.log(' • Ensure dependencies are listed in package.json');
|
|
415
|
-
|
|
416
|
-
} else {
|
|
417
|
-
console.error(`❌ Error loading ${path.basename(filePath)}:`);
|
|
418
|
-
console.log();
|
|
419
|
-
console.error(` ${error.message || error.toString()}`);
|
|
420
|
-
|
|
421
|
-
// Add stack trace for debugging if available
|
|
422
|
-
if (process.env.BUNOSH_DEBUG) {
|
|
423
|
-
console.log();
|
|
424
|
-
console.log('🐛 Debug stack trace:');
|
|
425
|
-
console.log(error.stack || 'No stack trace available');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
console.log();
|
|
429
|
-
console.log('💡 Try:');
|
|
430
|
-
console.log(` • Check the file exists: ${color.blue(`ls -la ${path.basename(filePath)}`)}`);
|
|
431
|
-
console.log(` • Validate syntax: ${color.blue(`bun --check ${path.basename(filePath)}`)}`);
|
|
432
|
-
console.log(` • Edit the file: ${color.blue('bunosh edit')}`);
|
|
433
|
-
console.log(` • Run with debug: ${color.blue('BUNOSH_DEBUG=1 bunosh')}`);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
console.log();
|
|
437
|
-
process.exit(1);
|
|
438
|
-
}
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import exec from "./src/tasks/exec.js";
|
|
2
1
|
import shell from "./src/tasks/shell.js";
|
|
2
|
+
// Deprecated: `exec` is an alias for `shell`, kept for backward compatibility.
|
|
3
|
+
const exec = shell;
|
|
3
4
|
import fetch from "./src/tasks/fetch.js";
|
|
4
5
|
import writeToFile from "./src/tasks/writeToFile.js";
|
|
5
6
|
import copyFile from "./src/tasks/copyFile.js";
|
|
@@ -11,7 +12,7 @@ export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, tr
|
|
|
11
12
|
|
|
12
13
|
export function buildCmd(cmd) {
|
|
13
14
|
return function (args) {
|
|
14
|
-
return
|
|
15
|
+
return shell`${cmd} ${args}`;
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -35,7 +36,7 @@ global.bunosh = {
|
|
|
35
36
|
silent,
|
|
36
37
|
TaskResult,
|
|
37
38
|
buildCmd,
|
|
38
|
-
$:
|
|
39
|
+
$: shell,
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
export default global.bunosh;
|
package/package.json
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunosh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.6",
|
|
4
|
+
"description": "Task runner that turns JavaScript functions into CLI commands. Runs on Bun and Node.js.",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"module": "index.js",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/DavertMik/bunosh.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/DavertMik/bunosh#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/DavertMik/bunosh/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "davert",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"task-runner",
|
|
18
|
+
"cli",
|
|
19
|
+
"bun",
|
|
20
|
+
"automation",
|
|
21
|
+
"scripts"
|
|
22
|
+
],
|
|
6
23
|
"bin": {
|
|
7
24
|
"bunosh": "./bunosh.js"
|
|
8
25
|
},
|
|
@@ -19,7 +36,6 @@
|
|
|
19
36
|
"@ai-sdk/openai": "^2.0.23",
|
|
20
37
|
"@babel/parser": "^7.27.5",
|
|
21
38
|
"@babel/traverse": "^7.27.4",
|
|
22
|
-
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
23
39
|
"ai": "^5.0.29",
|
|
24
40
|
"chalk": "^5.4.1",
|
|
25
41
|
"commander": "^14.0.0",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { codeFrameColumns } from "@babel/code-frame";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import color from "chalk";
|
|
5
|
+
|
|
6
|
+
function parseErrorLocation(error) {
|
|
7
|
+
if (!error.stack) return null;
|
|
8
|
+
|
|
9
|
+
const lines = error.stack.split('\n');
|
|
10
|
+
for (const line of lines) {
|
|
11
|
+
const match = line.match(/at .+? \((.+?):(\d+):(\d+)\)/) ||
|
|
12
|
+
line.match(/at (.+?):(\d+):(\d+)/);
|
|
13
|
+
if (!match) continue;
|
|
14
|
+
|
|
15
|
+
const file = match[1];
|
|
16
|
+
if (file.includes('node_modules/') || file.startsWith('node:')) continue;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
file,
|
|
20
|
+
line: parseInt(match[2], 10),
|
|
21
|
+
column: parseInt(match[3], 10),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function cleanStack(error) {
|
|
29
|
+
if (!error.stack) return '';
|
|
30
|
+
|
|
31
|
+
return error.stack
|
|
32
|
+
.split('\n')
|
|
33
|
+
.filter(line => {
|
|
34
|
+
if (!line.trim().startsWith('at ')) return false;
|
|
35
|
+
if (line.includes('node_modules/')) return false;
|
|
36
|
+
if (line.includes('node:')) return false;
|
|
37
|
+
return true;
|
|
38
|
+
})
|
|
39
|
+
.join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatError(error) {
|
|
43
|
+
const loc = parseErrorLocation(error);
|
|
44
|
+
const parts = [];
|
|
45
|
+
|
|
46
|
+
if (loc) {
|
|
47
|
+
const displayFile = path.relative(process.cwd(), loc.file) || loc.file;
|
|
48
|
+
parts.push(color.red.bold('Error') + ` in ${color.bold(displayFile + ':' + loc.line)}`);
|
|
49
|
+
} else {
|
|
50
|
+
parts.push(color.red.bold('Error'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
parts.push(` ${error.message}`);
|
|
54
|
+
|
|
55
|
+
if (loc) {
|
|
56
|
+
const absFile = path.isAbsolute(loc.file) ? loc.file : path.resolve(process.cwd(), loc.file);
|
|
57
|
+
if (existsSync(absFile)) {
|
|
58
|
+
try {
|
|
59
|
+
const source = readFileSync(absFile, 'utf-8');
|
|
60
|
+
const frame = codeFrameColumns(source, {
|
|
61
|
+
start: { line: loc.line, column: loc.column },
|
|
62
|
+
}, {
|
|
63
|
+
highlightCode: true,
|
|
64
|
+
linesAbove: 2,
|
|
65
|
+
linesBelow: 1,
|
|
66
|
+
});
|
|
67
|
+
parts.push('');
|
|
68
|
+
parts.push(frame);
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const stack = cleanStack(error);
|
|
74
|
+
if (stack) {
|
|
75
|
+
parts.push('');
|
|
76
|
+
parts.push(stack);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return parts.join('\n');
|
|
80
|
+
}
|
|
@@ -12,7 +12,11 @@ const STATUS_CONFIG = {
|
|
|
12
12
|
|
|
13
13
|
export class ConsoleFormatter extends BaseFormatter {
|
|
14
14
|
shouldDelayStart() {
|
|
15
|
-
return
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getStartDelay() {
|
|
19
|
+
return 1000;
|
|
16
20
|
}
|
|
17
21
|
format(taskName, status, taskType, extra = {}) {
|
|
18
22
|
const config = STATUS_CONFIG[status];
|
package/src/io.js
CHANGED
|
@@ -7,11 +7,6 @@ export function say(...args) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export async function ask(question, defaultValueOrOptions = {}, options = {}) {
|
|
10
|
-
// Check if we're in MCP mode and should use the interactive ask function
|
|
11
|
-
if (globalThis._mcpAskFunction) {
|
|
12
|
-
return globalThis._mcpAskFunction(question, defaultValueOrOptions, options);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
10
|
// Track that we're in an ask operation to prevent duplicate exit summaries
|
|
16
11
|
globalThis._bunoshInAskOperation = true;
|
|
17
12
|
// Smart parameter detection
|
package/src/printer.js
CHANGED
|
@@ -85,7 +85,10 @@ export class Printer {
|
|
|
85
85
|
const delay = this.formatter.getStartDelay ? this.formatter.getStartDelay() : 50;
|
|
86
86
|
|
|
87
87
|
if (this.formatter.shouldDelayStart && this.formatter.shouldDelayStart()) {
|
|
88
|
+
this.pendingStart = { taskName, extra };
|
|
88
89
|
this.startTimeout = setTimeout(() => {
|
|
90
|
+
this.startTimeout = null;
|
|
91
|
+
this.pendingStart = null;
|
|
89
92
|
this.hasStarted = true;
|
|
90
93
|
this.print(taskName, 'start', extra);
|
|
91
94
|
}, delay);
|
|
@@ -95,19 +98,37 @@ export class Printer {
|
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
|
|
98
|
-
|
|
101
|
+
_flushPendingStart() {
|
|
102
|
+
if (!this.startTimeout) return;
|
|
103
|
+
clearTimeout(this.startTimeout);
|
|
104
|
+
this.startTimeout = null;
|
|
105
|
+
const pending = this.pendingStart;
|
|
106
|
+
this.pendingStart = null;
|
|
107
|
+
if (pending) {
|
|
108
|
+
this.hasStarted = true;
|
|
109
|
+
this.print(pending.taskName, 'start', pending.extra);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_cancelPendingStart() {
|
|
99
114
|
if (this.startTimeout) {
|
|
100
115
|
clearTimeout(this.startTimeout);
|
|
101
116
|
this.startTimeout = null;
|
|
102
117
|
}
|
|
118
|
+
this.pendingStart = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cancel() {
|
|
122
|
+
this._cancelPendingStart();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
finish(taskName, extra = {}) {
|
|
126
|
+
this._cancelPendingStart();
|
|
103
127
|
this.print(taskName, 'finish', extra);
|
|
104
128
|
}
|
|
105
129
|
|
|
106
130
|
error(taskName, error = null, extra = {}) {
|
|
107
|
-
|
|
108
|
-
clearTimeout(this.startTimeout);
|
|
109
|
-
this.startTimeout = null;
|
|
110
|
-
}
|
|
131
|
+
this._cancelPendingStart();
|
|
111
132
|
if (error) {
|
|
112
133
|
extra.error = typeof error === 'string' ? error : error.message;
|
|
113
134
|
}
|
|
@@ -115,10 +136,7 @@ export class Printer {
|
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
warning(taskName, error = null, extra = {}) {
|
|
118
|
-
|
|
119
|
-
clearTimeout(this.startTimeout);
|
|
120
|
-
this.startTimeout = null;
|
|
121
|
-
}
|
|
139
|
+
this._cancelPendingStart();
|
|
122
140
|
if (error) {
|
|
123
141
|
extra.error = typeof error === 'string' ? error : error.message;
|
|
124
142
|
}
|
|
@@ -128,6 +146,8 @@ export class Printer {
|
|
|
128
146
|
output(line, isError = false) {
|
|
129
147
|
if (!line.trim()) return;
|
|
130
148
|
|
|
149
|
+
this._flushPendingStart();
|
|
150
|
+
|
|
131
151
|
// Add task prefix for parallel tasks on output lines
|
|
132
152
|
const prefix = this.taskId ? getTaskPrefix(this.taskId) : '';
|
|
133
153
|
const prefixedLine = prefix ? `${prefix} ${line}` : line;
|