How to make beautiful, simple CLI apps with Node
Tags: javascript, node, cli, productivity
Published: 2018-06-20 13:28:49


---
title: How to make beautiful, simple CLI apps with Node
published: true
tags: JavaScript,node,cli,productivity
canonical_url: https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node/
cover_image: https://codewithhugo.com/img/cli-screenshot.jpeg
---

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 💡](https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node#the-idea)
- [Piping to the command line 🚇](https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node#piping-to-the-command-line)
- [Dealing with sequential actions ✨](https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node#dealing-with-sequential-actions)
- [Executable JavaScript files 🦅](https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node#executable-javascript-files)
- [Adding package binaries 📦](https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node#adding-package-binaries)
- [Publishing to npm 🚀](https://codewithhugo.com/how-to-make-beautiful-simple-cli-apps-with-node#publishing-to-npm)

[Subscribe](https://buttondown.email/hugo) 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`
```js
const execa = require('execa');

execa('ls').then(result => console.log(result.stdout));
```

```sh
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](https://github.com/SamVerschueren/listr), it allows us to do things that look like:

<img src="https://raw.githubusercontent.com/HugoDF/fix-package-lock/master/fix-package-lock.gif">

Run `npm install --save listr` and add leverage Listr as follows:

`index.js`:
```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](/img/20180619-running-screenshot.jpeg)

[Listr](https://github.com/SamVerschueren/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](https://github.com/SamVerschueren/listr)

## 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:
```js
#!/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](https://github.com/sindresorhus/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](https://buttondown.email/hugo) to get the latest posts right in your inbox (before anyone else).