Before Node.js, most of the command-line programs we saw were developed in scripting languages like shell, Ruby, python, etc. Now, Node.js has been widely used to develop various command-line programs, improving the development efficiency of engineers.
With node.js, you can quickly implement a command-line application if you have an idea. However, there are many areas of concern when developing a real “production environment” command-line program.
This article attempts to summarize my best practices when developing a real-world command-line program. I hope it helps.
Code organization
A well-organized code makes development, maintenance, and extension easier.
In essence, a command line program developed using Node.js is an NPM package that meets certain constraints, and we can organize the code in any way we see fit.
I suggest splitting the command line into two parts, the CLI and core, the operating system-like shell and the kernel.
The CLI is just a portal to commands that call core to do the real work. This architecture approach has the following benefits:
- Easier to scale. For example, if we want to develop a GUI program someday, the core part of the code is directly reusable.
- The CLI is so lightweight that core can update independently (and even do silent updates in the background).
- Easier to test. Because Core is programmable, it will be easier to write unit tests against it.
Parsing command line
To make the command line program written by Node.js work, we need to parse the command line arguments. Normally we parse in the entry file. Such as:
#! /usr/bin/env node require('.. /lib/cli').parse(process.argv);Copy the code
The first line of the entry file, which must be provided, is actually a Shellbang. The operating system uses the program specified by shellBang to execute the script, which is node. If we want to pass extra parameters to Node, for example, we can set more old space to avoid running out of memory: #! The/usr/bin/env node – Max – old – space – size = 10240.
The command-line arguments passed when executing the command are available through process.argv. Argv [0] is always equal to the program executing the script, and process.argv[1] is always equal to the path of the script executing. We typically start parsing with process.argv.slice(2).
There are many command line parsing modules in the community, such as commander. Js and Yargs.
Caporal.js is similar to commander. Js, but provides more customization. It has a built-in terminal log module with log level, which can realize autocomplete and generate more “beautiful” help information. You can try it out.
Appropriate interaction
Since you are developing a command line program, you will inevitably need to do some human-computer interaction with the user. For example, confirm user actions, provide user options, and load prompts for time-consuming operations.
The most well-developed form of interaction in the community is inquier.js, which provides validation boxes, list selections (single or multiple), input fields, and other very useful interactive components that you can use on demand.
Ora is recommended for loading prompts because of its high appearance level and ease of use. There is also support to display success or error graphical flags after loading.
As we implement specific interaction strategies, it would be a good feature to make all command-line options interactive.
For example, the command line supports the following commands:
- cli cmd -a
- cli cmd -b
Consider that when the user enters CMD on the CLI, a list pops up that lets the user choose whether to use the -a option or the -B option.
Update strategy
Update policy should be the first feature to consider when publishing a command line program.
Updating is not just about NPM publish publishing the new version to NPM Registry, but also about how we inform users that our command line program is updated and what policies we use to perform update checking.
Pkg-updater is recommended for update checking. It has the following features:
- Pull version information directly from NPM Registry
- Use background daemon processes to check for updates without blocking command execution
- Supports custom update copy, check interval, check tag, etc
- Support for forced upgrade policies (must be updated to use)
This is a pretty good update strategy, and you can find out more about it by using the Node.js command-line tool to check the correct posture of updates.
Error reporting
Errors will inevitably occur in command-line programs, and how to deal with them is our focus.
The best practice would be to write a detailed error log to a log file, preferably to be reported to the server for our analysis.
Error collection and reporting can be done using code like the following:
process.on('uncaughtException', onFatal); process.on('unhandledRejection', onFatal); cli .exec() .catch(onFatal); Function onFatal(e) {// Collect data const data = {}; data.code = e.code; data.message = e.message; data.stack = e.stack; data.os = process.platform; data.node_version = process.version; data.cli_version = pkg.version; // Write a file try {fs.writefilesync ('cli-error.log', json.stringify (data), 'utf8'); } catch (e) {} require('child_process').spawn(process.execPath, ['_report.js', 'http://api.example.com/error/report', JSON.stringify(data) ], {'stdio': ['ignore', 'ignore', 'ignore'], 'detached': true} ).unref(); / / out of the process. The exit (1); }Copy the code
The resources
- Zh.wikipedia.org/wiki/Sheban…
- Github.com/tj/commande…
- github.com/yargs/yargs
- Github.com/mattallty/C…
- Github.com/SBoudrias/I…
- Github.com/sindresorhu…
- Github.com/KohPoll/pkg…