commander.js usage and source code analysis

commander.js

commander is a lightweight nodejs module that provides powerful functions for user command line input and parameter parsing.

Characteristics of commander:

  • Self recording code
  • Auto generate help
  • Merge short parameters
  • Default options
  • Mandatory options
  • Command parsing
  • Prompt

install

npm install commander

use

Introducing a commander into a file can be used by directly introducing a program object or creating an instance.

const { program } = require('commander');
program.version("v1.0.0");
const { Command } = require('commander');
const program = new Command();
program.version("v1.0.0");

By looking at the source code, we can know that program is actually a newly created instance, which can more clearly access the global commands.

Source code fragment:

exports = module.exports = new Command();
exports.program = exports;

exports.Command = Command;

Option option

The Commander uses the. option() method to define options, and can attach an introduction to the options. Each option can define a short option name (- followed by a single character) and a long option name (- followed by one or more words), separated by commas, spaces, or |.

Syntax:
options(flags, description, fn, defaultValue)

commander.option(
    "-f, --filename [filename]",
    "The filename to use when reading from stdin. This will be used in source-maps, errors etc."
);

Source code analysis:

lib\command.js option()

Using the Corey writing method, the actual call is_ optionsEx() method

  options(flags, description, fn, defaultValue) {
    return this._optionEx({}, flags, description, fn, defaultValue);
  }

_ optionsEx(), create an options instance. fn can be in the form of function, regular, etc. note that the regular form should not be used as far as possible. Since Commander v7, this function is no longer recommended.

  _optionEx(config, flags, description, fn, defaultValue) {
    // Create an option instance
    const option = this.createOption(flags, description);
    if (typeof fn === "function") {
      option.default(defaultValue).argParser(fn);
    } else if (fn instanceof RegExp) {
      // deprecated
      ...
    } else {
      option.default(fn);
    }

    return this.addOption(option);
  }

In the Option constructor, a splitOptionFlags() method will be called to resolve long and broken identifiers, such as - m, - mixed < value >. attributeName() will return a character of camelcase, for example -- file name will be resolved to fileName.

Cut out different characters through spaces, | and.

function splitOptionFlags(flags) {
  let shortFlag;
  let longFlag;
  const flagParts = flags.split(/[ |,]+/);
  if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1]))
    shortFlag = flagParts.shift();
  longFlag = flagParts.shift();
  if (!shortFlag && /^-[^-]$/.test(longFlag)) {
    shortFlag = longFlag;
    longFlag = undefined;
  }
  return { shortFlag, longFlag };
}

At the same time, judge whether the reception parameters are set according to the characters in flags< Value > means that a parameter must be passed in when executing the command, < value... > means that multiple parameters can be accepted, and [value] means configurable parameters.

this.required = flags.includes("<"); 
this.optional = flags.includes("[");
this.variadic = /\w\.\.\.[>\]]$/.test(flags)

After creating an option object, put it into addOption() for processing. This method will register and listen for options.

  addOption(option) {
    const oname = option.name();
    const name = option.attributeName();
    // register
    this.options.push(option);

    const handleOptionValue = (val, invalidValueMessage, valueSource) => {
      // ...
    };

    this.on("option:" + oname, (val) => {
      const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
      handleOptionValue(val, invalidValueMessage, "cli");
    });

    if (option.envVar) {
      this.on("optionEnv:" + oname, (val) => {
        const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
        handleOptionValue(val, invalidValueMessage, "env");
      });
    }

    return this;
  }

Command inherits the event module EventEmitter in node to monitor and trigger option s.

After writing the option configuration, you need to call the program.parse(process.argv) method to parse the user's input.

parse(argv, parseOptions)

parse(argv, parseOptions) {
  const userArgs = this._prepareUserArgs(argv, parseOptions);
  this._parseCommand([], userArgs);

  return this;
}

In_ Parse and read the user's input in the prepareUserArgvs method. The first two elements of the array obtained by process.argv are the node installation address and the running script path respectively, followed by the user's input. Therefore, these two elements need to be filtered out first. Multiple argv conventions are also supported.

_prepareUserArgs(argv, parseOptions) {
  parseOptions = parseOptions || {};
  this.rawArgs = argv.slice();

  let userArgs;
  switch (parseOptions.from) {
    case undefined:
    case 'node':
      this._scriptPath = argv[1];
      userArgs = argv.slice(2);
      break;
    case 'electron':
      if (process.defaultApp) {
        this._scriptPath = argv[1];
        userArgs = argv.slice(2);
      } else {
        userArgs = argv.slice(1);
      }
      break;
    case 'user':
      userArgs = argv.slice(0);
      break;
    default:
      throw new Error(
        `unexpected parse option { from: '${parseOptions.from}' }`
      );
  }
  if (!this._scriptPath && require.main) {
    this._scriptPath = require.main.filename;
  }

  this._name =
    this._name ||
    (this._scriptPath &&
      path.basename(this._scriptPath, path.extname(this._scriptPath)));

  return userArgs;
}

After the user input is obtained, these parameters are parsed.

_parseCommand()

_parseCommand(operands, unknown) {
    const parsed = this.parseOptions(unknown);
    // The command section continues below
}

parseOptions reads the configuration of the input

parseOptions(argv) {
  const operands = [];
  const unknown = [];
  let dest = operands;
  const args = argv.slice();

  // To judge whether it is an option configuration, it must start with -
  function maybeOption(arg) {
    return arg.length > 1 && arg[0] === '-';
  }

  let activeVariadicOption = null;
  // Step by step read configuration
  while (args.length) {
    const arg = args.shift();
    // --Stop reading, that is -- the subsequent configuration will not take effect
    // For example, node Src / index.js -- number 1 2 3 -- C a B
    // -c it doesn't work
    if (arg === '--') {
      if (dest === unknown) dest.push(arg);
      dest.push(...args);
      break;
    }

    // Handle multi parameter situations
    if (activeVariadicOption && !maybeOption(arg)) {
      // When this listening is triggered, handleOptionValue will be executed, in which the value of variadic will be judged, and all parameters will be put into an array
      this.emit(`option:${activeVariadicOption.name()}`, arg);
      continue;
    }
    activeVariadicOption = null;

    if (maybeOption(arg)) {
      // Check to see if the command is already configured
      const option = this._findOption(arg);
      if (option) {
          // As mentioned earlier, when configuring an option, if there is a required parameter to configure < value >, the required value of the option is true
        if (option.required) {
            // Read value
          const value = args.shift();
          if (value === undefined) this.optionMissingArgument(option);
          // Trigger monitoring method
          this.emit(`option:${option.name()}`, value);
        // Optional parameters configured
        } else if (option.optional) {
          let value = null;
          if (args.length > 0 && !maybeOption(args[0])) {
            value = args.shift();
          }
          this.emit(`option:${option.name()}`, value);
        } else {
          // boolean flag
          this.emit(`option:${option.name()}`);
        }
        // As mentioned earlier, when... > such as < value... > exists in the configured option, the variadic value is true
        activeVariadicOption = option.variadic ? option : null;
        continue;
      }
    }

    // Combining flag s, such as - abc, will traverse to determine whether - a -b -c is a configured option
    if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
      // Disassembly and assembly
      const option = this._findOption(`-${arg[1]}`);
      if (option) {
        if (
          option.required ||
          (option.optional && this._combineFlagAndOptionalValue)
        ) {
          this.emit(`option:${option.name()}`, arg.slice(2));
        } else {
          this.emit(`option:${option.name()}`);
          args.unshift(`-${arg.slice(2)}`);
        }
        continue;
      }
    }

    // Parsing -- foo=bar parameter transfer format
    if (/^--[^=]+=/.test(arg)) {
      const index = arg.indexOf('=');
      const option = this._findOption(arg.slice(0, index));
      if (option && (option.required || option.optional)) {
        this.emit(`option:${option.name()}`, arg.slice(index + 1));
        continue;
      }
    }

    if (maybeOption(arg)) {
      dest = unknown;
    }

    // Parsing command is explained below
    dest.push(arg);
  }

  return { operands, unknown };
}

From the above source code analysis, it can be summarized as follows:

  1. Multiple short options can be combined and abbreviated, and the last option can be attached with parameters. For example, - a -b -c 1 can be written as - abc 1.
  2. --You can mark the end of the option. Subsequent parameters will not be interpreted by the command and can be used normally.
  3. Parameters can be passed through
  4. Multiple parameters can be passed

Common option types

  1. The boolean option does not require configuration parameters. We usually use this type.
  2. Set parameters (after the option is declared with angle brackets, such as -- expect < value >), which will be defined as undefined if no specific options and parameters are specified on the command line.
const { Command } = require('commander');
const program = new Command();

program.option('-e --example', 'this is a boolean type option');

program.option('-t --type <type>', 'must set an param or an error will occur');

program.parse(process.argv);
console.log(program.opts());

eg.

node index.js -e
{ example: true }

node index.js -t
error: option '-t --type <type>' argument missing


node index.js -t a
{ type: 'a' }
  1. Negative, you can define a boolean type long option starting with no -. When you use this option on the command line, the value of the corresponding option is set to false. When only the option with no - is defined and the corresponding option without no - is not defined, the default value of this option will be set to true.
program.option('--no-example', 'no example');
node index.js --no-example
{ example: false }

Source code analysis:

After the option is configured, the command will be monitored. Executing the flag will trigger the handleOptionValue method to set the value of the option according to whether the user has set negative or default value

handleOptionValue

const handleOptionValue = (val, invalidValueMessage, valueSource) => {
  const oldValue = this.getOptionValue(name);
  
  // Parameter processing is explained in the user defined options section
  // .. 

  if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') {
    if (val == null) {
      this.setOptionValueWithSource(
        name,
        // Whether to take the negative value. If the negative value is taken, the value is false. If not, judge whether there is a value. If not, assign the value to true
        option.negate ? false : defaultValue || true,
        valueSource
      );
    } else {
      this.setOptionValueWithSource(name, val, valueSource);
    }
  } else if (val !== null) {
    this.setOptionValueWithSource(
      name,
      option.negate ? false : val,
      valueSource
    );
  }
};
  1. Optional parameter (- - optional [value]). This option can be used as a boolean option without parameters, and get the value from the parameters with parameters.
program.option('-f [filename]', 'optional');
node index.js -f
{ f: true }

node index.js -f index.js
{ f: 'index.js' }
  1. Required options
    You can set the option to be mandatory through the. requiredOption() method. Required options are either set with default values or must be entered on the command line. The corresponding attribute fields must be assigned during parsing. The remaining parameters of this method are consistent with. option().
program.requiredOption('-r --required <type>', 'must');
node index.js   
error: required option '-r --required <type>' not specified

Of course, you can set a default value

program.requiredOption('-r --required <type>', 'must', 'a');
node index.js   
{ required: 'a' }
  1. Variable length parameter options
    When defining options, you can set parameters to variable length parameters by using. In the command line, you can enter multiple parameters, which will be stored in the corresponding attribute field in the form of array after parsing. Instructions entered by the user are treated as variable length parameters until the next option is entered (- or --). As with normal parameters, you can mark the end of the current command through -- to mark the end of the current command.
program.option('-n <numbers...>', 'set numbers');
program.option('-c <chars...>', 'set chars');
node index.js -n 1 2 3 4 -c a b c
{ n: [ '1', '2', '3', '4' ], c: [ 'a', 'b', 'c' ] }
  1. edition
    The. version() method can set the version. Its default options are - V and -- version. After setting the version, the command line will output the current version number.
program.version('v1.0.0')

Version options also support custom setting option names. You can pass some parameters (long option names and descriptions) in the. version() method. The usage is similar to that of the. option() method.

program.version('v1.0.0', '-a --aversion', 'current version');

Source code analysis:

The version method is actually very simple, which is to judge whether the user has customized information when configuring version information, such as starting commands. Then create an option to listen after registration.

version(str, flags, description) {
  if (str === undefined) return this._version;
  this._version = str;
  flags = flags || '-V, --version';
  description = description || 'output the version number';
  const versionOption = this.createOption(flags, description);
  this._versionOptionName = versionOption.attributeName();
  this.options.push(versionOption);
  this.on('option:' + versionOption.name(), () => {
    this._outputConfiguration.writeOut(`${str}\n`);
    this._exit(0, 'commander.version', str);
  });
  return this;
}

  1. Using the addOption method
    In most cases, options can be added through the. option() method. However, for some uncommon use cases, you can also directly construct option objects to configure options in more detail.
program
  .addOption(
    new Option('-t, --timeout <delay>', 'timeout in seconds').default(
      60,
      'one minute'
    )
  )
  .addOption(
    new Option('-s, --size <type>', 'size').choices([
      'small',
      'medium',
      'large',
    ])
  );
node index.js -s small
{ timeout: 60, size: 'small' }

node index.js -s mini
error: option '-s, --size <type>' argument 'mini' is invalid. Allowed choices are small, medium, large.

Source code analysis: just add a fn and judge the value when passing in the value.

function choices(values) {
  this.argChoices = values;
  this.parseArg = (arg, previous) => {
    if (!values.includes(arg)) {
      throw new InvalidArgumentError(
        `Allowed choices are ${values.join(', ')}.`
      );
    }
    if (this.variadic) {
      return this._concatValue(arg, previous);
    }
    return arg;
  };
  return this;
}
  1. Custom options
    The parameters of the option can be processed through the user-defined function, which receives two parameters, namely, the parameter value newly entered by the user and the current existing parameter value (that is, the return value after the user-defined processing function was called last time), and returns the new option parameter value. That is, the third parameter in option. The fourth parameter is the initial value

User defined functions are applicable to scenarios, including parameter type conversion, parameter temporary storage, or other user-defined processing scenarios.

program.option(
  '-f --float <number>',
  'process float argument',
  (value, previous) => {
    return Number(value) + previous;
  },
  2
);
node index.js -f 3
{ float: 5 }

Source code analysis:

const handleOptionValue = (val, invalidValueMessage, valueSource) => {
  const oldValue = this.getOptionValue(name);
  
  // When the third parameter of option is configured
  if (val !== null && option.parseArg) {
    try {
      val = option.parseArg(
        val,
        // defaultValue is the fourth parameter
        oldValue === undefined ? defaultValue : oldValue
      );
    } catch (err) {
      if (err.code === "commander.invalidArgument") {
        const message = `${invalidValueMessage} ${err.message}`;
        this._displayError(err.exitCode, err.code, message);
      }
      throw err;
    }

  // Handle multi parameter situations
  } else if (val !== null && option.variadic) {
    val = option._concatValue(val, oldValue);
  }

  // ...
  // View the explanation of inversion
}

Configuration command

Commands can be configured through. command() or. addCommand(), which can be implemented in two ways: binding processing functions for commands, or writing commands as an executable file separately. Subcommands support nesting.

The first argument to. command() is the command name. Command parameters can follow the name or be specified separately with. argument(). The parameter can be required (indicated by angle brackets), optional (indicated by square brackets) or variable length parameter (indicated by a dot. If used, it can only be the last parameter).

program
  .command('clone <source> [destination]')
  .description('clone a repository into a newly created directory')
  .action((source, destination) => {
    console.log('clone command called');
  });

Use. argument() on the Command object to specify Command parameters in order. The method accepts parameter names and parameter descriptions. Parameters can be required (indicated by angle brackets, such as < required >) or optional (indicated by square brackets, such as [optional]).

program
  .command('login')
  .description('login')
  .argument('<username>', 'user')
  .argument('[password]', 'password', 'no password')
  .action((username, password) => {
    console.log(`login, ${username} - ${password}`);
  });

Variable parameters are declared by adding... After the parameter name, and only the last parameter supports this usage. Variable parameters are passed to the handler as an array.

program
  .command('readFile')
  .description('read multiple file')
  .argument('<username>', 'user')
  .argument('[password]', 'password', 'no password')
  .argument('<filepath...>')
  .action((username, password, args) => {
    args.forEach((dir) => {
      console.log('rmdir %s', dir);
    });
    console.log(`username: ${username}, pass: ${password}, args: ${args}`);
  });

Source code analysis: create a new command

command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
  let desc = actionOptsOrExecDesc;
  let opts = execOpts;
  if (typeof desc === 'object' && desc !== null) {
    opts = desc;
    desc = null;
  }
  opts = opts || {};
  // Parsing and obtaining input commands and parameters
  const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);

  // Create a command
  const cmd = this.createCommand(name);
  if (desc) {
    cmd.description(desc);
    cmd._executableHandler = true;
  }
  if (opts.isDefault) this._defaultCommandName = cmd._name;
  cmd._hidden = !!(opts.noHelp || opts.hidden);
  cmd._executableFile = opts.executableFile || null;
  // Add parameter
  if (args) cmd.arguments(args);
  this.commands.push(cmd);
  cmd.parent = this;
  // Inheritance properties
  cmd.copyInheritedSettings(this);

  if (desc) return this;
  return cmd;
}

action is used to register command callbacks.

action(fn) {
  const listener = (args) => {
    const expectedArgsCount = this._args.length;
    const actionArgs = args.slice(0, expectedArgsCount);
    if (this._storeOptionsAsProperties) {
      actionArgs[expectedArgsCount] = this;
    } else {
      actionArgs[expectedArgsCount] = this.opts();
    }
    actionArgs.push(this);

    return fn.apply(this, actionArgs);
  };
  this._actionHandler = listener;
  return this;
}

The next step is how to parse the execution command. Remember the process when we parsed the option before
parse() -> _ Parsecommand(), in the parseOptions method, we still have some code left to process the command. Now let's take a look.

Source code analysis:

parseOptions(argv) {
  const operands = [];
  const unknown = [];
  let dest = operands;
  const args = argv.slice();

  while (args.length) {
    // Parsing the option section
    // ...

    // When enablePositionalOptions is enabled, all the parameters behind the command will be defined as unknown parameters instead of command options. At the same time, if you want to start enablePositionalOptions in the sub command, you need to start enablePositionalOptions in the parent command first.
    if (
      (this._enablePositionalOptions || this._passThroughOptions) &&
      operands.length === 0 &&
      unknown.length === 0
    ) {
      if (this._findCommand(arg)) {
        operands.push(arg);
        if (args.length > 0) unknown.push(...args);
        break;
      } else if (
        arg === this._helpCommandName &&
        this._hasImplicitHelpCommand()
      ) {
        operands.push(arg);
        if (args.length > 0) operands.push(...args);
        break;
      } else if (this._defaultCommandName) {
        unknown.push(arg);
        if (args.length > 0) unknown.push(...args);
        break;
      }
    }

    if (this._passThroughOptions) {
      dest.push(arg);
      if (args.length > 0) dest.push(...args);
      break;
    }
    dest.push(arg);
  }
  return { operands, unknown };
}

If the enablePositionalOptions configuration is not enabled, the command and subsequent parameters will be saved in the operands array. When enabled, the parameters behind the command will become unknown.

After parsing the parameters entered by the user, continue to execute_ parseCommand method

_parseCommand(operands, unknown) {
  // Resolution configuration
  const parsed = this.parseOptions(unknown);
  this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
  operands = operands.concat(parsed.operands);
  unknown = parsed.unknown;
  this.args = operands.concat(unknown);

  // Find the matching command and run the command using the child process
  if (operands && this._findCommand(operands[0])) {
    return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
  }

  // If the command is not found, judge whether there is a help command, and the first parameter entered by the user is help
  if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
    // There are and only one command
    if (operands.length === 1) {
      this.help();
    }
    // Execute the second command
    return this._dispatchSubcommand(operands[1], [], [this._helpLongFlag]);
  }
  // The help command was not found, or the first parameter entered by the user is unknown, but there is a default name
  if (this._defaultCommandName) {
    outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
    return this._dispatchSubcommand(
      this._defaultCommandName,
      operands,
      unknown
    );
  }
  // ...

  const commandEvent = `command:${this.name()}`;
  // command processor present
  if (this._actionHandler) {
    checkForUnknownOptions();
    // Processing parameters
    this._processArguments();

    let actionResult;
    actionResult = this._chainOrCallHooks(actionResult, 'preAction');
    actionResult = this._chainOrCall(actionResult, () =>
      this._actionHandler(this.processedArgs)
    );
    // Trigger parent command
    if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy
    actionResult = this._chainOrCallHooks(actionResult, 'postAction');
    return actionResult;
  }
  // Trigger parent listener
  if (this.parent && this.parent.listenerCount(commandEvent)) {
    checkForUnknownOptions();
    this._processArguments();
    this.parent.emit(commandEvent, operands, unknown);
  // Processing parameters
  } else if (operands.length) {
    if (this._findCommand('*')) {
      return this._dispatchSubcommand('*', operands, unknown);
    }
    if (this.listenerCount('command:*')) {
      this.emit('command:*', operands, unknown);
    } else if (this.commands.length) {
      this.unknownCommand();
    } else {
      checkForUnknownOptions();
      this._processArguments();
    }
  // No command exists
  } else if (this.commands.length) {
    checkForUnknownOptions();
    this.help({ error: true });
  } else {
    checkForUnknownOptions();
    this._processArguments();
  }
}

At this point, the whole parsing process is completed.

Declare uniform parameters

The parameters of the command processing function. In addition to all the parameters declared for the command, two additional parameters will be attached: one is the parsed option, and the other is the command object itself.

program
  .argument('<name>')
  .option('-t, --title <title>', 'title to use before name')
  .option('-d, --de')
  .action((name, options, command) => {
    console.log(name);
    console.log(options);
    console.log(command.name());
  });

Help information

The help information is automatically generated by the Commander based on your program. The default help options are - H, - help.

node index.js -h
Usage: index [options]

Options:
  -h, --help  display help for command

Source code analysis:
commander.js provides a help class, but its methods are static and can be overridden. There is a createHelp method in the command class, which overrides the original method of help through the Object.assign() method.

createHelp() {
    return Object.assign(new Help(), this.configureHelp());
}

formatHelp generates help text, parses and processes the set command, option, argument, etc

formatHelp(cmd, helper) {
  const termWidth = helper.padWidth(cmd, helper);
  const helpWidth = helper.helpWidth || 80;
  const itemIndentWidth = 2;
  const itemSeparatorWidth = 2;
  // format
  function formatItem(term, description) {
    if (description) {
      const fullText = `${term.padEnd(
        termWidth + itemSeparatorWidth
      )}${description}`;
      return helper.wrap(
        fullText,
        helpWidth - itemIndentWidth,
        termWidth + itemSeparatorWidth
      );
    }
    return term;
  }
  function formatList(textArray) {
    return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
  }

  let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];

  // descriptor 
  const commandDescription = helper.commandDescription(cmd);
  if (commandDescription.length > 0) {
    output = output.concat([commandDescription, '']);
  }

  // parameter
  const argumentList = helper.visibleArguments(cmd).map((argument) => {
    return formatItem(
      helper.argumentTerm(argument),
      helper.argumentDescription(argument)
    );
  });
  if (argumentList.length > 0) {
    output = output.concat(['Arguments:', formatList(argumentList), '']);
  }

  // option
  const optionList = helper.visibleOptions(cmd).map((option) => {
    return formatItem(
      helper.optionTerm(option),
      helper.optionDescription(option)
    );
  });
  if (optionList.length > 0) {
    output = output.concat(['Options:', formatList(optionList), '']);
  }

  // command
  const commandList = helper.visibleCommands(cmd).map((cmd) => {
    return formatItem(
      helper.subcommandTerm(cmd),
      helper.subcommandDescription(cmd)
    );
  });
  if (commandList.length > 0) {
    output = output.concat(['Commands:', formatList(commandList), '']);
  }

  return output.join('\n');
}

custom

Use the addHelpText method to add additional help information.

program.addHelpText('after', `call help`);
node index.js -h
Usage: index [options]

Options:
  -h, --help  display help for command
call help

The first parameter of the addHelpText method is the location where the added help information is displayed,
It includes the following:

  • Before all: displayed as a global header bar

  • Before: show before built-in help information

  • After: display after built-in help information

  • After all: displayed as the global end column

Source code analysis:
After the addHelpText method is executed, the event will be monitored. When the input help command is executed, all events will be executed in order to display user-defined information.

addHelpText

addHelpText(position, text) {
  const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
  // filter
  if (!allowedValues.includes(position)) {
    throw new Error(`Unexpected value for position to addHelpText.
Expecting one of '${allowedValues.join("', '")}'`);
  }
  const helpEvent = `${position}Help`;
  // Listening events
  this.on(helpEvent, (context) => {
    let helpStr;
    if (typeof text === 'function') {
      helpStr = text({ error: context.error, command: context.command });
    } else {
      helpStr = text;
    }
    // Ignore falsy value when nothing to output.
    if (helpStr) {
      context.write(`${helpStr}\n`);
    }
  });
  return this;
}

outputHelp

outputHelp(contextOptions) {
  let deprecatedCallback;
  if (typeof contextOptions === 'function') {
    deprecatedCallback = contextOptions;
    contextOptions = undefined;
  }
  const context = this._getHelpContext(contextOptions);
  // Traverse the beforeAllHelp event that triggers all ancestor commands
  getCommandAndParents(this)
    .reverse()
    // Trigger beforeAllHelp
    .forEach((command) => command.emit('beforeAllHelp', context));
  // Trigger beforeHelp
  this.emit('beforeHelp', context);

  let helpInformation = this.helpInformation(context);
  if (deprecatedCallback) {
    helpInformation = deprecatedCallback(helpInformation);
    if (
      typeof helpInformation !== 'string' &&
      !Buffer.isBuffer(helpInformation)
    ) {
      throw new Error('outputHelp callback must return a string or a Buffer');
    }
  }
  context.write(helpInformation);
  // Trigger help instruction
  this.emit(this._helpLongFlag); // deprecated
  // Trigger afterHelp
  this.emit('afterHelp', context);
  // Traverse the afterAllHelp event that triggers all ancestor commands
  getCommandAndParents(this).forEach((command) =>
    // Trigger afterAllHelp
    command.emit('afterAllHelp', context)
  );
}

showHelpAfterError displays help information

program.showHelpAfterError();
// perhaps
program.showHelpAfterError('(add --help for additional information)');
node index.js -asd
error: unknown option '-asd'
(add --help for additional information)

Keywords: Javascript

Added by huhn_m on Fri, 19 Nov 2021 11:21:31 +0200