bunosh 0.4.14 → 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,6 +1,8 @@
1
- #!/usr/bin/env node
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(); // Ensure consistent order
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,51 +100,42 @@ 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
127
  const mcpFlagIndex = process.argv.indexOf('-mcp');
145
128
  if (mcpFlagIndex !== -1) {
146
- // Remove the flag from process.argv
147
129
  process.argv.splice(mcpFlagIndex, 1);
148
130
 
149
- // Set environment variable to indicate MCP mode
150
131
  process.env.BUNOSH_MCP_MODE = 'true';
151
132
 
152
- // Import MCP server and start it
153
133
  const { createMcpServer, startMcpServer } = await import('./src/mcp-server.js');
154
134
 
155
135
  let tasksFile;
156
136
  let bunoshfileDir;
157
137
  if (customBunoshfile) {
158
138
  const resolvedPath = path.isAbsolute(customBunoshfile) ? customBunoshfile : path.resolve(customBunoshfile);
159
- // If it's a directory, append the default BUNOSHFILE
160
139
  if (existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
161
140
  tasksFile = path.join(resolvedPath, BUNOSHFILE);
162
141
  bunoshfileDir = resolvedPath;
@@ -174,33 +153,27 @@ async function main() {
174
153
  process.exit(1);
175
154
  }
176
155
 
177
- // Load environment files from the Bunoshfile directory
178
156
  loadEnvFiles(bunoshfileDir, customEnvFile);
179
157
 
180
- // Load tasks and sources
181
158
  const { tasks, sources } = await loadBunoshfiles(tasksFile);
182
159
 
183
- // Create and start MCP server
184
160
  const server = createMcpServer(tasks, sources);
185
161
  await startMcpServer(server);
186
162
 
187
- return; // Exit early for MCP mode
163
+ return;
188
164
  }
189
165
 
190
166
  let tasksFile;
191
167
  let bunoshfileDir;
192
168
  if (customBunoshfile) {
193
169
  const resolvedPath = path.isAbsolute(customBunoshfile) ? customBunoshfile : path.resolve(customBunoshfile);
194
- // If it's a directory, append the default BUNOSHFILE
195
170
  if (existsSync(resolvedPath) && statSync(resolvedPath).isDirectory()) {
196
171
  tasksFile = path.join(resolvedPath, BUNOSHFILE);
197
172
  bunoshfileDir = resolvedPath;
198
- // Change working directory to the bunoshfile directory
199
173
  process.chdir(resolvedPath);
200
174
  } else {
201
175
  tasksFile = resolvedPath;
202
176
  bunoshfileDir = path.dirname(resolvedPath);
203
- // Change working directory to the bunoshfile's directory
204
177
  process.chdir(path.dirname(resolvedPath));
205
178
  }
206
179
  } else {
@@ -208,44 +181,37 @@ async function main() {
208
181
  bunoshfileDir = process.cwd();
209
182
  }
210
183
 
211
- // Load environment files from the Bunoshfile directory
212
184
  loadEnvFiles(bunoshfileDir, customEnvFile);
213
185
 
214
- // Handle -e flag for executing JavaScript code
215
186
  const eFlagIndex = process.argv.indexOf('-e');
216
187
  if (eFlagIndex !== -1) {
217
188
  (async () => {
218
189
  let jsCode = '';
219
-
220
- // Check if code is provided as argument
190
+
221
191
  if (eFlagIndex + 1 < process.argv.length && !process.argv[eFlagIndex + 1].startsWith('-')) {
222
192
  jsCode = process.argv[eFlagIndex + 1];
223
193
  } else if (!process.stdin.isTTY) {
224
- // Read from stdin only if it's not a TTY (i.e., it's being piped)
225
194
  const chunks = [];
226
195
  for await (const chunk of process.stdin) {
227
196
  chunks.push(chunk);
228
197
  }
229
198
  jsCode = Buffer.concat(chunks).toString('utf8').trim();
230
199
  }
231
-
200
+
232
201
  if (!jsCode) {
233
202
  console.error('No JavaScript code provided');
234
203
  process.exit(1);
235
204
  }
236
-
205
+
237
206
  try {
238
- // Import bunosh globals before executing JavaScript
239
207
  await import('./index.js');
240
-
241
- // Make bunosh globals available to the function
208
+
242
209
  for (const [key, value] of Object.entries(global.bunosh)) {
243
210
  if (typeof value === 'function') {
244
211
  globalThis[key] = value;
245
212
  }
246
213
  }
247
-
248
- // Execute the JavaScript code with bunosh globals available
214
+
249
215
  const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
250
216
  const func = new AsyncFunction(jsCode);
251
217
  await func();
@@ -268,7 +234,7 @@ async function main() {
268
234
 
269
235
  console.log();
270
236
  console.error(`Bunoshfile not found: ${tasksFile}`);
271
- console.log(customBunoshfile ?
237
+ console.log(customBunoshfile ?
272
238
  `Run \`bunosh init\` in the directory or specify a valid --bunoshfile path` :
273
239
  "Run `bunosh init` to create a new Bunoshfile here")
274
240
  console.log();
@@ -277,9 +243,8 @@ async function main() {
277
243
 
278
244
  await import('./index.js');
279
245
 
280
- // Load all Bunoshfile variants
281
- const { tasks, sources } = await loadBunoshfiles(tasksFile);
282
- await bunosh(tasks, sources);
246
+ const { tasks, sources } = await loadBunoshfiles(tasksFile);
247
+ await bunosh(tasks, sources);
283
248
  }
284
249
 
285
250
  main().catch((error) => {
@@ -287,27 +252,22 @@ main().catch((error) => {
287
252
  process.exit(1);
288
253
  });
289
254
 
290
- // Handle unhandled promise rejections
291
255
  process.on('unhandledRejection', (reason, promise) => {
292
256
  if (!process.env.BUNOSH_COMMAND_STARTED) return;
293
-
294
- console.error('\n❌ Unhandled Promise Rejection:');
295
- console.error(reason instanceof Error ? reason.message : reason);
296
- if (reason instanceof Error && reason.stack && process.env.BUNOSH_DEBUG) {
297
- 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);
298
263
  }
299
264
  process.exit(1);
300
265
  });
301
266
 
302
- // Handle uncaught exceptions
303
267
  process.on('uncaughtException', (error) => {
304
268
  if (!process.env.BUNOSH_COMMAND_STARTED) return;
305
-
306
- console.error('\n Uncaught Exception:');
307
- console.error(error.message);
308
- if (error.stack) {
309
- console.error(error.stack);
310
- }
269
+
270
+ console.error('\n' + formatError(error));
311
271
  process.exit(1);
312
272
  });
313
273
 
@@ -327,7 +287,6 @@ process.on('exit', (code) => {
327
287
 
328
288
  const commandArgs = process.argv.slice(2);
329
289
 
330
- // Test environment detection
331
290
  const isTestEnvironment = process.env.NODE_ENV === 'test' ||
332
291
  (typeof jest !== 'undefined' && jest.isRunning) ||
333
292
  (process.env.VITEST_WORKER_ID !== undefined) ||
@@ -349,90 +308,9 @@ process.on('exit', (code) => {
349
308
  const success = finalExitCode === 0;
350
309
 
351
310
  if (globalThis._bunoshExitHandlerCalled) {
352
- console.log('\n[DEBUG] Exit handler already called, skipping duplicate');
353
311
  return;
354
312
  }
355
313
  globalThis._bunoshExitHandlerCalled = true;
356
314
 
357
315
  console.log(`\n🍲 ${success ? '' : 'FAIL '}Exit Code: ${finalExitCode} | Tasks: ${tasksExecuted.length}${tasksFailed ? ` | Failed: ${tasksFailed}` : ''}${tasksWarning ? ` | Warnings: ${tasksWarning}` : ''} | Time: ${totalTime}ms`);
358
316
  });
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunosh",
3
- "version": "0.4.14",
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
+ }