preface

Recently, I was working on a developer-oriented project, and needed to develop a CLI to connect the developer and the project site as a bridge between the developer and the site. Therefore, the CLI must at least provide commands for login, logout, and upload and download operations. Because you set up for some local operations, there are some environment checks, cleanup, and initialization that need to be done before or after each different operation. In the beginning, I chose Commander to develop the CLI as I did before. However, as I was writing, I found that commander was increasingly unable to meet my complex CLI requirements, mainly in the following aspects:

  1. With Multiple Command, there is no way to determine exactly what an instruction needs to do based on multiple parameters in the context

  2. No hooks configuration, there is always a need for various hard codes to do something before and after

  3. A complex CLI cannot be elegantly divided into modules of instructions for different actions, and when the CLI is very complex and verbose, the code becomes difficult to maintain

    Therefore, I imagine that there is such a basic tool for CLI development, which can solve the above problems, for example, let me elegantly distinguish the code of different instructions reasonably, for example, can easily do various pre-post and pre-post processing. Looking around, I found the protagonist I want to recommend in this article — OCLIF. Not only does it address all of the issues I mentioned above, but it also provides a lot of very useful functionality. However, when I compared Commander, Yargs and OClif in NPMTrend, I found that the spread of OClif was really limited and the number of downloads was pitifully small.

So next, I will introduce ocLIF, amway’s good helper for developing CLI 🔧

oclif

Oclif is a framework to help build the Node CLI, as you can see on the oclif website. How does it help developers build the CLI?

Oclif Single&OClif Multi: Customizes and initializes CLI directories as required

Oclif first divides CLI into two categories, namely single-command and multi-command. Single-command refers to a single-command similar to LS or curl, which combines various flags to achieve various execution effects, such as LS -L; The other is multi-command, such as NPM or Git, which has many secondary commands and various flags to distinguish CLI commands. With such a clear distinction, OCLIF provides different instructions for creating CLI projects when developing single-command and multi-command. NPX oclif single ${cliname} NPX OClif multi ${cliname} Modify existing configurations or add commands. For example, run NPX oclif multi newcli, press enter to use the default configuration, you will get a project directory like this. By default, ts and TSC are used for compilation. Each file in the SRC/Commands directory represents a different command —

. ├ ─ ─ the README. Md ├ ─ ─ bin │ ├ ─ ─ the run # # bin for the execution path of │ └ ─ ─ the run the CMD # # compatible with Windows to perform ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ commands # # │ ├ ─ ├ ─ 07.02, ├ ─ 07.02 ├─ ├─ 02.txt ├─ 02.txt ├─ 02.txt ├─ 02.txt ├─ 02.txtCopy the code

performyarn install & yarn prepackComplete the packaging and execute./bin/run, you can see the whole CLI, a CLI project has been created very quicklyIn the package.json of the initialized project, there is an oclif field that registers the address of the commands, plugins, hooks execution files that follow. In the root directory, I’m going to use oneoclif.manifest.jsonTo record the current information about the CLI. This file will also be updated if the CLI commands are added, deleted, or recompiled.

A qualitycommand class

As mentioned above, each file in the command directory of the multi-command CLI of a project created through OClif corresponds to a secondary instruction, such as the Hello file in the example. Each command file is a class derived from the oclif package @oclif/ Command, which provides the command class. Command is similar to commander, but in the form of a class, it is richer and more extensible than Commander. Personally, I also feel that the class approach is more like CLI coding than the Commander approach. Take SRC /commands/hello for example,

  • Provides many different static members, such asdescription(Instruction description),flags(Optional parameters are configured), andargs(Set mandatory parameters) andexamples(An array, can be configured with a variety of flags, ARGS combination demo).
  • The run method, which is the function that the instruction executes, gets flags and ARgs information and executes the branch in run as configured.

If we need to add a secondary command, we need to add a directory under Commands (oclif command), the new command file is also an inherited command class, and write its flags, args, and run directives.

import {Command, flags} from '@oclif/command'

export default class Hello extends Command {
  static description = 'describe the command here'

  static examples = [
    `$ newcli2 hello
hello world from ./src/hello.ts!
`,]static flags = {
    help: flags.help({char: 'h'}),
    // flag with a value (-n, --name=VALUE)
    name: flags.string({char: 'n'.description: 'name to print'}),
    // flag with no value (-f, --force)
    force: flags.boolean({char: 'f'}}),static args = [{name: 'file'}]

  async run() {
    const {args, flags} = this.parse(Hello)

    const name = flags.name ?? 'world'
    this.log(`hello ${name} from ./src/commands/hello.ts`)
    if (args.file && flags.force) {
      this.log(`you input --force and --file: ${args.file}`)}}}Copy the code

Hooks configuration for elegant handling of various pre&POST operations

As mentioned in the introduction, it is sometimes necessary to do some pre-execution or post-execution operations. Oclif also recognizes this need and provides a more elegant way to handle it. Oclif defines four phases (events) that hooks can execute: init, PRERun, PoSTRUn, and command_not_found. Oclif hook ${hookName} –event=${eventName} create a hook execution file in the SRC /hooks/${eventName}/${hookName} directory as follows: Perform NPX oclif hook beforeAll – event = prerun, represents a before all the instruction execution to perform processing operations, will be in the SRC/hooks/prerun/beforeAll path appear such template, Also register this hook in the oclif field of package.json.

import {Hook} from '@oclif/config'

const hook: Hook<'prerun'> = async function (opts) {
  process.stdout.write(`example hook running ${opts.id}\n`)}export default hook

Copy the code

Of course, if we don’t want to do pre and post processing before and after all instructions, but only for certain instructions, as we did in this scenario. Create the corresponding execution file in the hooks directory, and then execute it in the run method of the command that needs to perform the pre and post processing using the this.config.runHook(${eventName}) method call. See the official Demo custom Events section for details.

Plugins, plug-in configuration

Plug-in configuration is also provided by OClif, which meets my needs. Basic official plug-ins cover a large part of the common CLI plug-in requirements, which are plug-and-play. After installing dependencies directly, add this plug-in to the ocliF field plugins.

  • @oclif/plugin-help— Used to add the help directive
  • @oclif/plugin-warn-if-update-available— Used to prompt users to install a new version when the CLI is updated

Of course, there are other plugins, these are the two I need to use for the CLI, see the oclif plugin for details

Thoughtfully generate usage documentation

As mentioned earlier, oclif.manifest.json is updated to match the latest functionality each time prepack and Postpack are recompiled. At the same time, information such as the document used in the readme. md document, version number, and so on will be includedoclif devThe information is of course taken from the directives and manifest, so the README is accurate as long as we write it correctly. In fact, when the CLI is very large, it is a headache to write your own documentation. I believe that many developers may have their own set of documentation steps, if a code word is a very bad development experience.

In fact, before writing this article, I was wondering why oclif, which in my opinion is superior, is used and distributed far less than COMMANDER and Yargs. As I was writing, I realized that Commander and Yargs already meet the needs of many lightweight CLI development scenarios. But a more sophisticated tool like OCLIF deserves to be seen by more people.