runok

NPM scripts on steroids! Replace your scripts with pure JS

Usage no npm install needed!

<script type="module">
  import runok from 'https://cdn.skypack.dev/runok';
</script>

README

🌅 runok

🥋 No BASH-fu is required anymore!

💡 Write your shell scripts in JavaScript!



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.
#!/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:
// function definition:
async runMe(arg1, arg1, { print: false, retries: 3 })

👇

./runok.js run:me value1 value2 --print --retries 1
  1. Add a description of a command as the first comment in a function
async runMe() {
  // executes very important command
}

👇

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

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

Commands:
  run:me                            executes very important command
  1. Import required tasks from runok package.
  2. 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:

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

Tasks API

Table of Contents

npmRun

Executes npm script

await npmRun('deploy');
Parameters
  • command
  • config ExecConfig

Returns Promise<Result>

npx

Executes npx script

await npx('create-codeceptjs .');
Parameters
  • command
  • config ExecConfig

Returns Promise<Result>

copy

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

copy('src/', 'dst/');
Parameters

git

Provides flexible interface to running multiple git commands:

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<Result>

exec

Executes shell command and returns a promise.

await exec('ls -ll');

A second parameter can be used to pass in ExecOptions from "child_process" module:

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

To hide output pass in output: false:

await exec('docker build', { output: false });
Parameters
  • command string
  • config execConfigType?

Returns Promise<Result>

writeToFile

Writes a data to file.

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

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
  • 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

await npmRun('deploy');

Parameters

  • command
  • config ExecConfig

Returns Promise<Result>

npx

Executes npx script

await npx('create-codeceptjs .');

Parameters

  • command
  • config ExecConfig

Returns Promise<Result>

copy

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

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

Parameters

writeToFile

Writes a data to file.

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

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
  • 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 instance (or Promise)
  • 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
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 command as an example.

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

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

chdir

Changes directory for commands executed in callback function.

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

Parameters

  • workDir string
  • callback CallableFunction

Returns Promise<any>

stopOnFail

Prevents execution of next tasks on fail:

stopOnFail();

Ignore failures and continue:

stopOnFail(false);

Parameters

  • stop boolean (optional, default true)

Credits

Created by Michael Bodnarchuk @davertmik.

Inspired by Robo PHP Task Runner

LICENSE MIT