An open API service indexing awesome lists of open source software.

https://github.com/mrmaxie/bunbun

Node.JS based simple, lightweight task bunner... runner*
https://github.com/mrmaxie/bunbun

automation bunbun flow runner task-runner

Last synced: 30 days ago
JSON representation

Node.JS based simple, lightweight task bunner... runner*

Awesome Lists containing this project

README

        

# bunbun

Node.JS based simple, lightweight task bunner... I mean runner



## Why?

⏱ **Lightweight and very fast** - very simple in design, without any library compilers that you will never use, just plain task runner

📐 **Ready to use** - just add to your project, create file and prepare your first task

🔌 **Extendable** - "extends-by-code" philosophy - you are owner of your build flow, without any magic and invisible parts/configurations etc., neither no compilers included

✨ **Universal** - this library contains **only** language/tool/framework agnostic tools, all compilations/copying things are in your hands

🎈 **Without magic** - all of your tasks are just JS code, without any configs, 3rd-party plugins etc. just plain source code, therefore the learning curve is very low

🐞 **Debuggable** - debugging is very simple because none of all tasks are done invisibly underhood, it's easy to find erroring part if you see all steps of build process, also you can enable debugging mode in Bunbun to log all things

🧰 **Safe versioning** - version of Bunbun will never break any of building processes because Bunbun don't conains any API for them, updating Bunbun will need from you only update your building script - it's all, you will never have to wait for a some plugin to be updated

## Requirements

- `Node.JS >= 7.6v`
- ...because of default support of async/await syntax
- or `Node.JS >= 10v`
- ...for execution and handling processes
- `NPM`

## How to install?

Simplest way is just using npm:

```bash
npm install --save-exact --save-dev bunbun
```

## Real example

📚 Click to expand the sample code

Example code:

```js
const { build } = require('esbuild');
const nodeSass = require('node-sass');
const Bunbun = require('./../dist/main.js');

const sass = args => new Promise((res, rej) => {
nodeSass.render(args, (err, data) => {
err ? rej(err) : res(data);
});
});

const hash = async file => {
const res = await $.fs.hash(file, 'md5', 'base64');
return res.replace(/[^a-z0-9]/gi, '');
};

const SOURCE_EXTS = '(scss|ts|tsx|js|jsx|html)';

const $ = new Bunbun;

$.task('build:typescript', async () => {
await $.until('hash');
await build({
sourcemap: true,
format: 'iife',
minify: true,
bundle: true,
outfile: './build/index.js',
entryPoints: ['./src/index.tsx'],
platform: 'browser',
tsconfig: './tsconfig.json',
define: {
'process.env.NODE_ENV': '"develop"',
},
});
$.run('hash');
});

$.task('build:scss', async () => {
await $.until('hash');
const result = await sass({
file: './src/index.scss',
sourceMap: './index.css.map',
outFile: './index.css',
outputStyle: 'compressed',
sourceMapContents: true,
});
await $.fs.write('./build/index.css', result.css || '');
await $.fs.write('./build/index.css.map', result.map || '');
$.run('hash');
});

$.task('hash', async () => {
await $.fs.edit('./src/index.html', async html => {
const jsHash = await hash('./build/index.js');
const cssHash = await hash('./build/index.css');
return html
.replace('__JS_HASH__', jsHash)
.replace('__CSS_HASH__', cssHash);
});
});

$.alias('build', ['build:typescript', 'build:scss']);

$.task('assets', async () => {
let list = await $.fs.list(['./src/**/*.*', `!**/*.${SOURCE_EXTS}`]);
for (const x of list) {
await $.fs.copy(x, x.replace(/^\.\/src/, './build'));
}
});

$.task('watch', async () => {
const server = $.serve('./build', 8080);

$.fs.watch(`./src/**/*.${SOURCE_EXTS}`, async () => {
await $.run('build');
server.reload();
});

$.fs.watch(`./src/**/*.!${SOURCE_EXTS}`, async () => {
await $.run('assets');
server.reload();
});
});

$.start('build');
```

## Table of Contents

- [`class Bunbun`](#class-bunbun)
- [`fs`](#class-bunbun--fs) - filesystem API
- [`logger`](#class-bunbun--logger) - logger API
- [`alias()`](#class-bunbun--alias) - regiser alias of multiple tasks
- [`debounce()`](#class-bunbun--debounce) - allows to debounce by promise and time
- [`exec()`](#class-bunbun--exec) - execute terminal command
- [`hash()`](#class-bunbun--hash) - hash given file
- [`rescue()`](#class-bunbun--rescue) - catch exception making result optional
- [`run()`](#class-bunbun--run) - run registered task
- [`start()`](#class-bunbun--start) - run tasks by `process.argv`
- [`serve()`](#class-bunbun--serve) - create new http server
- [`task()`](#class-bunbun--task) - register new task
- [`until()`](#class-bunbun--until) - waits until task has been done
- [`wait()`](#class-bunbun--wait) - `setTimeout` but in `await`/`async` convention
- [`class Logger`](#class-logger)
- [`debugging`](#class-logger--debugging) - determine if logger have to be louder
- [`silent`](#class-logger--silent) - determine if logger have to log nothing
- [`debug()`](#class-logger--debug) - debug log
- [`error()`](#class-logger--error) - error log
- [`format()`](#class-logger--format) - prepare message before log
- [`log()`](#class-logger--log) - basic log
- [`success()`](#class-logger--success) - success log
- [`class Fs`](#class-fs)
- [`cwd`](#class-fs--cwd) - current working directory
- [`copy()`](#class-fs--copy) - copy (also recursive and glob option) files/dirs
- [`createDir()`](#class-fs--createdir) - create dir
- [`createTempDir()`](#class-fs--createtempdir) - create unique temporary dir
- [`edit()`](#class-fs--edit) - reads and writes back file
- [`exists()`](#class-fs--exists) - check whenever file/dir exists and returns type
- [`hash()`](#class-fs--hash) - hash given file
- [`list()`](#class-fs--list) - list files/dirs
- [`read()`](#class-fs--read) - reads data from file
- [`remove()`](#class-fs--remove) - removes file/dir
- [`rename()`](#class-fs--rename) - renames (also moves) file/dir
- [`watch()`](#class-fs--watch) - watches files/dirs for changes
- [`write()`](#class-fs--write) - writes data into file
- [`class HttpServer`](#class-httpserver)
- [`reload()`](#class-httpserver--reload) - reloads all html pages

class Bunbun

Main class of whole script, it's also default exported thing from module

```typescript
type Options = {
// Determines if logger should be louder by default
debugging: boolean; // default: false

// Determines if logger should be silent by default
// this option has higher priority than `debugging`
silent: boolean; // default: false

// Determines if tasks should wait for previous promise by default
debouncePromise: boolean; // default: true

// Determines if tasks should debounce and with what time by default
// 0 means turn off
debounceTime: number; // default: 200

// Current working directory for fs/exec etc.
cwd: string; // default: process.cwd()
};

type Bunbun = {
new(options?: Partial);
// ...
};
```

📚 Click to expand the sample code

```typescript
const Bunbun = require('bunbun');

// Debugging instance
const $1 = new Bunbun({
debugging: true,
});

// Default instance
const $2 = new Bunbun();
```

bunbun.fs

[`Fs`](#class-fs) (filesystem) instance available for external usage

```typescript
fs: Fs;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.fs.read('./file.txt').then(data => {
// ...
});
```

bunbun.logger

[`Logger`](#class-logger) instance used by Bunbun and available for external usage as well

```typescript
logger: Logger;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

const myVar = { test: 1 };

$.logger.log('my var = $$', myVar);
```

bunbun.alias()

Registers alias of tasks, those tasks will be executed asynchronously. Name of such alias will be in same pool where tasks are so you can't register task of same name which already exists

```typescript
alias(
name: string,
tasks: string[],
): void;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.task('foo', () => {
console.log('foo');
});
$.task('bar', async () => {
await someLongAction();
console.log('bar');
});

$.alias('baz', ['bar', 'foo']);
// is equal to
$.task('qux', async () => {
return Promise.all([
$.run('bar'),
$.run('foo'),
]);
});

$.run('baz'); // or $.run('qux');
// output:
// >> foo
// >> bar (because it's waited for long running function)
```

bunbun.debounce()

Creates debounce function of given function, this kind of function allows you avoid multiple calls of time-critical functions, e.g:
- To avoid overloop building process for same code
- To avoid overwriting things at overwriting they in previous call

> 💡 **Tip** - time debouncing don't call function until given timeout, promise debouncing call function and wait for fulfilling last promise

```typescript
debounce(
fn: () => Promise | T,
time: number = 0,
): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

// Not working debounce (without promise nor time)
let i1 = 0;
const fnNothing = $.debounce(() => {
console.log('1', i1 += 1);
});
// Promise based debounce
let i2 = 0;
const fnPromise = $.debounce(() => {
new Promise(res => setTimeout(() => {
console.log('2', i2 += 1);
res();
}, 10 * 1000))
});
// Time based debounce
let i3 = 0;
const fnTime = $.debounce(() => {
console.log('3', i3 += 1);
}, 10 * 1000);
// Promise and time based debounce
let i4 = 0;
const fnPromiseAndTime = $.debounce(() =>
new Promise(res => setTimeout(() => {
console.log('4', i4 += 1);
res();
}, 9 * 1000))
, 9 * 1000);

// --- results ---

fnNothing(); // > 1
fnNothing(); // > 2
fnNothing(); // > 3
fnNothing(); // > 4

fnPromise(); // > wait for promise > 1
// after 5s
fnPromise(); // > wait for previous result > 1
// after 5s
fnPromise(); // > wait for new promise > 2
// after 10s
fnPromise(); // > wait for new promise > 3

fnTime(); // > wait for timeout > 1
// after 5s
fnTime(); // > wait for previous timeout > 1
// after 5s
fnTime(); // > wait for new timeout > 2
// after 10s
fnTime(); // > wait for new timeout > 3

fnPromiseAndTime(); // > wait for timeout > wait for promise > 1
// after 5s
fnPromiseAndTime(); // > wait for previous timeout > wait for previous promise > 1
// after 5s
fnPromiseAndTime(); // > wait for previous promise > 1
// after 10s
fnPromiseAndTime(); // > wait for new timeout > wait for new promise > 2
```

bunbun.exec()

Allows you to execute command in shell and wait for result

> 💡 **Tip** - if timeout is equal or less than 0 then timeout will don't be turned on

```typescript
exec(
command: string,
timeout: number = 0,
): Promise<{
stdout: string;
stderr: string;
}>;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.exec('node -v').then(({ stdout }) => {
console.log(stdout);
});

// or

$.task('node-v', async () => {
const { stdout } = await $.exec('node -v');
console.log(stdout);
});
```

bunbun.hash()

Hash given string

```typescript
hash(
text: string,
algorithm: 'md5' | 'sha1' | 'sha256' | 'sha512' = 'md5',
encoding: 'hex' | 'base64' | 'buffer' | 'latin1' = 'base64'
): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.hash('test').then(hash => console.log(hash));
```

bunbun.rescue()

Allows catch promise and keep `async`/`await` syntax

> 💡 **Tip** - be careful and never call (`await `)`rescue(await someFunc())` because this `someFunc()` will throw in higher, current context instead of `rescue`'s context, instead of that call `await rescue(someFunc())`

> 🎈 **Fun fact** - in Ruby language `rescue` keyword is used instead of typical `catch` keyword - that's why Bunbun use this name in this context

```typescript
rescue(
promise: Promise | T1,
alter?: T2,
): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

const justReject = async () => {
throw 'wowie, such error';
};

const dontReject = async () => 'ok!';

$.task('test-1', async () => {
const error = await $.rescue(justReject());
console.log(error); // > 'wowie, such error'
});

$.task('test-2', async () => {
const error = await $.rescue(await justReject());
// unreachable code because `justReject` is executed
// in current context!
});

$.task('test-3', async () => {
const ok = await $.rescue(dontReject());
console.log(ok); // > 'ok!'
});

$.task('test-4', async () => {
const ok = await $.rescue('ok!');
console.log(ok); // > 'ok!'
});

$.task('test-5', async () => {
const alternativeValue = await $.rescue(justReject(), 'ok!');
console.log(alternativeValue); // > 'ok!'
});
```

bunbun.run()

Run single task by name and returns promise of call of this task

> 💡 **Tip** - if task with such name will be unable to found then this function will throw `false`

```typescript
run(name: string): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.task('reject', async () => {
throw 'oh no!';
});

$.task('resolve', async () => {
return 'ok!';
});

$.task('test', async () => {
// try run task
await $.rescue($.run('reject'));
console.log(
await $.rescue($.run('reject')
); // > 'oh no!'
console.log(
await $.rescue($.run('?')
); // > false
console.log(
await $.rescue($.run('resolve')
); // > true
});
```

bunbun.start()

Similar to [`bunbun.run()`](#class-bunbun--run) but instead of running single task allows run multiple tasks, with single fallback task. This function will don't throw at error (only log it)

> 💡 **Tip** - this function is created mainly for using as main method

```typescript
start(
defaultTask: string = 'default',
tasks: string[] = process.argv.slice(2),
): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.task('reject', async () => {
throw 'oh no!';
});

$.task('resolve', async () => {
return 'ok!';
});

$.start(); // run 'default' task or given names from terminal
// or
$.start('resolve'); // run 'resolve' task or given names from terminal
// or
$.start(
'resolve',
['resolve', 'reject'],
); // run always 'resolve' and 'reject'
```

bunbun.serve()

Creates new instance of [`class HttpServer`](#class-httpserver)

```typescript
type Options = {
fallback: string = './index.html',,
reload: boolean = true,
reloadPort: number = 8181,
};

serve(
directory: string,
port: number,
options?: Partial,
): HttpServer;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

const server = $.serve('build', 8080);

// after some action you can force reload of pages
server.reload();
```

bunbun.task()

Register new task under given name. Task can be simple function or async function if needed

> 💡 **Tip** - if you cannot use `async`/`await` syntax then returning `Promise` instance will give same effect

```typescript
task(
name: string,
fn: () => unknown,
): void;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.task('foo', () => {
console.log('foo');
});
$.task('bar', async () => {
await someLongAction();
console.log('bar');
});
$.task('baz', async () => {
await $.run('foo');
await $.run('bar');
});
$.task('qux', async () => {
await $.run('bar');
await $.run('foo');,
});

$.run('baz');
// output:
// >> foo
// >> bar (because it's waited for long running function)

$.run('qux');
// output:
// >> bar
// >> foo (because it's waited until previous task ended)
```

bunbun.until()

Waits until given task has been completed, it doesn't matter if it ended successfully, throws if task has been not found

```typescript
until(name: string): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.task('long', async () => {
await $.wait(1500);
});
$.task('run-and-wait', async () => {
$.run('long');
await $.until('long');
// waits until 'long' ended
await $.until('long');
// pass immediately because 'long' is not running
});
```

bunbun.wait()

`setTimeout()` alternative for `async`/`await` syntax

```typescript
wait(time: number): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.task('long', async () => {
console.log('wait moment');
await $.wait(1500);
console.log('hello again');
});
```

class Logger

Class prepared for logging purposes. Logger is already constructed with [`class Bunbun`](#class-bunbun) with default settings

```typescript
type Logger = {
new();
// ...
};
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();
$.logger; // here you are
```

logger.debugging

Determines if logger should log more useful data for debugging purposes

```typescript
debugging: boolean = false;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();
$.logger.debugging = true;
// Now bunbun will throw more logs
$.logger.debugging = false;
// Now bunbun will don't throw so much data
```

logger.silent

Determines if logger should be silent

```typescript
silent: boolean = false;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();
$.logger.silent = true;
// Now bunbun will be silent
$.logger.silent = false;
// Now bunbun will be loud
```

logger.debug()


logger.error()


logger.log()


logger.success()

Using [`logger.format()`](#class-logger--format) function to colorize data, adds prefix and log given thing. Nothing will be logged if [`logger.silent`](#class-logger--silent) is true. `logger.debug()` will log things only if [`logger.debugging`](#class-logger--debugging) is true

```typescript
debug(template: string, ...args: any[]): void;
// or
error(template: string, ...args: any[]): void;
// or
log(template: string, ...args: any[]): void;
// or
success(template: string, ...args: any[]): void;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.logger.debug('my var = $$', 10);
// logs only if 'logger.debugging' is equal to 'true'
// > '? ~ my var = 10'

$.logger.error('my var = $$', 10);
// > '✗ ~ my var = 10'

$.logger.log('my var = $$', 10);
// > ' ~ my var = 10'

$.logger.success('my var = $$', 10);
// > '✔ ~ my var = 10'
```

logger.format()

Format given template using built-in Node's functions, but uses `$$` as placeholder for any type of variable

```typescript
format(template: string, ...args: any[]): string;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

console.log(
$.logger.format(
'1.$$\n2.$$\n3.$$',
10,
'test',
{ test: 10 },
)
);
// > output:
// 1.10
// 2.'test'
// 3.{ test: 10 }
```

class Fs

Class prepared for filesystem manipulations or fetching data purposes. Fs is already constructed with [`class Bunbun`](#class-bunbun) with default settings

```typescript
type Fs = {
new();
// ...
};
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();
$.fs; // here you are
```

fs.cwd

Current working directory for all methods of [`class Fs`](#class-fs)

```typescript
cwd: string = process.cwd();
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();
$.fs.cwd === process.cwd(); // > true
$.fs.cwd = 'C:/src'; // now all methods starts from "C:/src"
```

fs.copy()

Copy single file or whole dir (recursively), returns `true` if done without any errors, errors will be throwed as exception

```typescript
copy(
source: string,
target: string
): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('maybe-copy-file', async () => {
const res = await $.rescue(
$.fs.copy('file.txt', 'second-file.txt'),
false
);

if (res) {
// file has been copied
} else {
// file has NOT been copied
}
});

$.task('swallow-copy-file', async () => {
await $.rescue($.fs.copy('file.txt', 'second-file.txt'));
// nobody cares if file has been copied
});

$.task('force-copy-file', async () => {
await $.fs.copy('file.txt', 'second-file.txt');
// unreachable without successful copying file
});
```

fs.createDir()

If any directory in tree does not exists then they will be created - otherwise nothing will be created

```typescript
createDir(
path: string,
mode?: number // e.g 0o776
): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('maybe-create-dir', async () => {
const res = await $.rescue(
$.fs.createDir('test/test/test'),
false
);

if (res) {
// dir is prepared
} else {
// dir isn't prepared for some reason
}
});

$.task('swallow-create-dir', async () => {
await $.rescue($.fs.createDir('test/test/test'));
// nobody cares if dir exists
});

$.task('force-create-dir', async () => {
await $.fs.createDir('file.txt', 'second-file.txt');
// unreachable without successful dir creating or check if exists
});
```

fs.createTempDir()

Creates dir in temporary directory of your operating system, those directories are often cleared

```typescript
createTempDir(): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('temp-dir', async () => {
const dir = await $.fs.createTempDir();
// "dir" variable contains temporary directory path
});
```

fs.edit()

Allows seamlessly edit content of file

```typescript
edit(
path: string,
fn: (data: string) => (Promise | string)
): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('edit-file', async () => {
await $.fs.edit(
'test.txt',
data => data.replace('$$', 'USD')
);
// file has been edited
});
```

fs.exists()

Check if given path exists and if so, returns type of given path

```typescript
exists(path: string): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('check-path', async () => {
const type = await $.fs.exists('test');

switch (type) {
case false:
// does not exists
break;

case 'file':
// path is file
break;

case 'dir':
// path is directory
break;
}
});
```

fs.hash()

Hash given file

```typescript
hash(
file: string,
algorithm: 'md5' | 'sha1' | 'sha256' | 'sha512' = 'md5',
encoding: 'hex' | 'base64' | 'buffer' | 'latin1' = 'base64'
): Promise;
```

📚 Click to expand the sample code

```javascript
const $ = new Bunbun;

$.fs.hash('test.txt').then(hash => console.log(hash));
```

fs.list()

Returns list of files matching given pattern

```typescript
type ListOptions = {
absolute: boolean = false,
cwd: string = fs.cwd,
onlyDirectories: boolean = false,
onlyFiles: boolean = false,
};

exists(
pattern: string | string[],
options: Partial = {}
): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('assets-files', async () => {
const images = await $.fs.list([
'./src/**/*.(png|jpg)',
'!./src/**/*.raw.png'
]);

// images is array of matching paths
});
```

fs.read()

Reads content of file as string

```typescript
read(path: string): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('read-files', async () => {
const text = await $.fs.read('package.json');
// text is package.json content
});
```

fs.remove()

Removes given file or dir

```typescript
remove(path: string): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('remove-file', async () => {
const text = await $.fs.remove('package.json');
// package.json is removed here
});
```

fs.rename()

Renames given file or dir

```typescript
rename(source: string, target: string): Promise;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.task('rename-file', async () => {
await $.fs.rename('package.json', 'package.json.bak');
// package.json is renamed here
});
```

fs.watch()

Watches given files which matches given paths and returns disposer, every time any matching file has been changed, removed or added then given function will be executed, also after initial scanning function is executed once

```typescript
watch(
pattern: string | string[],
fn: () => any
): () => void;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.fs.watch('./src/**/*.js', () => {
console.log('some file has been changed!');
});
```

fs.write()

Writes given content into file

```typescript
watch(
path: string,
data: string | Buffer
): void;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun();

$.fs.write('test.txt').then(() => {
console.log('DONE!');
});
```

class HttpServer

Can be created via [`bunbun.serve()`](#class-bunbun--serve)

```typescript
type HttpServer = {
new();
// ...
};
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun;

const server = $.serve('build', 8080); // here you are
```

httpserver.reload()

Reloads all pages with injected reloading script if turned on at creating

```typescript
reload(): void;
```

📚 Click to expand the sample code

```typescript
const $ = new Bunbun;

const server = $.serve('build', 8080);

// after some change you can ask for reload pages:
server.reload();
```