preface

, along with the development of the front-end engineering has become each front-end should attach importance to and familiar with, and as one of engineering scaffold is played a very important role in the daily development, and can according to the business and the project team of the need for their front personalize their own team of scaffolding will also become a bright spot in the engineer project resume, This article is mainly based on vuE-CLI scaffolding source code, practice their own scaffolding encountered in the process of problems and their own thinking. (PS: A mature scaffold usually includes the following features: 1. 2. Template pulling; 3. Local services; 4. Package construction; 5. Integrated deployment; 6, peripheral, this paper is mainly to explore the general function of scaffolding, handwritten a rough version of scaffolding, if there is a project practice, will continue to update and share.)

The command line

Large frame construction

[Directory structure]

  • bin
    • WWW (global command execution portal connection, using NPM link)
  • src
    • Main.js (entry file that distributes commands)
    • Create.js (create command, all create logic is written in this file)
    • Config.js (config commands, all config commands are written in this file)
    • Constants.js (constant file, in which all constants are configured)
  • package.json

[directory description] bin under the WWW script to obtain the global execution environment, can import the entry file, we here import it into main.js, all commands can be distributed in main.js, for better use, you can divide different commands into different JS files, Here, the commander package is used to distribute commands. Specific services can be further expanded in corresponding files

The command configuration

The entrance

The executable script WWW in the bin directory,

#! /usr/bin/env node

Package. json configuration:

“bin”: { “vee-cli”: “./bin/www” },

If the permission is not enough, it may need to use sudo, etc. For specific pit climbing, please refer to this article. NPM Link has no effect. You can print a command “Hello vee” in the entry file for testing, as shown in the figure.

The command

After completing the test, is the main logic of command distribution, here is mainly used in the commander package, source part of the interpretation in the next section, mapAction is mainly mapping command behavior, which is mainly alias, description, examples, and then is the display and execution of each behavior. The key code here is require(path.resolve(__dirname,action))(… Process.argv.slice (3)) retrieves the corresponding file name for parsing, so that functions can be separated into the corresponding file for specific logic

const program = require('commander');
const { version } = require('./constants');
const path = require('path');

const mapAction = {
    create: {
        alias: 'c', 
        description: 'create a project', 
        examples: [
            'vee-cli create <project-name>'
        ],
    },
    config: {
        alias: 'conf',
        description: 'config project variable',
        examples: [
            'vee-cli config set <k> <v>'.'vee-cli config get <k>',]},The '*': { 
        alias: ' ',
        description: 'command not found', examples: [],}} // Reflect, Keys (mapAction).foreach ((action) => {program.command(action).alias(mapAction[action].alias) .description(mapAction[action].description) .action(() => {if (action === The '*') { 
                console.log(mapAction[action].description)
            } else{ console.log(action); require(path.resolve(__dirname,action))(... process.argv.slice(3)) } }) }); program.on('--help',()=>{
    console.log('\r\nExamples:');
    Object.keys(mapAction).forEach((action)=>{
        mapAction[action].examples.forEach(example=>{
            console.log(' '+example)
        })
    })
});

program.version(version).parse(process.argv);
Copy the code

Related package source code analysis

Commander is written by TJ, and the package mainly depends on the core modules of Event, Child_Process, FS, PATH, and Error. Among them, the implementation of EventEmitter is more common in front-end interviews. Realizing EventEmitter, Node is the base for constructing scaffolding.

  • Rely on
const EventEmitter = require('events').EventEmitter; Const spawn = require(const spawn = require()'child_process').spawn; // Execute a new process with the given command and parameters const path = require('path'); // path module const fs = require('fs'); // File moduleCopy the code
  • The Option class
class Option {
  constructor(flags, description) {
    this.flags = flags;
    this.required = flags.indexOf('<') > = 0; this.optional = flags.indexOf('[') > = 0; this.mandatory =false; 
    this.negate = flags.indexOf('-no-') !== -1;
    const flagParts = flags.split(/[ ,|]+/);
    if(flagParts.length > 1 && ! /^[[<]/.test(flagParts[1])) this.short = flagParts.shift(); this.long = flagParts.shift(); this.description = description ||' ';
    this.defaultValue = undefined;
  }

  name() {
    return this.long.replace(/^--/, ' ');
  };

 
  attributeName() {
    return camelcase(this.name().replace(/^no-/, ' '));
  };

  is(arg) {
    return this.short === arg || this.long === arg;
  };
}
Copy the code
  • CommandError class
class CommanderError extends Error {
  constructor(exitCode, code, message) {
    super(message);
    // properly capture stack trace in Node.js
    Error.captureStackTrace(this, this.constructor);
    this.name = this.constructor.name;
    this.code = code;
    this.exitCode = exitCode; this.nestedError = undefined; }}Copy the code
  • The Command class
class Command extends EventEmitter {

  constructor(name) {
    super();
    this.commands = [];
    this.options = [];
    this.parent = null;
    this._allowUnknownOption = false;
    this._args = [];
    this.rawArgs = null;
    this._scriptPath = null;
    this._name = name || ' ';
    this._optionValues = {};
    this._storeOptionsAsProperties = true; 
    this._passCommandToAction = true;
    this._actionResults = [];
    this._actionHandler = null;
    this._executableHandler = false;
    this._executableFile = null; 
    this._defaultCommandName = null;
    this._exitCallback = null;
    this._aliases = [];

    this._hidden = false;
    this._helpFlags = '-h, --help';
    this._helpDescription = 'display help for command';
    this._helpShortFlag = '-h';
    this._helpLongFlag = '--help';
    this._hasImplicitHelpCommand = undefined; 
    this._helpCommandName = 'help';
    this._helpCommandnameAndArgs = 'help [command]';
    this._helpCommandDescription = 'display help for command';
  }

  command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
    let desc = actionOptsOrExecDesc;
    let opts = execOpts;
    if (typeof desc === 'object'&& desc ! == null) { opts = desc; desc = null; } opts = opts || {}; const args = nameAndArgs.split(/ +/); const cmd = this.createCommand(args.shift());if (desc) {
      cmd.description(desc);
      cmd._executableHandler = true;
    }
    if(opts.isDefault) this._defaultCommandName = cmd._name; cmd._hidden = !! (opts.noHelp || opts.hidden); cmd._helpFlags = this._helpFlags; cmd._helpDescription = this._helpDescription; cmd._helpShortFlag = this._helpShortFlag; cmd._helpLongFlag = this._helpLongFlag; cmd._helpCommandName = this._helpCommandName; cmd._helpCommandnameAndArgs = this._helpCommandnameAndArgs; cmd._helpCommandDescription = this._helpCommandDescription; cmd._exitCallback = this._exitCallback; cmd._storeOptionsAsProperties = this._storeOptionsAsProperties; cmd._passCommandToAction = this._passCommandToAction; cmd._executableFile = opts.executableFile || null; this.commands.push(cmd); cmd._parseExpectedArgs(args); cmd.parent = this;if (desc) return this;
    return cmd;
  };


  createCommand(name) {
    return new Command(name);
  };

  addCommand(cmd, opts) {
    if(! cmd._name) throw new Error('Command passed to .addCommand() must have a name');

    function checkExplicitNames(commandArray) {
      commandArray.forEach((cmd) => {
        if(cmd._executableHandler && ! cmd._executableFile) { throw new Error(`Must specify executableFilefor deeply nested executable: ${cmd.name()}`);
        }
        checkExplicitNames(cmd.commands);
      });
    }
    checkExplicitNames(cmd.commands);

    opts = opts || {};
    if (opts.isDefault) this._defaultCommandName = cmd._name;
    if (opts.noHelp || opts.hidden) cmd._hidden = true; 
    this.commands.push(cmd);
    cmd.parent = this;
    return this;
  };

  arguments(desc) {
    return this._parseExpectedArgs(desc.split(/ +/));
  };

  addHelpCommand(enableOrNameAndArgs, description) {
    if (enableOrNameAndArgs === false) {
      this._hasImplicitHelpCommand = false;
    } else {
      this._hasImplicitHelpCommand = true;
      if (typeof enableOrNameAndArgs === 'string') {
        this._helpCommandName = enableOrNameAndArgs.split(' ') [0]; this._helpCommandnameAndArgs =enableOrNameAndArgs;
      }
      this._helpCommandDescription = description || this._helpCommandDescription;
    }
    return this;
  };

  _lazyHasImplicitHelpCommand() {
    if(this._hasImplicitHelpCommand === undefined) { this._hasImplicitHelpCommand = this.commands.length && ! this._actionHandler && ! this._findCommand('help');
    }
    return this._hasImplicitHelpCommand;
  };

  _parseExpectedArgs(args) {
    if(! args.length)return;
    args.forEach((arg) => {
      const argDetails = {
        required: false,
        name: ' ',
        variadic: false
      };

      switch (arg[0]) {
        case '<':
          argDetails.required = true;
          argDetails.name = arg.slice(1, -1);
          break;
        case '[':
          argDetails.name = arg.slice(1, -1);
          break;
      }

      if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '... ') {
        argDetails.variadic = true;
        argDetails.name = argDetails.name.slice(0, -3);
      }
      if(argDetails.name) { this._args.push(argDetails); }}); this._args.forEach((arg, i) => {if (arg.variadic && i < this._args.length - 1) {
        throw new Error(`only the last argument can be variadic '${arg.name}'`); }});return this;
  };

  exitOverride(fn) {
    if (fn) {
      this._exitCallback = fn;
    } else {
      this._exitCallback = (err) => {
        if(err.code ! = ='commander.executeSubCommandAsync') {
          throw err;
        } else{}}; }return this;
  };

  _exit(exitCode, code, message) {
    if (this._exitCallback) {
      this._exitCallback(new CommanderError(exitCode, code, message));
    }
    process.exit(exitCode);
  };

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

      const actionResult = fn.apply(this, actionArgs);
      let rootCommand = this;
      while (rootCommand.parent) {
        rootCommand = rootCommand.parent;
      }
      rootCommand._actionResults.push(actionResult);
    };
    this._actionHandler = listener;
    returnthis; }; _optionEx(config, flags, description, fn, defaultValue) { const option = new Option(flags, description); const oname = option.name(); const name = option.attributeName(); option.mandatory = !! config.mandatory;if(typeof fn ! = ='function') {
      if (fn instanceof RegExp) {
        const regex = fn;
        fn = (val, def) => {
          const m = regex.exec(val);
          return m ? m[0] : def;
        };
      } else{ defaultValue = fn; fn = null; }}if (option.negate || option.optional || option.required || typeof defaultValue === 'boolean') {
      if (option.negate) {
        const positiveLongFlag = option.long.replace(/^--no-/, The '-');
        defaultValue = this._findOption(positiveLongFlag) ? this._getOptionValue(name) : true;
      }
      if(defaultValue ! == undefined) { this._setOptionValue(name, defaultValue); option.defaultValue = defaultValue; } } this.options.push(option); this.on('option:' + oname, (val) => {
      if(val ! == null && fn) { val = fn(val, this._getOptionValue(name) === undefined ? defaultValue : this._getOptionValue(name)); }if (typeof this._getOptionValue(name) === 'boolean' || typeof this._getOptionValue(name) === 'undefined') {
        if (val == null) {
          this._setOptionValue(name, option.negate
            ? false
            : defaultValue || true);
        } else{ this._setOptionValue(name, val); }}else if(val ! == null) { this._setOptionValue(name, option.negate ?false: val); }});return this;
  };

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

  requiredOption(flags, description, fn, defaultValue) {
    return this._optionEx({ mandatory: true }, flags, description, fn, defaultValue);
  };

  allowUnknownOption(arg) {
    this._allowUnknownOption = (arg === undefined) || arg;
    return this;
  };

  storeOptionsAsProperties(value) {
    this._storeOptionsAsProperties = (value === undefined) || value;
    if (this.options.length) {
      throw new Error('call .storeOptionsAsProperties() before adding options');
    }
    return this;
  };

  passCommandToAction(value) {
    this._passCommandToAction = (value === undefined) || value;
    return this;
  };

  _setOptionValue(key, value) {
    if (this._storeOptionsAsProperties) {
      this[key] = value;
    } else{ this._optionValues[key] = value; }}; _getOptionValue(key) {if (this._storeOptionsAsProperties) {
      return this[key];
    }
    return this._optionValues[key];
  };

  parse(argv, parseOptions) {
    if(argv ! == undefined && ! Array.isArray(argv)) { throw new Error('first parameter to parse must be array or undefined');
    }
    parseOptions = parseOptions || {};

    if (argv === undefined) {
      argv = process.argv;
      if (process.versions && process.versions.electron) {
        parseOptions.from = 'electron';
      }
    }
    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 && process.mainModule) { this._scriptPath = process.mainModule.filename; } this._name = this._name || (this._scriptPath && path.basename(this._scriptPath, path.extname(this._scriptPath))); this._parseCommand([], userArgs);return this;
  };

  parseAsync(argv, parseOptions) {
    this.parse(argv, parseOptions);
    return Promise.all(this._actionResults).then(() => this);
  };

  _executeSubCommand(subcommand, args) {
    args = args.slice();
    let launchWithNode = false;
    const sourceExt = ['.js'.'.ts'.'.mjs'];

    this._checkForMissingMandatoryOptions();

    const scriptPath = this._scriptPath;

    let baseDir;
    try {
      const resolvedLink = fs.realpathSync(scriptPath);
      baseDir = path.dirname(resolvedLink);
    } catch (e) {
      baseDir = '. '; 
    }

    let bin = path.basename(scriptPath, path.extname(scriptPath)) + The '-' + subcommand._name;
    if (subcommand._executableFile) {
      bin = subcommand._executableFile;
    }

    const localBin = path.join(baseDir, bin);
    if (fs.existsSync(localBin)) {
      bin = localBin;
    } else {
      sourceExt.forEach((ext) => {
        if (fs.existsSync(`${localBin}${ext}`)) {
          bin = `${localBin}${ext}`; }}); } launchWithNode =sourceExt.includes(path.extname(bin));

    let proc;
    if(process.platform ! = ='win32') {
      if (launchWithNode) {
        args.unshift(bin);
        args = incrementNodeInspectorPort(process.execArgv).concat(args);

        proc = spawn(process.argv[0], args, { stdio: 'inherit' });
      } else {
        proc = spawn(bin, args, { stdio: 'inherit'}); }}else {
      args.unshift(bin);
      args = incrementNodeInspectorPort(process.execArgv).concat(args);
      proc = spawn(process.execPath, args, { stdio: 'inherit' });
    }

    const signals = ['SIGUSR1'.'SIGUSR2'.'SIGTERM'.'SIGINT'.'SIGHUP'];
    signals.forEach((signal) => {
      process.on(signal, () => {
        if (proc.killed === false&& proc.exitCode === null) { proc.kill(signal); }}); }); constexitCallback = this._exitCallback;
    if (!exitCallback) {
      proc.on('close', process.exit.bind(process));
    } else {
      proc.on('close', () = > {exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync'.'(close)'));
      });
    }
    proc.on('error', (err) => {
      if (err.code === 'ENOENT') {
        const executableMissing = `'${bin}' does not exist
 - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
 - if the default executable name is not suitable, use the executableFile option to supply a custom name`;
        throw new Error(executableMissing);
      } else if (err.code === 'EACCES') {
        throw new Error(`'${bin}' not executable`);
      }
      if (!exitCallback) {
        process.exit(1);
      } else {
        const wrappedError = new CommanderError(1, 'commander.executeSubCommandAsync'.'(error)');
        wrappedError.nestedError = err;
        exitCallback(wrappedError); }}); this.runningCommand = proc; }; _dispatchSubcommand(commandName, operands, unknown) {
    const subCommand = this._findCommand(commandName);
    if(! subCommand) this._helpAndError();if (subCommand._executableHandler) {
      this._executeSubCommand(subCommand, operands.concat(unknown));
    } else{ subCommand._parseCommand(operands, unknown); }}; _parseCommand(operands, unknown) { const parsed = this.parseOptions(unknown); operands = operands.concat(parsed.operands); unknown = parsed.unknown; this.args = operands.concat(unknown);if (operands && this._findCommand(operands[0])) {
      this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
    } else if (this._lazyHasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
      if (operands.length === 1) {
        this.help();
      } else{ this._dispatchSubcommand(operands[1], [], [this._helpLongFlag]); }}else if (this._defaultCommandName) {
      outputHelpIfRequested(this, unknown); 
      this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
    } else {
      if(this.commands.length && this.args.length === 0 && ! this._actionHandler && ! this._defaultCommandName) { this._helpAndError(); } outputHelpIfRequested(this, parsed.unknown); this._checkForMissingMandatoryOptions();if (parsed.unknown.length > 0) {
        this.unknownOption(parsed.unknown[0]);
      }

      if (this._actionHandler) {
        const args = this.args.slice();
        this._args.forEach((arg, i) => {
          if (arg.required && args[i] == null) {
            this.missingArgument(arg.name);
          } else if(arg.variadic) { args[i] = args.splice(i); }}); this._actionHandler(args); this.emit('command:' + this.name(), operands, unknown);
      } else if (operands.length) {
        if (this._findCommand(The '*')) {
          this._dispatchSubcommand(The '*', operands, unknown);
        } else if (this.listenerCount('command:*')) {
          this.emit('command:*', operands, unknown);
        } else if(this.commands.length) { this.unknownCommand(); }}else if (this.commands.length) {
        this._helpAndError();
      } else{}}}; _findCommand(name) {if(! name)return undefined;
    return this.commands.find(cmd => cmd._name === name || cmd._aliases.includes(name));
  };

  _findOption(arg) {
    return this.options.find(option => option.is(arg));
  };

  _checkForMissingMandatoryOptions() {
    for (let cmd = this; cmd; cmd = cmd.parent) {
      cmd.options.forEach((anOption) => {
        if(anOption.mandatory && (cmd._getOptionValue(anOption.attributeName()) === undefined)) { cmd.missingMandatoryOptionValue(anOption); }}); }}; parseOptions(argv) { const operands = []; const unknown = [];let dest = operands;
    const args = argv.slice();

    function maybeOption(arg) {
      return arg.length > 1 && arg[0] === The '-';
    }

    while (args.length) {
      const arg = args.shift();

      if (arg === The '-') {
        if(dest === unknown) dest.push(arg); dest.push(... args);break;
      }

      if (maybeOption(arg)) {
        const option = this._findOption(arg);
        if (option) {
          if (option.required) {
            const value = args.shift();
            if (value === undefined) this.optionMissingArgument(option);
            this.emit(`option:${option.name()}`, value);
          } else if (option.optional) {
            let value = null;
            if(args.length > 0 && ! maybeOption(args[0])) { value = args.shift(); } this.emit(`option:${option.name()}`, value);
          } else { 
            this.emit(`option:${option.name()}`);
          }
          continue; }}if (arg.length > 2 && arg[0] === The '-'&& arg[1] ! = =The '-') {
        const option = this._findOption(`-${arg[1]}`);
        if (option) {
          if (option.required || option.optional) {
            this.emit(`option:${option.name()}`, arg.slice(2));
          } else {
            this.emit(`option:${option.name()}`);
            args.unshift(`-${arg.slice(2)}`);
          }
          continue; }}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 (arg.length > 1 && arg[0] === The '-') {
        dest = unknown;
      }

      dest.push(arg);
    }

    return { operands, unknown };
  };

  opts() {
    if (this._storeOptionsAsProperties) {
      const result = {};
      const len = this.options.length;

      for (let i = 0; i < len; i++) {
        const key = this.options[i].attributeName();
        result[key] = key === this._versionOptionName ? this._version : this[key];
      }
      return result;
    }

    return this._optionValues;
  };

  missingArgument(name) {
    const message = `error: missing required argument '${name}'`;
    console.error(message);
    this._exit(1, 'commander.missingArgument', message);
  };

  optionMissingArgument(option, flag) {
    let message;
    if (flag) {
      message = `error: option '${option.flags}' argument missing, got '${flag}'`;
    } else {
      message = `error: option '${option.flags}' argument missing`;
    }
    console.error(message);
    this._exit(1, 'commander.optionMissingArgument', message);
  };

  missingMandatoryOptionValue(option) {
    const message = `error: required option '${option.flags}' not specified`;
    console.error(message);
    this._exit(1, 'commander.missingMandatoryOptionValue', message);
  };

  unknownOption(flag) {
    if (this._allowUnknownOption) return;
    const message = `error: unknown option '${flag}'`;
    console.error(message);
    this._exit(1, 'commander.unknownOption', message);
  };

  unknownCommand() {
    const partCommands = [this.name()];
    for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
      partCommands.unshift(parentCmd.name());
    }
    const fullCommand = partCommands.join(' ');
    const message = `error: unknown command '${this.args[0]}'. See '${fullCommand} ${this._helpLongFlag}'. `; console.error(message); this._exit(1,'commander.unknownCommand', message);
  };

  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 = new Option(flags, description);
    this._versionOptionName = versionOption.long.substr(2) || 'version';
    this.options.push(versionOption);
    this.on('option:' + this._versionOptionName, () => {
      process.stdout.write(str + '\n');
      this._exit(0, 'commander.version', str);
    });
    return this;
  };

  description(str, argsDescription) {
    if (str === undefined && argsDescription === undefined) return this._description;
    this._description = str;
    this._argsDescription = argsDescription;
    return this;
  };

  alias(alias) {
    if (alias === undefined) return this._aliases[0];
    let command = this;
    if(this.commands.length ! == 0 && this.commands[this.commands.length - 1]._executableHandler) {command = this.commands[this.commands.length - 1];
    }

    if (alias === command._name) throw new Error('Command alias can\'t be the same as its name'); command._aliases.push(alias); return this; }; aliases(aliases) { if (aliases === undefined) return this._aliases; aliases.forEach((alias) => this.alias(alias)); return this; }; usage(str) { if (str === undefined) { if (this._usage) return this._usage; const args = this._args.map((arg) => { return humanReadableArgName(arg); }); return '[options]' + (this.commands.length ? ' [command]':'') + (this._args.length ? ' ' + args.join(' ') :'); } this._usage = str; return this; }; name(str) { if (str === undefined) return this._name; this._name = str; return this; }; prepareCommands() { const commandDetails = this.commands.filter((cmd) => { return ! cmd._hidden; }).map((cmd) => { const args = cmd._args.map((arg) => { return humanReadableArgName(arg); }).join(' '); return [ cmd._name + (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') + (cmd.options.length ? ' [options]':'') + (args ? ' ' + args : ''),
        cmd._description
      ];
    });

    if (this._lazyHasImplicitHelpCommand()) {
      commandDetails.push([this._helpCommandnameAndArgs, this._helpCommandDescription]);
    }
    return commandDetails;
  };

  largestCommandLength() {
    const commands = this.prepareCommands();
    return commands.reduce((max, command) => {
      return Math.max(max, command[0].length);
    }, 0);
  };

  largestOptionLength() {
    const options = [].slice.call(this.options);
    options.push({
      flags: this._helpFlags
    });

    return options.reduce((max, option) => {
      return Math.max(max, option.flags.length);
    }, 0);
  };

  largestArgLength() {
    return this._args.reduce((max, arg) => {
      return Math.max(max, arg.name.length);
    }, 0);
  };

  padWidth() {
    let width = this.largestOptionLength();
    if (this._argsDescription && this._args.length) {
      if (this.largestArgLength() > width) {
        width = this.largestArgLength();
      }
    }

    if (this.commands && this.commands.length) {
      if (this.largestCommandLength() > width) {
        width = this.largestCommandLength();
      }
    }

    return width;
  };

  optionHelp() {
    const width = this.padWidth();
    const columns = process.stdout.columns || 80;
    const descriptionWidth = columns - width - 4;
    function padOptionDetails(flags, description) {
      return pad(flags, width) + '  ' + optionalWrap(description, descriptionWidth, width + 2); }; const help = this.options.map((option) => { const fullDesc = option.description + ((! option.negate && option.defaultValue ! == undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')':''); return padOptionDetails(option.flags, fullDesc); }); const showShortHelpFlag = this._helpShortFlag && ! this._findOption(this._helpShortFlag); const showLongHelpFlag = ! this._findOption(this._helpLongFlag); if (showShortHelpFlag || showLongHelpFlag) { let helpFlags = this._helpFlags; if (! showShortHelpFlag) { helpFlags = this._helpLongFlag; } else if (! showLongHelpFlag) { helpFlags = this._helpShortFlag; } help.push(padOptionDetails(helpFlags, this._helpDescription)); } return help.join('\n'); }; commandHelp() { if (! this.commands.length && ! this._lazyHasImplicitHelpCommand()) return ''; const commands = this.prepareCommands(); const width = this.padWidth(); const columns = process.stdout.columns || 80; const descriptionWidth = columns - width - 4; return [ 'Commands:', commands.map((cmd) => { const desc = cmd[1] ? '  ' + cmd[1] : ''; return (desc ? pad(cmd[0], width) : cmd[0]) + optionalWrap(desc, descriptionWidth, width + 2); }).join('\n').replace(/^/gm, '  '), '' ].join('\n'); }; helpInformation() { let desc = []; if (this._description) { desc = [ this._description, '']. const argsDescription = this._argsDescription; if (argsDescription && this._args.length) { const width = this.padWidth(); const columns = process.stdout.columns || 80; const descriptionWidth = columns - width - 5; desc.push('Arguments:'); desc.push(''); this._args.forEach((arg) => { desc.push('  ' + pad(arg.name, width) + '  ' + wrap(argsDescription[arg.name], descriptionWidth, width + 4)); }); desc.push(''); } } let cmdName = this._name; if (this._aliases[0]) { cmdName = cmdName + '|' + this._aliases[0]; } let parentCmdNames = ''; for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) { parentCmdNames = parentCmd.name() + ' ' + parentCmdNames; } const usage = [ 'Usage: ' + parentCmdNames + cmdName + ' ' + this.usage(), '']. let cmds = []; const commandHelp = this.commandHelp(); if (commandHelp) cmds = [commandHelp]; const options = [ 'Options:', '' + this.optionHelp().replace(/^/gm, '  '), '']. return usage .concat(desc) .concat(options) .concat(cmds) .join('\n'); }; outputHelp(cb) { if (! cb) { cb = (passthru) => { return passthru; }; } const cbOutput = cb(this.helpInformation()); if (typeof cbOutput ! = = 'string'&&! Buffer.isBuffer(cbOutput)) { throw new Error('outputHelp callback must return a string or a Buffer'); } process.stdout.write(cbOutput); this.emit(this._helpLongFlag); }; helpOption(flags, description) { this._helpFlags = flags || this._helpFlags; this._helpDescription = description || this._helpDescription; const splitFlags = this._helpFlags.split(/[ ,|]+/); this._helpShortFlag = undefined; if (splitFlags.length > 1) this._helpShortFlag = splitFlags.shift(); this._helpLongFlag = splitFlags.shift(); return this; }; help(cb) { this.outputHelp(cb); this._exit(process.exitCode || 0, 'commander.help', '(outputHelp)'); }; _helpAndError() { this.outputHelp(); this._exit(1, 'commander.help', '(outputHelp)'); }; };Copy the code
  • Export and function methods
exports = module.exports = new Command();
exports.program = exports; 
exports.Command = Command;
exports.Option = Option;
exports.CommanderError = CommanderError;

function camelcase(flag) {
  return flag.split(The '-').reduce((str, word) => {
    return str + word[0].toUpperCase() + word.slice(1);
  });
}

function pad(str, width) {
  const len = Math.max(0, width - str.length);
  return str + Array(len + 1).join(' ');
}

function wrap(str, width, indent) {
  const regex = new RegExp('{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+? ([\\s\u200B]|$)'.'g');
  const lines = str.match(regex) || [];
  return lines.map((line, i) => {
    if (line.slice(-1) === '\n') {
      line = line.slice(0, line.length - 1);
    }
    return ((i > 0 && indent) ? Array(indent + 1).join(' ') : ' ') + line.trimRight();
  }).join('\n');
}

function optionalWrap(str, width, indent) {
  if (str.match(/[\n]\s+/)) return str;
  const minWidth = 40;
  if (width < minWidth) return str;
  return wrap(str, width, indent);
}

function outputHelpIfRequested(cmd, args) {
  const helpOption = args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
  if (helpOption) {
    cmd.outputHelp();
    cmd._exit(0, 'commander.helpDisplayed'.'(outputHelp)'); }}function humanReadableArgName(arg) {
  const nameOutput = arg.name + (arg.variadic === true ? '... ' : ' ');
  return arg.required
    ? '<' + nameOutput + '>'
    : '[' + nameOutput + '] ';
}

function incrementNodeInspectorPort(args) {
  return args.map((arg) => {
    let result = arg;
    if (arg.indexOf('--inspect') = = = 0) {let debugOption;
      let debugHost = '127.0.0.1';
      let debugPort = '9229';
      let match;
      if((match = arg.match(/^(--inspect(-brk)?) $/))! == null) { debugOption = match[1]; }else if((match = arg.match(/^(--inspect(-brk|-port)?) = ([^ :] +) $/))! == null) { debugOption = match[1];if (/^\d+$/.test(match[3])) {
          debugPort = match[3];
        } else{ debugHost = match[3]; }}else if((match = arg.match(/^(--inspect(-brk|-port)?) =([^:]+):(\d+)$/)) ! == null) { debugOption = match[1]; debugHost = match[3]; debugPort = match[4]; }if(debugOption && debugPort ! = ='0') {
        result = `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`; }}return result;
  });
}
Copy the code

conclusion

This article mainly describes the basic configuration of self-built scaffolding and the configuration of basic commands. How about the configuration of specific commands, and listen to the next time

To be continued…

reference

  • Vue – cli’s official website
  • Nodejs website
  • Commander. Js source code
  • Commander source code
  • Spawn vs. Exec in Node.js