Last week, I wrote an article about building a Node.js CLI using yargs. I introduced it saying we use cli tools everyday to simplify common tasks in our jobs. I made myself a couple of cli like gitmoji-changelog. It is a changelog generator for gitmoji commit convention.
I would like to share with you a few libraries I used on this project and while contributing to Gatsby. Gatsby is a good source of inspiration, consider contributing to it. I learned a lot while doing it (they give free swag to thank contributions 🤫).
yargs
It is a library that helps you defining your tool's interface. It also parses arguments for you. The icing on the cake is that yargs generates automatically an help menu.
Here is a simple example that displays a message "Hello [something]" a certain amount of times.
require('yargs')
.command('$0 [name]', 'say hello', (yargs) => {
yargs
.positional('name', {
describe: 'hello\'s target',
default: 'world'
})
.option('times', {
alias: 't',
type: 'number',
default: 1,
description: 'number of times to say hello'
})
}, (argv) => {
for (let i = 0;i < argv.times; i++) {
console.log(`Hello ${argv.name}!`)
}
})
.argv
Result:
prompts
A common use case in cli tools is asking user for information. Prompts is a lightweight library based on promises. It implements an exhautive list of question's types.
(async () => {
const prompts = require('prompts')
const response = await prompts({
type: 'confirm',
name: 'value',
message: 'Can you confirm?',
initial: true
})
console.log('Reponse: ', response.value)
})()
Result:
signale
Standard console
API provides only a few methods to display information. Signale comes with 19 built-in methods (logs are prefixed with emojies ❤️)! You can also implements custom loggers.
const signale = require('signale')
signale.success('CLI started');
const options = {
types: {
santa: {
badge: '👽',
color: 'magenta',
label: 'alien',
logLevel: 'info'
}
}
}
const custom = new signale.Signale(options);
custom.santa('E.T go home')
signale.complete('Call sent')
Result:
chalk
It is a pain in the neck to add style to a cli output. Chalk provides an easy-to-use API to colorize logs. It also supports template literals!
const chalk = require('chalk')
console.log(`${chalk.blue('Welcome')} in the activity monitor${chalk.red('!')}`)
console.log(chalk.green(`Your computer seems in ${chalk.underline('great')} shape.`))
console.log(`
envinfo:
CPU: ${chalk.red('90%')}
RAM: ${chalk.green('40%')}
DISK: ${chalk.yellow('70%')}
`)
Result:
progress
Another common use case is dealing with asynchronous operations. It is nice to give user a percentage of completion when your cli is doing a heavy computation. Progress is an highly customizable ascii progress bar. It comes with a bunch of options and standard information (percentage, total, estimated completion, ...) to display on the progress bar. You can also add your own information.
const ProgressBar = require('progress')
let ticks = 0
const bar = new ProgressBar(
'Rocket launch :bar in :counter',
{ total: 10, width: 50 },
)
const timer = setInterval(function () {
ticks++
bar.tick({ counter: 10 - ticks })
if (bar.complete) {
console.log('\n🚀')
clearInterval(timer)
}
}, 100)
Result:
configstore
Earlier we saw Prompts to ask user information. It is also nice to store its answer to avoid asking it again and again. Configstore is a library that persists data for you. It stores it in a json file on the user's disk. It handles well the dot notation!
const Configstore = require('configstore')
const packageJson = require('../package.json')
const config = new Configstore(packageJson.name)
config.set('answer', true);
console.log('answer:', config.get('answer'));
config.set('a.really.deep.config', true);
console.log('a.really.deep.config:', config.get('a.really.deep.config'));
config.delete('answer');
console.log('answer:', config.get('answer'));
Result:
envinfo
As frontend developer I use user-agent to get information about my user device. It helps a lot to reproduce bugs for example. As cli developer you don't have access to this kind of information. envinfo is a library that generates reports that users can provide when opening issues on your project.
(async () => {
const envinfo = require('envinfo')
const environment = await envinfo.run(
{
System: ['OS', 'Shell'],
Binaries: ['Node', 'Yarn', 'npm'],
Utilities: ['Git'],
},
{ markdown: true }
)
console.log(environment)
})()
Result:
Alternatives exist for these libraries but I used these ones and I enjoy working with them. In my opinion, they covers the majority of problems you might encounter while coding cli tools.
Hope it will help 🙌 Happy cli coding!
Feedback is appreciated 🙏 Please tweet me if you have any questions @YvonnickFrin!
Top comments (28)
I absolutely love prompts! I recently switched from inquirer to prompts and it helped me reduce my dependency tree.
I love commander as well, It has 0 sub dependencies and since it is popular it has good resources available (there are a lot of articles available about commander).
Great article🦄 I haven't heard about a lot of them before.. I'll give them a try :D!
Thank you 🙏Yesterday, I asked someone why using inquirer over prompts. Thank you for the dependency tree argument 👍
Thank you Saurabh, I'm glad you find Prompts useful!
Few dependencies are one of the main goals of Prompts - High five! 🖐
inquirer and ora are also good
npmjs.com/package/inquirer
npmjs.com/package/ora
I used them too but I did a choice for the article 😢
Thank you for talking about them 👌
Might want to include some logging libraries next?
Segway: one thing I learned from other languages is your cli is just a client and you should keep the functionality in a separate libe that exposes bindings to this client or any future applications.
I'm not familiar with the subject but it seems interesting (I might need this on an issue on gitmoji-changelog, since I want to use it on a website). Do you have some articles about it? Maybe a library you had in mind?
I am such a fan of Signale and Prompts. I love how simple, yet effective they are.
Prompts is also a fan of you 🎉
There is also 🌈 React for interactive command-line apps, Ink :-)
Used it with pastel for several CLIs. Really nice :)
Nice write up! I didn't know about some of those packages.
If you are in the process of building NodeJS CLIs you definitely should check Oclif from Heroku, I wrote a post with an example some time ago here dev.to/fedekau/building-awesome-cl...
How about OCLIF? Have you thought of Oclif.io?
Didn't know this project, thank you for sharing 🙏
The gluegun framework is also incredible.
infinitered.github.io/gluegun
Thanks for this article! I have to try everything 🐘
Thank you 🐘
Author of Prompts here! Thank you for featuring the prompts library ❤️
Thank you for your awesome work 🙏
I've used gluegun to create a CLI, it's quite nice and have a lot of functionality out of the box, worth checking it out!