Comparing CLI Building Libraries
Published on April 20, 2021

Nexmo has a CLI, which we use as an alternative to the Dashboard. It allows you to manage your Nexmo account and use Vonage products from the command line. We've had this tool for about 4 years, and it is written in Node.js.

Last week I wrote about why we're taking the time to re-write it, and I shared a bit about the process we're using to re-write the Nexmo CLI.

Today, I'm going to go into more detail, share the frameworks we analyzed, and the criteria we used to do so. I'm also going to show you some pros and cons of the ones we picked to build our proofs of concept with.

Benchmark Criteria

After we went through our internal CLI retrospective and identified a set of requirements, we put together a list of example commands. These commands helped us come up with a set of criteria to benchmark libraries used to build Command-Line Interfaces. Our criteria looked to answer a few questions:

  • What language does the library support?

  • Is it actively maintained?

  • Does it support sub-commands? i.e. nexmo app list

  • Does it have built-in support for multiple output formats?

  • Does it have a plugin mechanism?

  • Can commands have multiple aliases?

  • Can it generate binaries?

  • How does config management look like?

  • Is it cross-platform?

  • Does it have command autocomplete?

  • Can it have interactive commands?

  • Can we define global flags?

Armed with this list of burning questions, we set on a quest to come up with as many CLI building libraries that ticked most of the boxes and check off their features against our list of qualifying criteria. In the end we narrowed it down to six libraries, for JavaScript, TypeScript and Go, based on the available language skills in the team: oclif, gluegun, ink, caporal, cli and cobra.

Feature Comparison

We went through each framework homepage and picked up on the features they supported, creating an analysis matrix. We used to mean the framework has full support for that feature, ❎ to mean the framework doesn't support that feature and ✳️ that there was only partial support. Here is how our matrix looked like for the 6 frameworks we identified:

Framework oclif gluegun ink caporal cli cobra
Language JS/TS JS React JS Go Go
Maintained ✳️
Sub-command
Output Formats ✳️ ? ?
Plugins ✅✅ ? ?
Alias
Bin ? ?
Config Management ? ?
Windows Support ✳️
Autocomplete plugin
Interactivity ✳️ ? ?
Global flag definition

Looking at the feature checklist we couldn't identify a clear winner, especially since there were still some unknowns. So we decided to pick 3 frameworks and build a proof of concept with each one of them.

PoCs

Our first pick to build a proof of concept was oclif. The main reason we chose it was because it seemed to tick most of our boxes, some even twice (it had plugin support, and a plugin to build plugins with).

The second pick was caporal because the library seemed reasonably similar to our current framework, commander. This would mean that the learning curve and the time to re-write it would have been considerably less.

Finally, our last pick for the proof of concepts was ink, and we chose it because it ticked enough of the boxes to make it worthwhile and has a massive ecosystem behind it.

Once we identified the frameworks, we came up with a scope for the proof of concepts. We wanted something representative of the final CLI instead of building a Hello World example. At the same time, it had to be small enough that we wouldn't feel bad throwing away the proof of concept at the end of this exercise. We landed on building the current nexmo setup and nexmo number:list commands. That meant we could test global flags, config management, sub-commands, output formats, interactivity, and various language frameworks.

Picking Our Next CLI Building Library

Lorna, Dwane and myself each picked up one of the three frameworks, and we started building our proofs of concepts. Once we were done, we showcased some of the pros and cons of working with each library and how that relates to some of our other requirements.

Caporal

Lorna built the caporal PoC. The biggest pro for it was that it was possible to migrate our current CLI from commander to caporal without requiring a full re-write. That would save us quite some time.

The cons were mostly similar to our current commander limitations, and the project isn't as actively maintained as we would have liked. We would probably have to fork the project and maintain a community around it, which would negate some of the speed we gained if we didn't have to re-write. It would also mean some of our requirements, like plugins, need to be built from scratch.

Ink

Dwane built the ink PoC. The biggest pro was that it was using React as the framework, which brings a massive community and ecosystem along with it. It had a lot of plugins available for most things we wanted for our next CLI, but some of them were not yet compatible with the latest ink version. It also had React-like diffing for the terminal output, meaning we could not only build interactive commands but also have dynamic output. The cons were not few, one of them being the fact that it was React-based, and the team needed to be familiar with it. Another con was that ink on its own wasn't suited for a big CLI like ours.

pastel, on the other hand, was a better-suited framework, built on top of ink, which gave us the same advantages, so Dwane built a PoC using that. pastel came with its own set of cons though, mostly the fact that it hadn't been actively maintained in the past year, with the last release being 10 months ago.

Oclif

I built the oclif PoC. The biggest pro was that oclif did tick most of our requirements, and they worked as advertised. So we wouldn't have to build a lot of the functionality for the non-user-facing requirements, like a plugin system. It was also better suited for building large CLIs. The code structure conventions it uses make it easier to maintain the code.

It did come with a bunch of cons as well, however. Even though the website advertises both JavaScript and TypeScript as supported, the docs were quite TypeScript heavy, to the point that most of the advanced use cases weren't documented in JavaScript.

The fact that I chose TypeScript for building the PoC also meant that importing the Nexmo Node.js SDK into it as is would be problematic, so we'd need to invest some time into adding TypeScript support there first.

What's Next?

After carefully considering how all those pros and cons affected us, we chose to go ahead and build the next Nexmo CLI using oclif.

We chose it because the support and documentation for it were great, along with the growing community of people using it. It's also actively maintained. We're also adding full support for TypeScript to our Node.js SDK, so it seemed like a good fit to keep the same stack across our SDK and CLI.

While we're working on improving our Nexmo CLI, you can track our progress at https://github.com/nexmo/nexmo-cli. If you have any suggestions or issues, please feel free to raise them in GitHub or in our community slack.

Alex LakatosVonage Alumni

Alex Lakatos is a JavaScript Developer Advocate for Nexmo. In his spare time he volunteers at Mozilla as a Tech Speaker and a Reps Mentor. JavaScript developer building on the open web, he has been pushing its boundaries every day. When he’s not programming in London, he likes to travel the world, so it’s likely you’ll bump into him in an airport lounge.

Ready to start building?

Experience seamless connectivity, real-time messaging, and crystal-clear voice and video calls-all at your fingertips.