bunosh 0.3.1 β 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 +616 -839
- package/bunosh.js +24 -0
- package/index.js +8 -2
- package/package.json +1 -1
- package/src/formatters/console.js +1 -0
- package/src/formatters/github-actions.js +4 -0
- package/src/printer.js +11 -0
- package/src/task.js +131 -8
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
|
|
12
31
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
|
|
55
|
+
|
|
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,553 +85,270 @@ 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
95
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```bash
|
|
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,
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Installs project dependencies
|
|
76
|
-
*/
|
|
77
|
-
export async function install() {
|
|
78
|
-
await exec`npm install`;
|
|
79
|
-
say('π¦ Dependencies installed!');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Starts development server
|
|
84
|
-
*/
|
|
85
|
-
export async function dev() {
|
|
86
|
-
say('π Starting development server...');
|
|
87
|
-
await exec`npm run dev`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Builds project for production
|
|
92
|
-
*/
|
|
93
|
-
export async function build(target = 'production') {
|
|
94
|
-
say(`π¨ Building for ${target}...`);
|
|
95
|
-
await exec`npm run build`;
|
|
96
|
-
|
|
97
|
-
if (target === 'production') {
|
|
98
|
-
await exec`npm run optimize`;
|
|
99
|
-
yell('BUILD COMPLETE!');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Deploys to specified environment
|
|
105
|
-
*/
|
|
106
|
-
export async function deploy(env = 'staging', options = { skipTests: false }) {
|
|
107
|
-
if (!options.skipTests) {
|
|
108
|
-
say('π§ͺ Running tests...');
|
|
109
|
-
await exec`npm test`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
say(`π Deploying to ${env}...`);
|
|
113
|
-
await build('production');
|
|
114
|
-
await exec`docker build -t myapp:${env} .`;
|
|
115
|
-
await exec`docker push myapp:${env}`;
|
|
116
|
-
|
|
117
|
-
yell(`DEPLOYED TO ${env.toUpperCase()}!`);
|
|
118
|
-
}
|
|
109
|
+
// Bunoshfile.js
|
|
110
|
+
const { exec, say } = global.bunosh;
|
|
119
111
|
|
|
120
112
|
/**
|
|
121
|
-
*
|
|
113
|
+
* Builds the project for production
|
|
122
114
|
*/
|
|
123
|
-
export async function
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Setup new project environment
|
|
130
|
-
*/
|
|
131
|
-
export async function setup() {
|
|
132
|
-
const projectName = await ask('What is your project name?');
|
|
133
|
-
const useTypescript = await ask('Use TypeScript? (y/n)') === 'y';
|
|
134
|
-
|
|
135
|
-
say('ποΈ Setting up project...');
|
|
136
|
-
|
|
137
|
-
// Create package.json
|
|
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
|
-
});
|
|
150
|
-
|
|
151
|
-
if (useTypescript) {
|
|
152
|
-
await exec`npm install typescript --save-dev`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
yell('PROJECT READY!');
|
|
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!');
|
|
156
119
|
}
|
|
157
120
|
```
|
|
158
121
|
|
|
159
|
-
|
|
122
|
+
That's it! Your function is now a CLI command.
|
|
160
123
|
|
|
124
|
+
3. **Run it:**
|
|
161
125
|
```bash
|
|
162
|
-
#
|
|
163
|
-
bunosh
|
|
164
|
-
|
|
165
|
-
# Run individual tasks
|
|
166
|
-
bunosh install
|
|
167
|
-
bunosh dev
|
|
126
|
+
# build for production
|
|
168
127
|
bunosh build
|
|
169
|
-
bunosh build staging
|
|
170
|
-
bunosh deploy production --skip-tests
|
|
171
|
-
bunosh clean
|
|
172
|
-
bunosh setup
|
|
173
|
-
```
|
|
174
128
|
|
|
175
|
-
|
|
129
|
+
# build for staging
|
|
130
|
+
bunosh build staging
|
|
176
131
|
|
|
132
|
+
# build for development
|
|
133
|
+
bunosh build development
|
|
177
134
|
```
|
|
178
|
-
π² Your exceptional task runner
|
|
179
|
-
|
|
180
|
-
Usage: bunosh <command> <args> [options]
|
|
181
135
|
|
|
182
|
-
|
|
136
|
+
## Commands
|
|
183
137
|
|
|
184
|
-
Commands
|
|
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
|
-
```
|
|
138
|
+
### Creating Commands
|
|
194
139
|
|
|
195
|
-
|
|
140
|
+
Every exported function in `Bunoshfile.js` becomes a CLI command:
|
|
196
141
|
|
|
197
142
|
```javascript
|
|
198
|
-
|
|
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
|
-
}
|
|
213
|
-
|
|
214
|
-
say('β
All services healthy!');
|
|
215
|
-
}
|
|
216
|
-
|
|
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
|
-
|
|
224
|
-
await exec`pg_dump ${database} | gzip > ${filename}`;
|
|
225
|
-
await exec`aws s3 cp ${filename} s3://backups/${filename}`;
|
|
226
|
-
await exec`rm ${filename}`;
|
|
227
|
-
|
|
228
|
-
say(`π¦ Backup saved: ${filename}`);
|
|
143
|
+
// Simple command
|
|
144
|
+
export function hello() {
|
|
145
|
+
console.log('Hello, World!');
|
|
229
146
|
}
|
|
230
147
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
export async function updateCerts() {
|
|
235
|
-
await exec`certbot renew`;
|
|
236
|
-
await exec`nginx -s reload`;
|
|
237
|
-
say('π Certificates updated!');
|
|
148
|
+
// Command with parameters
|
|
149
|
+
export function greet(name = 'friend') {
|
|
150
|
+
console.log(`Hello, ${name}!`);
|
|
238
151
|
}
|
|
239
152
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Scales application instances
|
|
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`);
|
|
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)' : ''}`);
|
|
256
157
|
}
|
|
257
158
|
```
|
|
258
159
|
|
|
259
|
-
**
|
|
260
|
-
|
|
160
|
+
**CLI Usage:**
|
|
161
|
+
```bash
|
|
162
|
+
bunosh hello
|
|
163
|
+
bunosh greet John
|
|
164
|
+
bunosh deploy production --force --verbose
|
|
261
165
|
```
|
|
262
|
-
Usage: bunosh <command> <args> [options]
|
|
263
166
|
|
|
264
|
-
|
|
167
|
+
### Arguments and Options
|
|
265
168
|
|
|
266
|
-
|
|
267
|
-
backup Backup database with compression
|
|
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
|
|
276
|
-
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Example: Content Management
|
|
169
|
+
Bunosh automatically maps function parameters to CLI arguments:
|
|
280
170
|
|
|
281
171
|
```javascript
|
|
282
|
-
const { exec, writeToFile, ask, say } = global.bunosh;
|
|
283
|
-
|
|
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
|
-
});
|
|
303
|
-
|
|
304
|
-
say(`π Created: posts/${date}-${slug}.md`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
172
|
/**
|
|
308
|
-
*
|
|
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
|
|
309
178
|
*/
|
|
310
|
-
export async function
|
|
311
|
-
await exec`
|
|
312
|
-
await exec`find ./images -name "*.png" -exec optipng -o2 {} \\;`;
|
|
313
|
-
say('πΌοΈ Images optimized!');
|
|
314
|
-
}
|
|
315
|
-
|
|
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
|
-
});
|
|
332
|
-
|
|
333
|
-
say(`π Created: content/pages/${slug}.md`);
|
|
334
|
-
}
|
|
335
|
-
|
|
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
|
-
}
|
|
179
|
+
export async function feature(name, base = 'main', options = { push: false }) {
|
|
180
|
+
await exec`git checkout -b feature/${name} ${base}`;
|
|
344
181
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
export async function serve(port = 1313) {
|
|
349
|
-
await exec`hugo server --port ${port} --buildDrafts`;
|
|
182
|
+
if (options.push) {
|
|
183
|
+
await exec`git push -u origin feature/${name}`;
|
|
184
|
+
}
|
|
350
185
|
}
|
|
351
186
|
```
|
|
352
187
|
|
|
353
|
-
**
|
|
354
|
-
|
|
188
|
+
**Generated CLI:**
|
|
189
|
+
```bash
|
|
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
|
|
355
193
|
```
|
|
356
|
-
Usage: bunosh <command> <args> [options]
|
|
357
194
|
|
|
358
|
-
|
|
195
|
+
### Command Naming
|
|
359
196
|
|
|
360
|
-
|
|
361
|
-
new:page Creates new page template
|
|
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]
|
|
197
|
+
Functions are automatically converted to kebab-case commands:
|
|
368
198
|
|
|
369
|
-
|
|
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` |
|
|
370
205
|
|
|
371
|
-
##
|
|
206
|
+
## Tasks
|
|
372
207
|
|
|
373
|
-
All Bunosh
|
|
208
|
+
All Bunosh utilities are available via `global.bunosh`:
|
|
374
209
|
|
|
375
210
|
```javascript
|
|
376
|
-
const { exec, shell, fetch, writeToFile, copyFile, say, ask, yell
|
|
211
|
+
const { exec, shell, fetch, writeToFile, copyFile, task, ai, say, ask, yell } = global.bunosh;
|
|
377
212
|
```
|
|
378
213
|
|
|
379
|
-
|
|
214
|
+
> We use global variables instead of imports to ensure you can use it with bunosh single-executable on any platform.
|
|
380
215
|
|
|
381
|
-
Bunosh provides two ways to execute shell commands:
|
|
382
216
|
|
|
383
|
-
#### `exec`
|
|
217
|
+
#### `exec`
|
|
384
218
|
|
|
385
|
-
|
|
219
|
+
Run single command using [child process `spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options)
|
|
386
220
|
|
|
387
221
|
```javascript
|
|
388
|
-
// Complex
|
|
389
|
-
await exec`
|
|
390
|
-
await exec`npm install --verbose`; // Shows progress in real-time
|
|
222
|
+
// Complex commands with pipes and streaming output
|
|
223
|
+
await exec`npm install --verbose`;
|
|
391
224
|
await exec`docker build . | tee build.log`;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
#### `shell` - Native Bun Shell (with Node.js fallback)
|
|
225
|
+
await exec`find . -name "*.js" | grep -v node_modules | wc -l`;
|
|
395
226
|
|
|
396
|
-
|
|
227
|
+
// With environment variables
|
|
228
|
+
await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
397
229
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
await shell`pwd`;
|
|
401
|
-
await shell`echo "Hello World"`;
|
|
402
|
-
await shell`ls -la`;
|
|
403
|
-
await shell`cat package.json`;
|
|
230
|
+
// In specific directory
|
|
231
|
+
await exec`npm install`.cwd('/tmp/project');
|
|
404
232
|
```
|
|
405
233
|
|
|
406
|
-
|
|
407
|
-
|
|
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:
|
|
234
|
+
By default task prints live line-by-line output from stdout and stderr. To disable output, use `silent` method:
|
|
421
235
|
|
|
422
236
|
```javascript
|
|
423
|
-
// Both tasks support environment variables
|
|
424
|
-
await shell`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
425
|
-
await exec`echo $NODE_ENV`.env({ NODE_ENV: 'production' });
|
|
426
237
|
|
|
427
|
-
//
|
|
428
|
-
await
|
|
429
|
-
await exec`ls -la`.cwd('/tmp');
|
|
238
|
+
// disable printing output
|
|
239
|
+
await task.silent(() => exec`npm install`);
|
|
430
240
|
|
|
431
|
-
//
|
|
432
|
-
await
|
|
433
|
-
await exec`npm install --verbose`; // Complex, streaming
|
|
241
|
+
// disable output for all commands
|
|
242
|
+
await task.silence();
|
|
434
243
|
```
|
|
435
244
|
|
|
436
|
-
|
|
245
|
+
See more [#silent](#silent)
|
|
437
246
|
|
|
438
|
-
|
|
247
|
+
#### `shell` - Fast Native Execution
|
|
439
248
|
|
|
440
|
-
|
|
441
|
-
const result = await exec`ls -la`;
|
|
442
|
-
// or
|
|
443
|
-
const result = await shell`ls -la`;
|
|
249
|
+
Optimized for simple, fast commands when running under Bun:
|
|
444
250
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
result.hasFailed // true if command failed (non-zero exit code)
|
|
451
|
-
result.hasSucceeded // true if command succeeded (exit code 0)
|
|
251
|
+
```javascript
|
|
252
|
+
// Simple, fast commands
|
|
253
|
+
await shell`pwd`;
|
|
254
|
+
await shell`ls -la`;
|
|
255
|
+
await shell`cat package.json`;
|
|
452
256
|
```
|
|
453
257
|
|
|
454
|
-
|
|
258
|
+
For more details see [bun shell](https://bun.sh/docs/runtime/shell) reference
|
|
455
259
|
|
|
456
|
-
|
|
457
|
-
// Check command success
|
|
458
|
-
const result = await exec`npm test`;
|
|
459
|
-
if (result.hasSucceeded) {
|
|
460
|
-
say('β
Tests passed!');
|
|
461
|
-
} else {
|
|
462
|
-
yell('β Tests failed!');
|
|
463
|
-
console.log(result.output); // Show error details
|
|
464
|
-
}
|
|
260
|
+
`shell` vs `exec`
|
|
465
261
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
say(`Current commit: ${commitHash}`);
|
|
471
|
-
}
|
|
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 |
|
|
472
266
|
|
|
473
|
-
|
|
474
|
-
const result = await exec`optional-command-that-might-fail`;
|
|
475
|
-
if (result.hasFailed) {
|
|
476
|
-
say('Command failed, but continuing...');
|
|
477
|
-
console.log('Error output:', result.output);
|
|
478
|
-
}
|
|
267
|
+
shell prints output from stdout and stderr. To disable output, [make tasks silent](#silent):
|
|
479
268
|
|
|
480
|
-
|
|
481
|
-
// β Old: Commands throw on failure
|
|
482
|
-
try {
|
|
483
|
-
await someOtherTaskRunner('failing-command');
|
|
484
|
-
} catch (error) {
|
|
485
|
-
// Handle error
|
|
486
|
-
}
|
|
269
|
+
### HTTP Requests
|
|
487
270
|
|
|
488
|
-
|
|
489
|
-
const result = await exec`failing-command`;
|
|
490
|
-
if (result.hasFailed) {
|
|
491
|
-
// Handle failure explicitly
|
|
492
|
-
}
|
|
493
|
-
```
|
|
271
|
+
Built-in fetch with progress indicators:
|
|
494
272
|
|
|
495
|
-
### HTTP Requests (`fetch`)
|
|
496
273
|
```javascript
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Check service health
|
|
276
|
+
*/
|
|
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
|
+
}
|
|
286
|
+
}
|
|
500
287
|
```
|
|
501
288
|
|
|
502
289
|
### File Operations
|
|
290
|
+
|
|
291
|
+
Template-based file writing and copying:
|
|
292
|
+
|
|
503
293
|
```javascript
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
line
|
|
509
|
-
|
|
510
|
-
}
|
|
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
|
+
});
|
|
305
|
+
|
|
306
|
+
say('π Config file created');
|
|
307
|
+
}
|
|
511
308
|
|
|
512
309
|
// Copy files
|
|
513
|
-
copyFile('template.
|
|
310
|
+
copyFile('template.env', '.env');
|
|
514
311
|
```
|
|
515
312
|
|
|
516
|
-
|
|
313
|
+
## Input/Output
|
|
517
314
|
|
|
518
|
-
|
|
315
|
+
### `say` - Normal Output
|
|
519
316
|
|
|
520
|
-
|
|
317
|
+
Standard output with visual indicator:
|
|
521
318
|
|
|
522
319
|
```javascript
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
]);
|
|
320
|
+
say('Building project...');
|
|
321
|
+
say('π¦ Dependencies installed');
|
|
322
|
+
say(`Found ${count} files to process`);
|
|
323
|
+
```
|
|
542
324
|
|
|
543
|
-
|
|
544
|
-
const features = await ask('Select features to include:', [
|
|
545
|
-
'TypeScript', 'ESLint', 'Prettier', 'Tests', 'CI/CD'
|
|
546
|
-
], { multiple: true });
|
|
325
|
+
### `ask` - User Input
|
|
547
326
|
|
|
548
|
-
|
|
327
|
+
Flexible user input with smart parameter detection:
|
|
549
328
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
});
|
|
329
|
+
```javascript
|
|
330
|
+
// Text input with default
|
|
331
|
+
const name = await ask('Project name:', 'my-app');
|
|
554
332
|
|
|
555
|
-
//
|
|
556
|
-
const
|
|
557
|
-
editor: true,
|
|
558
|
-
default: 'Initial content here...'
|
|
559
|
-
});
|
|
333
|
+
// Boolean confirmation (auto-detects)
|
|
334
|
+
const proceed = await ask('Continue?', true);
|
|
560
335
|
|
|
561
|
-
//
|
|
562
|
-
const
|
|
563
|
-
type: 'password'
|
|
564
|
-
});
|
|
336
|
+
// Single selection (auto-detects from array)
|
|
337
|
+
const env = await ask('Select environment:', ['dev', 'staging', 'prod']);
|
|
565
338
|
|
|
566
|
-
//
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
|
|
339
|
+
// Multiple selection
|
|
340
|
+
const features = await ask('Select features:',
|
|
341
|
+
['TypeScript', 'ESLint', 'Tests'],
|
|
342
|
+
{ multiple: true }
|
|
343
|
+
);
|
|
571
344
|
|
|
572
|
-
|
|
345
|
+
// Password input
|
|
346
|
+
const password = await ask('Enter password:', { type: 'password' });
|
|
573
347
|
|
|
574
|
-
|
|
575
|
-
|
|
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
|
|
348
|
+
// Multiline editor input
|
|
349
|
+
const description = await ask('Enter description:', { editor: true });
|
|
586
350
|
```
|
|
587
351
|
|
|
588
|
-
#### Ask Options Reference
|
|
589
|
-
|
|
590
352
|
| Parameter/Option | Type | Description | Example |
|
|
591
353
|
|------------------|------|-------------|---------|
|
|
592
354
|
| **Smart Detection** | | |
|
|
@@ -601,493 +363,508 @@ ask('Colors?', ['red', 'blue'], { multiple: true }) // Array + options
|
|
|
601
363
|
| `type` | String | Input type: `'input'`, `'confirm'`, `'password'`, `'number'` | `'password'` |
|
|
602
364
|
| `validate` | Function | Custom validation function | `(input) => input.length > 0` |
|
|
603
365
|
|
|
604
|
-
|
|
366
|
+
|
|
367
|
+
### `yell`
|
|
368
|
+
|
|
369
|
+
Emphasized Output
|
|
370
|
+
|
|
371
|
+
ASCII art output for important messages:
|
|
605
372
|
|
|
606
373
|
```javascript
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
}
|
|
374
|
+
yell('BUILD COMPLETE!');
|
|
375
|
+
yell('DEPLOYMENT SUCCESSFUL!');
|
|
376
|
+
```
|
|
641
377
|
|
|
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
|
-
}
|
|
378
|
+
### `silent`
|
|
654
379
|
|
|
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
|
-
}
|
|
380
|
+
Stop printing realtime output
|
|
679
381
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
}
|
|
382
|
+
```javascript
|
|
383
|
+
// Silence all task output
|
|
384
|
+
task.silence();
|
|
385
|
+
await shell`npm build`;
|
|
386
|
+
|
|
387
|
+
// restore printing output
|
|
388
|
+
task.prints();
|
|
389
|
+
|
|
390
|
+
// Silent specific task
|
|
391
|
+
const labels = await task.silent(() => shell(`gh api repos/:org/:repo/labels`));
|
|
704
392
|
```
|
|
705
393
|
|
|
706
|
-
|
|
394
|
+
## Task Control
|
|
395
|
+
|
|
396
|
+
### Parallel Executions
|
|
397
|
+
|
|
398
|
+
No magic here. Use `Promise.all()` to run tasks in parallel:
|
|
707
399
|
|
|
708
400
|
```javascript
|
|
709
|
-
//
|
|
710
|
-
|
|
711
|
-
|
|
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
|
+
]);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Custom Tasks
|
|
410
|
+
|
|
411
|
+
Name and group your tasks operations:
|
|
712
412
|
|
|
713
|
-
|
|
714
|
-
await task('
|
|
715
|
-
await exec`npm
|
|
413
|
+
```js
|
|
414
|
+
await task('Build', () => {
|
|
415
|
+
await exec`npm run build:frontend`);
|
|
416
|
+
await exec`npm run build:docs`);
|
|
716
417
|
});
|
|
717
|
-
|
|
418
|
+
````
|
|
718
419
|
|
|
719
|
-
|
|
420
|
+
### Stop on Failure
|
|
720
421
|
|
|
721
|
-
|
|
422
|
+
By default bunosh executes all tasks event if they fail. To stop execution immediately on failure, use the `task.stopOnFailures()` method.
|
|
722
423
|
|
|
723
|
-
### Quick Setup
|
|
724
424
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
|
|
729
431
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
+
}
|
|
735
437
|
|
|
736
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Cleanup - continue despite failures
|
|
440
|
+
*/
|
|
441
|
+
export async function cleanup() {
|
|
442
|
+
task.ignoreFailures(); // Continue even if tasks fail
|
|
737
443
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
+
```
|
|
741
450
|
|
|
742
|
-
###
|
|
451
|
+
### Try Operations
|
|
743
452
|
|
|
744
|
-
|
|
453
|
+
Gracefully handle operations that might fail:
|
|
745
454
|
|
|
746
455
|
```javascript
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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();
|
|
756
467
|
}
|
|
757
|
-
});
|
|
758
468
|
|
|
759
|
-
|
|
469
|
+
const apiHealthy = await task.try(() => fetch('http://localhost:3000/health');
|
|
760
470
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
ai.configure({
|
|
764
|
-
registerProvider: {
|
|
765
|
-
envVar: 'XAI_API_KEY',
|
|
766
|
-
provider: {
|
|
767
|
-
createInstance: (modelName) => xai(modelName)
|
|
768
|
-
}
|
|
471
|
+
if (!apiHealthy) {
|
|
472
|
+
yell('API IS DOWN!');
|
|
769
473
|
}
|
|
770
|
-
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
771
476
|
|
|
772
|
-
|
|
773
|
-
import { openrouter } from '@openrouter/ai-sdk-provider';
|
|
774
|
-
ai.configure({
|
|
775
|
-
registerProvider: {
|
|
776
|
-
envVar: 'OPENROUTER_API_KEY',
|
|
777
|
-
provider: {
|
|
778
|
-
createInstance: (modelName) => openrouter(modelName)
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
});
|
|
477
|
+
## π« AI Integration
|
|
782
478
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
// Your custom provider logic
|
|
790
|
-
return customAIProvider(modelName, {
|
|
791
|
-
apiKey: process.env.CUSTOM_AI_API_KEY,
|
|
792
|
-
endpoint: 'https://custom-ai.company.com/v1'
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
});
|
|
479
|
+
Built-in AI support for code generation, documentation, and automation.
|
|
480
|
+
Automatically responds to structured JSON output.
|
|
481
|
+
|
|
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
|
|
798
485
|
|
|
799
|
-
|
|
800
|
-
|
|
486
|
+
```bash
|
|
487
|
+
# Choose your AI model
|
|
488
|
+
export AI_MODEL=gpt-5 # or claude-4-sonnet, llama-3.3-70b, etc.
|
|
801
489
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
|
805
494
|
```
|
|
806
495
|
|
|
807
|
-
|
|
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
|
|
808
504
|
|
|
809
505
|
```javascript
|
|
810
|
-
const { ai, writeToFile
|
|
506
|
+
const { ai, writeToFile } = global.bunosh;
|
|
811
507
|
|
|
812
508
|
/**
|
|
813
|
-
* Generate
|
|
509
|
+
* Generate commit message from staged changes
|
|
814
510
|
*/
|
|
815
|
-
export async function
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
const result = await ai(
|
|
819
|
-
`Generate documentation for this code: ${codebase}`,
|
|
820
|
-
{
|
|
821
|
-
overview: 'Brief project overview',
|
|
822
|
-
apiReference: 'API documentation',
|
|
823
|
-
examples: 'Usage examples',
|
|
824
|
-
installation: 'Installation instructions'
|
|
825
|
-
}
|
|
826
|
-
);
|
|
827
|
-
|
|
828
|
-
writeToFile('README.md', (line) => {
|
|
829
|
-
line`# ${result.overview}`;
|
|
830
|
-
line``;
|
|
831
|
-
line`## Installation`;
|
|
832
|
-
line`${result.installation}`;
|
|
833
|
-
line``;
|
|
834
|
-
line`## API Reference`;
|
|
835
|
-
line`${result.apiReference}`;
|
|
836
|
-
line``;
|
|
837
|
-
line`## Examples`;
|
|
838
|
-
line`${result.examples}`;
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
say('π Documentation generated!');
|
|
842
|
-
}
|
|
511
|
+
export async function commit() {
|
|
512
|
+
const diff = await exec`git diff --staged`;
|
|
843
513
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
`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}`,
|
|
852
521
|
{
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
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'
|
|
858
526
|
}
|
|
859
527
|
);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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');
|
|
867
535
|
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
See more ai usage examples below:
|
|
868
539
|
|
|
540
|
+
## Examples
|
|
541
|
+
|
|
542
|
+
### Development Examples
|
|
543
|
+
|
|
544
|
+
#### Feature Branch Workflow
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
bunosh worktree:create
|
|
548
|
+
bunosh worktree:delete
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
```javascript
|
|
869
552
|
/**
|
|
870
|
-
*
|
|
553
|
+
* Create worktree for feature development
|
|
871
554
|
*/
|
|
872
|
-
export async function
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
testSuite: 'Complete test suite code',
|
|
879
|
-
edgeCases: 'List of edge cases covered',
|
|
880
|
-
mockSetup: 'Required mocks and setup code'
|
|
881
|
-
}
|
|
882
|
-
);
|
|
883
|
-
|
|
884
|
-
const testFile = sourceFile.replace('.js', '.test.js');
|
|
885
|
-
writeToFile(testFile, (line) => {
|
|
886
|
-
line`${tests.mockSetup}`;
|
|
887
|
-
line``;
|
|
888
|
-
line`${tests.testSuite}`;
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
say(`π§ͺ Tests generated: ${testFile}`);
|
|
892
|
-
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}`);
|
|
893
561
|
}
|
|
894
562
|
|
|
895
563
|
/**
|
|
896
|
-
*
|
|
564
|
+
* Remove worktree when feature is merged
|
|
897
565
|
*/
|
|
898
|
-
export async function
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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');
|
|
903
575
|
return;
|
|
904
576
|
}
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
await exec`git commit -m "${message}"`;
|
|
917
|
-
|
|
918
|
-
say(`β
Committed with AI-generated message:`);
|
|
919
|
-
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}`);
|
|
920
588
|
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
#### Generate Release Notes with AI
|
|
921
592
|
|
|
593
|
+
```javascript
|
|
922
594
|
/**
|
|
923
|
-
*
|
|
595
|
+
* Generate comprehensive release notes using AI
|
|
924
596
|
*/
|
|
925
|
-
export async function
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const
|
|
938
|
-
|
|
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.`,
|
|
939
624
|
{
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
recommendations: 'Strategic recommendations'
|
|
625
|
+
features: 'New features (bullet points with emoji)',
|
|
626
|
+
fixes: 'Bug fixes',
|
|
627
|
+
acknowledgments: 'Contributors and acknowledgments'
|
|
944
628
|
}
|
|
945
629
|
);
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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}`);
|
|
949
650
|
}
|
|
950
651
|
```
|
|
951
652
|
|
|
952
|
-
###
|
|
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
|
+
```
|
|
953
664
|
|
|
954
|
-
|
|
665
|
+
#### Build and Publish Containers in Parallel
|
|
955
666
|
|
|
956
667
|
```javascript
|
|
957
668
|
/**
|
|
958
|
-
*
|
|
669
|
+
* Build and publish multiple services in parallel
|
|
959
670
|
*/
|
|
960
|
-
export async function
|
|
961
|
-
const
|
|
962
|
-
say('π Generated copy:');
|
|
963
|
-
console.log(copy);
|
|
964
|
-
}
|
|
671
|
+
export async function publishContainers(registry = 'docker.io/myorg') {
|
|
672
|
+
const { exec, task, say, yell } = global.bunosh;
|
|
965
673
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
*/
|
|
969
|
-
export async function translate(text, language = 'Spanish') {
|
|
970
|
-
const translation = await ai(`Translate to ${language}: ${text}`);
|
|
971
|
-
say(`π Translation to ${language}:`);
|
|
972
|
-
console.log(translation);
|
|
973
|
-
}
|
|
974
|
-
```
|
|
674
|
+
const services = ['api', 'web', 'worker', 'admin'];
|
|
675
|
+
const version = process.env.VERSION || 'latest';
|
|
975
676
|
|
|
976
|
-
|
|
677
|
+
say(`π³ Building ${services.length} containers...`);
|
|
977
678
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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
|
+
);
|
|
985
686
|
|
|
986
|
-
|
|
687
|
+
say('β
All containers built successfully');
|
|
987
688
|
|
|
988
|
-
|
|
689
|
+
// Push all containers in parallel
|
|
690
|
+
say('π€ Publishing to registry...');
|
|
989
691
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
692
|
+
const pushResults = await Promise.all(
|
|
693
|
+
services.map(service =>
|
|
694
|
+
exec`docker push ${registry}/${service}:${version}`
|
|
695
|
+
)
|
|
696
|
+
);
|
|
995
697
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
// ...
|
|
698
|
+
yell('CONTAINERS PUBLISHED!');
|
|
699
|
+
say(`Published: ${pushResults.join(', ')}`);
|
|
700
|
+
say(`Registry: ${registry}`);
|
|
701
|
+
say(`Version: ${version}`);
|
|
1001
702
|
}
|
|
1002
|
-
|
|
1003
|
-
// CLI usage
|
|
1004
|
-
bunosh deploy production --force --verbose
|
|
1005
703
|
```
|
|
1006
704
|
|
|
1007
|
-
|
|
1008
|
-
```bash
|
|
1009
|
-
# List all commands
|
|
1010
|
-
bunosh
|
|
705
|
+
#### Kubernetes Deployment Control
|
|
1011
706
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
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
|
+
}
|
|
1015
728
|
|
|
1016
|
-
|
|
1017
|
-
|
|
729
|
+
// Set kubectl context
|
|
730
|
+
await task('Setting context', () =>
|
|
731
|
+
exec`kubectl config use-context ${environment}`
|
|
732
|
+
);
|
|
1018
733
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
734
|
+
// Apply configurations
|
|
735
|
+
await task('Applying configurations', () =>
|
|
736
|
+
exec`kubectl apply -f k8s/${environment}/`
|
|
737
|
+
);
|
|
1022
738
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
+
}
|
|
1027
745
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
+
}
|
|
1032
752
|
|
|
1033
|
-
|
|
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
|
+
}
|
|
1034
768
|
|
|
1035
|
-
|
|
769
|
+
/**
|
|
770
|
+
* Rollback Kubernetes deployment
|
|
771
|
+
*/
|
|
772
|
+
export async function kubeRollback(environment = 'staging') {
|
|
773
|
+
const { exec, say, ask } = global.bunosh;
|
|
1036
774
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
775
|
+
const confirmed = await ask(
|
|
776
|
+
`Rollback ${environment} deployment?`,
|
|
777
|
+
false
|
|
778
|
+
);
|
|
1041
779
|
|
|
1042
|
-
|
|
1043
|
-
|
|
780
|
+
if (!confirmed) {
|
|
781
|
+
say('Rollback cancelled');
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
1044
784
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
785
|
+
await exec`kubectl config use-context ${environment}`;
|
|
786
|
+
await exec`kubectl rollout undo deployment/app`;
|
|
787
|
+
await exec`kubectl rollout status deployment/app`;
|
|
1048
788
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
npm update -g bunosh
|
|
789
|
+
say(`β
Rolled back ${environment} deployment`);
|
|
790
|
+
}
|
|
1052
791
|
```
|
|
1053
792
|
|
|
1054
|
-
|
|
793
|
+
#### AWS Infrastructure Management
|
|
1055
794
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const results = await Promise.all([
|
|
1059
|
-
task('Task 1', () => exec`sleep 2 && echo "Done 1"`),
|
|
1060
|
-
task('Task 2', () => exec`sleep 2 && echo "Done 2"`),
|
|
1061
|
-
task('Task 3', () => exec`sleep 2 && echo "Done 3"`)
|
|
1062
|
-
]);
|
|
795
|
+
```
|
|
796
|
+
bunosh aws:spawn-server --count 3
|
|
1063
797
|
```
|
|
1064
798
|
|
|
1065
|
-
### Error Handling
|
|
1066
799
|
```javascript
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
}
|
|
1076
|
-
|
|
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
|
+
});
|
|
1077
830
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
+
});
|
|
1084
837
|
|
|
1085
|
-
|
|
838
|
+
return instances;
|
|
839
|
+
}
|
|
1086
840
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
+
```
|
|
1091
868
|
|
|
1092
869
|
## License
|
|
1093
870
|
|
|
@@ -1095,4 +872,4 @@ MIT License - see LICENSE file for details.
|
|
|
1095
872
|
|
|
1096
873
|
---
|
|
1097
874
|
|
|
1098
|
-
|
|
875
|
+
Cooked with β€οΈ from Ukraine πΊπ¦
|