# 🌅 runok

🥋 No BASH-fu is required anymore!

## 💡 Write your shell scripts in JavaScript!

* * *

<p align="center">
<img
  src="https://user-images.githubusercontent.com/220264/95273180-0a1a5780-084b-11eb-95ae-73e5f4af3ebb.png"
  align="center"
/>
</p>

* * *

Everyone ❤️ npm scripts. However, 
when you need to use conditionals, loops, regexp and parsers in yout scripts, you end up writing JavaScript.

**runok gives you a tool to transform your JavaScript scripts into CLI commands**. 

Each exported function will be a command which can be executed from CLI.
You can use full power of JavaScript & bundled tasks like:

-   exec
-   git
-   npmRun
-   npx
-   ...and others

## 🎁 Showcase

Write a CommonJS module that exports commands per function. 

-   Each argument of function will be an argument of a command
-   If you provide `options` argument, with default value of an object, this will declate options.

```js
#!/usr/bin/env node
const {
  tasks: { git, exec, npx },
  runok
} = require('runok');

module.exports = {

  async deploy(env = 'prod') {
    await this.test();
    await git(cmd => {
      cmd.add('-A');
      cmd.commit('releasing');
      cmd.pull();
      cmd.push();      
    });
    await exec(`ansible-playbook deploy.yml -i hosts ${env}`);
  }

  async test() {
    await npx('mocha');
  },
}

if (require.main === module) runok(module.exports);
```

-   Run `./runok` to list all available commands
-   Run `./runok deploy` to run a deploy script
-   Run `./runok deploy staging` to run a deploy script to staging

## 🚀 Installation

    npm i runok --save

Create a new `runok` scripts file:

    npx runok init

Each exported function of this file will be command.

## ⌨ Usage

When file is created execute runok script to see all available commands: 

    ./runok.js

Edit `runok.js` file.

1.  Add async function to `module.exports`
2.  Name a function, function name will be transformed for CLI format:


    myFunctionName => my:function-name

3.  Define function parameters:

-   function arguments will be used as command arguments
-   the last argument with object type will is set of options:

```js
// function definition:
async runMe(arg1, arg1, { print: false, retries: 3 })
```

👇

```bash
./runok.js run:me value1 value2 --print --retries 1
```

4.  Add a description of a command as the first comment in a function

```js
async runMe() {
  // executes very important command
}
```

👇

```bash
➜ ./runok.js     
Usage:  <command> [options]

Options:
  -V, --version                    output the version number
  -h, --help                       output usage information

Commands:
  run:me                            executes very important command
```

5.  Import required [tasks](#tasks) from `runok` package. 
6.  Implement your script!

## Export to NPM Scripts

Export your runok scripts into your current package.json:

    ./runok export:npm

## 🧰 Tasks

Runok has a set of built-in tasks.

They can be imported in runio script via `tasks` property of `runok`:

```js
const {
  tasks: {
    exec, writeToFile, npm
  }
} = require('runok');
```

### Tasks API

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

##### Table of Contents

-   [npmRun](#npmrun)
    -   [Parameters](#parameters)
-   [npx](#npx)
    -   [Parameters](#parameters-1)
-   [copy](#copy)
    -   [Parameters](#parameters-2)
-   [git](#git)
    -   [Commands API](#commands-api)
    -   [Parameters](#parameters-3)
-   [exec](#exec)
    -   [Parameters](#parameters-4)
-   [writeToFile](#writetofile)
    -   [Config API](#config-api)
    -   [Parameters](#parameters-5)
-   [GitConfig](#gitconfig)
    -   [tag](#tag)
        -   [Parameters](#parameters-6)
    -   [commit](#commit)
        -   [Parameters](#parameters-7)
    -   [pull](#pull)
        -   [Parameters](#parameters-8)
    -   [init](#init)
    -   [add](#add)
        -   [Parameters](#parameters-9)
    -   [clone](#clone)
        -   [Parameters](#parameters-10)
    -   [branch](#branch)
        -   [Parameters](#parameters-11)
    -   [checkout](#checkout)
        -   [Parameters](#parameters-12)

#### npmRun

Executes npm script

```js
await npmRun('deploy');
```

##### Parameters

-   `command`  
-   `config` **ExecConfig** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Result>** 

#### npx

Executes npx script

```js
await npx('create-codeceptjs .');
```

##### Parameters

-   `command`  
-   `config` **ExecConfig** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Result>** 

#### copy

Copies file or directory. copySync from 'fs-extra' is used.

```js
copy('src/', 'dst/');
```

##### Parameters

-   `from` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `to` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `options` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** 

#### git

Provides flexible interface to running multiple git commands:

```js
await git(cmd => {
   cmd.pull();
   cmd.commit('-m updated');
   cmd.push();
})
```

##### Commands API

-   `cmd.init`
-   `cmd.tag`
-   `cmd.branch`
-   `cmd.commit`
-   `cmd.pull`
-   `cmd.push`
-   `cmd.add`
-   `cmd.clone`
-   `cmd.cloneShallow`

##### Parameters

-   `configFn` **gitConfigType** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Result>** 

#### exec

Executes shell command and returns a promise.

```js
await exec('ls -ll');
```

A second parameter can be used to pass in [ExecOptions](https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback) from "child_process" module:

```js
await exec('rails s', { env: { RAILS_ENV: 'production' } })
```

To hide output pass in `output: false`:

```js
await exec('docker build', { output: false });
```

##### Parameters

-   `command` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `config` **execConfigType?** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Result>** 

#### writeToFile

Writes a data to file. 

Takes file name as first argument and config function as second.

```js
writeToFile('blog-post.md', cfg => {
   cfg.line('---');
   cfg.line('title: My blogpost');
   cfg.line('---');
   cfg.line();
   cfg.textFromFile('blog-post.txt');
   cfg.text += '// copyright by me';
});
```

A second argument is a config function that passes in an object for text manipulation.

##### Config API

-   `cfg.text` - string to be written to file
-   `cfg.textFromFile(file)` - loads a file and append its context to text
-   `cfg.line(text)` - appends a string to a text with "\\n" after
-   `cfg.append(text)` - appends a string to a text

##### Parameters

-   `file` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `configFn` **writeToFileCallback** 

Returns **Result** 

#### GitConfig

**Extends TaskConfig**

Git Config Class

##### tag

###### Parameters

-   `tag`  

##### commit

Commit params

###### Parameters

-   `message`   (optional, default `''`)

##### pull

###### Parameters

-   `branch`   (optional, default `''`)

##### init

Initialize git repository

##### add

###### Parameters

-   `params`   (optional, default `''`)

##### clone

###### Parameters

-   `url`  
-   `path`  

##### branch

###### Parameters

-   `command`  

##### checkout

###### Parameters

-   `params`  

### npmRun

Executes npm script

```js
await npmRun('deploy');
```

#### Parameters

-   `command`  
-   `config` **ExecConfig** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Result>** 

### npx

Executes npx script

```js
await npx('create-codeceptjs .');
```

#### Parameters

-   `command`  
-   `config` **ExecConfig** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;Result>** 

### copy

Copies file or directory. copySync from 'fs-extra' is used.

```js
copy('src/', 'dst/');
```

#### Parameters

-   `from` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `to` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `options` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** 

### writeToFile

Writes a data to file. 

Takes file name as first argument and config function as second.

```js
writeToFile('blog-post.md', cfg => {
   cfg.line('---');
   cfg.line('title: My blogpost');
   cfg.line('---');
   cfg.line();
   cfg.textFromFile('blog-post.txt');
   cfg.text += '// copyright by me';
});
```

A second argument is a config function that passes in an object for text manipulation.

#### Config API

-   `cfg.text` - string to be written to file
-   `cfg.textFromFile(file)` - loads a file and append its context to text
-   `cfg.line(text)` - appends a string to a text with "\\n" after
-   `cfg.append(text)` - appends a string to a text

#### Parameters

-   `file` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `configFn` **writeToFileCallback** 

Returns **Result** 

## Adding Custom Tasks

Tasks in runok use similar API and must follow conventions to match the output:

-   task must be a function
-   task must return [Result](https://github.com/runok-js/runok/blob/master/src/result.ts) instance (or Promise<Result>)
-   `Result.start` must be called in the beginning of a function, to create result object
-   `result.success` must be called on finish
-   `result.error(err)` must be called on failure

```js
const { Result } = require('runok');

module.exports = myTask(arg1, options) {
  const result = Result.start("Task Name", `argument: ${arg1}`);

  try {
    // do something
  } catch (err) {
    return result.error(err);
  }
  return result.success();  
}
```

## Extending Custom Tasks

If you want to extend current task, for instamce add custom wrapper to `exec` command you can check [npx](https://github.com/runok-js/runok/blob/master/src/tasks/npx.ts) command as an example. 

Similar way you can create a task for running Docker scripts: 

```ts
const { tasks: { exec } } = require('runok');

module.exports = (command, config) => {

  return exec(command, baseCfg => {
    baseCfg.TASK = 'docker'; // name of task
    baseCfg.prefix('docker'); // prefix to an executed command
    baseCfg.apply(config);
  });
}
```

## Helpers

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

### chdir

Changes directory for commands executed in callback function.

```js
// copy file in "base" directory
chdir('./base', () => copy('a.txt', 'b.txt')));
```

#### Parameters

-   `workDir` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** 
-   `callback` **CallableFunction** 

Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;any>** 

### stopOnFail

Prevents execution of next tasks on fail:

```js
stopOnFail();
```

Ignore failures and continue:

```js
stopOnFail(false);
```

#### Parameters

-   `stop` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)**  (optional, default `true`)

## Credits

Created by Michael Bodnarchuk @davertmik.

Inspired by [Robo PHP Task Runner](https://robo.li)

## LICENSE MIT
