bunosh 0.3.0 → 0.3.1
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 +243 -15
- package/index.js +10 -7
- package/package.json +1 -2
- package/src/io.js +55 -1
- package/src/open-editor.js +95 -0
- package/src/program.js +11 -7
- package/src/tasks/shell.js +119 -0
package/README.md
CHANGED
|
@@ -373,34 +373,74 @@ Commands:
|
|
|
373
373
|
All Bunosh functions are available via `global.bunosh`:
|
|
374
374
|
|
|
375
375
|
```javascript
|
|
376
|
-
const { exec, fetch, writeToFile, copyFile, say, ask, yell, task } = global.bunosh;
|
|
376
|
+
const { exec, shell, fetch, writeToFile, copyFile, say, ask, yell, task } = global.bunosh;
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
### Shell Execution
|
|
379
|
+
### Shell Execution
|
|
380
380
|
|
|
381
|
-
|
|
381
|
+
Bunosh provides two ways to execute shell commands:
|
|
382
|
+
|
|
383
|
+
#### `exec` - Universal Shell Execution
|
|
384
|
+
|
|
385
|
+
Best for complex commands, cross-platform compatibility, and when you need real-time streaming output.
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
// Complex shell commands with pipes and redirections
|
|
389
|
+
await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
|
|
390
|
+
await exec`npm install --verbose`; // Shows progress in real-time
|
|
391
|
+
await exec`docker build . | tee build.log`;
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### `shell` - Native Bun Shell (with Node.js fallback)
|
|
395
|
+
|
|
396
|
+
Best for simple commands when running under Bun for maximum performance.
|
|
382
397
|
|
|
383
398
|
```javascript
|
|
384
|
-
// Simple commands
|
|
385
|
-
await
|
|
386
|
-
await
|
|
399
|
+
// Simple, fast commands
|
|
400
|
+
await shell`pwd`;
|
|
401
|
+
await shell`echo "Hello World"`;
|
|
402
|
+
await shell`ls -la`;
|
|
403
|
+
await shell`cat package.json`;
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### When to Use Which?
|
|
387
407
|
|
|
388
|
-
|
|
408
|
+
**Use `shell` when:**
|
|
409
|
+
- ✅ Running under Bun for optimal performance
|
|
410
|
+
- ✅ Executing simple commands (`pwd`, `ls`, `echo`, `cat`)
|
|
411
|
+
- ✅ Want fastest possible execution
|
|
412
|
+
- ✅ Working with basic file operations
|
|
413
|
+
|
|
414
|
+
**Use `exec` when:**
|
|
415
|
+
- ✅ Need cross-platform compatibility (Node.js + Bun)
|
|
416
|
+
- ✅ Using complex shell features (pipes, redirections, command chaining)
|
|
417
|
+
- ✅ Want real-time streaming output for long-running commands
|
|
418
|
+
- ✅ Running package managers (`npm install`, `docker build`)
|
|
419
|
+
|
|
420
|
+
Both support the same API and return the same `TaskResult` object:
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
// Both tasks support environment variables
|
|
424
|
+
await shell`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
389
425
|
await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
390
426
|
|
|
391
|
-
//
|
|
427
|
+
// Both support working directory changes
|
|
428
|
+
await shell`pwd`.cwd('/tmp');
|
|
392
429
|
await exec`ls -la`.cwd('/tmp');
|
|
393
430
|
|
|
394
|
-
//
|
|
395
|
-
await
|
|
431
|
+
// Choose based on complexity and performance needs
|
|
432
|
+
await shell`cat package.json`; // Simple, fast
|
|
433
|
+
await exec`npm install --verbose`; // Complex, streaming
|
|
396
434
|
```
|
|
397
435
|
|
|
398
436
|
#### TaskResult Object
|
|
399
437
|
|
|
400
|
-
|
|
438
|
+
Both `exec` and `shell` return a `TaskResult` object with the following properties and methods:
|
|
401
439
|
|
|
402
440
|
```javascript
|
|
403
441
|
const result = await exec`ls -la`;
|
|
442
|
+
// or
|
|
443
|
+
const result = await shell`ls -la`;
|
|
404
444
|
|
|
405
445
|
// Properties
|
|
406
446
|
result.status // 'success' or 'fail'
|
|
@@ -474,15 +514,203 @@ copyFile('template.js', 'output.js');
|
|
|
474
514
|
```
|
|
475
515
|
|
|
476
516
|
### User Interaction
|
|
517
|
+
|
|
518
|
+
#### `ask()` - Interactive User Input
|
|
519
|
+
|
|
520
|
+
The `ask()` function provides flexible ways to get user input with smart parameter detection and multiple modes:
|
|
521
|
+
|
|
477
522
|
```javascript
|
|
478
|
-
//
|
|
523
|
+
// === SIMPLE SYNTAX WITH SMART DETECTION ===
|
|
524
|
+
|
|
525
|
+
// Basic text input
|
|
479
526
|
const name = await ask('What is your name?');
|
|
480
527
|
|
|
528
|
+
// Text input with default value
|
|
529
|
+
const projectName = await ask('Project name:', 'my-awesome-app');
|
|
530
|
+
|
|
531
|
+
// Boolean confirmation (auto-detects confirm type)
|
|
532
|
+
const shouldContinue = await ask('Continue with deployment?', true);
|
|
533
|
+
const forceUpdate = await ask('Force update?', false);
|
|
534
|
+
|
|
535
|
+
// Number input with default
|
|
536
|
+
const port = await ask('Enter port number:', 3000);
|
|
537
|
+
|
|
538
|
+
// Single choice selection (auto-detects from array)
|
|
539
|
+
const framework = await ask('Choose your framework:', [
|
|
540
|
+
'React', 'Vue', 'Angular', 'Svelte'
|
|
541
|
+
]);
|
|
542
|
+
|
|
543
|
+
// Multiple choice selection (array + options)
|
|
544
|
+
const features = await ask('Select features to include:', [
|
|
545
|
+
'TypeScript', 'ESLint', 'Prettier', 'Tests', 'CI/CD'
|
|
546
|
+
], { multiple: true });
|
|
547
|
+
|
|
548
|
+
// === ADVANCED OPTIONS SYNTAX ===
|
|
549
|
+
|
|
550
|
+
// Multiline text input (opens system editor)
|
|
551
|
+
const description = await ask('Enter project description:', {
|
|
552
|
+
multiline: true // Same as editor: true
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Editor input with default content
|
|
556
|
+
const config = await ask('Edit configuration:', {
|
|
557
|
+
editor: true,
|
|
558
|
+
default: 'Initial content here...'
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Password input (hidden)
|
|
562
|
+
const password = await ask('Enter password:', {
|
|
563
|
+
type: 'password'
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Mixed: default value + additional options
|
|
567
|
+
const email = await ask('Email address:', 'user@example.com', {
|
|
568
|
+
validate: (input) => input.includes('@') || 'Please enter valid email'
|
|
569
|
+
});
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### Ask Function Signatures
|
|
573
|
+
|
|
574
|
+
```javascript
|
|
575
|
+
// Smart detection syntax
|
|
576
|
+
ask(question, defaultValue, options?)
|
|
577
|
+
ask(question, choices[], options?)
|
|
578
|
+
ask(question, options)
|
|
579
|
+
|
|
580
|
+
// Examples:
|
|
581
|
+
ask('Name?', 'John') // String default
|
|
582
|
+
ask('Continue?', true) // Boolean -> confirm type
|
|
583
|
+
ask('Port?', 3000) // Number default
|
|
584
|
+
ask('Color?', ['red', 'blue']) // Array -> choices
|
|
585
|
+
ask('Colors?', ['red', 'blue'], { multiple: true }) // Array + options
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### Ask Options Reference
|
|
589
|
+
|
|
590
|
+
| Parameter/Option | Type | Description | Example |
|
|
591
|
+
|------------------|------|-------------|---------|
|
|
592
|
+
| **Smart Detection** | | |
|
|
593
|
+
| `defaultValue` | String/Number | Sets default value for text/number input | `'John'`, `3000` |
|
|
594
|
+
| `defaultValue` | Boolean | Auto-detects as confirmation prompt | `true`, `false` |
|
|
595
|
+
| `choices` | Array | Auto-detects as selection list | `['A', 'B', 'C']` |
|
|
596
|
+
| **Options Object** | | |
|
|
597
|
+
| `multiple` | Boolean | Enables multiple selections (requires `choices`) | `true` |
|
|
598
|
+
| `multiline` | Boolean | Opens system editor for multi-line input | `true` |
|
|
599
|
+
| `editor` | Boolean | Opens system editor for multi-line input (same as `multiline`) | `true` |
|
|
600
|
+
| `default` | Any | Default value or content (when using options object) | `'default value'` |
|
|
601
|
+
| `type` | String | Input type: `'input'`, `'confirm'`, `'password'`, `'number'` | `'password'` |
|
|
602
|
+
| `validate` | Function | Custom validation function | `(input) => input.length > 0` |
|
|
603
|
+
|
|
604
|
+
#### Advanced Ask Examples
|
|
605
|
+
|
|
606
|
+
```javascript
|
|
607
|
+
/**
|
|
608
|
+
* Interactive project setup with smart syntax
|
|
609
|
+
*/
|
|
610
|
+
export async function setupProject() {
|
|
611
|
+
// Simple syntax with smart detection
|
|
612
|
+
const projectName = await ask('Project name:', 'my-awesome-project');
|
|
613
|
+
|
|
614
|
+
const projectType = await ask('Project type:', [
|
|
615
|
+
'Web App', 'API', 'CLI Tool', 'Library'
|
|
616
|
+
]);
|
|
617
|
+
|
|
618
|
+
const dependencies = await ask('Select dependencies:', [
|
|
619
|
+
'express', 'lodash', 'axios', 'moment', 'uuid'
|
|
620
|
+
], { multiple: true });
|
|
621
|
+
|
|
622
|
+
const useTypescript = await ask('Use TypeScript?', false);
|
|
623
|
+
|
|
624
|
+
// Editor input for complex configuration
|
|
625
|
+
const packageJson = await ask('Customize package.json:', {
|
|
626
|
+
editor: true,
|
|
627
|
+
default: JSON.stringify({
|
|
628
|
+
name: projectName,
|
|
629
|
+
version: '1.0.0',
|
|
630
|
+
description: '',
|
|
631
|
+
dependencies: {}
|
|
632
|
+
}, null, 2)
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
say(`Creating ${projectType}: ${projectName}`);
|
|
636
|
+
say(`Dependencies: ${dependencies.join(', ')}`);
|
|
637
|
+
say(`TypeScript: ${useTypescript ? 'Yes' : 'No'}`);
|
|
638
|
+
|
|
639
|
+
writeToFile('package.json', packageJson);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Git commit with editor input
|
|
644
|
+
*/
|
|
645
|
+
export async function interactiveCommit() {
|
|
646
|
+
const message = await ask('Enter commit message:', {
|
|
647
|
+
editor: true,
|
|
648
|
+
default: 'feat: \n\n# Write your commit message above\n# First line: brief summary (50 chars max)\n# Blank line, then detailed explanation'
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
await exec`git commit -m "${message}"`;
|
|
652
|
+
say('✅ Committed successfully!');
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Database migration with smart syntax
|
|
657
|
+
*/
|
|
658
|
+
export async function migrate() {
|
|
659
|
+
// Smart array detection for choices
|
|
660
|
+
const action = await ask('Migration action:', [
|
|
661
|
+
'Run pending migrations',
|
|
662
|
+
'Rollback last migration',
|
|
663
|
+
'Reset database',
|
|
664
|
+
'Create new migration'
|
|
665
|
+
]);
|
|
666
|
+
|
|
667
|
+
if (action === 'Reset database') {
|
|
668
|
+
// Smart boolean detection for confirmation
|
|
669
|
+
const confirmed = await ask('⚠️ This will DELETE ALL DATA. Are you sure?', false);
|
|
670
|
+
|
|
671
|
+
if (!confirmed) {
|
|
672
|
+
say('Migration cancelled');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Execute migration based on selection...
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Server configuration with mixed smart syntax
|
|
682
|
+
*/
|
|
683
|
+
export async function configureServer() {
|
|
684
|
+
// Simple defaults
|
|
685
|
+
const serverName = await ask('Server name:', 'my-server');
|
|
686
|
+
const port = await ask('Port number:', 8080);
|
|
687
|
+
const enableHTTPS = await ask('Enable HTTPS?', true);
|
|
688
|
+
|
|
689
|
+
// Array with additional options
|
|
690
|
+
const databases = await ask('Select databases to connect:', [
|
|
691
|
+
'PostgreSQL', 'MongoDB', 'Redis', 'MySQL'
|
|
692
|
+
], { multiple: true });
|
|
693
|
+
|
|
694
|
+
// Mix of default + validation
|
|
695
|
+
const adminEmail = await ask('Admin email:', 'admin@example.com', {
|
|
696
|
+
validate: (email) => email.includes('@') || 'Please enter a valid email'
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
say(`Configuring ${serverName} on port ${port}`);
|
|
700
|
+
say(`HTTPS: ${enableHTTPS ? 'Enabled' : 'Disabled'}`);
|
|
701
|
+
say(`Databases: ${databases.join(', ')}`);
|
|
702
|
+
say(`Admin: ${adminEmail}`);
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
#### Output Functions
|
|
707
|
+
|
|
708
|
+
```javascript
|
|
481
709
|
// Output messages
|
|
482
|
-
say('Building project...'); // Normal output
|
|
483
|
-
yell('BUILD COMPLETE!'); // Emphasized output
|
|
710
|
+
say('Building project...'); // Normal output with !
|
|
711
|
+
yell('BUILD COMPLETE!'); // Emphasized ASCII art output
|
|
484
712
|
|
|
485
|
-
// Wrap long operations
|
|
713
|
+
// Wrap long operations with progress
|
|
486
714
|
await task('Installing dependencies', async () => {
|
|
487
715
|
await exec`npm install`;
|
|
488
716
|
});
|
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import exec from "./src/tasks/exec.js";
|
|
2
|
+
import shell from "./src/tasks/shell.js";
|
|
2
3
|
import fetch from "./src/tasks/fetch.js";
|
|
3
4
|
import writeToFile from "./src/tasks/writeToFile.js";
|
|
4
5
|
import copyFile from "./src/tasks/copyFile.js";
|
|
@@ -6,19 +7,21 @@ import ai from "./src/tasks/ai.js";
|
|
|
6
7
|
import { ask, yell, say } from "./src/io.js";
|
|
7
8
|
import { task, stopOnFail, ignoreFail } from "./src/task.js";
|
|
8
9
|
|
|
9
|
-
export { exec, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
|
|
10
|
-
|
|
10
|
+
export { exec, shell, fetch, writeToFile, copyFile, ai, ask, yell, say, task, stopOnFail, ignoreFail };
|
|
11
11
|
|
|
12
12
|
export function buildCmd(cmd) {
|
|
13
|
-
return function(args) {
|
|
14
|
-
return exec`${cmd} ${args}
|
|
15
|
-
}
|
|
13
|
+
return function (args) {
|
|
14
|
+
return exec`${cmd} ${args}`;
|
|
15
|
+
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
global.bunosh = {
|
|
19
|
-
ask,
|
|
19
|
+
ask,
|
|
20
|
+
yell,
|
|
21
|
+
say,
|
|
20
22
|
fetch,
|
|
21
23
|
exec,
|
|
24
|
+
shell,
|
|
22
25
|
writeToFile,
|
|
23
26
|
copyFile,
|
|
24
27
|
ai,
|
|
@@ -27,6 +30,6 @@ global.bunosh = {
|
|
|
27
30
|
task,
|
|
28
31
|
buildCmd,
|
|
29
32
|
$: exec,
|
|
30
|
-
}
|
|
33
|
+
};
|
|
31
34
|
|
|
32
35
|
export default global.bunosh;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunosh",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"debug": "^4.4.1",
|
|
26
26
|
"fs-extra": "^11.3.0",
|
|
27
27
|
"inquirer": "^12.6.3",
|
|
28
|
-
"open-editor": "^5.1.0",
|
|
29
28
|
"timer-node": "^5.0.9",
|
|
30
29
|
"zod": "^4.1.5"
|
|
31
30
|
},
|
package/src/io.js
CHANGED
|
@@ -6,11 +6,65 @@ export function say(...args) {
|
|
|
6
6
|
console.log('!', ...args);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export async function ask(question,
|
|
9
|
+
export async function ask(question, defaultValueOrOptions = {}, options = {}) {
|
|
10
|
+
// Smart parameter detection
|
|
11
|
+
let opts = {};
|
|
12
|
+
|
|
13
|
+
// If second parameter is not an object, it's a default value
|
|
14
|
+
if (defaultValueOrOptions !== null && typeof defaultValueOrOptions !== 'object') {
|
|
15
|
+
opts.default = defaultValueOrOptions;
|
|
16
|
+
opts = { ...opts, ...options }; // Merge with third parameter options
|
|
17
|
+
|
|
18
|
+
// Auto-detect type based on default value
|
|
19
|
+
if (typeof defaultValueOrOptions === 'boolean') {
|
|
20
|
+
opts.type = 'confirm';
|
|
21
|
+
}
|
|
22
|
+
} else if (Array.isArray(defaultValueOrOptions)) {
|
|
23
|
+
// If it's an array, treat as choices
|
|
24
|
+
opts.choices = defaultValueOrOptions;
|
|
25
|
+
opts = { ...opts, ...options }; // Merge with third parameter options
|
|
26
|
+
} else {
|
|
27
|
+
// Traditional object parameter
|
|
28
|
+
opts = { ...defaultValueOrOptions, ...options };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Route to appropriate handler based on options
|
|
32
|
+
if (opts.editor || opts.multiline) {
|
|
33
|
+
return await askWithEditor(question, opts);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (opts.choices) {
|
|
37
|
+
return await askWithChoices(question, opts);
|
|
38
|
+
}
|
|
39
|
+
|
|
10
40
|
const answers = await inquirer.prompt({ name: question, message: question, ...opts })
|
|
11
41
|
return Object.values(answers)[0];
|
|
12
42
|
}
|
|
13
43
|
|
|
44
|
+
async function askWithEditor(question, opts = {}) {
|
|
45
|
+
const answers = await inquirer.prompt({
|
|
46
|
+
name: question,
|
|
47
|
+
message: question,
|
|
48
|
+
type: 'editor',
|
|
49
|
+
...opts
|
|
50
|
+
});
|
|
51
|
+
return Object.values(answers)[0];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function askWithChoices(question, opts = {}) {
|
|
55
|
+
const promptType = opts.multiple ? 'checkbox' : 'list';
|
|
56
|
+
|
|
57
|
+
const answers = await inquirer.prompt({
|
|
58
|
+
name: question,
|
|
59
|
+
message: question,
|
|
60
|
+
type: promptType,
|
|
61
|
+
choices: opts.choices,
|
|
62
|
+
...opts
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return Object.values(answers)[0];
|
|
66
|
+
}
|
|
67
|
+
|
|
14
68
|
export function yell(text) {
|
|
15
69
|
console.log();
|
|
16
70
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export default function openEditor(files, options = {}) {
|
|
5
|
+
if (!Array.isArray(files) || files.length === 0) {
|
|
6
|
+
throw new Error('Files array is required and cannot be empty');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const editor = options.editor || getDefaultEditor();
|
|
10
|
+
const fileArgs = buildEditorArgs(editor, files);
|
|
11
|
+
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const child = spawn(editor, fileArgs, {
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
detached: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
child.on('error', (err) => {
|
|
19
|
+
reject(new Error(`Failed to open editor '${editor}': ${err.message}`));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
child.on('spawn', () => {
|
|
23
|
+
resolve();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (child.pid) {
|
|
27
|
+
child.unref();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getDefaultEditor() {
|
|
33
|
+
if (process.env.EDITOR) {
|
|
34
|
+
return process.env.EDITOR;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const editors = ['code', 'subl', 'atom', 'vim', 'nvim', 'nano', 'gedit'];
|
|
38
|
+
|
|
39
|
+
for (const editor of editors) {
|
|
40
|
+
if (isCommandAvailable(editor)) {
|
|
41
|
+
return editor;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return process.platform === 'win32' ? 'notepad' : 'vi';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isCommandAvailable(command) {
|
|
49
|
+
try {
|
|
50
|
+
const { execSync } = require('child_process');
|
|
51
|
+
execSync(`which ${command}`, { stdio: 'ignore' });
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildEditorArgs(editor, files) {
|
|
59
|
+
const editorName = path.basename(editor);
|
|
60
|
+
const args = [];
|
|
61
|
+
|
|
62
|
+
for (const fileInfo of files) {
|
|
63
|
+
const filePath = typeof fileInfo === 'string' ? fileInfo : fileInfo.file;
|
|
64
|
+
|
|
65
|
+
if (!filePath) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (fileInfo.line && typeof fileInfo === 'object') {
|
|
70
|
+
switch (editorName) {
|
|
71
|
+
case 'code':
|
|
72
|
+
case 'code-insiders':
|
|
73
|
+
args.push('--goto', `${filePath}:${fileInfo.line}:${fileInfo.column || 1}`);
|
|
74
|
+
break;
|
|
75
|
+
case 'subl':
|
|
76
|
+
case 'sublime_text':
|
|
77
|
+
args.push(`${filePath}:${fileInfo.line}:${fileInfo.column || 1}`);
|
|
78
|
+
break;
|
|
79
|
+
case 'vim':
|
|
80
|
+
case 'nvim':
|
|
81
|
+
args.push(`+${fileInfo.line}`, filePath);
|
|
82
|
+
break;
|
|
83
|
+
case 'nano':
|
|
84
|
+
args.push(`+${fileInfo.line}`, filePath);
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
args.push(filePath);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
args.push(filePath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return args;
|
|
95
|
+
}
|
package/src/program.js
CHANGED
|
@@ -4,7 +4,7 @@ import traverseDefault from "@babel/traverse";
|
|
|
4
4
|
const traverse = traverseDefault.default || traverseDefault;
|
|
5
5
|
import color from "chalk";
|
|
6
6
|
import fs from 'fs';
|
|
7
|
-
import openEditor from 'open-editor';
|
|
7
|
+
import openEditor from './open-editor.js';
|
|
8
8
|
import { yell } from './io.js';
|
|
9
9
|
import cprint from "./font.js";
|
|
10
10
|
import { handleCompletion, detectCurrentShell, installCompletion, getCompletionPaths } from './completion.js';
|
|
@@ -251,12 +251,16 @@ export default function bunosh(commands, source) {
|
|
|
251
251
|
|
|
252
252
|
const editCmd = program.command('edit')
|
|
253
253
|
.description('Open the bunosh file in your editor. $EDITOR or \'code\' is used.')
|
|
254
|
-
.action(() => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
})
|
|
254
|
+
.action(async () => {
|
|
255
|
+
try {
|
|
256
|
+
await openEditor([{
|
|
257
|
+
file: BUNOSHFILE,
|
|
258
|
+
}]);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error(error.message);
|
|
261
|
+
console.error('Set $EDITOR environment variable to use a different editor');
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
260
264
|
});
|
|
261
265
|
|
|
262
266
|
internalCommands.push(editCmd);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { TaskResult, createTaskInfo, finishTaskInfo } from "../task.js";
|
|
2
|
+
import Printer from "../printer.js";
|
|
3
|
+
|
|
4
|
+
const isBun = typeof Bun !== 'undefined' && typeof Bun.spawn === 'function';
|
|
5
|
+
|
|
6
|
+
export default function shell(strings, ...values) {
|
|
7
|
+
const cmd = strings.reduce((accumulator, str, i) => {
|
|
8
|
+
return accumulator + str + (values[i] || "");
|
|
9
|
+
}, "");
|
|
10
|
+
|
|
11
|
+
let envs = null;
|
|
12
|
+
let cwd = null;
|
|
13
|
+
|
|
14
|
+
const cmdPromise = new Promise(async (resolve, reject) => {
|
|
15
|
+
const extraInfo = {};
|
|
16
|
+
if (cwd) extraInfo.cwd = cwd;
|
|
17
|
+
if (envs) extraInfo.env = envs;
|
|
18
|
+
|
|
19
|
+
if (!isBun) {
|
|
20
|
+
const { default: exec } = await import("./exec.js");
|
|
21
|
+
let execPromise = exec(strings, ...values);
|
|
22
|
+
if (envs) execPromise = execPromise.env(envs);
|
|
23
|
+
if (cwd) execPromise = execPromise.cwd(cwd);
|
|
24
|
+
const result = await execPromise;
|
|
25
|
+
resolve(result);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const taskInfo = createTaskInfo(cmd);
|
|
30
|
+
const printer = new Printer("shell", taskInfo.id);
|
|
31
|
+
printer.start(cmd, extraInfo);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { $ } = await import("bun");
|
|
35
|
+
|
|
36
|
+
let shell = $;
|
|
37
|
+
|
|
38
|
+
if (cwd) {
|
|
39
|
+
shell = shell.cwd(cwd);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (envs) {
|
|
43
|
+
shell = shell.env(envs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await shell(strings, ...values);
|
|
49
|
+
|
|
50
|
+
const output = await result.text();
|
|
51
|
+
|
|
52
|
+
printer.finish(cmd);
|
|
53
|
+
finishTaskInfo(taskInfo, true, null, output.trim());
|
|
54
|
+
resolve(TaskResult.success(output.trim()));
|
|
55
|
+
return;
|
|
56
|
+
|
|
57
|
+
} catch (shellError) {
|
|
58
|
+
const isCommandNotFound = shellError.stderr &&
|
|
59
|
+
(shellError.stderr.includes('command not found') ||
|
|
60
|
+
shellError.stderr.includes('bun: command not found'));
|
|
61
|
+
|
|
62
|
+
if (isCommandNotFound) {
|
|
63
|
+
printer.finish(cmd);
|
|
64
|
+
finishTaskInfo(taskInfo, true, null, "fallback to exec");
|
|
65
|
+
|
|
66
|
+
const { default: exec } = await import("./exec.js");
|
|
67
|
+
let execPromise = exec`${cmd}`;
|
|
68
|
+
if (envs) execPromise = execPromise.env(envs);
|
|
69
|
+
if (cwd) execPromise = execPromise.cwd(cwd);
|
|
70
|
+
const result = await execPromise;
|
|
71
|
+
resolve(result);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (shellError.exitCode !== undefined) {
|
|
76
|
+
const stderr = shellError.stderr ? Buffer.isBuffer(shellError.stderr) ? shellError.stderr.toString() : shellError.stderr : "";
|
|
77
|
+
const stdout = shellError.stdout ? Buffer.isBuffer(shellError.stdout) ? shellError.stdout.toString() : shellError.stdout : "";
|
|
78
|
+
const errorOutput = (stderr + stdout).trim() || `Command failed with exit code ${shellError.exitCode}`;
|
|
79
|
+
|
|
80
|
+
if (errorOutput) {
|
|
81
|
+
const lines = errorOutput.split('\n');
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
if (line.trim()) {
|
|
84
|
+
printer.output(line, true);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const error = new Error(`Exit code: ${shellError.exitCode}`);
|
|
90
|
+
printer.error(cmd, null, { exitCode: shellError.exitCode });
|
|
91
|
+
finishTaskInfo(taskInfo, false, error, errorOutput);
|
|
92
|
+
resolve(TaskResult.fail(errorOutput));
|
|
93
|
+
return;
|
|
94
|
+
} else {
|
|
95
|
+
const errorMessage = shellError.message || shellError.toString();
|
|
96
|
+
printer.error(cmd, shellError);
|
|
97
|
+
finishTaskInfo(taskInfo, false, shellError, errorMessage);
|
|
98
|
+
resolve(TaskResult.fail(errorMessage));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
printer.error(cmd, error);
|
|
103
|
+
finishTaskInfo(taskInfo, false, error, error.message);
|
|
104
|
+
resolve(TaskResult.fail(error.message));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
cmdPromise.env = (newEnvs) => {
|
|
109
|
+
envs = newEnvs;
|
|
110
|
+
return cmdPromise;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
cmdPromise.cwd = (newCwd) => {
|
|
114
|
+
cwd = newCwd;
|
|
115
|
+
return cmdPromise;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return cmdPromise;
|
|
119
|
+
}
|