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).
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
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
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 abin
key inpackage.json
."bin": { "github-pages-commit": "bin/commit.js" }
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 typinggithub-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.
Add
shelljs
as a dependencynpm install --save shelljs
This will download the
shelljs
package and the--save
flag will add it as a dependency inpackage.json
.Test that
shell.exec()
worksTo make sure that we have a handle on how
shell.exec()
works, we’ll add a simple echo statment tobin/commit.js
.#! /usr/bin/env node var shell = require("shelljs"); shell.exec("echo shell.exec works");
Make
github-pages-commit
run a commitTo 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 rungit log
after that, you should see the commit with the commit messagegh-pages update
.
Step 3: Add the other two commands
Add
bin/push.js
#! /usr/bin/env node var shell = require("shelljs"); shell.exec("git push origin master --force");
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");
Map commands to the files
"bin": { "github-pages-commit": "bin/commit.js", "github-pages-push": "bin/push.js", "github-pages-deploy": "bin/deploy.js" }
Run
npm link
againYou 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.