Channel your inner Sindre Sohrus and ship a beautifully simple CLI app using Node.
Command line apps are a neat way to package repetitive tasks. This will walk you through some tools
that are useful to build CLI apps.
- The idea 💡
- Piping to the command line 🚇
- Dealing with sequential actions ✨
- Executable JavaScript files 🦅
- Adding package binaries 📦
- Publishing to npm 🚀
Subscribe to get the latest posts right in your inbox (before anyone else).
The idea 💡
When merging/rebasing, the file that always seems to cause trouble is the package-lock.
We'll go through how to make a simple utility that deletes the package-lock.json file, regenerates it (npm install) and adds it to the git index.
You can find it here: https://github.com/HugoDF/fix-package-lock and run it using npx fix-package-lock
.
Piping to the command line 🚇
To start off, we'll leverage a package from Sindre Sohrus, execa
, which is described as “a better child_process
". For the following snippet to work, run npm install --save execa
:
index.js
const execa = require('execa');
execa('ls').then(result => console.log(result.stdout));
node index.js
index.js
node_modules
package-lock.json
package.json
Dealing with sequential actions ✨
To re-generate the package-lock
we'll need to first delete it, then run an npm install
.
To this end, we can use Listr, it allows us to do things that look like:
Run npm install --save listr
and add leverage Listr as follows:
index.js
:
const execa = require('execa');
const Listr = require('listr');
new Listr([
{
title: 'Removing package-lock',
task: () => execa('rm', ['package-lock.json'])
},
{
title: 'Running npm install',
task: () => execa('npm', ['install'])
},
{
title: 'Adding package-lock to git',
task: (ctx, task) =>
execa('git', ['add', 'package-lock.json'])
.catch(() => task.skip())
}
]).run();
Now the output of node index.js
looks like the following:
Listr gives you a loading state when you have a
long-running task that returns a Promise (like the execa
invocation of npm install
).
It's also possible to display a message that changes using Observables, for more information see the Listr docs
Executable JavaScript files 🦅
It's ideal to be able to execute our script using ./index.js
instead of node index.js
.
To do this, we need the file to be executable on UNIX systems that's: chmod +x
. So
chmod +x index.js
We then need to inform the system how it should attempt to run the file, that's using the following hashbang:
#!/usr/bin/env node
If we add it to index.js
we get:
#!/usr/bin/env node
const execa = require('execa');
const Listr = require('listr');
new Listr([
{
title: 'Removing package-lock',
task: () => execa('rm', ['package-lock.json'])
},
{
title: 'Running npm install',
task: () => execa('npm', ['install'])
},
{
title: 'Adding package-lock to git',
task: (ctx, task) =>
execa('git', ['add', 'package-lock.json'])
.catch(() => task.skip())
}
]).run();
Which we can now run using:
./index.js
Adding package binaries
npm has a bin
field which we can use like the following (in package.json
):
{
"name": "beautiful-cli",
"version": "1.0.0",
"description": "A simple CLI",
"main": "index.js",
"bin": {
"fix-package-json": "./index.js"
}
"dependencies": {
"execa": "^0.10.0",
"listr": "^0.14.1"
}
}
Publishing to npm 🚀
This is left to the reader as an exercise, although using the np
package, it's super straightforward.
Hint: run npx np
in whatever package you're trying to publish
You can find the full package at You can find it here: https://github.com/HugoDF/fix-package-lock and run it using npx fix-package-lock
.
Subscribe to get the latest posts right in your inbox (before anyone else).