bunosh 0.3.0 β 0.3.2
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 +639 -634
- package/bunosh.js +24 -0
- package/index.js +17 -8
- package/package.json +1 -2
- package/src/formatters/console.js +1 -0
- package/src/formatters/github-actions.js +4 -0
- package/src/io.js +55 -1
- package/src/open-editor.js +95 -0
- package/src/printer.js +11 -0
- package/src/program.js +11 -7
- package/src/task.js +131 -8
- package/src/tasks/shell.js +119 -0
package/README.md
CHANGED
|
@@ -1,27 +1,72 @@
|
|
|
1
1
|
# π² Bunosh
|
|
2
2
|
|
|
3
|
-
> *Named after **banosh**, a traditional Ukrainian dish from cornmeal cooked with various ingredients such as mushrooms, cheese, sour cream*
|
|
4
|
-
|
|
5
3
|
<p align="center">
|
|
6
4
|
<img src="assets/logo.png" alt="Logo" width="150">
|
|
7
5
|
</p>
|
|
8
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>ONE TOOL TO SCRIPT THEM ALL</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
Transform JavaScript functions into powerful CLI commands. Write once, run anywhere.
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
9
17
|
## What is Bunosh?
|
|
10
18
|
|
|
11
|
-
Bunosh is a modern task runner that
|
|
19
|
+
Bunosh is a modern task runner that turns your JavaScript functions into CLI commands instantly. No configuration, no boilerplate - just write functions and run them from the terminal.
|
|
20
|
+
|
|
21
|
+
> *Named after **banosh**, a traditional Ukrainian dish from cornmeal cooked with various ingredients*
|
|
22
|
+
|
|
23
|
+
### β¨ Key Features
|
|
24
|
+
|
|
25
|
+
- **π Zero Configuration** - Write functions, get CLI commands automatically
|
|
26
|
+
- **π¨ pure JavaScript** - write commands as JavaScript functions
|
|
27
|
+
- **π¦ Built-in Tasks** - Shell execution, HTTP requests, file operations
|
|
28
|
+
- **π€ AI-Powered** - integrate LLM calls into your daily tasks
|
|
29
|
+
- **π§ Cross-Platform** - Works seamlessly on macOS, Linux, and Windows. Via bun, npm, or as single executable.
|
|
30
|
+
- **π― Smart CLI** - Auto-completion, help generation, and intuitive argument handling
|
|
31
|
+
|
|
32
|
+
## Why Choose Bunosh?
|
|
33
|
+
|
|
34
|
+
### Over Bash Scripts
|
|
35
|
+
|
|
36
|
+
- **Readable** syntax if you already know JavaScript (no cryptic bash symbol)
|
|
37
|
+
- **Cross-platform** without compatibility headaches
|
|
38
|
+
- **Rich ecosystem** - use any npm package
|
|
39
|
+
|
|
40
|
+
### Over npm scripts
|
|
41
|
+
|
|
42
|
+
- **Real programming** - loops, conditions, async/await
|
|
43
|
+
- **Interactive** - outputs, prompts, confirmations, selections
|
|
44
|
+
- **Composable** - one file for everything! Call functions from other functions
|
|
45
|
+
- **Arguments & options** - full CLI parameter support
|
|
46
|
+
|
|
47
|
+
### Over Traditional Task Runners
|
|
48
|
+
|
|
49
|
+
- **No configuration files** - just export functions
|
|
50
|
+
- **No DSL to learn** - it's just JavaScript
|
|
51
|
+
- **Native speed** - runs on Bun or Node.js
|
|
52
|
+
- **Modern DX** - auto-completion, beautiful output
|
|
53
|
+
|
|
54
|
+
## Table of Contents
|
|
12
55
|
|
|
13
|
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
56
|
+
- [Installation](#installation)
|
|
57
|
+
- [Quickstart](#quickstart)
|
|
58
|
+
- [Commands](#commands)
|
|
59
|
+
- [Tasks](#tasks)
|
|
60
|
+
- [Input/Output](#inputoutput)
|
|
61
|
+
- [Task Control](#task-control)
|
|
62
|
+
- [AI Integration](#ai-integration)
|
|
63
|
+
- [Examples](#examples)
|
|
19
64
|
|
|
20
65
|
## Installation
|
|
21
66
|
|
|
22
67
|
### Option 1: Single Executable (Recommended)
|
|
23
68
|
|
|
24
|
-
Download
|
|
69
|
+
Download the standalone executable - no Node.js or Bun required:
|
|
25
70
|
|
|
26
71
|
**macOS:**
|
|
27
72
|
```bash
|
|
@@ -40,826 +85,786 @@ sudo mv bunosh-linux-x64 /usr/local/bin/bunosh
|
|
|
40
85
|
Invoke-WebRequest -Uri "https://github.com/davertmik/bunosh/releases/latest/download/bunosh-windows-x64.exe.zip" -OutFile "bunosh.zip"
|
|
41
86
|
Expand-Archive -Path "bunosh.zip" -DestinationPath .
|
|
42
87
|
Move-Item "bunosh-windows-x64.exe" "bunosh.exe"
|
|
43
|
-
# Add bunosh.exe to your PATH
|
|
44
88
|
```
|
|
45
89
|
|
|
46
|
-
### Option 2:
|
|
90
|
+
### Option 2: Package Managers
|
|
47
91
|
|
|
48
92
|
```bash
|
|
93
|
+
# Using Bun
|
|
49
94
|
bun add -g bunosh
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Option 3: NPM Package
|
|
53
95
|
|
|
54
|
-
|
|
96
|
+
# Using npm
|
|
55
97
|
npm install -g bunosh
|
|
56
98
|
```
|
|
57
99
|
|
|
100
|
+
## Quickstart
|
|
58
101
|
|
|
102
|
+
1. **Initialize your Bunoshfile:**
|
|
59
103
|
```bash
|
|
60
|
-
# Initialize a new Bunoshfile
|
|
61
104
|
bunosh init
|
|
62
|
-
|
|
63
|
-
# This creates Bunoshfile.js with sample tasks
|
|
64
105
|
```
|
|
65
106
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Create a `Bunoshfile.js`:
|
|
69
|
-
|
|
107
|
+
2. **Write your first command:**
|
|
70
108
|
```javascript
|
|
71
|
-
//
|
|
72
|
-
const { exec,
|
|
109
|
+
// Bunoshfile.js
|
|
110
|
+
const { exec, say } = global.bunosh;
|
|
73
111
|
|
|
74
112
|
/**
|
|
75
|
-
*
|
|
113
|
+
* Builds the project for production
|
|
76
114
|
*/
|
|
77
|
-
export async function
|
|
78
|
-
|
|
79
|
-
|
|
115
|
+
export async function build(env = 'production') {
|
|
116
|
+
say(`π¨ Building for ${env}...`);
|
|
117
|
+
await exec`npm run build`.env({ NODE_ENV: env });
|
|
118
|
+
say('β
Build complete!');
|
|
80
119
|
}
|
|
120
|
+
```
|
|
81
121
|
|
|
82
|
-
|
|
83
|
-
* Starts development server
|
|
84
|
-
*/
|
|
85
|
-
export async function dev() {
|
|
86
|
-
say('π Starting development server...');
|
|
87
|
-
await exec`npm run dev`;
|
|
88
|
-
}
|
|
122
|
+
That's it! Your function is now a CLI command.
|
|
89
123
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
say(`π¨ Building for ${target}...`);
|
|
95
|
-
await exec`npm run build`;
|
|
124
|
+
3. **Run it:**
|
|
125
|
+
```bash
|
|
126
|
+
# build for production
|
|
127
|
+
bunosh build
|
|
96
128
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
yell('BUILD COMPLETE!');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
129
|
+
# build for staging
|
|
130
|
+
bunosh build staging
|
|
102
131
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
132
|
+
# build for development
|
|
133
|
+
bunosh build development
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
### Creating Commands
|
|
111
139
|
|
|
112
|
-
|
|
113
|
-
await build('production');
|
|
114
|
-
await exec`docker build -t myapp:${env} .`;
|
|
115
|
-
await exec`docker push myapp:${env}`;
|
|
140
|
+
Every exported function in `Bunoshfile.js` becomes a CLI command:
|
|
116
141
|
|
|
117
|
-
|
|
142
|
+
```javascript
|
|
143
|
+
// Simple command
|
|
144
|
+
export function hello() {
|
|
145
|
+
console.log('Hello, World!');
|
|
118
146
|
}
|
|
119
147
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
export async function clean() {
|
|
124
|
-
await exec`rm -rf dist node_modules/.cache tmp`;
|
|
125
|
-
say('β¨ All clean!');
|
|
148
|
+
// Command with parameters
|
|
149
|
+
export function greet(name = 'friend') {
|
|
150
|
+
console.log(`Hello, ${name}!`);
|
|
126
151
|
}
|
|
127
152
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
153
|
+
// Command with options
|
|
154
|
+
export function deploy(env = 'staging', options = { force: false, verbose: false }) {
|
|
155
|
+
if (options.verbose) console.log('Verbose mode enabled');
|
|
156
|
+
console.log(`Deploying to ${env}${options.force ? ' (forced)' : ''}`);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
134
159
|
|
|
135
|
-
|
|
160
|
+
**CLI Usage:**
|
|
161
|
+
```bash
|
|
162
|
+
bunosh hello
|
|
163
|
+
bunosh greet John
|
|
164
|
+
bunosh deploy production --force --verbose
|
|
165
|
+
```
|
|
136
166
|
|
|
137
|
-
|
|
138
|
-
writeToFile('package.json', (line) => {
|
|
139
|
-
line`{`;
|
|
140
|
-
line` "name": "${projectName}",`;
|
|
141
|
-
line` "version": "1.0.0",`;
|
|
142
|
-
line` "type": "module"`;
|
|
143
|
-
if (useTypescript) {
|
|
144
|
-
line`, "devDependencies": {`;
|
|
145
|
-
line` "typescript": "^5.0.0"`;
|
|
146
|
-
line` }`;
|
|
147
|
-
}
|
|
148
|
-
line`}`;
|
|
149
|
-
});
|
|
167
|
+
### Arguments and Options
|
|
150
168
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
Bunosh automatically maps function parameters to CLI arguments:
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
/**
|
|
173
|
+
* Create a new feature branch
|
|
174
|
+
* @param {string} name - Feature name (required)
|
|
175
|
+
* @param {string} base - Base branch (optional, defaults to 'main')
|
|
176
|
+
* @param {object} options - CLI options
|
|
177
|
+
* @param {boolean} options.push - Push to remote after creation
|
|
178
|
+
*/
|
|
179
|
+
export async function feature(name, base = 'main', options = { push: false }) {
|
|
180
|
+
await exec`git checkout -b feature/${name} ${base}`;
|
|
154
181
|
|
|
155
|
-
|
|
182
|
+
if (options.push) {
|
|
183
|
+
await exec`git push -u origin feature/${name}`;
|
|
184
|
+
}
|
|
156
185
|
}
|
|
157
186
|
```
|
|
158
187
|
|
|
159
|
-
|
|
160
|
-
|
|
188
|
+
**Generated CLI:**
|
|
161
189
|
```bash
|
|
162
|
-
#
|
|
163
|
-
bunosh
|
|
164
|
-
|
|
165
|
-
# Run individual tasks
|
|
166
|
-
bunosh install
|
|
167
|
-
bunosh dev
|
|
168
|
-
bunosh build
|
|
169
|
-
bunosh build staging
|
|
170
|
-
bunosh deploy production --skip-tests
|
|
171
|
-
bunosh clean
|
|
172
|
-
bunosh setup
|
|
190
|
+
bunosh feature my-feature # Creates from main
|
|
191
|
+
bunosh feature my-feature develop # Creates from develop
|
|
192
|
+
bunosh feature my-feature --push # Creates and pushes
|
|
173
193
|
```
|
|
174
194
|
|
|
175
|
-
|
|
195
|
+
### Command Naming
|
|
176
196
|
|
|
177
|
-
|
|
178
|
-
π² Your exceptional task runner
|
|
197
|
+
Functions are automatically converted to kebab-case commands:
|
|
179
198
|
|
|
180
|
-
|
|
199
|
+
| Function Name | CLI Command |
|
|
200
|
+
|--------------|-------------|
|
|
201
|
+
| `build` | `bunosh build` |
|
|
202
|
+
| `gitPush` | `bunosh git:push` |
|
|
203
|
+
| `npmInstall` | `bunosh npm:install` |
|
|
204
|
+
| `buildAndDeploy` | `bunosh build:and-deploy` |
|
|
181
205
|
|
|
182
|
-
|
|
206
|
+
## Tasks
|
|
183
207
|
|
|
184
|
-
|
|
185
|
-
build Builds project for production
|
|
186
|
-
bunosh build [target]
|
|
187
|
-
clean Cleans up temporary files
|
|
188
|
-
deploy Deploys to specified environment
|
|
189
|
-
bunosh deploy [env] --skip-tests
|
|
190
|
-
dev Starts development server
|
|
191
|
-
install Installs project dependencies
|
|
192
|
-
setup Setup new project environment
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Example: DevOps Tasks
|
|
208
|
+
All Bunosh utilities are available via `global.bunosh`:
|
|
196
209
|
|
|
197
210
|
```javascript
|
|
198
|
-
const { exec, fetch, writeToFile,
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Checks service health across environments
|
|
202
|
-
*/
|
|
203
|
-
export async function healthCheck(env = 'production') {
|
|
204
|
-
const services = ['api', 'web', 'database'];
|
|
205
|
-
|
|
206
|
-
for (const service of services) {
|
|
207
|
-
const url = `https://${service}.${env}.example.com/health`;
|
|
208
|
-
await task(`Checking ${service}`, async () => {
|
|
209
|
-
const response = await fetch(url);
|
|
210
|
-
if (!response.ok) throw new Error(`${service} is down!`);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
211
|
+
const { exec, shell, fetch, writeToFile, copyFile, task, ai, say, ask, yell } = global.bunosh;
|
|
212
|
+
```
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
}
|
|
214
|
+
> We use global variables instead of imports to ensure you can use it with bunosh single-executable on any platform.
|
|
216
215
|
|
|
217
|
-
/**
|
|
218
|
-
* Backup database with compression
|
|
219
|
-
*/
|
|
220
|
-
export async function backup(database = 'main') {
|
|
221
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
222
|
-
const filename = `backup-${database}-${timestamp}.sql.gz`;
|
|
223
216
|
|
|
224
|
-
|
|
225
|
-
await exec`aws s3 cp ${filename} s3://backups/${filename}`;
|
|
226
|
-
await exec`rm ${filename}`;
|
|
217
|
+
#### `exec`
|
|
227
218
|
|
|
228
|
-
|
|
229
|
-
}
|
|
219
|
+
Run single command using [child process `spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options)
|
|
230
220
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
await exec`nginx -s reload`;
|
|
237
|
-
say('π Certificates updated!');
|
|
238
|
-
}
|
|
221
|
+
```javascript
|
|
222
|
+
// Complex commands with pipes and streaming output
|
|
223
|
+
await exec`npm install --verbose`;
|
|
224
|
+
await exec`docker build . | tee build.log`;
|
|
225
|
+
await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
|
|
239
226
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
*/
|
|
243
|
-
export async function deployWithChecks(env = 'staging') {
|
|
244
|
-
await exec`kubectl apply -f k8s/${env}/`;
|
|
245
|
-
await exec`kubectl rollout status deployment/myapp`;
|
|
246
|
-
await healthCheck(env);
|
|
247
|
-
say(`π Successfully deployed to ${env}!`);
|
|
248
|
-
}
|
|
227
|
+
// With environment variables
|
|
228
|
+
await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
249
229
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
*/
|
|
253
|
-
export async function scale(replicas = 3, service = 'myapp') {
|
|
254
|
-
await exec`kubectl scale deployment/${service} --replicas=${replicas}`;
|
|
255
|
-
say(`βοΈ Scaled ${service} to ${replicas} replicas`);
|
|
256
|
-
}
|
|
230
|
+
// In specific directory
|
|
231
|
+
await exec`npm install`.cwd('/tmp/project');
|
|
257
232
|
```
|
|
258
233
|
|
|
259
|
-
|
|
234
|
+
By default task prints live line-by-line output from stdout and stderr. To disable output, use `silent` method:
|
|
260
235
|
|
|
261
|
-
```
|
|
262
|
-
Usage: bunosh <command> <args> [options]
|
|
263
|
-
|
|
264
|
-
Commands are loaded from exported functions in Bunoshfile.js
|
|
236
|
+
```javascript
|
|
265
237
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
bunosh backup [database]
|
|
269
|
-
deploy:with-checks Deploys application with health checks
|
|
270
|
-
bunosh deploy:with-checks [env]
|
|
271
|
-
health:check Checks service health across environments
|
|
272
|
-
bunosh health:check [env]
|
|
273
|
-
scale Scales application instances
|
|
274
|
-
bunosh scale [replicas] [service]
|
|
275
|
-
update:certs Updates SSL certificates
|
|
238
|
+
// disable printing output
|
|
239
|
+
await task.silent(() => exec`npm install`);
|
|
276
240
|
|
|
241
|
+
// disable output for all commands
|
|
242
|
+
await task.silence();
|
|
277
243
|
```
|
|
278
244
|
|
|
279
|
-
|
|
245
|
+
See more [#silent](#silent)
|
|
246
|
+
|
|
247
|
+
#### `shell` - Fast Native Execution
|
|
248
|
+
|
|
249
|
+
Optimized for simple, fast commands when running under Bun:
|
|
280
250
|
|
|
281
251
|
```javascript
|
|
282
|
-
|
|
252
|
+
// Simple, fast commands
|
|
253
|
+
await shell`pwd`;
|
|
254
|
+
await shell`ls -la`;
|
|
255
|
+
await shell`cat package.json`;
|
|
256
|
+
```
|
|
283
257
|
|
|
284
|
-
|
|
285
|
-
* Creates new blog post template
|
|
286
|
-
*/
|
|
287
|
-
export async function newPost() {
|
|
288
|
-
const title = await ask('Post title:');
|
|
289
|
-
const slug = title.toLowerCase().replace(/\s+/g, '-');
|
|
290
|
-
const date = new Date().toISOString().split('T')[0];
|
|
291
|
-
|
|
292
|
-
writeToFile(`posts/${date}-${slug}.md`, (line) => {
|
|
293
|
-
line`---`;
|
|
294
|
-
line`title: "${title}"`;
|
|
295
|
-
line`date: ${date}`;
|
|
296
|
-
line`draft: true`;
|
|
297
|
-
line`---`;
|
|
298
|
-
line``;
|
|
299
|
-
line`# ${title}`;
|
|
300
|
-
line``;
|
|
301
|
-
line`Your content here...`;
|
|
302
|
-
});
|
|
258
|
+
For more details see [bun shell](https://bun.sh/docs/runtime/shell) reference
|
|
303
259
|
|
|
304
|
-
|
|
305
|
-
}
|
|
260
|
+
`shell` vs `exec`
|
|
306
261
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
await exec`find ./images -name "*.jpg" -exec jpegoptim --max=80 {} \\;`;
|
|
312
|
-
await exec`find ./images -name "*.png" -exec optipng -o2 {} \\;`;
|
|
313
|
-
say('πΌοΈ Images optimized!');
|
|
314
|
-
}
|
|
262
|
+
| Command | Best For | Use Cases | Implementation | Compatibility |
|
|
263
|
+
|---------|----------|-----------|----------------|---------------|
|
|
264
|
+
| `exec` | Single command execution | single command | spawn process | NodeJS + Bun but platform dependent |
|
|
265
|
+
| `shell` | Multiple cross-platform shell commands | exec + `pwd`, `ls`, `echo`, `cat`, basic file ops | bun shell | Bun only but Cross-platform |
|
|
315
266
|
|
|
316
|
-
|
|
317
|
-
* Creates new page template
|
|
318
|
-
*/
|
|
319
|
-
export async function newPage(name) {
|
|
320
|
-
const slug = name.toLowerCase().replace(/\s+/g, '-');
|
|
321
|
-
|
|
322
|
-
writeToFile(`content/pages/${slug}.md`, (line) => {
|
|
323
|
-
line`---`;
|
|
324
|
-
line`title: "${name}"`;
|
|
325
|
-
line`type: "page"`;
|
|
326
|
-
line`---`;
|
|
327
|
-
line``;
|
|
328
|
-
line`# ${name}`;
|
|
329
|
-
line``;
|
|
330
|
-
line`Page content here...`;
|
|
331
|
-
});
|
|
267
|
+
shell prints output from stdout and stderr. To disable output, [make tasks silent](#silent):
|
|
332
268
|
|
|
333
|
-
|
|
334
|
-
}
|
|
269
|
+
### HTTP Requests
|
|
335
270
|
|
|
336
|
-
|
|
337
|
-
* Generates site and deploys
|
|
338
|
-
*/
|
|
339
|
-
export async function publish() {
|
|
340
|
-
await exec`hugo --minify`;
|
|
341
|
-
await exec`rsync -avz public/ user@server:/var/www/site/`;
|
|
342
|
-
say('π Site published!');
|
|
343
|
-
}
|
|
271
|
+
Built-in fetch with progress indicators:
|
|
344
272
|
|
|
273
|
+
```javascript
|
|
345
274
|
/**
|
|
346
|
-
*
|
|
275
|
+
* Check service health
|
|
347
276
|
*/
|
|
348
|
-
export async function
|
|
349
|
-
|
|
277
|
+
export async function healthCheck(url) {
|
|
278
|
+
const response = await fetch(url);
|
|
279
|
+
|
|
280
|
+
if (response.ok) {
|
|
281
|
+
const data = await response.json();
|
|
282
|
+
say(`β
Service healthy: ${data.status}`);
|
|
283
|
+
} else {
|
|
284
|
+
yell(`β Service down: ${response.status}`);
|
|
285
|
+
}
|
|
350
286
|
}
|
|
351
287
|
```
|
|
352
288
|
|
|
353
|
-
|
|
289
|
+
### File Operations
|
|
354
290
|
|
|
355
|
-
|
|
356
|
-
Usage: bunosh <command> <args> [options]
|
|
291
|
+
Template-based file writing and copying:
|
|
357
292
|
|
|
358
|
-
|
|
293
|
+
```javascript
|
|
294
|
+
/**
|
|
295
|
+
* Generate configuration file
|
|
296
|
+
*/
|
|
297
|
+
export function generateConfig(name, port = 3000) {
|
|
298
|
+
writeToFile('config.json', (line) => {
|
|
299
|
+
line`{`;
|
|
300
|
+
line` "name": "${name}",`;
|
|
301
|
+
line` "port": ${port},`;
|
|
302
|
+
line` "environment": "development"`;
|
|
303
|
+
line`}`;
|
|
304
|
+
});
|
|
359
305
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
bunosh new:page <name>
|
|
363
|
-
new:post Creates new blog post template
|
|
364
|
-
optimize:images Optimizes and compresses images
|
|
365
|
-
publish Generates site and deploys
|
|
366
|
-
serve Builds and serves development site
|
|
367
|
-
bunosh serve [port]
|
|
306
|
+
say('π Config file created');
|
|
307
|
+
}
|
|
368
308
|
|
|
309
|
+
// Copy files
|
|
310
|
+
copyFile('template.env', '.env');
|
|
369
311
|
```
|
|
370
312
|
|
|
371
|
-
##
|
|
313
|
+
## Input/Output
|
|
314
|
+
|
|
315
|
+
### `say` - Normal Output
|
|
372
316
|
|
|
373
|
-
|
|
317
|
+
Standard output with visual indicator:
|
|
374
318
|
|
|
375
319
|
```javascript
|
|
376
|
-
|
|
320
|
+
say('Building project...');
|
|
321
|
+
say('π¦ Dependencies installed');
|
|
322
|
+
say(`Found ${count} files to process`);
|
|
377
323
|
```
|
|
378
324
|
|
|
379
|
-
###
|
|
325
|
+
### `ask` - User Input
|
|
380
326
|
|
|
381
|
-
|
|
327
|
+
Flexible user input with smart parameter detection:
|
|
382
328
|
|
|
383
329
|
```javascript
|
|
384
|
-
//
|
|
385
|
-
await
|
|
386
|
-
await exec`npm install`;
|
|
330
|
+
// Text input with default
|
|
331
|
+
const name = await ask('Project name:', 'my-app');
|
|
387
332
|
|
|
388
|
-
//
|
|
389
|
-
|
|
333
|
+
// Boolean confirmation (auto-detects)
|
|
334
|
+
const proceed = await ask('Continue?', true);
|
|
390
335
|
|
|
391
|
-
//
|
|
392
|
-
await
|
|
336
|
+
// Single selection (auto-detects from array)
|
|
337
|
+
const env = await ask('Select environment:', ['dev', 'staging', 'prod']);
|
|
393
338
|
|
|
394
|
-
//
|
|
395
|
-
|
|
339
|
+
// Multiple selection
|
|
340
|
+
const features = await ask('Select features:',
|
|
341
|
+
['TypeScript', 'ESLint', 'Tests'],
|
|
342
|
+
{ multiple: true }
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Password input
|
|
346
|
+
const password = await ask('Enter password:', { type: 'password' });
|
|
347
|
+
|
|
348
|
+
// Multiline editor input
|
|
349
|
+
const description = await ask('Enter description:', { editor: true });
|
|
396
350
|
```
|
|
397
351
|
|
|
398
|
-
|
|
352
|
+
| Parameter/Option | Type | Description | Example |
|
|
353
|
+
|------------------|------|-------------|---------|
|
|
354
|
+
| **Smart Detection** | | |
|
|
355
|
+
| `defaultValue` | String/Number | Sets default value for text/number input | `'John'`, `3000` |
|
|
356
|
+
| `defaultValue` | Boolean | Auto-detects as confirmation prompt | `true`, `false` |
|
|
357
|
+
| `choices` | Array | Auto-detects as selection list | `['A', 'B', 'C']` |
|
|
358
|
+
| **Options Object** | | |
|
|
359
|
+
| `multiple` | Boolean | Enables multiple selections (requires `choices`) | `true` |
|
|
360
|
+
| `multiline` | Boolean | Opens system editor for multi-line input | `true` |
|
|
361
|
+
| `editor` | Boolean | Opens system editor for multi-line input (same as `multiline`) | `true` |
|
|
362
|
+
| `default` | Any | Default value or content (when using options object) | `'default value'` |
|
|
363
|
+
| `type` | String | Input type: `'input'`, `'confirm'`, `'password'`, `'number'` | `'password'` |
|
|
364
|
+
| `validate` | Function | Custom validation function | `(input) => input.length > 0` |
|
|
399
365
|
|
|
400
|
-
The `exec` function returns a `TaskResult` object with the following properties and methods:
|
|
401
366
|
|
|
402
|
-
|
|
403
|
-
|
|
367
|
+
### `yell`
|
|
368
|
+
|
|
369
|
+
Emphasized Output
|
|
404
370
|
|
|
405
|
-
|
|
406
|
-
result.status // 'success' or 'fail'
|
|
407
|
-
result.output // Combined stdout/stderr as string
|
|
371
|
+
ASCII art output for important messages:
|
|
408
372
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
373
|
+
```javascript
|
|
374
|
+
yell('BUILD COMPLETE!');
|
|
375
|
+
yell('DEPLOYMENT SUCCESSFUL!');
|
|
412
376
|
```
|
|
413
377
|
|
|
414
|
-
|
|
378
|
+
### `silent`
|
|
379
|
+
|
|
380
|
+
Stop printing realtime output
|
|
415
381
|
|
|
416
382
|
```javascript
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
say('β
Tests passed!');
|
|
421
|
-
} else {
|
|
422
|
-
yell('β Tests failed!');
|
|
423
|
-
console.log(result.output); // Show error details
|
|
424
|
-
}
|
|
383
|
+
// Silence all task output
|
|
384
|
+
task.silence();
|
|
385
|
+
await shell`npm build`;
|
|
425
386
|
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
if (result.hasSucceeded) {
|
|
429
|
-
const commitHash = result.output.trim();
|
|
430
|
-
say(`Current commit: ${commitHash}`);
|
|
431
|
-
}
|
|
387
|
+
// restore printing output
|
|
388
|
+
task.prints();
|
|
432
389
|
|
|
433
|
-
//
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
say('Command failed, but continuing...');
|
|
437
|
-
console.log('Error output:', result.output);
|
|
438
|
-
}
|
|
390
|
+
// Silent specific task
|
|
391
|
+
const labels = await task.silent(() => shell(`gh api repos/:org/:repo/labels`));
|
|
392
|
+
```
|
|
439
393
|
|
|
440
|
-
|
|
441
|
-
// β Old: Commands throw on failure
|
|
442
|
-
try {
|
|
443
|
-
await someOtherTaskRunner('failing-command');
|
|
444
|
-
} catch (error) {
|
|
445
|
-
// Handle error
|
|
446
|
-
}
|
|
394
|
+
## Task Control
|
|
447
395
|
|
|
448
|
-
|
|
449
|
-
const result = await exec`failing-command`;
|
|
450
|
-
if (result.hasFailed) {
|
|
451
|
-
// Handle failure explicitly
|
|
452
|
-
}
|
|
453
|
-
```
|
|
396
|
+
### Parallel Executions
|
|
454
397
|
|
|
455
|
-
|
|
456
|
-
```javascript
|
|
457
|
-
// GET request with progress indicator
|
|
458
|
-
const response = await fetch('https://api.github.com/repos/user/repo');
|
|
459
|
-
const data = await response.json();
|
|
460
|
-
```
|
|
398
|
+
No magic here. Use `Promise.all()` to run tasks in parallel:
|
|
461
399
|
|
|
462
|
-
### File Operations
|
|
463
400
|
```javascript
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// Copy files
|
|
473
|
-
copyFile('template.js', 'output.js');
|
|
401
|
+
// Parallel tasks
|
|
402
|
+
const results = await Promise.all([
|
|
403
|
+
exec`npm run build:frontend`,
|
|
404
|
+
exec`npm run build:backend`,
|
|
405
|
+
exec`npm run build:docs`
|
|
406
|
+
]);
|
|
474
407
|
```
|
|
475
408
|
|
|
476
|
-
###
|
|
477
|
-
```javascript
|
|
478
|
-
// Get user input
|
|
479
|
-
const name = await ask('What is your name?');
|
|
409
|
+
### Custom Tasks
|
|
480
410
|
|
|
481
|
-
|
|
482
|
-
say('Building project...'); // Normal output
|
|
483
|
-
yell('BUILD COMPLETE!'); // Emphasized output
|
|
411
|
+
Name and group your tasks operations:
|
|
484
412
|
|
|
485
|
-
|
|
486
|
-
await task('
|
|
487
|
-
await exec`npm
|
|
413
|
+
```js
|
|
414
|
+
await task('Build', () => {
|
|
415
|
+
await exec`npm run build:frontend`);
|
|
416
|
+
await exec`npm run build:docs`);
|
|
488
417
|
});
|
|
489
|
-
|
|
418
|
+
````
|
|
490
419
|
|
|
491
|
-
|
|
420
|
+
### Stop on Failure
|
|
492
421
|
|
|
493
|
-
|
|
422
|
+
By default bunosh executes all tasks event if they fail. To stop execution immediately on failure, use the `task.stopOnFailures()` method.
|
|
494
423
|
|
|
495
|
-
### Quick Setup
|
|
496
424
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
425
|
+
```javascript
|
|
426
|
+
/**
|
|
427
|
+
* Strict deployment - stop on any failure
|
|
428
|
+
*/
|
|
429
|
+
export async function deployStrict() {
|
|
430
|
+
task.stopOnFailures(); // Exit immediately on any task failure
|
|
501
431
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
432
|
+
await exec`npm test`;
|
|
433
|
+
await exec`npm run build`;
|
|
434
|
+
await exec`deploy-script`;
|
|
435
|
+
// If any task fails, script exits immediately
|
|
436
|
+
}
|
|
507
437
|
|
|
508
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Cleanup - continue despite failures
|
|
440
|
+
*/
|
|
441
|
+
export async function cleanup() {
|
|
442
|
+
task.ignoreFailures(); // Continue even if tasks fail
|
|
509
443
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
444
|
+
await task('Remove temp files', () => shell`rm -rf tmp/*`);
|
|
445
|
+
await task('Clear logs', () => shell`rm -f logs/*.log`);
|
|
446
|
+
await task('Reset cache', () => shell`rm -rf .cache`);
|
|
447
|
+
// All tasks run regardless of failures
|
|
448
|
+
}
|
|
449
|
+
```
|
|
513
450
|
|
|
514
|
-
###
|
|
451
|
+
### Try Operations
|
|
515
452
|
|
|
516
|
-
|
|
453
|
+
Gracefully handle operations that might fail:
|
|
517
454
|
|
|
518
455
|
```javascript
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
456
|
+
/**
|
|
457
|
+
* Check service availability
|
|
458
|
+
*/
|
|
459
|
+
export async function checkServices() {
|
|
460
|
+
const dbConnected = await task.try(shell`nc -z localhost 5432`);
|
|
461
|
+
|
|
462
|
+
if (dbConnected) {
|
|
463
|
+
say('β
Database connected');
|
|
464
|
+
} else {
|
|
465
|
+
say('β οΈ Database unavailable, using fallback');
|
|
466
|
+
await useFallbackDatabase();
|
|
528
467
|
}
|
|
529
|
-
});
|
|
530
468
|
|
|
531
|
-
|
|
469
|
+
const apiHealthy = await task.try(() => fetch('http://localhost:3000/health');
|
|
532
470
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
ai.configure({
|
|
536
|
-
registerProvider: {
|
|
537
|
-
envVar: 'XAI_API_KEY',
|
|
538
|
-
provider: {
|
|
539
|
-
createInstance: (modelName) => xai(modelName)
|
|
540
|
-
}
|
|
471
|
+
if (!apiHealthy) {
|
|
472
|
+
yell('API IS DOWN!');
|
|
541
473
|
}
|
|
542
|
-
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
543
476
|
|
|
544
|
-
|
|
545
|
-
import { openrouter } from '@openrouter/ai-sdk-provider';
|
|
546
|
-
ai.configure({
|
|
547
|
-
registerProvider: {
|
|
548
|
-
envVar: 'OPENROUTER_API_KEY',
|
|
549
|
-
provider: {
|
|
550
|
-
createInstance: (modelName) => openrouter(modelName)
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
});
|
|
477
|
+
## π« AI Integration
|
|
554
478
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
registerProvider: {
|
|
558
|
-
envVar: 'CUSTOM_AI_API_KEY',
|
|
559
|
-
provider: {
|
|
560
|
-
createInstance: (modelName) => {
|
|
561
|
-
// Your custom provider logic
|
|
562
|
-
return customAIProvider(modelName, {
|
|
563
|
-
apiKey: process.env.CUSTOM_AI_API_KEY,
|
|
564
|
-
endpoint: 'https://custom-ai.company.com/v1'
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
});
|
|
479
|
+
Built-in AI support for code generation, documentation, and automation.
|
|
480
|
+
Automatically responds to structured JSON output.
|
|
570
481
|
|
|
571
|
-
|
|
572
|
-
|
|
482
|
+
AI provider automatically detected, but you need to provide API key and model name.
|
|
483
|
+
Use `.env` file with `AI_MODEL` and `OPENAI_API_KEY` variables.
|
|
484
|
+
In case you use provider other than OpenAI, Anthropic, Groq, you may need to configure it manually in top of Bunoshfile
|
|
573
485
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
486
|
+
```bash
|
|
487
|
+
# Choose your AI model
|
|
488
|
+
export AI_MODEL=gpt-5 # or claude-4-sonnet, llama-3.3-70b, etc.
|
|
489
|
+
|
|
490
|
+
# Set API key for your provider
|
|
491
|
+
export OPENAI_API_KEY=your_key_here # For OpenAI
|
|
492
|
+
# export ANTHROPIC_API_KEY=your_key_here # For Claude
|
|
493
|
+
# export GROQ_API_KEY=your_key_here # For Groq
|
|
577
494
|
```
|
|
578
495
|
|
|
579
|
-
|
|
496
|
+
|
|
497
|
+
Use the `ai` function to interact with the AI.
|
|
498
|
+
|
|
499
|
+
```js
|
|
500
|
+
const resp = await ai(message, { field1: 'what should be there', field2: 'what should be there' })
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Usage
|
|
580
504
|
|
|
581
505
|
```javascript
|
|
582
|
-
const { ai, writeToFile
|
|
506
|
+
const { ai, writeToFile } = global.bunosh;
|
|
583
507
|
|
|
584
508
|
/**
|
|
585
|
-
* Generate
|
|
509
|
+
* Generate commit message from staged changes
|
|
586
510
|
*/
|
|
587
|
-
export async function
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
const result = await ai(
|
|
591
|
-
`Generate documentation for this code: ${codebase}`,
|
|
592
|
-
{
|
|
593
|
-
overview: 'Brief project overview',
|
|
594
|
-
apiReference: 'API documentation',
|
|
595
|
-
examples: 'Usage examples',
|
|
596
|
-
installation: 'Installation instructions'
|
|
597
|
-
}
|
|
598
|
-
);
|
|
599
|
-
|
|
600
|
-
writeToFile('README.md', (line) => {
|
|
601
|
-
line`# ${result.overview}`;
|
|
602
|
-
line``;
|
|
603
|
-
line`## Installation`;
|
|
604
|
-
line`${result.installation}`;
|
|
605
|
-
line``;
|
|
606
|
-
line`## API Reference`;
|
|
607
|
-
line`${result.apiReference}`;
|
|
608
|
-
line``;
|
|
609
|
-
line`## Examples`;
|
|
610
|
-
line`${result.examples}`;
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
say('π Documentation generated!');
|
|
614
|
-
}
|
|
511
|
+
export async function commit() {
|
|
512
|
+
const diff = await exec`git diff --staged`;
|
|
615
513
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
`Review this code for improvements: ${code}`,
|
|
514
|
+
if (!diff.output.trim()) {
|
|
515
|
+
say('No staged changes');
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const commit = await ai(
|
|
520
|
+
`Generate a conventional commit message for: ${diff.output}`,
|
|
624
521
|
{
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
rating: 'Overall code quality rating (1-10)'
|
|
522
|
+
type: 'Commit type (feat/fix/docs/chore)',
|
|
523
|
+
scope: 'Commit scope (optional)',
|
|
524
|
+
subject: 'Brief subject line (50 chars max)',
|
|
525
|
+
body: 'Detailed explanation'
|
|
630
526
|
}
|
|
631
527
|
);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
528
|
+
|
|
529
|
+
const message = commit.scope
|
|
530
|
+
? `${commit.type}(${commit.scope}): ${commit.subject}\n\n${commit.body}`
|
|
531
|
+
: `${commit.type}: ${commit.subject}\n\n${commit.body}`;
|
|
532
|
+
|
|
533
|
+
await exec`git commit -m "${message}"`;
|
|
534
|
+
say('β
AI-generated commit created');
|
|
639
535
|
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
See more ai usage examples below:
|
|
539
|
+
|
|
540
|
+
## Examples
|
|
541
|
+
|
|
542
|
+
### Development Examples
|
|
543
|
+
|
|
544
|
+
#### Feature Branch Workflow
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
bunosh worktree:create
|
|
548
|
+
bunosh worktree:delete
|
|
549
|
+
```
|
|
640
550
|
|
|
551
|
+
```javascript
|
|
641
552
|
/**
|
|
642
|
-
*
|
|
553
|
+
* Create worktree for feature development
|
|
643
554
|
*/
|
|
644
|
-
export async function
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
testSuite: 'Complete test suite code',
|
|
651
|
-
edgeCases: 'List of edge cases covered',
|
|
652
|
-
mockSetup: 'Required mocks and setup code'
|
|
653
|
-
}
|
|
654
|
-
);
|
|
655
|
-
|
|
656
|
-
const testFile = sourceFile.replace('.js', '.test.js');
|
|
657
|
-
writeToFile(testFile, (line) => {
|
|
658
|
-
line`${tests.mockSetup}`;
|
|
659
|
-
line``;
|
|
660
|
-
line`${tests.testSuite}`;
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
say(`π§ͺ Tests generated: ${testFile}`);
|
|
664
|
-
say(`Edge cases: ${tests.edgeCases}`);
|
|
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}`);
|
|
665
561
|
}
|
|
666
562
|
|
|
667
563
|
/**
|
|
668
|
-
*
|
|
564
|
+
* Remove worktree when feature is merged
|
|
669
565
|
*/
|
|
670
|
-
export async function
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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');
|
|
675
575
|
return;
|
|
676
576
|
}
|
|
677
|
-
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
await exec`git commit -m "${message}"`;
|
|
689
|
-
|
|
690
|
-
say(`β
Committed with AI-generated message:`);
|
|
691
|
-
console.log(message);
|
|
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
|
+
}
|
|
585
|
+
|
|
586
|
+
await exec`git worktree remove ${rmDir} --force`;
|
|
587
|
+
say(`Deleted worktree for feature ${worktreeName} in ${rmDir}`);
|
|
692
588
|
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
#### Generate Release Notes with AI
|
|
693
592
|
|
|
593
|
+
```javascript
|
|
694
594
|
/**
|
|
695
|
-
*
|
|
595
|
+
* Generate comprehensive release notes using AI
|
|
696
596
|
*/
|
|
697
|
-
export async function
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const
|
|
710
|
-
|
|
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:
|
|
616
|
+
|
|
617
|
+
Commits:
|
|
618
|
+
${gitLog.output}
|
|
619
|
+
|
|
620
|
+
Statistics:
|
|
621
|
+
${stats.output}
|
|
622
|
+
|
|
623
|
+
Group changes logically and write user-friendly descriptions.`,
|
|
711
624
|
{
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
recommendations: 'Strategic recommendations'
|
|
625
|
+
features: 'New features (bullet points with emoji)',
|
|
626
|
+
fixes: 'Bug fixes',
|
|
627
|
+
acknowledgments: 'Contributors and acknowledgments'
|
|
716
628
|
}
|
|
717
629
|
);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
630
|
+
|
|
631
|
+
// Write release notes
|
|
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
|
+
});
|
|
648
|
+
|
|
649
|
+
say(`π Release notes generated for v${version}`);
|
|
721
650
|
}
|
|
722
651
|
```
|
|
723
652
|
|
|
724
|
-
###
|
|
653
|
+
### Analyze Logs with AI
|
|
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
|
+
});
|
|
663
|
+
```
|
|
725
664
|
|
|
726
|
-
|
|
665
|
+
#### Build and Publish Containers in Parallel
|
|
727
666
|
|
|
728
667
|
```javascript
|
|
729
668
|
/**
|
|
730
|
-
*
|
|
669
|
+
* Build and publish multiple services in parallel
|
|
731
670
|
*/
|
|
732
|
-
export async function
|
|
733
|
-
const
|
|
734
|
-
say('π Generated copy:');
|
|
735
|
-
console.log(copy);
|
|
736
|
-
}
|
|
671
|
+
export async function publishContainers(registry = 'docker.io/myorg') {
|
|
672
|
+
const { exec, task, say, yell } = global.bunosh;
|
|
737
673
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
*/
|
|
741
|
-
export async function translate(text, language = 'Spanish') {
|
|
742
|
-
const translation = await ai(`Translate to ${language}: ${text}`);
|
|
743
|
-
say(`π Translation to ${language}:`);
|
|
744
|
-
console.log(translation);
|
|
745
|
-
}
|
|
746
|
-
```
|
|
674
|
+
const services = ['api', 'web', 'worker', 'admin'];
|
|
675
|
+
const version = process.env.VERSION || 'latest';
|
|
747
676
|
|
|
748
|
-
|
|
677
|
+
say(`π³ Building ${services.length} containers...`);
|
|
749
678
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
);
|
|
757
686
|
|
|
758
|
-
|
|
687
|
+
say('β
All containers built successfully');
|
|
759
688
|
|
|
760
|
-
|
|
689
|
+
// Push all containers in parallel
|
|
690
|
+
say('π€ Publishing to registry...');
|
|
761
691
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
692
|
+
const pushResults = await Promise.all(
|
|
693
|
+
services.map(service =>
|
|
694
|
+
exec`docker push ${registry}/${service}:${version}`
|
|
695
|
+
)
|
|
696
|
+
);
|
|
767
697
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
// ...
|
|
698
|
+
yell('CONTAINERS PUBLISHED!');
|
|
699
|
+
say(`Published: ${pushResults.join(', ')}`);
|
|
700
|
+
say(`Registry: ${registry}`);
|
|
701
|
+
say(`Version: ${version}`);
|
|
773
702
|
}
|
|
774
|
-
|
|
775
|
-
// CLI usage
|
|
776
|
-
bunosh deploy production --force --verbose
|
|
777
703
|
```
|
|
778
704
|
|
|
779
|
-
|
|
780
|
-
```bash
|
|
781
|
-
# List all commands
|
|
782
|
-
bunosh
|
|
705
|
+
#### Kubernetes Deployment Control
|
|
783
706
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
+
}
|
|
787
728
|
|
|
788
|
-
|
|
789
|
-
|
|
729
|
+
// Set kubectl context
|
|
730
|
+
await task('Setting context', () =>
|
|
731
|
+
exec`kubectl config use-context ${environment}`
|
|
732
|
+
);
|
|
790
733
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
734
|
+
// Apply configurations
|
|
735
|
+
await task('Applying configurations', () =>
|
|
736
|
+
exec`kubectl apply -f k8s/${environment}/`
|
|
737
|
+
);
|
|
794
738
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
+
}
|
|
799
745
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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
|
+
}
|
|
804
752
|
|
|
805
|
-
|
|
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;
|
|
759
|
+
|
|
760
|
+
if (runningPods === options.replicas) {
|
|
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
|
+
}
|
|
806
768
|
|
|
807
|
-
|
|
769
|
+
/**
|
|
770
|
+
* Rollback Kubernetes deployment
|
|
771
|
+
*/
|
|
772
|
+
export async function kubeRollback(environment = 'staging') {
|
|
773
|
+
const { exec, say, ask } = global.bunosh;
|
|
808
774
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
775
|
+
const confirmed = await ask(
|
|
776
|
+
`Rollback ${environment} deployment?`,
|
|
777
|
+
false
|
|
778
|
+
);
|
|
813
779
|
|
|
814
|
-
|
|
815
|
-
|
|
780
|
+
if (!confirmed) {
|
|
781
|
+
say('Rollback cancelled');
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
816
784
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
785
|
+
await exec`kubectl config use-context ${environment}`;
|
|
786
|
+
await exec`kubectl rollout undo deployment/app`;
|
|
787
|
+
await exec`kubectl rollout status deployment/app`;
|
|
820
788
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
npm update -g bunosh
|
|
789
|
+
say(`β
Rolled back ${environment} deployment`);
|
|
790
|
+
}
|
|
824
791
|
```
|
|
825
792
|
|
|
826
|
-
|
|
793
|
+
#### AWS Infrastructure Management
|
|
827
794
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const results = await Promise.all([
|
|
831
|
-
task('Task 1', () => exec`sleep 2 && echo "Done 1"`),
|
|
832
|
-
task('Task 2', () => exec`sleep 2 && echo "Done 2"`),
|
|
833
|
-
task('Task 3', () => exec`sleep 2 && echo "Done 3"`)
|
|
834
|
-
]);
|
|
795
|
+
```
|
|
796
|
+
bunosh aws:spawn-server --count 3
|
|
835
797
|
```
|
|
836
798
|
|
|
837
|
-
### Error Handling
|
|
838
799
|
```javascript
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
|
|
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
|
+
});
|
|
849
830
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
831
|
+
// Output connection info
|
|
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
|
+
});
|
|
856
837
|
|
|
857
|
-
|
|
838
|
+
return instances;
|
|
839
|
+
}
|
|
858
840
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
+
});
|
|
864
|
+
|
|
865
|
+
say(`β
DNS configured: ${domain} β ${ipAddress}`);
|
|
866
|
+
}
|
|
867
|
+
```
|
|
863
868
|
|
|
864
869
|
## License
|
|
865
870
|
|
|
@@ -867,4 +872,4 @@ MIT License - see LICENSE file for details.
|
|
|
867
872
|
|
|
868
873
|
---
|
|
869
874
|
|
|
870
|
-
|
|
875
|
+
Cooked with β€οΈ from Ukraine πΊπ¦
|