The original link

This is a collection of best practices on how to build a successful, portable, user-friendly Node.js command-line tool (CLI).

Why write this article?

A bad CLI tool can be difficult to use, and building a successful CLI requires a lot of attention to detail and a good user experience from the user’s point of view. It’s not easy to do that.

In this guide, I’ve outlined best practices in each of the areas of focus that represent the most desirable user experience for CLI tool interaction.


  • ✅ 21 best practices for building a successful Node.js CLI tool
  • ❤️ help translate into other languages
  • 🙏 Donations welcome
  • Last updated at: 2020-02-14

Why me?

My name is Liran Tal, and I’ve been building command-line tools.

Some of my recent work has been building the Node.js CLI, including the following open source projects:

Dockly npq lockfile-lint is-website-vulnerable
An immersive terminal interface for managing Docker containers and services Check during installation to securely use the NPM/YARN installed software package Sort NPM or YARN LOCK files to analyze and detect security problems Look for public security vulnerabilities in the JS library referenced by the site

1 Command Line experience

This section covers best practices related to creating beautiful and highly available Node.js command-line tools.

1.1 respect the POSIX

✅ Correct: Use posiX-compliant command line syntax, as this is the widely accepted standard for command line tools.

❌ Error: Users may find it difficult to adapt when using the CLI and its command-line arguments are inconsistent with their past usage.

➡ ️ details:

Unix-like operating systems popularized command-line tools such as AWk, sed. Such tools have effectively standardized the behavior of command-line options “options” (aka flags), option arguments, and other operations.

Some cases:

  • Mark the option arguments “option-arguments” as square brackets ([]) in the help “help” to indicate that they are optional, or Angle brackets (<>) to indicate that they are required.
  • Arguments can be single-character abbreviations, usually-Add a letter or number.
  • Multiple types without values can be combined, such as:cli -abcIs equivalent tocli -a -b -c.

Users generally expect your command-line tools to have conventions similar to those of other Unix tools.

1.2 Building a friendly CLI

✅ True: Output as much information as possible to help users successfully use the CLI.

❌ Error: Users are frustrated because the CLI fails to start and does not provide sufficient help for users.

➡ ️ details:

The interface of the command line tool should be similar to the Web user interface to ensure that the program works as well as possible.

Building a user-friendly CLI should be as user-friendly as possible. As an example, the curl command will prompt the user to read through the curl –help output if the URL is not provided. A user-friendly CLI tool, however, displays an interactive prompt that captures the user’s input and works.

1.3 Stateful data

✅ True: Provide a stateful experience during multiple CLI calls, remembering this data to provide a seamless interactive experience.

❌ error: Users are bored when they repeatedly invoke the CLI to provide the same information.

➡ ️ details:

You need to provide continuous caching for CLI tools, such as remembering usernames, emails, tokens, or preferences that the CLI calls multiple times. You can use the following tools to preserve these configurations for users.

  • configstore

  • conf

1.4 Provide colorful experience

✅ Correct: Use colors to highlight some information in the CLI tool, and provide a demotion scheme to detect and exit automatically to avoid garbled characters.

❌ error: Pale output can cause users to lose important information, especially if there is a lot of text.

➡ ️ details:

Most command line tools support color text, enabled by a specific ANSI encoding. The command line tool outputs colored text for a richer experience and more interaction. However, unsupported terminals may output gibberish messages on the screen. In addition, CLI may be used in continuous integration where color output is not supported.

  • chalk
  • colors

1.5 Rich interaction

✅ True: Provides other forms of interaction besides text input to provide users with a richer experience.

❌ Error: The form of text input can cause problems for users when input information is a fixed option (similar to a drop-down menu).

➡ ️ details:

Richer interactions can be introduced in the form of prompt input, which is more advanced than free text input. For example, drop – down lists, radio button toggles, and hidden password input. Another aspect of rich interaction is animations and progress bars, both of which provide a better user experience when performing asynchronous work on the CLI.

Many CLI’s provide default command-line arguments without further user interaction. Do not force the user to provide unnecessary parameters.

  • prompts
  • enquirer
  • ink
  • ora

1.6 Ubiquitous hyperlinks

✅ correct: the URL ( and source code (SRC/util.js :2:75) are output using properly formatted text, as both are clickable links for modern terminals.

❌ error: Avoid non-interactive links such as gits. IO/ABC that require users to manually copy and paste.

➡ ️ details:

If the information you want to share is in a Url link, or a specific line in a file, you need to provide the user with links in the correct format that, once clicked, will open the browser or jump to a specific location in the IDE.

1.7 the zero configuration

✅ Correct: Automatically detects required configuration and command line parameters to achieve out-of-the-box experience.

❌ Error: Do not force user interaction if command line arguments can be automatically detected in a reliable way and the action invoked does not require explicit user confirmation (such as confirmation of deletion).

➡ ️ details:

Designed to provide an “out of the box” experience when running CLI tools.

  • The Jest JavaScript Testing Framework
  • Parcel, a web application bundler

2 release

This section describes best practices on how to best distribute and package node.js CLI tools.

2.1 Minimal dependencies

✅ True: Minimize production dependencies and use the smallest alternative dependency package, making sure it is the smallest Node.js package possible. However, it is not prudent to over-optimize dependence by reinventing the wheel.

❌ Error: The size of dependencies in the application determines the installation time of the CLI, resulting in a poor user experience.

➡ ️ details:

Using NPX allows you to quickly invoke node.js CLI modules installed through NPM Install, which provides a better user experience. This helps keep overall and transitive dependencies to a reasonable size.

NPM installs modules globally, and the installation process is slow, which is a bad experience. NPX always gets the modules installed by the current project (node_modules of the current folder), so using NPX to call the CLI may slow down performance.

2.2 Using file Locks

✅ Correct: Use package-lock.json provided by NPM to lock the installation package to ensure that the dependency version used by the user is correct.

❌ error: Not locking dependent versions means that NPM will resolve them itself during installation, resulting in a wider range of installed dependent versions, which introduces uncontrollable changes that may make CLI unsuccessful.

➡ ️ details:

Typically, NPM packages define only their direct dependencies and their version ranges at release time, and NPM resolves the versions of all indirect dependencies at install time. Indirect dependency versions can vary over time, as new versions of dependencies are released at any time. Although versioning semantics are widely used by maintainers, NPM introduces many indirect dependencies to installed packages that increase the risk of breaking your application. Using package-lock.json gives users a greater sense of security. The dependencies to be installed are fixed to a particular version, so they are not installed even if they are released in a newer version. This puts the onus on you to keep an eye on your dependencies, be aware of any security-related fixes in your dependencies, and make security updates by regularly releasing CLI tools. Consider using Snyk to automatically fix security issues throughout the dependency tree. Note: I’m a developer for Snyk. Reference:

  • Do you really know how a lockfile works for yarn and npm packages?
  • Yarn docs: Should lockfiles be committed to the repository?

Three general

This section describes best practices for seamlessly integrating the Node.js CLI with other command line tools, and follows conventions for proper CLI operation.

This section will answer the following questions:

  • Can I export the CLI output for analysis?
  • Can I pipe output from the CLI to input from another command line tool?
  • Can results from other tools be piped to this CLI?

3.1 Accept STDIN as input

✅ True: For data-driven command line applications, users can easily pipe data into STDIN.

❌ error: Other command line tools may not be able to provide data input directly to your CLI, which prevents some code from working properly, such as:

$ curl -s "" | your_cli
Copy the code

➡ ️ details:

If the command line tool needs to process some data, such as specifying a JSON file to perform a task, the –file file. JSON command line argument is used.

3.2 Structured Output

✅ True: Pass a parameter to allow the result of the application to be output in a structured manner, making the data easier to process and parse.

❌ Error: Users may need to use complex re’s to parse and match the CLI output.

➡ ️ details:

It is often useful for CLI users to parse data and use it to perform other tasks (for example, to feed to a Web dashboard or E-mail). The ability to easily extract the required data from the command line output will provide a better experience for CLI users.

3.3 cross-platform

✅ True: If you want the CLI to work across platforms, you must pay attention to the semantics of the command-line shell and subsystems such as file systems.

❌ error: CLI will not run on some operating systems due to factors such as incorrect path separators, even if there are no significant functional differences in the code.

➡ ️ details:

From a pure code point of view, functionality is not stripped away and should perform well on different operating systems, but some missing details can make the program unusable. Let’s look at a few cases where cross-platform specifications must be adhered to.

An incorrect command was generated

Sometimes we need to run the node. js process, assuming you have the following script:

// program.js
#! /usr/bin/env bin

// your app code
Copy the code

Then use the following method to start.

const cliExecPath = 'program.js'
const process = childProcess.spawn(cliExecPath, [])
Copy the code

The above code works, but the following is better.

const cliExecPath = 'program.js'
const process = childProcess.spawn('node', [cliExecPath])
Copy the code

Why is this better? Because the program.js code starts with a Unix-like Shebang symbol, but since this is not a cross-platform standard, Windows doesn’t know how to parse it.

The same is true in package.json, where it is incorrect to define NPM script as follows:

"scripts": {
  "postinstall": "myInstall.js"
Copy the code

This is because Windows does not understand Shebang in myinstall.js and does not know how to run it using the Node interpreter.

Instead, use the following method:

"scripts": {
  "postinstall": "node myInstall.js"
Copy the code

Different shell interpreters

Not all characters are treated the same in different shell interpreters.

For example, the Windows command prompt does not treat single quotes as double quotes, as bash shells do, so it does not know that all characters inside single quotes belong to the same string group, which can cause an error.

The following command will cause invalidation on Windows:

// package.json
"scripts": {
  "format": "prettier-standard '**/*.js'",
Copy the code

It should be as follows:

// package.json
"scripts": {
  "format": "prettier-standard \"**/*.js\"",
Copy the code

Avoid manually connecting paths

Different platforms use different path connectors, and when connecting them manually, programs cannot interoperate with each other on different platforms.

Let’s take a look at a bad example:

const myPath = `${__dirname}/.. /bin/myBin.js`
Copy the code

It uses a forward slash, but Windows uses a backslash as a path separator. So instead of building file system paths manually, use the Node.js path module:

const myPath = path.join(__dirname, '.. '.'bin'.'myBin.js')
Copy the code

Avoid semicolon link commands

We use semicolons on Linux to sequentially link commands to run, such as CD/TMP; Ls. However, the same operation will fail on Windows.

const process = childProcess.exec(`${cliExecPath}; ${cliExecPath2}`)
Copy the code

We can use && and | | :

const process = childProcess.exec(`${cliExecPath} || ${cliExecPath2}`)
Copy the code

3.4 Allowing Environment overwrite

✅ True: allows configuration to be read from environment variables, and overwrites environment variables when it conflicts with command line arguments.

❌ Error: Try not to use custom configurations.

➡ ️ details:

Adjusting the configuration using environment variables is a common method used in many tools to modify the behavior of CLI tools.

When command line arguments and environment variables are configured with the same setting, the environment variable should be given a priority to override the setting.

4 ease of use

This section shows you how to make it easier to use the Node.js CLI when users lack the environment they need for developer design tools.

4.1 Allow environment overwrite

✅ True: Create a Docker image for the CLI and publish it to a public repository like docker Hub so that users without a Node.js environment can use it.

❌ Error: Users without a Node.js environment will not have NPM or NPX and will therefore not be able to run your CLI tools.

➡ ️ details:

Downloading node.js CLI tools from the NPM repository will usually be done using the Node.js tool chain (such as NPM or NPX). This is easy to do among JavaScript and Node.js developers.

However, if you make CLI programs available to the general public, regardless of whether they are familiar with JavaScript or whether the tool is available, you limit the INSTALLATION distribution of CLI programs to NPM repositories. If your CLI tools are intended to be used in a CI environment, you may also need to install those Node.js-related toolchain dependencies.

There are many ways to package and distribute executables. Containerizing Docker containers pre-bound with CLI tools is an easy way to use and requires few dependencies (other than the Docker environment).

4.2 Graceful Degradation

✅ True: Provide output without colorful and rich interactions in an environment where the user is not supported, such as providing jSON-formatted output directly by skipping some interactions.

❌ Error: For unsupported end users, using terminal interaction can significantly degrade the end user experience and prevent them from using your CLI tools.

➡ ️ details:

Color output, ASCII charts, and terminal animation are great for users with a rich form of interaction, but for end users without these features, it can be garbled or completely unusable.

To enable unsupported terminal users to use your CLI tools correctly, you have the following options:

  • Automatic detection of terminal capabilities and run-time evaluation of CLI interactivity degradation;

  • Provide the user with an option to explicitly degrade, for example by providing a — JSON command-line argument to force the raw data out.

4.3 Node.js Versions Compatible

✅ Correct: Supports the node.js version that is still being maintained.

❌ Error: Codebases that try to be compatible with unsupported versions of Node.js will be difficult to maintain and will lose the benefit of using new features in the language.

➡ ️ details:

Sometimes it may be necessary to be specifically compatible with older versions of Node.js that lack new ECAMScript features. For example, if you are building a Node.js CLI that is primarily devops-oriented, they may not have an ideal Node.js environment or the latest runtime. For example, Debian Stretch (Oldstable) comes with Node.js 8.11.1.

If you need to be compatible with older versions of Node.js such as Node.js 8, 6, and 4, it is best to use a compiler such as Babel to ensure that the generated code is compatible with versions of the V8 JavaScript engine and with the Node.js Runtime that comes with those versions.

Never simplify your code to use some old ECMAScript language specification, as this can cause code maintenance issues.

4.4 Automatically Detecting Node.js Runtime

✅ Correct: Use a location-independent reference in the Shebang declaration that automatically locates Node.js run based on the runtime environment

The time.

❌ error: hardcode node.js runtime locations such as #! /usr/local/bin/node is specific to your own environment, which may prevent CLI tools from working in environments where other Node.js installation directories are different.

➡ ️ details:

Start by adding #! To the top of the cli.js file. /usr/local/bin/node and then start node.js CLI with nodecli.js is an easy start. However, this is a flawed approach because other users’ environments cannot guarantee the location of node executables.

We can combine #! /usr/bin/env node is a best practice, but this still assumes that the Node.js runtime is referenced by bin/node, not bin/nodejs or something else.

5 test

5.1 Do not trust the locale

✅ True: Do not assume that the output text is equivalent to the string you declared, because the test may be run in a different locale than your own, such as on a non-English speaking system.

❌ error: When a developer tests on a system other than an English language environment, the developer will encounter a test failure.

➡ ️ details:

When you run the CLI and parse the output to test the CLI, you might be inclined to use the grep command to ensure that certain characters are present in the output, such as when running the CLI without arguments:

const output = execSync(cli);
Copy the code

If you run the test in a non-English locale and the CLI parameter parsing library supports automatic locale detection and adoption, the output is converted from Examples to the language of locale and the test fails.

6 error

6.1 Error Information

✅ True: When error messages are presented, provide traceable error code that can be found in the project documentation to simplify error message exclusion.

❌ error: Common error messages are often ambiguous, making it difficult for users to search for solutions.

➡ ️ details:

When you return error messages, make sure they contain specific error codes for later review. Very similar to HTTP status codes, CLI tools require naming or encoding errors.

Such as:

$ my-cli-tool --doSomething

Error (E4002): please provide an API token via environment variables
Copy the code

6.2 Feasible errors

✅ True: The error message should tell the user what the solution is, not just that there is an error.

❌ Error: If no error message is displayed, you may fail to use the CLI.

➡ ️ details:

Such as:

$ my-cli-tool --doSomething

Error (E4002): please provide an API token via environment variables
Copy the code

6.3 Debugging mode

✅ True: Provide advanced users with more detailed information if they need to diagnose problems

❌ Error: Do not turn off debugging. Because just collecting feedback from users and letting them pinpoint the cause of the error would be particularly difficult.

➡ ️ details:

Use environment variables or command-line arguments to set debug mode and turn on verbose output information. Where it makes sense in the code, embed debug messages to help users and maintainers understand the program, inputs and outputs, and other information that makes problem solving easier.

Refer to open source software packages:

  • debug

The author

Js CLI Apps Best Practices © Liran Tal, Released Under CC BY-SA 4.0 License.