bunosh 0.3.2 โ 0.4.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/README.md +225 -328
- package/bunosh.js +173 -48
- package/index.js +3 -3
- package/package.json +1 -1
- package/src/init.js +2 -2
- package/src/io.js +90 -21
- package/src/printer.js +14 -2
- package/src/program.js +321 -21
- package/src/task.js +41 -43
- package/src/tasks/exec.js +14 -2
- package/src/tasks/fetch.js +4 -3
- package/src/tasks/shell.js +37 -5
- package/src/open-editor.js +0 -95
package/README.md
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>
|
|
8
|
+
<strong>Your exceptional task runner</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
Transform JavaScript functions into
|
|
12
|
+
Transform JavaScript functions into CLI commands.
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
---
|
|
@@ -23,39 +23,55 @@ Bunosh is a modern task runner that turns your JavaScript functions into CLI com
|
|
|
23
23
|
### โจ Key Features
|
|
24
24
|
|
|
25
25
|
- **๐ Zero Configuration** - Write functions, get CLI commands automatically
|
|
26
|
-
- **๐จ
|
|
26
|
+
- **๐จ Pure JavaScript** - write commands as JavaScript functions
|
|
27
|
+
- **๐ Personal Commands** - Global commands from `~/Bunoshfile.js` available everywhere with `my:` namespace
|
|
27
28
|
- **๐ฆ Built-in Tasks** - Shell execution, HTTP requests, file operations
|
|
28
29
|
- **๐ค AI-Powered** - integrate LLM calls into your daily tasks
|
|
29
30
|
- **๐ง Cross-Platform** - Works seamlessly on macOS, Linux, and Windows. Via bun, npm, or as single executable.
|
|
30
31
|
- **๐ฏ Smart CLI** - Auto-completion, help generation, and intuitive argument handling
|
|
31
32
|
|
|
32
|
-
##
|
|
33
|
+
## Hello World
|
|
34
|
+
|
|
35
|
+
No nore words, just code:
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
```js
|
|
38
|
+
// this is a command in Bunoshfile.js
|
|
39
|
+
// bunosh hello:world
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
export async function helloWorld(name = 'person') {
|
|
42
|
+
name = await ask("What's your name?", name);
|
|
43
|
+
say(`๐ Hello, ${name}!`);
|
|
44
|
+
const city = await ask('Which city do you live in?')
|
|
45
|
+
const result = await fetch(`https://wttr.in/${city}?format=3`)
|
|
46
|
+
say(`Weather in your city ${result.output}`)
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
const toCleanup = await ask('Do you want me to cleanup tmp for you?', true);
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
if (!toCleanup) {
|
|
51
|
+
say('Bye, then!');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
await shell`rm -rf ${require('os').tmpdir()}/*`;
|
|
56
|
+
say('๐งน Cleaned up! Have a great day!');
|
|
57
|
+
}
|
|
58
|
+
````
|
|
59
|
+
|
|
60
|
+
## Why Choose Bunosh?
|
|
48
61
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
| Comparison | ๐ Bash Scripts | ๐ฆ npm scripts | ๐ ๏ธ Task Runners | ๐ฒ **Bunosh** |
|
|
63
|
+
|------------|-----------------|----------------|------------------------------|----------------|
|
|
64
|
+
| **Syntax** | bash/zsh | Simple commands | Custom DSL | โ
JavaScript |
|
|
65
|
+
| **Cross-platform** | No | Yes | Yes | โ
Yes |
|
|
66
|
+
| **Ecosystem** | CLI tools | npm packages | Plugin dependent | โ
Bash + npm |
|
|
67
|
+
| **Composability** | Commands | Separate scripts | Task dependencies | โ
Import any JS code |
|
|
53
68
|
|
|
54
|
-
##
|
|
69
|
+
## TOC
|
|
55
70
|
|
|
56
71
|
- [Installation](#installation)
|
|
57
72
|
- [Quickstart](#quickstart)
|
|
58
73
|
- [Commands](#commands)
|
|
74
|
+
- [Personal Commands (My Namespace)](#personal-commands-my-namespace)
|
|
59
75
|
- [Tasks](#tasks)
|
|
60
76
|
- [Input/Output](#inputoutput)
|
|
61
77
|
- [Task Control](#task-control)
|
|
@@ -203,17 +219,107 @@ Functions are automatically converted to kebab-case commands:
|
|
|
203
219
|
| `npmInstall` | `bunosh npm:install` |
|
|
204
220
|
| `buildAndDeploy` | `bunosh build:and-deploy` |
|
|
205
221
|
|
|
222
|
+
### Personal Commands (My Namespace)
|
|
223
|
+
|
|
224
|
+
Bunosh automatically loads commands from your home directory (`~/Bunoshfile.js`) and makes them available in any project with the `my:` namespace prefix.
|
|
225
|
+
|
|
226
|
+
**Create your personal toolkit:**
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// ~/Bunoshfile.js - Your global commands available everywhere
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Quick deployment to personal staging server
|
|
233
|
+
*/
|
|
234
|
+
export function deploy(app, env = 'staging') {
|
|
235
|
+
say(`๐ Deploying ${app} to personal ${env} environment...`);
|
|
236
|
+
await exec`ssh deploy@myserver.com "deploy.sh ${app} ${env}"`;
|
|
237
|
+
say('โ
Deployment complete!');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Personal backup script
|
|
242
|
+
*/
|
|
243
|
+
export function backup(target = 'documents') {
|
|
244
|
+
say(`๐พ Creating backup of ${target}...`);
|
|
245
|
+
await exec`rsync -av ~/${target}/ ~/Backups/${target}-$(date +%Y%m%d)/`;
|
|
246
|
+
say('๐ฆ Backup completed!');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Quick project setup
|
|
251
|
+
*/
|
|
252
|
+
export function newProject(name, template = 'basic') {
|
|
253
|
+
say(`๐๏ธ Creating new project: ${name}`);
|
|
254
|
+
await exec`git clone https://github.com/my-templates/${template}.git ${name}`;
|
|
255
|
+
await exec`cd ${name} && npm install`;
|
|
256
|
+
say(`โ
Project ${name} ready!`);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Available in any project:**
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# From any directory, these commands work:
|
|
264
|
+
bunosh my:deploy my-app production
|
|
265
|
+
bunosh my:backup projects
|
|
266
|
+
bunosh my:new-project awesome-app react
|
|
267
|
+
|
|
268
|
+
# List all your personal commands
|
|
269
|
+
bunosh --help # Shows "My Commands" section
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Key Features:**
|
|
273
|
+
- ๐ **Global availability** - Access your personal commands from any project directory
|
|
274
|
+
- ๐ท๏ธ **Namespaced** - `my:` prefix prevents conflicts with project commands
|
|
275
|
+
- ๐ **Same syntax** - Uses identical JavaScript function syntax as project commands
|
|
276
|
+
- ๐ง **Parameters & options** - Full support for arguments and CLI options
|
|
277
|
+
- ๐ **Help integration** - Shows in help output with descriptions
|
|
278
|
+
- ๐ซ **Graceful fallback** - Works seamlessly when no home Bunoshfile exists
|
|
279
|
+
|
|
206
280
|
## Tasks
|
|
207
281
|
|
|
208
|
-
|
|
282
|
+
Bunosh provides built-in tasks which are available via `global.bunosh`:
|
|
209
283
|
|
|
210
284
|
```javascript
|
|
211
|
-
const { exec, shell, fetch, writeToFile, copyFile, task
|
|
285
|
+
const { exec, shell, fetch, writeToFile, copyFile, task } = global.bunosh;
|
|
212
286
|
```
|
|
213
287
|
|
|
214
288
|
> We use global variables instead of imports to ensure you can use it with bunosh single-executable on any platform.
|
|
215
289
|
|
|
216
290
|
|
|
291
|
+
* Async tasks: `exec`, `shell`, `fetch`
|
|
292
|
+
* Sync tasks: `writeToFile`, `copyFile`
|
|
293
|
+
* Task wrapper: `task`
|
|
294
|
+
|
|
295
|
+
Each executed task returns `TaskResult` object which can be analyzed and used in next steps:
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
const result = await shell`echo "Hello"`;
|
|
299
|
+
console.log(result.status); // 'success', 'fail', or 'warning'
|
|
300
|
+
console.log(result.output); // Command output or result data
|
|
301
|
+
console.log(result.hasFailed); // true if status is 'fail'
|
|
302
|
+
console.log(result.hasSucceeded); // true if status is 'success'
|
|
303
|
+
console.log(result.hasWarning); // true if status is 'warning'
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Now let's look into other tasks:
|
|
307
|
+
|
|
308
|
+
#### `task`
|
|
309
|
+
|
|
310
|
+
General method that transforms a function into a task. Adds it to tasks registry and prints task information:
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
// register operation as a task
|
|
314
|
+
const result = task('Fetch Readme file', () => {
|
|
315
|
+
const content = fs.readFileSync('README.md', 'utf8');
|
|
316
|
+
console.log(content);
|
|
317
|
+
return content;
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
If a another task is executed inside a task function, its description will be appended to all child tasks.
|
|
322
|
+
|
|
217
323
|
#### `exec`
|
|
218
324
|
|
|
219
325
|
Run single command using [child process `spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options)
|
|
@@ -266,9 +372,9 @@ For more details see [bun shell](https://bun.sh/docs/runtime/shell) reference
|
|
|
266
372
|
|
|
267
373
|
shell prints output from stdout and stderr. To disable output, [make tasks silent](#silent):
|
|
268
374
|
|
|
269
|
-
|
|
375
|
+
###$ `fetch`
|
|
270
376
|
|
|
271
|
-
|
|
377
|
+
`fetch` task wraps fetch:
|
|
272
378
|
|
|
273
379
|
```javascript
|
|
274
380
|
/**
|
|
@@ -294,22 +400,23 @@ Template-based file writing and copying:
|
|
|
294
400
|
/**
|
|
295
401
|
* Generate configuration file
|
|
296
402
|
*/
|
|
297
|
-
export function
|
|
298
|
-
writeToFile('
|
|
299
|
-
line`{
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
line
|
|
403
|
+
export function generatePage(name, description = '') {
|
|
404
|
+
writeToFile('index.mdx', (line) => {
|
|
405
|
+
line`name": "${name}",`;
|
|
406
|
+
if (description) {
|
|
407
|
+
line`description: "${description}"`;
|
|
408
|
+
}
|
|
409
|
+
line`---`;
|
|
304
410
|
});
|
|
305
411
|
|
|
306
|
-
say('๐
|
|
412
|
+
say('๐ Page created');
|
|
307
413
|
}
|
|
308
414
|
|
|
309
415
|
// Copy files
|
|
310
416
|
copyFile('template.env', '.env');
|
|
311
417
|
```
|
|
312
418
|
|
|
419
|
+
|
|
313
420
|
## Input/Output
|
|
314
421
|
|
|
315
422
|
### `say` - Normal Output
|
|
@@ -466,7 +573,7 @@ export async function checkServices() {
|
|
|
466
573
|
await useFallbackDatabase();
|
|
467
574
|
}
|
|
468
575
|
|
|
469
|
-
const apiHealthy = await task.try(() => fetch('http://localhost:3000/health');
|
|
576
|
+
const apiHealthy = await task.try(() => fetch('http://localhost:3000/health'));
|
|
470
577
|
|
|
471
578
|
if (!apiHealthy) {
|
|
472
579
|
yell('API IS DOWN!');
|
|
@@ -535,336 +642,126 @@ export async function commit() {
|
|
|
535
642
|
}
|
|
536
643
|
```
|
|
537
644
|
|
|
538
|
-
See more ai usage examples
|
|
645
|
+
See more ai usage examples in [docs/examples.md](docs/examples.md)
|
|
539
646
|
|
|
540
|
-
## Examples
|
|
541
647
|
|
|
542
|
-
|
|
648
|
+
## Execute JavaScript Code
|
|
543
649
|
|
|
544
|
-
|
|
650
|
+
Bunosh supports executing JavaScript code directly using the `-e` flag, allowing for powerful one-liners and integration with shell scripts and CI/CD systems.
|
|
545
651
|
|
|
546
|
-
|
|
547
|
-
bunosh worktree:create
|
|
548
|
-
bunosh worktree:delete
|
|
549
|
-
```
|
|
652
|
+
### Basic Usage
|
|
550
653
|
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
*/
|
|
555
|
-
export async function worktreeCreate(name = '') {
|
|
556
|
-
const worktreeName = name || await ask('What is feature name?');
|
|
557
|
-
const newDir = `../app-${worktreeName}`;
|
|
558
|
-
|
|
559
|
-
await exec`git worktree add ${newDir}`;
|
|
560
|
-
say(`Created worktree for feature ${worktreeName} in ${newDir}`);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
/**
|
|
564
|
-
* Remove worktree when feature is merged
|
|
565
|
-
*/
|
|
566
|
-
export async function worktreeDelete(worktree = '') {
|
|
567
|
-
const worktrees = await shell`git worktree list`;
|
|
568
|
-
const worktreePaths = worktrees.output
|
|
569
|
-
.split('\n')
|
|
570
|
-
.map(line => line.split(' ')[0])
|
|
571
|
-
.filter(path => path !== process.cwd());
|
|
572
|
-
|
|
573
|
-
if (worktreePaths.length === 0) {
|
|
574
|
-
say('No worktrees found');
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const worktreeName = worktree || await ask('Select worktree to delete', worktreePaths);
|
|
579
|
-
const rmDir = worktreePaths.find(path => path.includes(worktreeName));
|
|
580
|
-
|
|
581
|
-
if (!rmDir) {
|
|
582
|
-
say(`Worktree for feature ${worktreeName} not found`);
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
654
|
+
```bash
|
|
655
|
+
# Execute inline JavaScript
|
|
656
|
+
bunosh -e "say('Hello')"
|
|
585
657
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
658
|
+
# Execute JavaScript from stdin
|
|
659
|
+
echo "say('Hello')" | bunosh -e
|
|
589
660
|
```
|
|
590
661
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
```javascript
|
|
594
|
-
/**
|
|
595
|
-
* Generate comprehensive release notes using AI
|
|
596
|
-
*/
|
|
597
|
-
export async function generateReleaseNotes(fromTag = '', toTag = 'HEAD') {
|
|
598
|
-
const { ai, writeToFile, exec, say, ask } = global.bunosh;
|
|
599
|
-
|
|
600
|
-
// Get version
|
|
601
|
-
const version = await ask('Release version:', '1.0.0');
|
|
602
|
-
|
|
603
|
-
// Get commit history
|
|
604
|
-
const gitLog = fromTag
|
|
605
|
-
? await exec`git log ${fromTag}..${toTag} --pretty=format:"%h %s" --no-merges`
|
|
606
|
-
: await exec`git log -n 50 --pretty=format:"%h %s" --no-merges`;
|
|
607
|
-
|
|
608
|
-
// Get diff statistics
|
|
609
|
-
const stats = fromTag
|
|
610
|
-
? await exec`git diff --stat ${fromTag}..${toTag}`
|
|
611
|
-
: await exec`git diff --stat HEAD~50..HEAD`;
|
|
612
|
-
|
|
613
|
-
// Generate release notes with AI
|
|
614
|
-
const releaseNotes = await ai(
|
|
615
|
-
`Generate professional release notes for version ${version} based on these commits and changes:
|
|
662
|
+
### Heredoc Syntax
|
|
616
663
|
|
|
617
|
-
|
|
618
|
-
${gitLog.output}
|
|
664
|
+
For multi-line scripts, use heredoc syntax for clean, readable code:
|
|
619
665
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
);
|
|
666
|
+
```bash
|
|
667
|
+
bunosh -e << 'EOF'
|
|
668
|
+
say('๐ Starting build process...')
|
|
669
|
+
await task('Install Dependencies', () => shell`npm ci`)
|
|
670
|
+
await task('Build', () => shell`npm run build`)
|
|
671
|
+
await task('Test', () => shell`npm test`)
|
|
672
|
+
say('โ
All tasks completed successfully!')
|
|
673
|
+
EOF
|
|
674
|
+
```
|
|
630
675
|
|
|
631
|
-
|
|
632
|
-
writeToFile(`CHANGELOG.md`, (line) => {
|
|
633
|
-
line`# Release v${version}`;
|
|
634
|
-
line`*${new Date().toLocaleDateString()}*`;
|
|
635
|
-
line``;
|
|
636
|
-
line`## โจ New Features`;
|
|
637
|
-
line`${releaseNotes.features}`;
|
|
638
|
-
line``;
|
|
639
|
-
line`## ๐ Bug Fixes`;
|
|
640
|
-
line`${releaseNotes.fixes}`;
|
|
641
|
-
line``;
|
|
642
|
-
line`## ๐ Acknowledgments`;
|
|
643
|
-
line`${releaseNotes.acknowledgments}`;
|
|
644
|
-
|
|
645
|
-
// append previous contents
|
|
646
|
-
line.fromFile('CHANGELOG.md');
|
|
647
|
-
});
|
|
676
|
+
### With Environment Variables and Control Flow
|
|
648
677
|
|
|
649
|
-
|
|
678
|
+
```bash
|
|
679
|
+
# Complex script with conditions
|
|
680
|
+
bunosh -e << 'EOF'
|
|
681
|
+
const env = process.env.NODE_ENV || 'development'
|
|
682
|
+
say(`Building for ${env}...`)
|
|
683
|
+
|
|
684
|
+
if (env === 'production') {
|
|
685
|
+
await shell`npm run build:prod`
|
|
686
|
+
await task('Deploy', () => shell`./deploy.sh`)
|
|
687
|
+
} else {
|
|
688
|
+
await shell`npm run build:dev`
|
|
650
689
|
}
|
|
651
|
-
```
|
|
652
690
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
```js
|
|
656
|
-
const fileContents = await shell`tail -n 500 error.log`
|
|
657
|
-
const analysis = await ai(`Analyze this error log ${fileContents.output}`, {
|
|
658
|
-
severity: "critical/high/medium/low",
|
|
659
|
-
rootCause: "specific issue identified",
|
|
660
|
-
solution: "step-by-step fix",
|
|
661
|
-
preventionTips: "how to avoid this"
|
|
662
|
-
});
|
|
691
|
+
yell('BUILD COMPLETE!')
|
|
692
|
+
EOF
|
|
663
693
|
```
|
|
664
694
|
|
|
665
|
-
|
|
695
|
+
### Error Handling
|
|
666
696
|
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
task.stopOnFailures();
|
|
680
|
-
// Build all containers in parallel
|
|
681
|
-
const buildResults = await Promise.all(
|
|
682
|
-
services.map(service =>
|
|
683
|
-
exec`docker build -t ${registry}/${service}:${version} -f ${service}/Dockerfile ${service}`
|
|
684
|
-
)
|
|
685
|
-
);
|
|
686
|
-
|
|
687
|
-
say('โ
All containers built successfully');
|
|
688
|
-
|
|
689
|
-
// Push all containers in parallel
|
|
690
|
-
say('๐ค Publishing to registry...');
|
|
691
|
-
|
|
692
|
-
const pushResults = await Promise.all(
|
|
693
|
-
services.map(service =>
|
|
694
|
-
exec`docker push ${registry}/${service}:${version}`
|
|
695
|
-
)
|
|
696
|
-
);
|
|
697
|
-
|
|
698
|
-
yell('CONTAINERS PUBLISHED!');
|
|
699
|
-
say(`Published: ${pushResults.join(', ')}`);
|
|
700
|
-
say(`Registry: ${registry}`);
|
|
701
|
-
say(`Version: ${version}`);
|
|
697
|
+
```bash
|
|
698
|
+
# Script with error handling
|
|
699
|
+
bunosh -e << 'EOF'
|
|
700
|
+
task.stopOnFailures()
|
|
701
|
+
|
|
702
|
+
try {
|
|
703
|
+
await shell`npm test`
|
|
704
|
+
await shell`npm run build`
|
|
705
|
+
say('โ
Success!')
|
|
706
|
+
} catch (error) {
|
|
707
|
+
yell(`โ Build failed: ${error.message}`)
|
|
708
|
+
process.exit(1)
|
|
702
709
|
}
|
|
710
|
+
EOF
|
|
703
711
|
```
|
|
704
712
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
```javascript
|
|
708
|
-
/**
|
|
709
|
-
* Deploy to Kubernetes with health checks
|
|
710
|
-
*/
|
|
711
|
-
export async function kubeDeploy(
|
|
712
|
-
environment = 'staging',
|
|
713
|
-
options = { wait: true, replicas: 3 }
|
|
714
|
-
) {
|
|
715
|
-
const { exec, task, say, yell, ask } = global.bunosh;
|
|
716
|
-
|
|
717
|
-
// Confirm production deployments
|
|
718
|
-
if (environment === 'production') {
|
|
719
|
-
const confirmed = await ask(
|
|
720
|
-
`โ ๏ธ Deploy to PRODUCTION?`,
|
|
721
|
-
false
|
|
722
|
-
);
|
|
723
|
-
if (!confirmed) {
|
|
724
|
-
say('Deployment cancelled');
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Set kubectl context
|
|
730
|
-
await task('Setting context', () =>
|
|
731
|
-
exec`kubectl config use-context ${environment}`
|
|
732
|
-
);
|
|
733
|
-
|
|
734
|
-
// Apply configurations
|
|
735
|
-
await task('Applying configurations', () =>
|
|
736
|
-
exec`kubectl apply -f k8s/${environment}/`
|
|
737
|
-
);
|
|
738
|
-
|
|
739
|
-
// Scale if needed
|
|
740
|
-
if (options.replicas) {
|
|
741
|
-
await task(`Scaling to ${options.replicas} replicas`, () =>
|
|
742
|
-
exec`kubectl scale deployment/app --replicas=${options.replicas}`
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Wait for rollout
|
|
747
|
-
if (options.wait) {
|
|
748
|
-
await task('Waiting for rollout', () =>
|
|
749
|
-
exec`kubectl rollout status deployment/app --timeout=5m`
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// Verify deployment
|
|
754
|
-
const pods = await exec`kubectl get pods -l app=myapp -o json`;
|
|
755
|
-
const podData = JSON.parse(pods.output);
|
|
756
|
-
const runningPods = podData.items.filter(
|
|
757
|
-
pod => pod.status.phase === 'Running'
|
|
758
|
-
).length;
|
|
713
|
+
### JavaScript Execution in GitHub Actions
|
|
759
714
|
|
|
760
|
-
|
|
761
|
-
yell('DEPLOYMENT SUCCESSFUL!');
|
|
762
|
-
say(`โ
${runningPods} pods running in ${environment}`);
|
|
763
|
-
} else {
|
|
764
|
-
yell('DEPLOYMENT ISSUES!');
|
|
765
|
-
say(`โ ๏ธ Only ${runningPods}/${options.replicas} pods running`);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
715
|
+
Use JavaScript execution to run Bunosh scripts inside CI/CD workflows without creating separate files:
|
|
768
716
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
717
|
+
```yaml
|
|
718
|
+
- name: Build and Deploy
|
|
719
|
+
run: |
|
|
720
|
+
bunosh -e << 'EOF'
|
|
721
|
+
say('๐ Starting deployment...')
|
|
774
722
|
|
|
775
|
-
|
|
776
|
-
`Rollback ${environment} deployment?`,
|
|
777
|
-
false
|
|
778
|
-
);
|
|
723
|
+
if (!process.env.NODE_ENV === 'production') return;
|
|
779
724
|
|
|
780
|
-
|
|
781
|
-
say('Rollback cancelled');
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
725
|
+
shell`./deploy.sh`
|
|
784
726
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
727
|
+
const response = await fetch('${{ secrets.DEPLOY_WEBHOOK }}', {
|
|
728
|
+
method: 'POST',
|
|
729
|
+
headers: { 'Authorization': 'Bearer ${{ secrets.API_TOKEN }}' }
|
|
730
|
+
})
|
|
788
731
|
|
|
789
|
-
|
|
790
|
-
|
|
732
|
+
if (response.ok) {
|
|
733
|
+
yell('๐ DEPLOYMENT COMPLETE!')
|
|
734
|
+
} else {
|
|
735
|
+
yell('โ DEPLOYMENT FAILED!')
|
|
736
|
+
process.exit(1)
|
|
737
|
+
}
|
|
738
|
+
EOF
|
|
739
|
+
env:
|
|
740
|
+
NODE_ENV: production
|
|
791
741
|
```
|
|
792
742
|
|
|
793
|
-
|
|
743
|
+
### Shell Integration
|
|
794
744
|
|
|
795
|
-
```
|
|
796
|
-
bunosh
|
|
745
|
+
```bash
|
|
746
|
+
bunosh -e << 'EOF'
|
|
747
|
+
say('Running database migrations...')
|
|
748
|
+
await shell`npm run migrate`
|
|
749
|
+
say('Migrations completed')
|
|
750
|
+
EOF
|
|
797
751
|
```
|
|
798
752
|
|
|
799
|
-
```javascript
|
|
800
|
-
/**
|
|
801
|
-
* Spawn EC2 instances and configure
|
|
802
|
-
*
|
|
803
|
-
*/
|
|
804
|
-
export async function awsSpawnServer(
|
|
805
|
-
instanceType = 't3.micro',
|
|
806
|
-
options = { count: 1, region: 'us-east-1' }
|
|
807
|
-
) {
|
|
808
|
-
const { exec, task, say, writeToFile } = global.bunosh;
|
|
809
|
-
|
|
810
|
-
const result = await exec`aws ec2 run-instances \
|
|
811
|
-
--image-id ami-0c55b159cbfafe1f0 \
|
|
812
|
-
--instance-type ${instanceType} \
|
|
813
|
-
--count ${options.count} \
|
|
814
|
-
--region ${options.region} \
|
|
815
|
-
--output json`;
|
|
816
|
-
|
|
817
|
-
const instanceIds = JSON.parse(result.output).Instances.map(i => i.InstanceId);
|
|
818
|
-
say(`๐ Launched instances: ${instanceIds.join(', ')}`);
|
|
819
|
-
|
|
820
|
-
exec`aws ec2 wait instance-running --instance-ids ${instanceIds.join(' ')}`
|
|
821
|
-
|
|
822
|
-
const details = await exec`aws ec2 describe-instances \
|
|
823
|
-
--instance-ids ${instanceIds.join(' ')} \
|
|
824
|
-
--output json`;
|
|
825
|
-
const instances = JSON.parse(details.output).Reservations[0].Instances;
|
|
826
|
-
|
|
827
|
-
writeToFile('instances.json', (line) => {
|
|
828
|
-
line`${JSON.stringify(instances, null, 2)}`;
|
|
829
|
-
});
|
|
830
753
|
|
|
831
|
-
|
|
832
|
-
instances.forEach(instance => {
|
|
833
|
-
say(`Instance ${instance.InstanceId}:`);
|
|
834
|
-
say(` Public IP: ${instance.PublicIpAddress}`);
|
|
835
|
-
say(` SSH: ssh -i key.pem ec2-user@${instance.PublicIpAddress}`);
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
return instances;
|
|
839
|
-
}
|
|
754
|
+
## Examples
|
|
840
755
|
|
|
841
|
-
|
|
842
|
-
* Configure Cloudflare DNS for new servers
|
|
843
|
-
*/
|
|
844
|
-
export async function cloudflareSetup(domain, ipAddress) {
|
|
845
|
-
const { exec, task, say } = global.bunosh;
|
|
846
|
-
|
|
847
|
-
const zoneId = process.env.CLOUDFLARE_ZONE_ID;
|
|
848
|
-
const apiToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
849
|
-
|
|
850
|
-
await task('Creating DNS record', async () => {
|
|
851
|
-
const result = await exec`curl -X POST \
|
|
852
|
-
"https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records" \
|
|
853
|
-
-H "Authorization: Bearer ${apiToken}" \
|
|
854
|
-
-H "Content-Type: application/json" \
|
|
855
|
-
--data '{
|
|
856
|
-
"type": "A",
|
|
857
|
-
"name": "${domain}",
|
|
858
|
-
"content": "${ipAddress}",
|
|
859
|
-
"ttl": 3600
|
|
860
|
-
}'`;
|
|
861
|
-
|
|
862
|
-
return JSON.parse(result.output);
|
|
863
|
-
});
|
|
756
|
+
For comprehensive examples of Bunosh in action, see [docs/examples.md](docs/examples.md).
|
|
864
757
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
758
|
+
This includes:
|
|
759
|
+
- Feature branch workflow with git worktrees
|
|
760
|
+
- AI-powered release note generation
|
|
761
|
+
- Container building and publishing
|
|
762
|
+
- Kubernetes deployment and rollback
|
|
763
|
+
- AWS infrastructure management
|
|
764
|
+
- And more practical examples
|
|
868
765
|
|
|
869
766
|
## License
|
|
870
767
|
|