npm Blog (Archive)

The npm blog has been discontinued.

Updates from the npm team are now published on the GitHub Blog and the GitHub Changelog.

Building a simple command line tool with npm

We asked what you planned to use private modules for, and one of the most common answers was command line tools for teams to use when developing projects. In this two part series, we’ll walk through how to make one of those command line tools.

The scenario: easy deployment for GitHub pages

Deployment can be a headache, which is why you want to automate it. There are lots of task runners that can automate tasks for you, but one of the simplest ways is simply to use npm run scripts.

We use GitHub pages deployment as an example here, but you could really use this for deployment to any service, such as Heroku or AWS. And if you publish it as a private package, you can even include company-specific code or config.

npm run scripts

With npm run scripts, you can define strings which will be run in the command line when you invoke the script. For example, you could have a script called patch-release


#package.json
{
  "name": "my-project",
  "version": "1.0.0",
  "scripts": {
    "patch-release": "npm version patch && npm publish && git push --follow-tags"
  }
}

When you run npm run patch-release, it will use npm version to update the version number in package.json and commit the change, then publish the changed package to npm, and then push the changes to GitHub.

You don’t have to limit your commands to those that you have available globally on the command line. If you have modules installed as dependencies or devDependencies, their commands will be available to you. And you can even invoke other npm run scripts within your scripts.

Publishing pages with a script

So here’s our example script object for deploying to GitHub pages (inspired by git-it), where the build step is specific to the project:

"scripts": {
    "build": "...",
    "git-commit": "git add -A . && git commit -a -m 'gh-pages update'",
    "git-push": "git push origin gh-pages --force && git checkout master",
    "deploy": "npm run build && npm run git-commit && npm run git-push"
  },

Then, it’s as easy as running npm run deploy whenever you want to deploy.

What could be better?

This is great. It means that you don’t have to remember a long string of commands and the flags that go with those commands. However, you do have to copy and paste this object to every one of your projects if you you want to deploy it this way. And that means lots of duplicated code across your projects.

So how can we follow the “don’t repeat yourself” principle? We could create a module which bundles up each string of commands into a single command. This means that we can easily use and maintain the commands across different projects and share them with the rest of our team.

You can find the code on GitHub, or follow the steps below.

Step 1: Make a basic command line interface

First, we’ll create a basic command line interface (also called a CLI).

  1. Create the package manifest

    The package manifest (the `package.json` file) contains the metadata for your package, like the package name and the version.

    You’ll want to use a scope in the package name for this package. If you’re building the package for an internal team, you may want to consider making it a private package. The scope should be your username.

    npm init --scope=linclark
  2. Create a script to run as a command file

    Because we’re going to be running this from the command line, we’ll need to start the file with an interpreter directive (sometimes called a shebang line).

    #! /usr/bin/env node
    console.log("console.log output")
          

    Now test this by running node bin/commit.js

  3. Tell npm what your command is

    We want to be able to run this as a command. To do this, we need to tell Node where it can find the file to run (the executable). We’re going to use the command name github-pages-commit. To do this, we add a bin key in package.json.

    "bin": {
      "github-pages-commit": "bin/commit.js"
    }
    
  4. Make the command available

    In order to test that your command is being picked up, use npm link to have the system perform some symlinking operations. This should make the command available for you to run by typing github-pages-commit on the command line.

Step 2: Make the commit command work

Now that we’ve verified that we have a working CLI, we can add our commands to it.

npm run scripts are convenient because you can simply use the commands that you use on the command line. Running those scripts inside a reusable module can be just as convenient, and we want it to be convenient… otherwise our teammates won’t make their scripts reusable.

We’ll be using the shelljs module to do this. You could also use Node’s child_process for this, but one nice thing about shelljs is that it makes it possible for you to use a lot of Unix commands in Windows. See the docs for a full list of supported commands.

  1. Add shelljs as a dependency

    npm install --save shelljs

    This will download the shelljs package and the --save flag will add it as a dependency in package.json.

  2. Test that shell.exec() works

    To make sure that we have a handle on how shell.exec() works, we’ll add a simple echo statment to bin/commit.js.

    #! /usr/bin/env node
    var shell = require("shelljs");
    
    shell.exec("echo shell.exec works");
          
  3. Make github-pages-commit run a commit

    To do this, we’ll simply add the commands that we’d use to run a commit on the command line.

    #! /usr/local/bin/node
    var shell = require("shelljs");
    
    shell.exec("git add -A . && git commit -a -m 'gh-pages update'");
          

    To test this, open up another command line window and go to the repo that you’re using with GitHub pages. Make a change and then run github-pages-commit. When you run git log after that, you should see the commit with the commit message gh-pages update.

Step 3: Add the other two commands

  1. Add bin/push.js

    #! /usr/bin/env node
    var shell = require("shelljs");
    
    shell.exec("git push origin master --force");
    
  2. Add bin/deploy.js

    We can compose this command using the other commands we’ve defined.

    #! /usr/bin/env node
    var shell = require("shelljs");
    
    shell.exec("github-pages-commit && github-pages-push");
    
  3. Map commands to the files

    
    "bin": {
      "github-pages-commit": "bin/commit.js",
      "github-pages-push": "bin/push.js",
      "github-pages-deploy": "bin/deploy.js"
    }
    
  4. Run npm link again

    You need to run npm link again so that it will create the two new symlinks.

    Now you can make another change and run github-pages-deploy to test the full task.

Step 4: Publish your package

If you’ve never published a package to npm before, you can read the docs to get started with it.

Because this is a scoped package, if you want to publish it publicly, you’ll have to use the access option. You can read more in the scoped packages docs.

npm publish --access=public

If you’re going to publish this as a private package, and you you’re already a paid member, then you can just run npm publish.

Step 5: Add your commands as npm run scripts

We could tell our team to install this module globally. Then all of the commands would be available for them to run on the command line and we wouldn’t need to use npm run scripts. However, global installation has a couple drawbacks: it means that new developers have one more thing they have to do before getting started on your project, and it means that your team needs to remember to run updates on global modules in addition to updating the project dependencies.

Instead, we’re going to make it one of the devDependencies in the package.json for the project we want to deploy. This way, it will be downloaded when the new developer runs npm install to get started with your project, and updated every time the developer runs npm update.

npm install --save-dev @linclark/github-pages-deploy

Now you can add the reusable command as an npm run script.

"scripts": {
  "build": "...",
  "deploy": "github-pages-deploy"
},

Where to go from here

So now you have a basic command line tool for deploying GitHub pages. How can you improve this tool? In the next post, you’ll add things like configuration and help documentation to make your commands more useful to the rest of your team.