├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── cli.js ├── commands ├── ask.js ├── chalk.js ├── colors.js ├── enquirer.js ├── hello.js ├── icons.js ├── ora-customized.js ├── ora-promise.js ├── ora.js ├── package.js ├── run-system-command.js ├── run-tasks-parallel.js └── run-tasks.js └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "standard", 8 | "prettier", 9 | "prettier/standard" 10 | ], 11 | "plugins": [ 12 | "prettier", 13 | "standard" 14 | ], 15 | "parserOptions": { 16 | "ecmaVersion": 8 17 | }, 18 | "rules": { 19 | "prettier/prettier": [ 20 | "error", 21 | { 22 | "semi": false, 23 | "singleQuote": true, 24 | "printWidth": 120 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # NPM package-lock 61 | package-lock.json 62 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Future Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build a Node.js CLI with Ace 2 | > Automate repetitive tasks and build a CLI with Node.js 🔥 3 | 4 | Command line apps are powerful helpers for automation. Need to run a sequence of tasks? Use the command line! 5 | 6 | The best part: **you can build a CLI in Node.js**. Stay with the platform you love. Build a CLI with Node.js! 7 | 8 | 9 | ------ 10 | 11 |

This free video series is sponsored by Future Studio University 🚀 12 |
13 | Join the Future Studio University and Skyrocket in Node.js 14 |

15 | 16 | ------ 17 | 18 | 19 | ## Free Video Series 20 | This repository is the companion to a [**free YouTube video series**](https://www.youtube.com/watch?v=qE_TKIcu4pE&list=PLpUMhvC6l7ANfqV2erFmMJ6v9DQDS28L9). The videos walk you through the process of creating your own CLI with Node.js 21 | 22 | 23 | ## Content 24 | The first, free season of this video series covers the following topics: 25 | 26 | - [Project Setup](https://github.com/fs-opensource/build-a-node-cli/blob/master/cli.js) 27 | - [Create commands using ECMAScript classes](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/hello.js) 28 | - Use [Node.js modules](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/package.js#L3) and [NPM packages](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/load.js#L4) 29 | - [Add aliases/flags](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/hello.js#L13) 30 | - [Define arguments](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/hello.js#L12) 31 | - Generate a help output: add `-h` to any command 32 | - [Grab inputs from the console](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/ask.js#L31) 33 | - [Ask a y/n question](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/ask.js#L34) 34 | - [Select an answer from a list of choices](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/ask.js#L37) 35 | - [Loading indicators](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/ora.js) (for async tasks) 36 | - [Run a list of sequential tasks](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/run-tasks.js) 37 | - [Colorful outputs](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/colors.js) 38 | - [Make use of icons](https://github.com/fs-opensource/build-a-node-cli/blob/master/commands/icons.js) 39 | 40 | 41 | ## Let Us Know What You Build! 42 | We love to hear what tools and utilities you build. Please share it with us and tweet us a picture on Twitter: [@futurestud_io](https://twitter.com/futurestud_io) 43 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const Ace = require('@adonisjs/ace') 5 | 6 | // add commands as ES2015 classes 7 | Ace.addCommand(require('./commands/ask')) 8 | Ace.addCommand(require('./commands/ora')) 9 | Ace.addCommand(require('./commands/ora-promise')) 10 | Ace.addCommand(require('./commands/ora-customized')) 11 | Ace.addCommand(require('./commands/hello')) 12 | Ace.addCommand(require('./commands/chalk')) 13 | Ace.addCommand(require('./commands/icons')) 14 | Ace.addCommand(require('./commands/colors')) 15 | Ace.addCommand(require('./commands/package')) 16 | Ace.addCommand(require('./commands/enquirer')) 17 | Ace.addCommand(require('./commands/run-tasks')) 18 | Ace.addCommand(require('./commands/run-tasks-parallel')) 19 | Ace.addCommand(require('./commands/run-system-command')) 20 | 21 | // Boot ace to execute commands 22 | Ace.wireUpWithCommander() 23 | Ace.invoke() 24 | -------------------------------------------------------------------------------- /commands/ask.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | 5 | class Ask extends Command { 6 | /** 7 | * The method signature describes the comannd, arguments and flags/aliases 8 | * The words flags and aliases mean the same thing in this context 😃 9 | */ 10 | static get signature() { 11 | return `ask` 12 | } 13 | 14 | /** 15 | * Use this description to provide additional details 16 | * about the command 17 | */ 18 | static get description() { 19 | return 'Ask a user for input on stdin' 20 | } 21 | 22 | /** 23 | * Handle the command 24 | * 25 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 26 | * @param {*} flags an object of flags where each value is either "null" or "true". 27 | * Check the signature for available flags 28 | */ 29 | async handle(args, flags) { 30 | // ask for the user's name, default to "Cool Friend" 31 | const input = await this.ask('What’s your name?', 'Cool Friend') 32 | console.log(`\nOh yeah, nice name ${this.chalk.bold.magenta(input)}!\n`) 33 | 34 | const confirm = await this.confirm('Do you want another question?', { default: false }) 35 | 36 | if (confirm) { 37 | const like = await this.choice( 38 | 'I guess you’re passionate about Node.js 🔥?', 39 | [ 40 | // create an answer which is also the value 41 | 'Of course', 42 | // or split answer text and related value 43 | { 44 | name: 'Hell yes!', // this is the selectable answer 45 | value: 'hell' // this is the value 46 | } 47 | ], 48 | 'hell' // value of the selected default answer 49 | ) 50 | 51 | console.log(this.chalk.bold.green(like)) 52 | } 53 | } 54 | } 55 | 56 | module.exports = Ask 57 | -------------------------------------------------------------------------------- /commands/chalk.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | 5 | class Chalk extends Command { 6 | static get signature() { 7 | return `chalk` 8 | } 9 | 10 | static get description() { 11 | return 'Access the chalk instance of Ace' 12 | } 13 | 14 | async handle() { 15 | console.log(this.chalk.bold.bgBlue('I am the bonfire')) 16 | } 17 | } 18 | 19 | module.exports = Chalk 20 | -------------------------------------------------------------------------------- /commands/colors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | 5 | class Colors extends Command { 6 | /** 7 | * The method signature describes the comannd, arguments and flags/aliases 8 | * The words flags and aliases mean the same thing in this context 😃 9 | */ 10 | static get signature() { 11 | return `colorful` 12 | } 13 | 14 | /** 15 | * Use this description to provide additional details 16 | * about the command 17 | */ 18 | static get description() { 19 | return 'Colorful outputs :)' 20 | } 21 | 22 | /** 23 | * Handle the command 24 | * 25 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 26 | * @param {*} flags an object of flags where each value is either "null" or "true". 27 | * Check the signature for available flags 28 | */ 29 | async handle(args, flags) { 30 | this.success('Went smooth like chocolate!') 31 | this.info('Well, this is an info text ;-)') 32 | this.warn('Warning warning: get ur freak on!!!') 33 | this.error('This seems bad. Really bad. I mean, like so bad to stay in bed.') 34 | 35 | console.log('') // empty line 36 | 37 | this.completed('action', 'message.js') 38 | this.failed('action', 'fail.js') 39 | } 40 | } 41 | 42 | module.exports = Colors 43 | -------------------------------------------------------------------------------- /commands/enquirer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command, enquirer } = require('@adonisjs/ace') 4 | 5 | class Enquirer extends Command { 6 | static get signature() { 7 | return `enquirer` 8 | } 9 | 10 | static get description() { 11 | return 'Access the enquirer instance through Ace' 12 | } 13 | 14 | async handle() { 15 | const color = enquirer.question('color', 'What is your favorite color?', { 16 | default: 'blue' 17 | }) 18 | 19 | const favoriteColor = await enquirer.prompt(color) 20 | console.log(favoriteColor) 21 | } 22 | } 23 | 24 | module.exports = Enquirer 25 | -------------------------------------------------------------------------------- /commands/hello.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | 5 | class Hello extends Command { 6 | /** 7 | * The method signature describes the comannd, arguments and flags/aliases 8 | * The words flags and aliases mean the same thing in this context 😃 9 | */ 10 | static get signature() { 11 | return `hello 12 | { name?: Say hello to the named user } 13 | { -f, --friendly: a friendly hello } 14 | { -g, --grumpy-cat: a grumpy cat hello :( } 15 | ` 16 | } 17 | 18 | /** 19 | * Use this description to provide additional details 20 | * about the command 21 | */ 22 | static get description() { 23 | return 'Say hello' 24 | } 25 | 26 | /** 27 | * Handle the command 28 | * 29 | * @param {*} args arguments object 30 | * @param {*} flags arguments object 31 | */ 32 | async handle({ name }, { friendly, grumpyCat }) { 33 | console.log(`Hello ${friendly ? 'friendly ' : ''}${name || 'Stranger'} ${grumpyCat ? ':(' : ''}`) 34 | } 35 | } 36 | 37 | module.exports = Hello 38 | -------------------------------------------------------------------------------- /commands/icons.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | 5 | class Icons extends Command { 6 | static get signature() { 7 | return `icons` 8 | } 9 | 10 | static get description() { 11 | return 'Print icons' 12 | } 13 | 14 | /** 15 | * Handle the command 16 | * 17 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 18 | * @param {*} flags an object of flags where each value is either "null" or "true". 19 | * Check the signature for available flags 20 | */ 21 | async handle(args, flags) { 22 | console.log(`${this.icon('info')} Did you know Ace supports icons?`) 23 | console.log(`${this.icon('success')} Yeeezzzzz!`) 24 | console.log(`${this.icon('warn')} even for warnings and ${this.icon('error')} errors.`) 25 | 26 | console.log('') // empty line 27 | 28 | this.success(`${this.icon('success')} Went smooth like chocolate!`) 29 | this.warn(`${this.icon('error')} but has warnings`) 30 | } 31 | } 32 | 33 | module.exports = Icons 34 | -------------------------------------------------------------------------------- /commands/ora-customized.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | const Ora = require('ora') 5 | 6 | class Load extends Command { 7 | static get signature() { 8 | return `ora:customized` 9 | } 10 | 11 | static get description() { 12 | return 'Ora spinner shortcut for a promise' 13 | } 14 | 15 | async handle(args, flags) { 16 | // Customize your Ora spinner 17 | // this works with a spinner assignment "const spinner = Ora({ options })" as well 18 | Ora.promise(this.wait(), { 19 | text: 'Custom spinners are freeeeekin awesome!', 20 | spinner: 'dots12', 21 | color: 'magenta' 22 | }) 23 | } 24 | 25 | wait() { 26 | return new Promise(resolve => setTimeout(resolve, 3000)) 27 | } 28 | } 29 | 30 | module.exports = Load 31 | -------------------------------------------------------------------------------- /commands/ora-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | const Ora = require('ora') 5 | 6 | class Load extends Command { 7 | static get signature() { 8 | return `ora:promise` 9 | } 10 | 11 | static get description() { 12 | return 'Ora spinner shortcut for a promise' 13 | } 14 | 15 | async handle(args, flags) { 16 | // Ora promise shortcut: spinner succeeds if the promise resolves, fails if rejects 17 | Ora.promise(this.wait(), 'Waiting for the task to finish') 18 | 19 | console.log('\nRemember: Ora.promise is a function, not an awaitable Promise.') 20 | console.log('\rYou’ll see this text right after starting the command.') 21 | } 22 | 23 | async wait() { 24 | await new Promise(resolve => setTimeout(resolve, 4000)) 25 | 26 | console.log('\nIn contrast, this text shows up after the awaited Promise (4s)') 27 | } 28 | } 29 | 30 | module.exports = Load 31 | -------------------------------------------------------------------------------- /commands/ora.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | const Ora = require('ora') 5 | 6 | class Load extends Command { 7 | static get signature() { 8 | return `ora` 9 | } 10 | 11 | static get description() { 12 | return 'Ora spinners for long running tasks' 13 | } 14 | 15 | /** 16 | * Handle the command 17 | * 18 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 19 | * @param {*} flags an object of flags where each value is either "null" or "true". 20 | * Check the signature for available flags 21 | */ 22 | async handle(args, flags) { 23 | const spinner = Ora('Fueling the rocket') 24 | spinner.start() 25 | 26 | await this.waitASecond() 27 | spinner.color = 'magenta' 28 | spinner.text = 'Boarding passengers' 29 | 30 | await this.waitASecond() 31 | spinner.color = 'yellow' 32 | spinner.text = 'Starting the engines' 33 | 34 | await this.waitASecond() 35 | spinner.color = 'green' 36 | spinner.text = 'Launching the rocket!!!!' 37 | 38 | await this.waitASecond() 39 | spinner.color = 'cyan' 40 | spinner.text = 'Shooting for the stars' 41 | 42 | await this.waitASecond() 43 | spinner.succeed('Houston? We landed!') 44 | } 45 | 46 | waitASecond() { 47 | return new Promise(resolve => setTimeout(resolve, 1000)) 48 | } 49 | } 50 | 51 | module.exports = Load 52 | -------------------------------------------------------------------------------- /commands/package.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Fs = require('fs') 4 | const { Command } = require('@adonisjs/ace') 5 | 6 | class Package extends Command { 7 | /** 8 | * The method signature describes the comannd, arguments and flags/aliases 9 | * The words flags and aliases mean the same thing in this context 😃 10 | */ 11 | static get signature() { 12 | return `pkg` 13 | } 14 | 15 | /** 16 | * Use this description to provide additional details 17 | * about the command 18 | */ 19 | static get description() { 20 | return 'Show the package.json content' 21 | } 22 | 23 | /** 24 | * Handle the command 25 | * 26 | * @param {*} args arguments object 27 | * @param {*} flags arguments object 28 | */ 29 | async handle() { 30 | const pkg = Fs.readFileSync('package.json') 31 | console.log(pkg.toString()) 32 | } 33 | } 34 | 35 | module.exports = Package 36 | -------------------------------------------------------------------------------- /commands/run-system-command.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('@adonisjs/ace') 2 | const Execa = require('execa') 3 | 4 | class RunSystemCommand extends Command { 5 | /** 6 | * The method signature describes the comannd, arguments and flags/aliases 7 | * The words flags and aliases mean the same thing in this context 😃 8 | */ 9 | static get signature() { 10 | return `run-command` 11 | } 12 | 13 | /** 14 | * Use this description to provide additional details 15 | * about the command 16 | */ 17 | static get description() { 18 | return 'Run a command, like “npm test”' 19 | } 20 | 21 | /** 22 | * Handle the command 23 | * 24 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 25 | * @param {*} flags an object of flags where each value is either "null" or "true". 26 | * Check the signature for available flags 27 | */ 28 | async handle(args, flags) { 29 | try { 30 | // grab your Node.js version 31 | const node = await Execa('node', ['-v']) 32 | console.log(`Node.js: ${this.chalk.bold.green(node.stdout)}`) 33 | 34 | // 👇 this is the same as above, but prefer Execa() over Execa.shell() 35 | // Exexa() seems to be faster and safer than Execa.shell 36 | // const node = await Execa.shell('node -v') 37 | 38 | // grab your NPM version 39 | const npm = await Execa.shell('npm -v') 40 | console.log(`NPM: ${this.chalk.bold.green(npm.stdout)}`) 41 | 42 | // you can also run your project tests, like this: 43 | // await Execa('npm', ['test']) 44 | } catch (err) { 45 | // catch any error and print the error message 46 | console.log(`❗️ Error: ${this.error(err.message)}`) 47 | // exit the process to stop everything 48 | process.exit(1) 49 | } 50 | } 51 | } 52 | 53 | module.exports = RunSystemCommand 54 | -------------------------------------------------------------------------------- /commands/run-tasks-parallel.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | const Listr = require('listr') 5 | 6 | class RunTasks extends Command { 7 | /** 8 | * The method signature describes the comannd, arguments and flags/aliases 9 | * The words flags and aliases mean the same thing in this context 😃 10 | */ 11 | static get signature() { 12 | return `run-tasks:parallel` 13 | } 14 | 15 | /** 16 | * Use this description to provide additional details 17 | * about the command 18 | */ 19 | static get description() { 20 | return 'Run a list of tasks in parallel.' 21 | } 22 | 23 | /** 24 | * Handle the command 25 | * 26 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 27 | * @param {*} flags an object of flags where each value is either "null" or "true". 28 | * Check the signature for available flags 29 | */ 30 | async handle(args, { skipFuel, skipPassengers, captain }) { 31 | // deployment task list 32 | const tasks = new Listr([ 33 | { 34 | title: 'Allow extra bag for passengers', 35 | // you can disable tasks on wish 36 | // return a falsy value for "enabled" to disable this task in the list 37 | enabled: () => false, 38 | task: () => this.waitASecond() 39 | }, 40 | { 41 | title: 'Prepare start', 42 | skip: skipPassengers, 43 | task: () => 44 | new Listr( 45 | [ 46 | { 47 | title: 'Fueling the rocket', 48 | skip: () => { 49 | // returning a truthy value for "skip" will actually skip the task 50 | // a falsy value will not skip the task execution 51 | return skipFuel ? 'Skip fueling.' : false 52 | }, 53 | task: () => this.wait(5) 54 | }, 55 | { 56 | title: 'Adding extra snacks for the trip', 57 | skip: () => true, 58 | task: () => this.waitASecond() 59 | }, 60 | { 61 | title: 'Boarding passengers', 62 | skip: skipPassengers, 63 | task: () => this.wait(5) 64 | }, 65 | { 66 | title: 'Starting the engines', 67 | task: () => this.wait(3) 68 | } 69 | ], 70 | { concurrent: true } 71 | ) 72 | }, 73 | { 74 | title: typeof captain === 'string' ? `${captain} is launching the rocket!!!!` : 'Launching the rocket!!!!', 75 | task: () => this.waitASecond() 76 | }, 77 | { 78 | title: 'Shooting for the stars', 79 | task: () => this.waitASecond() 80 | }, 81 | { 82 | title: 'Houston? We landed!', 83 | task: () => this.waitASecond() 84 | } 85 | ]) 86 | 87 | await tasks.run() 88 | } 89 | 90 | waitASecond() { 91 | return this.wait(1) 92 | } 93 | 94 | wait(seconds) { 95 | return new Promise(resolve => setTimeout(resolve, seconds * 1000)) 96 | } 97 | } 98 | 99 | module.exports = RunTasks 100 | -------------------------------------------------------------------------------- /commands/run-tasks.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Command } = require('@adonisjs/ace') 4 | const Listr = require('listr') 5 | 6 | class RunTasks extends Command { 7 | /** 8 | * The method signature describes the comannd, arguments and flags/aliases 9 | * The words flags and aliases mean the same thing in this context 😃 10 | */ 11 | static get signature() { 12 | return `run-tasks 13 | { -f, --skip-fuel: Skip fueling the rocket } 14 | { -p, --skip-passengers: Skip boarding passengers } 15 | { -c, --captain=@value: Specify the captain's name }` 16 | } 17 | 18 | /** 19 | * Use this description to provide additional details 20 | * about the command 21 | */ 22 | static get description() { 23 | return 'Run a list of tasks.' 24 | } 25 | 26 | /** 27 | * Handle the command 28 | * 29 | * @param {*} args arguments object, contains only data if you’ve added arguments in the signature 30 | * @param {*} flags an object of flags where each value is either "null" or "true". 31 | * Check the signature for available flags 32 | */ 33 | async handle(args, { skipFuel, skipPassengers, captain }) { 34 | // deployment task list 35 | const tasks = new Listr([ 36 | { 37 | title: 'Fueling the rocket', 38 | skip: () => { 39 | // returning a truthy value for "skip" will actually skip the task 40 | // a falsy value will not skip the task execution 41 | return skipFuel ? 'Skip fueling.' : false 42 | }, 43 | task: () => this.waitASecond() 44 | }, 45 | { 46 | title: 'Adding extra snacks for the trip', 47 | skip: () => true, 48 | task: () => this.waitASecond() 49 | }, 50 | { 51 | title: 'Allow extra bag for passengers', 52 | // you can disable tasks on wish 53 | // return a falsy value for "enabled" to disable this task in the list 54 | enabled: () => false, 55 | task: () => this.waitASecond() 56 | }, 57 | { 58 | title: 'Boarding passengers', 59 | skip: skipPassengers, 60 | task: () => this.waitASecond() 61 | }, 62 | { 63 | title: 'Starting the engines', 64 | task: () => this.waitASecond() 65 | }, 66 | { 67 | title: typeof captain === 'string' ? `${captain} is launching the rocket!!!!` : 'Launching the rocket!!!!', 68 | task: () => this.waitASecond() 69 | }, 70 | { 71 | title: 'Shooting for the stars', 72 | task: () => this.waitASecond() 73 | }, 74 | { 75 | title: 'Houston? We landed!', 76 | task: () => this.waitASecond() 77 | } 78 | ]) 79 | 80 | await tasks.run() 81 | } 82 | 83 | waitASecond() { 84 | return new Promise(resolve => setTimeout(resolve, 1000)) 85 | } 86 | } 87 | 88 | module.exports = RunTasks 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build-a-node-cli", 3 | "description": "Build a CLI with Node.js", 4 | "version": "1.0.0", 5 | "author": "Future Studio ", 6 | "bugs": { 7 | "url": "https://github.com/fs-opensource/build-a-node-cli/issues" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "Marcus Poehls", 12 | "email": "marcus@futurestud.io" 13 | }, 14 | { 15 | "name": "Norman Peitek", 16 | "email": "norman@futurestud.io" 17 | }, 18 | { 19 | "name": "Christian Fellner", 20 | "email": "christian@futurestud.io" 21 | } 22 | ], 23 | "dependencies": { 24 | "@adonisjs/ace": "~5.0.1", 25 | "execa": "~0.10.0", 26 | "listr": "~0.13.0", 27 | "ora": "~2.1.0", 28 | "release-it": "~7.4.5", 29 | "semver": "~5.5.0", 30 | "shipit-cli": "~4.1.0" 31 | }, 32 | "devDependencies": { 33 | "eslint": "~4.19.1", 34 | "eslint-config-prettier": "~2.9.0", 35 | "eslint-config-standard": "~11.0.0", 36 | "eslint-plugin-import": "~2.12.0", 37 | "eslint-plugin-node": "~6.0.1", 38 | "eslint-plugin-prettier": "~2.6.0", 39 | "eslint-plugin-promise": "~3.7.0", 40 | "eslint-plugin-standard": "~3.1.0", 41 | "prettier": "~1.12.1" 42 | }, 43 | "homepage": "https://github.com/fs-opensource/build-your-node-cli#readme", 44 | "license": "MIT", 45 | "main": "index.js", 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/fs-opensource/build-your-node-cli.git" 49 | }, 50 | "scripts": { 51 | "test": "echo \"Error: no test specified\" && exit 1" 52 | } 53 | } 54 | --------------------------------------------------------------------------------