August 5, 2019
TLDR at the end of the blog post.
Introduction
If you are like me, you want to read less and do more. I'll try to be brief.
We are going to build a simple CLI tool that can be installed globally using npm
or executed without prior installation using npx
. This tool will simply log "Hello World!" and I'll name it cli-tool
.
In other words, the goal is to be able to run npx cli-tool
or npm i -g cli-tool && cli-tool
.
MVP (Minimum Viable Product)
The simplest CLI tool consists of 3 steps:
-
package.json
(must include "name" and "bin") -
index.js
(must include the node shebang) -
npm link
{
"name": "cli-tool",
"bin": "./path/to/bin.js"
}
#!/usr/bin/env node
console.log("Hello World!")
We can now run npm link
inside the repo and enjoy running cli-tool
in the terminal. Note, you might want to run npm unlink
if you want to revert this.
How does it work? npm link
grabs the name from package.json
and creates a symlink to the global modules. You can read more here.
MVP + npm + npx
We could also publish our module using npm publish
. We would have to add "version" and "fields" props to package.json
.
{
"name": "cli-tool",
"version": "1.0.0",
"bin": "./path/to/bin/bin.js",
"fields": ["./path/to/bin"]
}
Note: I modified the bin path to make this easier to understand.
The version will be displayed in npmjs.com when published and the fields property is a list of whitelisted paths to include. Meaning, only those fields will be uploaded. You can read more about the "fields" property here.
Now, after publishing our module in npmjs.com, running npx cli-tool
or npm i -g cli-tool && cli-tool
is possible.
Conclusion / What next?
The MVP is very important! It shows us our goal. No matter what we do, we MUST end with a package.json
and a binary (node script).
Now that we know the goal, we can use bundlers like Webpack, Rollup, or Brunch to use the latest ECMAScript features and bundle the app into our ./path/to/bin
directory.
We can also use tools that will help us define the behavior of our CLI tool, like Commander or Yargs.
And lastly, we can use tools that allow us to release the module without all the groin pains of keeping track of the version, changes, tags, and more, like release-it and release.
I myself do not like to rebuild the wheel. I love create-react-app and there is this tool which uses Rollup
and create-react-app
internally to create react libraries, this tool is called create-react-library. I simply remove the unneeded dependencies (i.e. React) and make sure that package.json contains what is needed for the module to run as a binary.
But all this is not part of this blog post. I will release part 2 where I add these tools.
TLDR
npm init
- add "name", "bin", "version", and "fields" to
package.json
- add node "shebang" to the JavaScript script
npm publish
-
npx cli-tool
ornpm i -g cli-tool && cli-tool
End result:
package.json
{
"name": "cli-tool",
"version": "1.0.0",
"bin": "./path/to/bin/bin.js",
"fields": ["./path/to/bin"]
}
./path/to/bin/bin.js
#!/usr/bin/env node
console.log("Hello World!")
Top comments (0)