/ #JavaScript #Node 

How to make beautiful, simple CLI apps with Node

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 πŸ’‘

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: `node index.js` output

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.

Author

Hugo Di Francesco

A developer, working out of London writing CSS, JavaScript and Python.