CLI commands to wrap third party tools with ejectable default config

David Barral
Trabe
Published in
3 min readApr 29, 2019
Photo by Emily Simenauer on Unsplash

One way to ease the pain of getting started with a JavaScript project is to hide all the nuances of the myriad of third party tools you need to use: a transpiler, a linter, a test framework, etc. How can we do that? Just following this simple recipe:

  • Offer some CLI commands, with reduced options. This commands will invoke the third party tool, passing a set of predefined options: the most common ones or those that make more sense in your scenario.
  • Use default config files. Set the values you think 99% percent of the projects will use, 99% of the time. Sprinkle tons of comments explaining what each option does and why is there. If possible, add some external reference for further info.
  • Offer a way to override the config by the user. Allow importing and modifying the default, generating a template file (based on the default one) or both.

This is basically what many other projects do. Take create-react-app as one of the most common examples.

Following this approach offers some benefits:

  • 99% of the time you just have to run one simple optionless command without writing any config. The other 1% of the time you have a clear escape route.
  • Your CLI commands can have a common API. No need to remember if the debug option was -v,-vvv or --logLevel debug for a given command. You use --debug everywhere (because it makes more sense to you).
  • By hiding both the tool and the config, you can switch to a different tool. This could need some code migration, but, it’s still possible. Using this approach with switched from mocha to jest in a project with almost no drama.

Ok, show me some code

We are building a prettify command, which is going to wrap Prettier, to format our JavaScript code.

Note: to simplify and reduce the example, we are not making any sanity checks and we are assuming lots of things.

The prettify script

The prettify script is divided in two main parts. The option parsing and the execution.

Option parsing is achieved using the commander library (there are many alternatives, but we kinda like this one). We define three options to configure Prettier:

  • -c, --config to use a specific config file. If not set, the command will use the default configuration.
  • -w, --write to make prettier overwrite the source files instead of dumping the code on stdout (the default behaviour).
  • --debug to make Prettier output its debug messages.

The prettier command understands around 40 options. We are supporting just the ones we use 99% of the time.

In the execution phase we build the final prettier command using the options passed. If no config is passed we use the default one. We run the command using spawnSync. Notice we run synchronously and return the subprocess status code, to correctly support command concatenation in the shell.

Finally, to be able to run the command, we add the executable prettify script.

The default config

The prettify command also relies on a default prettier.config.js file located in the awesome-tools package. It defines our preferred config values, and is thoroughly commented to be useful once ejected.

Ejecting

To eject the config, we use another command: eject. There’s nothing new to say about the options parsing. In the execution phase we just copy the default config files for the ejected config in the project’s root.

Packaging

Last, but not least, we need to add a bin entry to our package.json, to declare our package binaries.

And that’s it. Ready to publish and share.

You can now add this package to your project devDependencies and start running the prettify command. Then try eject, change the config, and see how the command behaves now.

To infinity and Beyond!

This was just a simple example to illustrate the point. You can grow it further by adding as many commands as you need. Refactor things. Add some sanity checks. You can go even further and do much more: mix some chalk to improve and colorize the output, add a version checker, support Windows with cross-spawn, add some kind of “did you mean” support or make the ejections discoverable by eject, to name a few.

--

--

David Barral
Trabe
Editor for

Co-founder @Trabe. Developer drowning in a sea of pointless code.