├── .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 |
--------------------------------------------------------------------------------