Back button to all articlesAll articles

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 (via npm 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.

<br>

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.

<br>

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.

<br>

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 :-)