UNPKG

planify

Version:

Plan a series of steps and display the output in a beautiful way

276 lines (195 loc) 9.45 kB
# planify [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url] [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url] [npm-url]:https://npmjs.org/package/planify [downloads-image]:http://img.shields.io/npm/dm/planify.svg [npm-image]:http://img.shields.io/npm/v/planify.svg [travis-url]:https://travis-ci.org/IndigoUnited/node-planify [travis-image]:http://img.shields.io/travis/IndigoUnited/node-planify/master.svg [coveralls-url]:https://coveralls.io/r/IndigoUnited/node-planify [coveralls-image]:https://img.shields.io/coveralls/IndigoUnited/node-planify/master.svg [david-dm-url]:https://david-dm.org/IndigoUnited/node-planify [david-dm-image]:https://img.shields.io/david/IndigoUnited/node-planify.svg [david-dm-dev-url]:https://david-dm.org/IndigoUnited/node-planify?type=dev [david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/node-planify.svg [greenkeeper-image]:https://badges.greenkeeper.io/IndigoUnited/node-planify.svg [greenkeeper-url]:https://greenkeeper.io/ Plan a series of concrete steps and display their output in a beautiful way. Ever wanted to write a simple CLI that run a series of tasks with beautiful output? I did and I always ended up doing a thin wrapper repeatedly. This library provides a concise way to define these tasks while offering a handful of reporters to output the progress in a variety of ways. ## Installation `$ npm install planify` ## Usage The API is very simple, yet very powerful: ```js const planify = require('planify'); planify() .step('Synchronous step', (data) => { console.log('A sync step in which data is', data); data.foz = 'baz'; // Set some data to the next step }) .step('Callback step', (data, done) => { console.log('A callback step in which data is', data); setTimeout(done, 500); }) .step('Promise step', (data) => { console.log('A promise step in which data is', data); return new Promise((resolve) => { setTimeout(resolve, 500); }); }) .phase('Some group of steps', (phase) => { phase.step('Synchronous step inside a phase', (data) => { console.log('A sync step inside a phase in which data is', data); }); }) .run() // Run returns a promise but callback style is also supported .then(() => process.exit(), (err) => process.exit(1)); ``` You can build as many nested phases as you wish. API is chainable to make it easier to build your plan. Running this would look like this: <img src="./doc/example-blocks.png" width="600"> But changing the reporter to `spec` would look like this: <img src="./doc/example-spec.png" width="600"> This is very similar to the [mocha](https://github.com/mochajs/mocha)'s spec reporter isn't it? In fact a lot of the API was inspired in it. Finally, changing the reporter to `progress` would show a beautiful progress bar instead: <img src="./doc/example-progress.png" width="600"> As you can see, changing the appearance of the output is very easy. You may even allow your CLI users to choose the reporter by mapping `--reporter` to `options.reporter` by using [yargs](https://github.com/bcoe/yargs) or something similar. Cool huh? ### Reporters The are several built-in reporters: - `blocks`: blocks is the default reporter and it outputs everything, including stuff printed to the stdout and stderr. - `spec`: spec reporter is very similar to mocha's spec reporter but stuff written to the stdout and stderr are hidden. - `progress`: progress reporter shows a beautiful progress bar showing the overall progress of the plan; stuff written to the stdout and stderr are hidden. - `json`: json reporter outputs a machine readable object with all the plan lifecycle events. - `silent`: silent reporter which simply outputs nothing. - `spinner`: a simple reporter which prints errors or a spinner when the process is running. *NOTE*: Some reporters will auto-indent output unless when doing [prompting](https://nodejs.org/api/readline.html). You can easily make your own reporter. Take a look at the [json](./reporters/json.js) reporter implementation for an example. You may use a custom reporter like this: ```js // my-reporter.js function myReporter(options) { /* ... */ return { plan: { start() { /* ... */ }, }, }; } module.exports = myReporter; // example.js const planify = require('planify'); const myReporter = require('./my-reporter'); planify() .step('Do something', () => { /* ... */ }) .run({ reporter: myReporter({ /* reporter options if any */ }) }); /* ... */ ``` One cool feature of the reporters is that they can be async. You may return promises or use callbacks to do async reporting (e.g.: save stuff to a database). Feel free to make a PR to add your reporter to the built-in reporters. #### Errors All reporters print errors in a consistent way: ``` [err.code|err.name if !== 'Error'] err.message [err.detail] [err.stack if err.hideStack === false] ``` By default, the stack trace will be printed if there's no `err.detail`. You can override this behavior by setting `err.hideStack` to `true` or `false` to hide or show respectively. ### Full API #### planify([data]) Creates a plan where `data` is the object that is going to be passed to steps. ```js const planify = require('planify'); const plan = planify({ foo: 'bar' }); ``` #### .step(label, [options], fn) Adds a step with `label`, executing `fn` when it's time for the step to run. The `fn` function will receive the plan `data` as the first argument which allows you to pass data to other steps. As show above, `fn` may return a promise or use callbacks to do asynchronous stuff. Available options: - `fatal`: False to continue executing if this step fails, defaults to `true`. - `mute`: True to mute stdout and stderr completely during the execution of this step independently of the reporter being used , defaults to `false`; stdout and stderr can be muted independently by passing an object, e.g.: `{ stdout: true, stderr: false }`. - `slow`: Amount of time in ms to consider this step slow, defaults to `200`. ```js const planify = require('planify'); const plan = planify(); plan.step('Some cool step', { fatal: false }, (data) => { throw new Error('This will fail but continue to the next step'); }); plan.step('Some cool step', { mute: true }, (data) => { console.log('This will not be logged'); }); plan.step('Some cool step', { slow: 500 }, (data, done) => { // Will be considered slow setTimeout(done, 600); }); ``` #### .phase(label, fn) Adds a phase with `label` to the plan, executing `fn` with a `phase` object to define the phase plan. The `phase` object has the `step` and `phase` methods, allowing you to build a hierarchy of other phases and steps. ```js const planify = require('planify'); const plan = planify(); plan.phase('Phase 1', (phase) => { phase.step('Inner step', () => {}); phase.phase('Inner phase', () => {}); }); ``` #### .run([options]) Runs the plan. Returns a promise that will be resolved when the plan succeeds or rejected if any of the steps failed. You may pass a callback as the second argument instead. ```js const planify = require('planify'); const plan = planify(); plan.step('Some cool step', (data) => { /* ... */ }); plan.run() .then(() => process.exit(0), () => process.exit(1)); // or you may use callback style plan.run((err) => { process.exit(err ? 1 : 0); }); ``` Available options: - `reporter`: The reporter to be used which can be a string or a reporter object, defaults to `blocks`. - `exit`: True to exit automatically after running, defaults to `false`. If the plan fails with an error that has `err.exitCode`, the program will exit with that code. ### Caveats `planify` hooks into `process.stdout.write` and `process.stderr.write` to allow reporters to style or mute output done inside steps. Thought, it's impossible to do that when using `child_process#spawn` or `child_process#exec` with `options.stdio` set to `inherit`. Please avoid it and listen to `data` events from stdout and stderr instead: ```js // Example using child_process#spawn const spawn = require('cross-spawn-async'); const planify = require('planify'); planify({ exit: true }) .step('Executing npm install', (done) => { const npm = spawn('npm', ['install']); // Use cross-spawn to make this work on Windows npm.stdout.on('data', (buffer) => process.stdout.write(buffer)); npm.stderr.on('data', (buffer) => process.stderr.write(buffer)); npm.on('error', done); npm.on('exit', (code) => { done(code ? new Error('npm exited with code ' + code) : null); }); }) .run(); ``` ```js // Example using child_process#exec const cp = require('child_process'); const planify = require('planify'); planify({ exit: true }) .step('Executing npm install', (done) => { // Note that output is buffered :( cp.exec('npm install', (err, stdout, stderr) => { stdout && process.stdout.write(stdout); stderr && process.stderr.write(stderr); done(err); }); }) .run(); ``` ## Tests `$ npm test` `$ npm test-cov` to get coverage report ## License Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).