chenglong

Apr 05, 2023

The Landscape of npm Packages for CLI Apps

notion image

Node.js is a very popular choice for developers writing CLI applications. The platform’s rich developer ecosystem has a large number of mature packages to help create elegant, interactive command line tools.
One challenge for developers is understanding the landscape and selecting the right set of packages. There are tons of libraries to choose from but many are rather dated. Some have stalled in development, some have been abandoned by their maintainers, and many have not caught up with modern development practices. This post is meant to categorize the most popular packages and highlight a few of them and their current development state.
To quickly try out any of the mentioned packages, you can use the following tool:
$ npm install --global try-node-cli-packages $ try commander $ try meow $ try chalk
The repo for this tool is available here: joeykilpatrick/try-node-cli-packages.

🗒 A Note

Modern JavaScript/TypeScript development has evolved dramatically over the last decade. TypeScript’s ever-growing popularity and the introduction of new Node.js and ECMAScript features has changed common programming idioms. Unlike modern web development, modern CLI development doesn’t have new frameworks and libraries popping up every week. Instead, a much more mature ecosystem has emerged, but it is slow to adapt to changes in the language.
Remember that many of the most popular libraries listed here use programming styles that are slightly dated. Most major ones were designed before ES6 classes were released in 2015 and while all libraries listed here have either added type declarations (.d.ts files) to the package or have types available through DefinitelyTyped, virtually none were designed with strong type inference in mind. Today, no major library has been rewritten in TypeScript.
Also, be cautious not to give too much weight to the number of weekly downloads for each package. These metrics should not be relied upon as an indicator of popularity with developers. Inclusion in a major package can dramatically skew the download numbers. For example, this week there were 22 million downloads of arg. However, the popular ts-node package includes arg as a dependency and it has been downloaded 19 million times this week.
Many packages here have trade-offs. Some prioritize speed, some prioritize a small package size, and some prioritize developer experience. Only you can choose which library best fits your needs.

🤝 Major Contributors

While there are hundreds of contributors to these libraries, there are some names that repeatedly come up as creators, maintainers, and contributors to packages in this space. Consider sponsoring their open-source work on GitHub. I am not affiliated in any way. Packages with large contributions from these users are marked with the corresponding emoji in the lists below.

sindresorhus 🦄

Sindre Sorhus is responsible for many of the most widely used packages for output styling in the terminal. Besides well-known packages like chalk and meow, he has authored many packages that are used internally by other major packages such as cli-cursor (used by inquirer, ora, and ink) and supports-color (used by mocha, nodemon, and serverless).

lukeed 🐒

Luke Edwards is the author of multiple lightweight, performance-focused CLI libraries. His package kleur is a lightweight alternative to chalk and is used primarily by prompts. His package mri is a high-speed alternative to minimist.

Qix- 🦀

Qix is the creator of arg and is a frequent collaborator of Sindre Sorhus with both users co-authoring packages in the chalk GitHub Org.

Argument Parsing

These libraries form the base of most CLI apps. Some of these packages style themselves as “argument parsers”, some as “CLI helpers”, but at their core they all help developers declare which arguments, commands, and flags that the app expects and automatically parse the arguments provided via process.argv. Typically, a developer should only need one of these.
Package
Version
Downloads
Last Publish
First Publish
Types
10.0.0
109 M
3 days ago
August 2011
Includes .d.ts
21.1.1
79 M
6 months ago
January 2016
DT Types
17.6.2
77 M
2 months ago
November 2013
DT Types
1.2.7
53 M
3 months ago
June 2013
DT Types
7.0.0
25 M
3 months ago
March 2011
DT Types
arg🦀
5.0.2
22 M
7 months ago
October 2017*
Includes .d.ts
meow🦄
11.0.0
18 M
3 months ago
October 2014
Includes .d.ts
mri🐒
1.2.0
7 M
1 year ago
April 2017
Includes .d.ts
sade🐒
1.8.1
1.9 M
1 year ago
May 2017
Includes .d.ts

📦 commander, sade, yargs

These are opinionated packages that all use a “fluent” interface of chained methods to describe the CLI app. They all come with automatically generated help text and other useful features out-of-the-box. Because of the “fluent” interface, the possibility for strong type inference for flags and arguments is limited.
The oldest package in this list is commander which is used by CLI apps like webpack-cli and babel-cli. This no-dependency library is very large and feature-rich with lots of documentation. Development is still active on the project with new features released often and a very high response rate to issues and PRs on GitHub.
A faster, lightweight alternative to commander is sade. With a package size of 31.5 KB as opposed to commander’s 174 KB, it offers all the same core functionality with an almost identical interface. Development is not active on the project, but it still serves well as a simpler replacement for commander.
The other comparable package is yargs, used by CLI apps such as mocha and nyc. One of its advantages is support for messages in a variety of different locales. While bigger than commander at 290 KB, it doesn’t provide a lot more functionality. The syntax also relies more heavily on nested callback-style arrow functions which may be less readable for some developers.

📦 meow

As an alternative to the opinionated ones above, meow is a smaller, unopinionated package. It uses a declarative style interface to define the expected flags instead of the “fluent” one used in the packages above. Due to this declarative style, type inference is much stronger than all others in this list. It comes with a few useful features like automatic version and help flags and provides lots of flexibility for the developer. It is also one of the first to only be available as an ES Module. Its repo is active but new releases are mostly limited to maintenance and bug-fixes.

📦 minimist, yargs-parser, mri, nopt, arg

For developers who are looking for the maximum amount of control, these packages are bare-bones argument parsers. Their scope is only to parse the provided flags and arguments. While all of them include a feature for flag aliases, these packages typically don’t even include features like required flags, unexpected flags, default flag values, or help messages. Type inference for parsed flags is virtually non-existent, with most typed as any. Most of these packages are no longer actively developed and are considered completed.
The minimist package was the argument parser for the now-defunct optimist package.
The yargs-parser package is the underlying argument parser for yargs and meow and styles itself as the successor of minimist. At 128 KB, it is substantially larger than every other parser in this section and is feature-rich (some might say bloated). It is used directly by popular CLI apps like mocha and ts-jest.
The mri package is an ultra-fast, lightweight alternative to minimist and yargs-parser with an identical interface. It is about 10% of the size of yargs-parser. It is also the underlying parser for the package sade.
An alternative to the interface in the previous three packages can be found in the nopt package which was developed by npm for the npm package. It is feature-rich with multiple additional types of flag values like “path” or “stream” values. It also supports flag aliases a bit differently than the others listed here which may help some developers write more expressive flag aliases.
The arg package has a similar interface to nopt but a bit more stripped down. It is used by ts-node.

Output Styling

These libraries allow developers to manipulate how terminal output is displayed. They add string formatting, colors, or small text-based animations.
Package
Version
Downloads
Last Publish
First Publish
Types
ansi-styles🦄🦀
6.2.1
232 M
3 months ago
July 2013
Includes .d.ts
chalk🦄🦀
5.2.0
189 M
1 month ago
August 2013
Includes .d.ts
8.0.1
70 M
1 year ago
August 2015
DT Types
6.0.0
31 M
4 months ago
August 2015
Includes .d.ts
kleur🐒
4.1.5
17 M
7 months ago
November 2018
Includes .d.ts
ora🦄
6.1.2
16 M
7 months ago
March 2016
Includes .d.ts
boxen🦄
7.0.1
12 M
1 month ago
December 2015
Includes .d.ts
0.14.3
2 M
4 years ago
June 2016
DT Types

📦 chalk, kleur, ansi-styles, …

There are many, many packages dedicated to providing colors and formatting for terminal text. The process includes inserting ANSI escape sequences into the text to tell the terminal how the text should be formatted. There are lots of different terminal implementations and lots of edge cases to consider.
The most well known of these packages is chalk which is one of the most depended-on packages on npm with over 90,000 dependent packages. It is easy to use, extremely well maintained, and extremely reliable.
Over the years, similar packages to chalk have been developed, forked, and rebranded, leading to lots of different packages that all do nearly the same thing. Competitors to chalk include colors, picocolors, ansi-colors, colorette, kleur, and nanocolors. The history of some of these other packages is wild.
Of the packages listed here, only colors predates chalk. It was famously sabotaged by its owner in January 2022 causing widespread issues for numerous tools (article, article, article, owner’s manifesto). The package was reverted by npm and the author was suspended from GitHub. Since he was the only maintainer of the colors package, it is de-facto abandoned. In the aftermath, many dependent packages switched to chalk.
While not as exciting as the colors disaster, there has been plenty of drama in the open-source community around other chalk alternatives. Consider this August 2018 clash between the maintainers of ansi-colors and colorette, or this September 2021 train wreck between the maintainers of colorette and nanocolors (with input from the maintainers of chalk).
Those who have attempted to improve on chalk have tried to create alternatives that are faster or smaller or that have fewer dependencies. In these attempts, authors have removed features, missed edge cases, and sometimes introduced bugs and memory leaks. Multiple authors have published metrics using their own benchmarks for comparing their creations to chalk. But a true apples-to-apples comparison between these packages is impossible if they don’t cover the same edge cases and include the same set of features. Meanwhile, chalk has continued to improve and is now much faster (as of v3), much smaller (as of v5), and dependency-free (as of v5).
For the vast majority of packages both large and small, chalk should be used. For those who need a smaller package size with fewer features, consider kleur which is well maintained and non-controversial. For a much smaller package size with only the absolute basics, consider the new yoctocolors package from the same author as chalk at a tiny 7.6 KB.
For packages that need very fine-grained controls for inserting ANSI escape codes, ansi-styles is from the same maintainers as chalk and has a lower-level interface that allows developers to insert individual ANSI codes into their text.

📦 ora

The ora package provides controllable spinner animations for the terminal. A large number of spinner animations are included through the cli-spinners package, but custom animations are possible as well. The provided interface works great for common use cases such as displaying an action in progress that may succeed or fail.

📦 boxen, wrap-ansi

These packages help format blocks of terminal text. To wrap text to a specific number of columns, use wrap-ansi. To draw a box around your text, use boxen. It comes built-in with lots of different types of borders.

User Input

Node.js provides user keyboard input through process.stdin and readline, but using either of these directly is challenging. Multiple packages are available that output a prompt to the console and wait for user input. Some single-purpose libraries are available for some common use cases such as password-prompt for displaying typed input as ‘***’ and yesno or prompt-confirm for user confirmation. The following libraries are general-purpose user input libraries. Typically, a developer should only need one of these.
Package
Version
Downloads
Last Publish
First Publish
Types
9.1.4
26 M
3 months ago
May 2013
DT Types
2.4.2
20 M
1 year ago
February 2018
DT Types
2.3.6
13 M
3 years ago
August 2016
Includes .d.ts
3.2.0
1 M
2 years ago
January 2013
DT Types
1.4.10
900 K
4 years ago
August 2013
DT Types
1.3.0
600 K
9 months ago
March 2011
DT Types

📦 inquirer, prompts, enquirer

These three packages have similar interfaces and have built-in support for common prompt types. They use some slightly different terminology in their interfaces (e.g. default vs. initial, or title vs. name) and sometimes behave differently in unexpected ways. For instance, when a user presses the CTRL+C sequence, inquirer immediately kills the process, enquirer throws an error, and prompts cancels the prompt but continues execution without error. Additionally, all three have some demonstrably wrong types that make development with TypeScript difficult.
The inquirer package is the oldest of the three and has the largest market-share by downloads and number of dependent packages. It is strengthened by its ability to register custom prompt types via other 3rd party packages such as inquirer-autocomplete-prompt, inquirer-directory, and inquirer-checkbox-plus-prompt. It is still under active development by its author and has been undergoing a full refactor since 2018.
The prompts package has the most accurate type declarations and a full TypeScript rewrite is planned, though development has stalled. Currently, there is no easy way to extend and customize prompt types. For tests, the package allows developers to “inject” answers to the prompts, bypassing stdin.
The enquirer package was originally meant to be a smaller, faster, better alternative to inquirer, but today enquirer is twice the size of inquirer. It added many additional types of useful, elegant prompts like scales, sorts, and snippets. It also introduced a new way to extend and customize existing prompt types using ES6 classes. However, type declarations were never added for this new feature and the package has not seen a new release in over 3 years.

📦 promptly, readline-sync, prompt

These are less-mature packages than the ones above and don’t offer nearly any additional functionality. They have less elegant user interfaces and do not offer built-in features for common kinds of prompts such as numbers, lists, or selections. Also, while promptly is substantially smaller than the ones above at only 15.5 KB, the other two are about the same size as prompts and enquirer. They are not actively developed or maintained and should probably not be used in new projects.

Frameworks

All the packages above are designed to handle one particular aspect of CLI development. However, there are a few other packages that don’t fit into any of the above categories and aim to provide developers with an entire suite of tools to build their CLI app.
Package
Version
Downloads
Last Publish
First Publish
Types
2.0.8
794 K
5 hours ago
February 2018*
Native
ink🦄
3.2.0
283 K
1 year ago
July 2017*
Native
1.12.0
39 K
6 years ago
August 2015
DT Types

📦 oclif

In 2018, Salesforce announced that they were open-sourcing their oclif framework that they had built to develop some of their CLI apps (e.g. the Heroku CLI). It is a large, sprawling framework with plugins, auto-generated scaffolding, built-in testing tools, and more. It is heavily opinionated and is reminiscent of large web frameworks like Angular or React.
For giant CLI apps with lots of git-style subcommands, oclif is a standout choice. For smaller CLI apps, it may be overkill.

📦 ink

A fundamentally different paradigm than all other packages listed in this post, ink allows developers to define React components that are then rendered for the terminal. Instead of having to format and print individual strings of text output, ink allows users to define reactive XML components that are repeatedly re-drawn in the user’s terminal. The expressive interface lets developers define multi-line terminal animations that would otherwise be extremely complex and brittle.
Out of all the packages in this list, I find this one to be the most astonishing. The concept and the execution of this package are wildly impressive. This package redefines what is possible with terminal output and a CLI user’s experience.

📦 vorpal

Most CLI apps follow the same execution model: a user runs a terminal command which spawns a process that then parses the provided arguments and options, executes some action, and then closes. The vorpal package takes a very different approach, styling its method as “immersive”. When a vorpal application is run, its process opens a new sub-terminal that allows users to input further application-specific commands. This kind of application can be viewed not as a set of isolated commands, but an entire implementation of a shell. An entirely new set of possibilities emerge with this new paradigm. Variables set by previous commands can be accessed by subsequent calls. Applications can support autocompleting commands. Users can pipe commands together. The code for defining sub-commands uses the same style of “fluent” interface of commander and others, which comes with the same set of drawbacks.
While this approach is novel, it has failed to see significant usage and development. The vorpal project was abandoned in 2018. An effort to fork and “reforge” it was also abandoned in 2019. The cliffy package appears to be inspired by vorpal and is under somewhat active development, but is even less mature and has seen little adoption.
Today, vorpal is more of an inspiration for a future “immersive” CLI package rather than a viable option for new applications.

Copyright © 2024 chenglong

logo