The npm blog has been discontinued.
Updates from the npm team are now published on the GitHub Blog and the GitHub Changelog.
Adding subcommands to your command line tool
This is the second post in a tutorial on command line tools.
- Building a simple command line tool with npm
- Adding subcommands to your command line tool
- Making your command line tool configurable
In the last post, we made a simple command line tool. In this post, we’ll add subcommands to that tool.
You might not have heard of subcommands, but you’ve certainly used them before. Examples of subcommands are install
in npm install
or push
in git push
. Subcommands make it possible for you to group related command line functionality under one root command.
Before we start
This builds on the last post. To get started, you can either read that, or just download the code.
At the end of the post, we had a version 1.0.0
of our module. We’re going to be making breaking changes to the code. For example, in the 1.0.0
version of the module we had commands like github-pages-push
. We’re going to change this to be github-pages push
, which means anyone who is using the original command in their code is going to need to update it.
In semantic versioning, when you make a breaking change, you increment the first number. So our new version will be 2.0.0
, but since we’re still working on it we might make some breaking changes before it’s really ready for 2.0.0
. To indicate that to our consumers, we’ll use 2.0.0-alpha.0
.
npm version 2.0.0-alpha.0
Step 1: Add the root command
The root command is the command that comes before the subcommand, like npm
or git
.
Add
yargs
as a dependencynpm install --save yargs
We’ll be using
yargs
to handle the subcommands.Create an executable for the root command
Create a file named
bin/github-pages.js
with the following code:
#! /usr/bin/env node
var yargs = require("yargs");
var argv = yargs.usage("$0 command")
.command("commit", "commit changes to the repo")
.command("push", "push changes up to GitHub")
.command("deploy", "commit and push changes in one step")
.demand(1, "must provide a valid command")
.help("h")
.alias("h", "help")
.argv
This code defines three subcommands, commit
, push
, and deploy
and provides help messages for each. It also adds a demand. If none of these commands are given when the root command is run, then the demand will not be met and the output will say “must provide a valid command”.
In addition, it adds options for showing help. Running the root command without any subcommands will display this help.
Add the root command
In `package.json`, we defined three separate commands in the `bin` key. We now want to replace those separate commands with a single root command.
"bin": {
"github-pages": "bin/github-pages.js"
}
Run npm link
to pick up the change. Then run github-pages
to test the command. You should see a help message.
Step 2: Make the subcommands work
The subcommands will run the code that we previously had in bin/commit.js
, bin/push.js
, and bin/deploy.js
.
Add a handler for the subcommand
We need to tell yargs what should happen when someone passes in a subcommand, so we"ll pass in a function to handle the subcommand.
.command("commit", "commit changes to the repo", function (yargs) { console.log("committed") })
Now when you run
github-pages commit
, it should logcommitted
.Require
shelljs
You will need to require
shelljs
like you did in the previous post.var shell = require("shelljs");
Fill in the functionality for the subcommands
.command("commit", "commit changes to the repo", function (yargs) { shell.exec("git add -A . && git commit -a -m 'gh-pages update'"); }) .command("push", "push changes up to GitHub", function (yargs) { shell.exec("git push origin master --force"); }) .command("deploy", "commit and push changes in one step", function (yargs) { shell.exec("github-pages commit && github-pages push"); })
Step 3: Update the module on npm
Set a stable version number
npm version patch
Publish the module to npm
If you’re publishing this as a private module, you can just run
npm publish
. If you’re publishing it as a public scoped module and this is your first time running the publish command, you will need to use the access option:--access=public
.