Building a CLI with Yargs
Yargs is a great library to build command line apps, simply put, it will make the process of creating an application that runs in the console a breeze. What could make it even better? It's PIRATE themed (it's called YARgs you guys), making it officially the best tool ever.
You may know other CLIs such as vue-cli to easily set up a Vue.js project or create-react-app, so the concept should be familiar to most of you.
In today's article we'll be creating a basic CLI from start to finish covering the following points:
<br><br>
<a name="setup"></a> Project set up
Setting up the project is very easy, start by doing the following:
1mkdir yargs-project 2cd yargs-project 3npm init -y 4
We now have created our project's folder and initiated the package.json
file that contains its meta data.
Here's the file that was created:
package.json
:
1{ 2 "name": "yargs-example", 3 "version": "1.0.0", 4 "description": "", 5 "main": "index.js", 6 "scripts": { 7 "test": "echo \"Error: no test specified\" 8 && exit 1" 9 }, 10 "keywords": [], 11 "author": "", 12 "license": "ISC" 13} 14
We need to do a few adjustments to this file since we're creating a CLI. It should now look like this:
1{ 2 "name": "yargs-example", 3 "version": "1.0.0", 4 "description": "A simple Yargs CLI", 5 "bin": { 6 "yargs-example": "./yargs-example.js" 7 }, 8 "keywords": ["cli"], 9 "preferGlobal": true, 10 "author": "Christopher Kade", 11 "license": "ISC" 12} 13
Here are the important changes to note:
- We've added a
bin
value, which maps the entry file we'll create later to its executable name (you can set it to whatever you'd like) - We've set
preferGlobal
to true, meaning that our package would prefer to be installed globally (vianpm install -g
for example)
Other tweaks include changing the description
, removing usuned scripts
, adding an author
name etc.
Before we can start coding our CLI, we need to install yargs
, do it like so:
npm install yargs
Let's get to it.
<br><br>
<a name="cli"></a> Creating a basic CLI
Yargs makes it very easy to parse command line parameters, many example projects can be found here.
We'll create a basic CLI that takes in a file as parameter and counts the number of lines it has.
To do so, start by creating our main script file.
touch yargs-example.js
And fill it with the following:
1#!/usr/bin/env node 2const argv = require("yargs") 3 .usage("Usage: $0 <command> [options]") 4 .help("h") 5 .alias("h", "help").argv; 6
Let's cover everything line by line:
1 - #!/usr/bin/env node
is an instance of a shebang line that tells our system what interpreter to use to execute that file.
2 - const argv = require("yargs")
imports the yargs
package.
3 - .usage('Usage: $0 <command> [options]')
sets the usage information of your CLI that will be displayed when the --help
command is called.
4 - .help('h')
binds the help command to the option h
.
5 - .alias('h', 'help')
creates an alias for the option -h
, namely --help
.
As you can see, this first step is extremely simple, and yargs
syntax is intuitive.
Next we'll add the count
command.
Just add the following lines to your already existing CLI:
1.command("count", "Count the lines in a file") 2.example("$0 count -f foo.js", 3 "count the lines in the given file") 4
Once again, let's review them line by line.
1 - .command("count", "Count the lines in a file")
creates a new command with the name count
and sets a description.
2 - .example("$0 count -f foo.js", "count the lines in the given file")
creates an example with a description, it will be displayed when the user calls the --help
option or when they mess up the command.
That's all great, but right now running node yargs-example.js count
doesn't do much, next up we'll require a file name and finish the CLI by counting and displaying its number of lines.
To do so, add the following:
1.alias("f", "file") 2.nargs("f", 1) 3.describe("f", "Load a file") 4.demandOption(["f"]) 5
Your file should end up looking like this:
1#!/usr/bin/env node 2const argv = require("yargs") 3 .usage("Usage: $0 <command> [options]") 4 .command("count", "Count the lines in a file") 5 .example("$0 count -f foo.js", "count the lines in the given file") 6 .alias("f", "file") 7 .nargs("f", 1) 8 .describe("f", "Load a file") 9 .demandOption(["f"]) 10 .help("h") 11 .alias("h", "help").argv; 12
1 - .alias("f", "file")
creates the alias --file
for the -f
option.
2 - .nargs("f", 1)
sets the requirement of one argument for that option (the file name), otherwise display the --help
menu.
3 - .describe("f", "Load a file")
adds a description for the option.
4 - .demandOption(["f"])
since we'll need a file name, we're demanding the option -f
.
Finally, let's add the program's logic like so:
1const fs = require("fs"); 2 3// Create stream with the file 4const s = fs.createReadStream(argv.file); 5 6var lines = 0; 7s.on("data", (buf) => { 8 // Get the number of lines 9 lines += buf.toString().match(/\n/g).length; 10}); 11 12s.on("end", () => { 13 // Display the number of lines 14 console.log(lines); 15}); 16
And that's it, let's test it out.
1$ node line-count.js -f package.json 221 3
Up until now, we've been running our program this way, but if we tried running it by calling it directly we would get an error.
$ line-count count -f package.json
zsh: command not found: line-count
We can fix that by registering the binary (that we defined earlier as bin
in the package.json
) globally using the npm link
command.
In your application's directory, run the following:
npm link
Hurray ! You can now run your script locally like so:
yargs-example count -f package.json
<br><br>
<a name="deploy"></a> Deploying our CLI to NPM
Before deploying it, we'll need to add some information to our package.json
.
1"homepage": "YOUR GITHUB REPO OR SITE HERE", 2"repository": { 3 "type": "git", 4 "url": "git+YOUR GITHUB REPOSITORY HERE" 5}, 6"engines": { 7 "node": ">=8" 8}, 9
Don't forget to replace the homepage
and repository
info with your own, this will allow the npmjs.com website to fill up your future project's page.
The engine
value simply defines the minimum version of node your project should work on. Set it to whatever your project requires (depending on what JS features you may end up using, such as async/await
).
Here are the next steps:
- Create an account on npmjs.com
- Run the
npm login
command and input your information - Run the
npm publish
command which will automatically publish it in a matter of minutes
That's it ! If you wish to update your project in the future you'll need to change its version number in the package.json
file and then run the publish command again.
You now have your own NPM package published and accessible to the community, congrats !
If you have any questions, feel free to ask them on twitter @christo_kade.
Thank you for reading :-)