Building a Better CLI
Node Brigade 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.
We hope those
four simple suggestions
help improve your CLI.