bunosh 0.4.13 → 0.5.0

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