Fil Maj & Michael Brooks

Master the CLI with Node

JSConf 2013



Fil Maj


Michael Brooks


We want to

share patterns

that help us build good CLIs.

These patterns emerged from

concepts we discovered

the hard way.

For years

we have been building tools

to assist our project.

Some of the tools are

used by people

to simplify development.

Some of the tools are

used by tools

to delegate responsibility.

While building the tools, we

found CLI qualities

that we think are useful.

So we have

hand picked four

to share with you today.

And we'll explain

how to implement each

of these in Node.

But remember

these are suggestions

not requirements.

A command-line interface should be testable.

For a long long time

we struggled to test

the command-line interface.

Originally we

tested by shell execution

but tests were slow and vague.

What we really want are

programmatic tests

for the command-line interface.

Turns out, we can do this by

treating the CLI as a module

instead of an executable script.

First, we have to

slim down the bin/

to proxy into the CLI module.

Second, the

CLI should be a module

that consumes the main library.

This allows us to

test the CLI as a module

by faking process.argv.

Lastly, the CLI module

should only have CLI logic

keeping it focused.

Allowing us to

mock the main library

and keep tests running fast.

And with that

we have a testable CLI

without doing anything fancy.

A command-line interface should be helpful.

The fact is

people are using your tool

not omniscient gods.

We need to have

meaningful help output

to assist people with the tool.

And while it is really

convenient to hard-code

those helpful messages.

It results in

ugly strings

buried deep in the code.

That we

forget to update

because it's out of sight.

But thankfully

we can do better

with something very obvious.

We can

use text files

that are easy to read and edit.

And we can write code to

find, read, and regurgitate

the files from given commands.

And when

plain text isn't good enough

use templating and markup libraries.

A command-line interface should be bipolar.

Sometimes it's nice to have

a verbose tool

when something goes wrong.

Sometimes it's nice to have

a quiet tool

when everything goes right.

But when the CLI is decoupled

how do we implement this

into the tool?

We have found

event emitters work well

and add no extra dependencies.

The idea is that the

main library is verbose

like a chatty kid.

And the

CLI module can subscribe

or unsubscribe to the chatty kid.

So now we have

detailed messages

that can be toggled on or off.

A command-line interface should be interoperable.

Our parents always said it's

important to play nice

with the other kids.

It's not uncommon for

tools to use tools

to build better tools.

But this is only possible with

good communication

between the tools.

In other words,

exit codes

are important.

We may also want

consumable output

for other tools to parse.

And don't forget about

playing nice on Windows

the kid that's always picked last.

Try to

avoid hard-coding paths

into your code.

Remember to

use environment variables

for the details.

And thankfully

npm is Windows-friendly

scripts add binaries to PATH.

And that's it.

We hope those

four simple suggestions

help improve your CLI.


@filmaj & @mwbrooks