• NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践


    NodeJS 下构建
    命令行工具 与 交互式命令界面 的实践

    【文章简介】:你想快速搭建 如 vue-cli、create-vue 那样的炫酷脚手架吗?本文将讲解以下内容:
    Commander.JS是一个在NodeJS 环境下便捷地用于构建搞质量命令行工具的库,vue-cli 等很多命令行工具都是由它构建。本文详细讲解Commander.JS的相关概念,并通过案例介绍Commander.JS的使用方法。

    inquirer.js 是一个实现交互式命令行界面的NodeJS库,文本还介绍了如何通过 inquirer.js 来实现交互式命令行界面。
    此外,本文还介绍了一些其他的小工具,如:

    • kolorist ,在终端中使用色彩;
    • boxen,用于在终端中创建方框;

    通过阅读本文,能够迅速帮助你掌握搭建脚手架的常用基本工具,让你拥有迅速搭建自己的专属脚手架的基本能力!
    在这里插入图片描述

    注:本文部分内容仍在编辑,预计本月内完工 (202208)

    jcLee95 的个人博客
    邮箱 :291148484@163.com
    CSDN 主页https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
    本文地址https://blog.csdn.net/qq_28550263/article/details/126220698

    目 录

    1. 概述

    2. 命令的相关概念

    3. 使用 Commander.js 搭建 命令工行工具

    4. 使用 inquirer.js 构建 交互式命令界面

    5. 奇技淫巧:让你的CLI更美观


    1. 概述

    Commander.js是一个在NodeJS 环境下便捷地用于构建搞质量命令行工具的库,vue-cli 等很多命令行工具都是由它构建。inquirer.js 是一个实现交互式命令行界面的 NodeJS 库,通过使用该库能够帮我们实现命令界面的交互式。kolorist 是一个

    2. 命令的相关概念

    3. 使用 Commander.js 搭建命令工行工具

    3.1 安装

    npm install commander
    # 或者:
    yarn add commander
    
    • 1
    • 2
    • 3

    3.2 引入 commander 的两种方式

    方式1:

    const { program } = require('commander');
    
    • 1

    方式2:

    const { Command } = require('commander');
    const program = new Command();
    
    • 1
    • 2

    区别:

    方式1中直接引入 commander 库中暴露(export)的变量 programCommand对象的实例,不需要再使用new来创建 Command对象的实例了,但对象名字只能是 program。
    方式2中直接导入了 Command 对象,你需要手动创建它的实例:

    const program = new Command();
    
    • 1

    这种方法下当然你可以将 Command 对象命名为其他的合法标识符,而不一定是program

    3.3 从案例入门

    3.3.1 例 1

    在这里插入图片描述

    1. 新建项目

    新建一个 NodeJS 项目,默认初始化项目信息:

    npm init -y
    
    • 1

    2. 安装commander.js

    npm install commander
    
    • 1

    3. 编写脚本

    建立文件eg1.js,输入以下内容:

    // eg1.js
    const { Command } = require('commander');
    const program = new Command();
    
    program
      .name('字符串工具')
      .description('一些JavaScript字符串实用程序的 命令行工具')
      .version('0.0.1');
    
    program.command('split')
      .description('将字符串拆分成子字符串,并显示为数组')
      .argument('', '要拆分的字符串')
      .option('--first', '仅显示第一个子字符串')
      .option('-s, --separator ', '分隔符字符', ',')
      .action((str, options) => {
        const limit = options.first ? 1 : undefined;
        console.log(str.split(options.separator, limit));
    });
    
    program.parse();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4. 运行脚本

    现在在该项目的根目录下打开终端。在终端中测试我们的命令行工具脚本。

    (1)获取你的 CLI tool 版本
    node .\eg1.js -V
    
    • 1

    Out[]:

    0.0.1
    
    • 1

    【评注】:可以看到,这个版本号就是上面代码块第8行.version('0.0.1');所指定的版本号。

    (2)获取CLI模块级帮助信息
    node .\eg1.js -h
    
    • 1

    Out[]:

    Usage: 字符串工具 [options] [command]
    
    一些JavaScript字符串实用程序的 命令行工具
    
    Options:
      -V, --version             output the version number
      -h, --help                display help for command
    
    Commands:
      split [options]   将字符串拆分成子字符串,并显示为数组
      help [command]            display help for command
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    【评注】:可以看到,当前你的 CLI 模块一共有两个选项,分别是 -V-h 这是模块级选项,不需要你手动定义,由 commander.js 内置提供。除了你自定义的命令split 外,还有一个名为help的命令,它同样是由 commander.js 内置提供的。

    (3)获取该 CLI 模块中某个命令的帮助信息
    node .\eg1.js split -h
    
    • 1

    Out[]:

    Usage: 字符串工具 split [options] 
    
    将字符串拆分成子字符串,并显示为数组
    
    Arguments:
      string                  要拆分的字符串
    
    Options:
      --first                 仅显示第一个子字符串
      -s, --separator   分隔符字符 (default: ",")
      -h, --help              display help for command
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    (4)测试你编写的命令

    在脚本中,我们通过 .command(’split‘) 定义了一个名为split的命令。
    下面这个例子使用空格 作为分隔符:

    node .\eg1.js split --separator=" " "hello, my name is Jack!"
    
    • 1

    Out[]:

    [ 'hello,', 'my', 'name', 'is', 'Jack!' ]
    
    • 1

    下面这个例子指点逗号(,)作为分隔符:

    node .\eg1.js split --separator="," "hello, my name is Jack!"
    
    • 1

    Out[]:

    [ 'hello', ' my name is Jack!' ]
    
    • 1

    以下是在 powershell 窗口实际运行的截图:
    在这里插入图片描述

    5. 归纳1:声明命令

    3.2 小节中,我们介绍了引入commander的两种方式,默认情况下,使用program作为实例对象的变量名。
    在本例(【例1】) 我们使用program构建了我们的第一个命令行工具。可以看到,这个例子中唯一的自定义命令split就是通过program.command()声明的的。

    变量 program 是一个 Command 实例,而command()是其下的一个实例方法,仍返回的Command:

    Command.command(nameAndArgs: string, opts?: CommandOptions | undefined): Command
    
    • 1

    其详细的接口描述如下:

      command(nameAndArgs: string, opts?: CommandOptions): ReturnType<this['createCommand']>;
      /**
       * 定义一个命令,在单独的可执行文件中实现。
       *
       * @remarks
       * 命令描述作为第二个参数提供给 `.command`.
       *
       * @param nameAndArgs - 命令名和参数, args是 `` 或者 `[optional]` ,last也可以是`variadic...`
       * @param description - 可执行命令的描述
       * @param opts - 配置选项
       * @returns 用于链接的 “this” 的 command 
       */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    例如:

    program
      .command('start ', 'start named service')
      .command('stop [service]', 'stop named service, or all if no name supplied');
    
    • 1
    • 2
    • 3

    该方法还有一个重载

      command(nameAndArgs: string, description: string, opts?: ExecutableCommandOptions): this;
    
      /**
       * 创建新的独立 command 的工厂例程。
       *
       * 有关创建附加子命令的信息,请参见`.command()',该子命令使用此例程来创建命令。您可以覆盖 createCommand 来定制子命令。
       */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6. 归纳2:声明选项

      7. 归纳3:选项 类型 和 默认值

        8. 拓展1:取反选项

          9. 拓展2:可选参数的选项

            10. 归纳4:处理函数

              11. 归纳5:其它细节

              (1)工具名称

              通过.name()方法可以用于声明你的命令行工具的版本号,例如在本例中:

              program
                .name('字符串工具')
              
              • 1
              • 2

              这样以后,我们在帮助文档中就可以看到你的命令行工具最开始的

              Usage: 字符串工具 [options] [command]
              
              • 1
              (3)版本选项

              通过.version()方法可以用于声明你的命令行工具的版本号,用户在使用时,通过 3-3-1-4-1 小节 中的方法来来确定自己使用的工具脚本。例如:

              program
                .version('0.0.1');
              
              • 1
              • 2

              表示当前的版本号为0.0.1

              3.4 进阶

              这部分当前正在编辑中

              3.4.1 补充

              3.4.2 生命周期钩子

              3.4.3 自动化帮助信息

              3.5 接口声明

              更多功能以及详细的接口语法格式,建议仔细阅读接口声明文件。在本章节中,给出了主要功能类的中文接口声明。

              3.5.1 Command 类 接口声明

              export class Command {
                args: string[];
                processedArgs: any[];
                commands: Command[];
                parent: Command | null;
              
                constructor(name?: string);
              
                /**
                 * 将程序版本设置为 `str`
                 *
                 * 该方法自动注册 "-V, --version" 标志,当传递时,该标志将打印版本号。
                 *
                 * 您可以选择提供标志和描述来覆盖默认值。
                 */
                version(str: string, flags?: string, description?: string): this;
              
                /**
                 * 定义一个命令,使用 action 处理程序实现。
                 *
                 * @remarks
                 * 命令描述是使用 `.description` 提供的,而不是作为 `.command` 的参数。
                 *
                 * @example
                 * ```ts
                 * program
                 *   .command('clone  [destination]')
                 *   .description('clone a repository into a newly created directory')
                 *   .action((source, destination) => {
                 *     console.log('clone command called');
                 *   });
                 * ```
                 *
                 * @param nameAndArgs - 命令名和参数,args 是  `` 或者 `[optional]` 和 last 也可以是 `variadic...`
                 * @param opts - 配置选项
                 * @returns new command
                 */
                command(nameAndArgs: string, opts?: CommandOptions): ReturnType<this['createCommand']>;
                /**
                 * 定义一个命令,在单独的可执行文件中实现。
                 *
                 * @remarks
                 * 命令描述作为第二个参数提供给 `.command`。
                 *
                 * @example
                 * ```ts
                 *  program
                 *    .command('start ', 'start named service')
                 *    .command('stop [service]', 'stop named service, or all if no name supplied');
                 * ```
                 *
                 * @param nameAndArgs - 命令名和参数,args 是 `` 或者 `[optional]` 和 last 也可以是 `variadic...`
                 * @param description - 可执行命令的描述
                 * @param opts - 配置选项
                 * @returns 用于链接的 `this` 命令
                 */
                command(nameAndArgs: string, description: string, opts?: ExecutableCommandOptions): this;
              
                /**
                 * 创建新的独立命令的工厂例程。
                 *
                 * 有关创建附加子命令的信息,请参见`.command()`,该子命令使用此例程来创建命令。您可以覆盖createCommand来定制子命令。
                 */
                createCommand(name?: string): Command;
              
                /**
                 * 添加准备好的子命令。
                 *
                 * 有关创建从其父命令继承设置的附加子命令,请参见 `.command()`。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                addCommand(cmd: Command, opts?: CommandOptions): this;
              
                /**
                 * 创建新的独立参数的工厂例程。
                 *
                 * 有关创建附加参数的信息,请参见`.argument()`,它使用此例程来创建参数。您可以重写 createArgument 以返回自定义参数。
                 */
                createArgument(name: string, description?: string): Argument;
              
                /**
                 * 定义命令的参数语法。
                 *
                 * 默认情况下,参数是必需的,您可以在名称前后使用<>来明确指出这一点。
                 * 在可选参数的名称两边加上[]。
                 *
                 * @example
                 * ```
                 * program.argument('');
                 * program.argument('[output-file]');
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                argument<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
                argument(name: string, description?: string, defaultValue?: unknown): this;
              
                /**
                 * 定义命令的参数语法,添加准备好的参数。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                addArgument(arg: Argument): this;
              
                /**
                 * 定义命令的参数语法,一次添加多个(不带描述)。
                 *
                 * See also .argument().
                 *
                 * @example
                 * ```
                 * program.arguments(' [env]');
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                arguments(names: string): this;
              
                /**
                 * 覆盖是否添加隐式帮助命令的默认决定。
                 *
                 * @example
                 * ```
                 * addHelpCommand() // 强制打开
                 * addHelpCommand(false); // 强制关闭
                 * addHelpCommand('help [cmd]', 'display help for [cmd]'); // 强制使用自定义细节
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                addHelpCommand(enableOrNameAndArgs?: string | boolean, description?: string): this;
              
                /**
                 * 为生命周期事件添加挂钩。
                 */
                hook(event: HookEvent, listener: (thisCommand: Command, actionCommand: Command) => void | Promise<void>): this;
              
                /**
                 * 注册回调以替换调用 process.exit。
                 */
                exitOverride(callback?: (err: CommanderError) => never | void): this;
              
                /**
                 * 显示错误信息并退出(或调用exitOverride)。
                 */
                error(message: string, errorOptions?: ErrorOptions): never;
              
                /**
                 * 您可以通过覆盖 createhelp 或使用 “configureHelp()” 覆盖帮助属性,用Help的子类自定义帮助。
                 */
                createHelp(): Help;
              
                /**
                 * 您可以通过使用configureHelp()覆盖帮助属性来自定义帮助,或者通过覆盖createHelp()使用帮助的子类。
                 */
                configureHelp(configuration: HelpConfiguration): this;
                /** 获取配置 */
                configureHelp(): HelpConfiguration;
              
                /**
                 * 默认输出到stdout和stderr。您可以为特殊应用定制此功能。
                 * 您还可以通过覆盖outputError来自定义错误的显示。
                 *
                 * 配置属性都是函数:
                 * ```
                 * // 用于更改写入位置的函数,stdout 和 stderr
                 * writeOut(str)
                 * writeErr(str)
                 * // 用于指定帮助换行宽度的匹配函数
                 * getOutHelpWidth()
                 * getErrHelpWidth()
                 * // functions based on what is being written out
                 * outputError(str, write) // 用于显示错误,不用于显示帮助
                 * ```
                 */
                configureOutput(configuration: OutputConfiguration): this;
                /** 获取配置 */
                configureOutput(): OutputConfiguration;
              
                /**
                 * 复制在 根命令 和 子命令 之间通用的有用设置。
                 *
                 * (使用 `.command()' 添加命令时在内部使用,以便子命令继承父设置。)
                 */
                copyInheritedSettings(sourceCommand: Command): this;
              
                /**
                 * 出现错误后显示帮助或自定义消息。
                 */
                showHelpAfterError(displayHelp?: boolean | string): this;
              
                /**
                 * 为未知命令显示类似命令的建议,或为未知选项显示选项。
                 */
                showSuggestionAfterError(displaySuggestion?: boolean): this;
              
                /**
                 * 为命令注册回调 `fn`。
                 *
                 * @example
                 * ```
                 * program
                 *   .command('serve')
                 *   .description('start service')
                 *   .action(function() {
                 *     // do work here
                 *   });
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                action(fn: (...args: any[]) => void | Promise<void>): this;
              
                /**
                 * 用 `flags`、`description`和可选强制`fn`定义选项。
                 *
                 * `flags` 字符串包含短标志和/或长标志,由逗号、竖线或空格分隔。当使用 `-help' 时,以下都是有效的输出。
                 *
                 *     "-p, --pepper"
                 *     "-p|--pepper"
                 *     "-p --pepper"
                 *
                 * @example
                 * ```
                 * // 简单boolean默认为false
                 *  program.option('-p, --pepper', 'add pepper');
                 *
                 *  --pepper
                 *  program.pepper
                 *  // => Boolean
                 *
                 *  // 简单boolean默认为false
                 *  program.option('-C, --no-cheese', 'remove cheese');
                 *
                 *  program.cheese
                 *  // => true
                 *
                 *  --no-cheese
                 *  program.cheese
                 *  // => false
                 *
                 *  // 必需的参数
                 *  program.option('-C, --chdir ', 'change the working directory');
                 *
                 *  --chdir /tmp
                 *  program.chdir
                 *  // => "/tmp"
                 *
                 *  // 可选参数
                 *  program.option('-c, --cheese [type]', 'add cheese [marble]');
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                option(flags: string, description?: string, defaultValue?: string | boolean | string[]): this;
                option<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
                /** @deprecated 从v7开始,改为使用 `choices` 或自定义功能 */
                option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean | string[]): this;
              
                /**
                 * 定义一个必需的选项,该选项在解析后必须有一个值。这通常意味着必须在命令行上指定该选项。 (否则与 `.option()` 相同。)
                 *
                 * `flags` 字符串包含 短标志 和/或 长标志,由逗号、竖线或空格分隔。
                 */
                requiredOption(flags: string, description?: string, defaultValue?: string | boolean | string[]): this;
                requiredOption<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
                /** @deprecated 从v7开始,改为使用 `choices` 或自定义功能 */
                requiredOption(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean | string[]): this;
              
                /**
                 * 创建新的独立选项的工厂例程。
                 *
                 * 有关创建附加选项的信息,请参见 `.option()`,它使用此例程来创建选项。您可以重写 createOption 以返回自定义选项。
                 */
              
                createOption(flags: string, description?: string): Option;
              
                /**
                 * 添加一个准备好的选项。
                 *
                 * 有关在单个调用中创建和附加选项的信息,请参见 `.option()` 和 `.requiredOption()`。
                 */
                addOption(option: Option): this;
              
                /**
                 * 是将选项值作为属性存储在命令对象上,还是单独存储(指定false)。在这两种情况下,都可以使用 `.opts()` 来访问选项值。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                storeOptionsAsProperties<T extends OptionValues>(): this & T;
                storeOptionsAsProperties<T extends OptionValues>(storeAsProperties: true): this & T;
                storeOptionsAsProperties(storeAsProperties?: boolean): this;
              
                /**
                 * 检索选项值。
                 */
                getOptionValue(key: string): any;
              
                /**
                 * 存储选项值。
                 */
                setOptionValue(key: string, value: unknown): this;
              
                /**
                 * 存储选项值以及该值的来源。
                 */
                setOptionValueWithSource(key: string, value: unknown, source: OptionValueSource): this;
              
                /**
                 * 检索选项值源。
                 */
                getOptionValueSource(key: string): OptionValueSource;
              
                /**
                 * 用可选值改变短标志的解析。
                 *
                 * @example
                 * ```
                 * // for `.option('-f,--flag [value]'):
                 * .combineFlagAndOptionalValue(true)  // `-f80` 被视为 `--flag=80`, 这是默认行为
                 * .combineFlagAndOptionalValue(false) // `-fb` 被视为 `-f -b`
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                combineFlagAndOptionalValue(combine?: boolean): this;
              
                /**
                 * 允许命令行上的未知选项。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                allowUnknownOption(allowUnknown?: boolean): this;
              
                /**
                 * 允许命令行上有过多的命令参数。传递false使多余的参数成为错误。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                allowExcessArguments(allowExcess?: boolean): this;
              
                /**
                 * 启用位置选项。位置意味着在子命令之前指定全局选项,这允许子命令重用相同的选项名,也允许子命令打开 passThroughOptions 。
                 *
                 * 默认行为是非定位的,全局选项可能出现在命令行的任何地方。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                enablePositionalOptions(positional?: boolean): this;
              
                /**
                 * 传递命令参数之后的选项,而不是将它们视为命令选项,
                 * 因此实际的命令选项在命令参数之前。
                 * 为子命令启用此选项需要在程序(父命令)上启用位置选项。
                 *
                 * 默认行为是非定位的,选项可能出现在命令参数之前或之后。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                passThroughOptions(passThrough?: boolean): this;
              
                /**
                 * 解析 `argv`,设置选项并在定义时调用命令。
                 *
                 * 默认情况下,参数来自 node,应用程序为 argv[0],脚本在 argv[1] 中运行,其后是用户参数。
                 *
                 * @example
                 * ```
                 * program.parse(process.argv);
                 * program.parse(); // 隐式使用 process.argv 和自动检测 node vs electron conventions
                 * program.parse(my-args, { from: 'user' }); // 只是用户提供的参数,argv[0]没有什么特别的
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                parse(argv?: readonly string[], options?: ParseOptions): this;
              
                /**
                 * 解析 `argv`,设置选项并在定义时调用命令。
                 *
                 * 如果您的任何操作处理程序是异步的,请使用 parseAsync 而不是 parse。 返回一个 Promise.
                 *
                 * 默认情况下,参数来自 node,应用程序为 argv[0],脚本在 argv[1] 中运行,其后是用户参数。
                 *
                 * @example
                 * ```
                 * program.parseAsync(process.argv);
                 * program.parseAsync(); // 隐式使用 process.argv 和自动检测 node vs electron conventions
                 * program.parseAsync(my-args, { from: 'user' }); // 只是用户提供的参数,argv[0]没有什么特别的
                 * ```
                 *
                 * @returns Promise
                 */
                parseAsync(argv?: readonly string[], options?: ParseOptions): Promise<this>;
              
                /**
                 * 从 `argv` 中解析选项,删除已知选项,并返回argv拆分为操作数和未知参数。
                 *
                 *     argv => operands, unknown
                 *     --known kkk op => [op], []
                 *     op --known kkk => [op], []
                 *     sub --unknown uuu op => [sub], [--unknown uuu op]
                 *     sub -- --unknown uuu op => [sub --unknown uuu op], []
                 */
                parseOptions(argv: string[]): ParseOptionsResult;
              
                /**
                 * 返回一个包含作为键值对的本地选项值的对象
                 */
                opts<T extends OptionValues>(): T;
              
                /**
                 * 返回一个对象,该对象包含作为键值对的合并的本地和全局选项值。
                 */
                optsWithGlobals<T extends OptionValues>(): T;
              
                /**
                 * 设置描述。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
              
                description(str: string): this;
                /** @deprecated 从v8开始,改为使用 .argument 来添加带有描述的命令参数 */
                description(str: string, argsDescription: {[argName: string]: string}): this;
                /**
                 * 获取描述。
                 */
                description(): string;
              
                /**
                 * 设置摘要。当作为父命令的子命令列出时使用。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
              
                summary(str: string): this;
                /**
                 * 获取摘要。
                 */
                summary(): string;
              
                /**
                 * 为命令设置别名。
                 *
                 * 您可以多次调用以添加多个别名。自动生成的帮助中只显示第一个别名。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                alias(alias: string): this;
                /**
                 * 获取命令的别名。
                 */
                alias(): string;
              
                /**
                 * 为命令设置别名。
                 *
                 * 自动生成的帮助中只显示第一个别名。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                aliases(aliases: readonly string[]): this;
                /**
                 * 获取命令的别名。
                 */
                aliases(): string[];
              
                /**
                 * 设置命令用法。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                usage(str: string): this;
                /**
                 * 获取命令用法。
                 */
                usage(): string;
              
                /**
                 * 设置命令的名称。
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                name(str: string): this;
                /**
                 * 获取命令的名称。
                 */
                name(): string;
              
                /**
                 * 从脚本文件名设置命令的名称,例如 process.argv[1]、require.main.filename 或 __filename。
                 *
                 * (虽然自述文件中没有记录,但在内部和公共场合使用。)
                 *
                 * @example
                 * ```ts
                 * program.nameFromFilename(require.main.filename);
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                nameFromFilename(filename: string): this;
              
                /**
                 * 设置搜索此命令的可执行子命令的目录。
                 *
                 * @example
                 * ```ts
                 * program.executableDir(__dirname);
                 * // 或者
                 * program.executableDir('subcommands');
                 * ```
                 *
                 * @returns 用于链接的 `this` 命令
                 */
                executableDir(path: string): this;
                /**
                 * 获取可执行文件搜索目录。
                 */
                executableDir(): string;
              
                /**
                 * 输出此命令的帮助信息。
                 *
                 * 输出内置帮助和使用 `.addHelpText()` 添加的自定义文本。
                 *
                 */
                outputHelp(context?: HelpContext): void;
                /** @deprecated since v7 */
                outputHelp(cb?: (str: string) => string): void;
              
                /**
                 * 返回命令帮助文档。
                 */
                helpInformation(context?: HelpContext): string;
              
                /**
                 * 您可以传入标志和描述来覆盖命令的帮助标志和帮助描述。
                 * 传入false以禁用内置帮助选项。
                 */
                helpOption(flags?: string | boolean, description?: string): this;
              
                /**
                 * 输出帮助信息并退出。
                 *
                 * 输出内置帮助和使用 `.addHelpText()` 添加的自定义文本。
                 */
                help(context?: HelpContext): never;
                /** @deprecated since v7 */
                help(cb?: (str: string) => string): never;
              
                /**
                 * 添加要与内置帮助一起显示的附加文本。
                 *
                 * Position为 `before` 或 `after` 时仅影响该命令,而 `beforeAll` 或 `afterAll` 时影响该命令及其所有子命令。
                 */
                addHelpText(position: AddHelpTextPosition, text: string): this;
                addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string): this;
              
                /**
                 * 添加事件发生时的侦听器(回调)。(使用EventEmitter实现。)
                 */
                on(event: string | symbol, listener: (...args: any[]) => void): this;
              }
              
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15
              • 16
              • 17
              • 18
              • 19
              • 20
              • 21
              • 22
              • 23
              • 24
              • 25
              • 26
              • 27
              • 28
              • 29
              • 30
              • 31
              • 32
              • 33
              • 34
              • 35
              • 36
              • 37
              • 38
              • 39
              • 40
              • 41
              • 42
              • 43
              • 44
              • 45
              • 46
              • 47
              • 48
              • 49
              • 50
              • 51
              • 52
              • 53
              • 54
              • 55
              • 56
              • 57
              • 58
              • 59
              • 60
              • 61
              • 62
              • 63
              • 64
              • 65
              • 66
              • 67
              • 68
              • 69
              • 70
              • 71
              • 72
              • 73
              • 74
              • 75
              • 76
              • 77
              • 78
              • 79
              • 80
              • 81
              • 82
              • 83
              • 84
              • 85
              • 86
              • 87
              • 88
              • 89
              • 90
              • 91
              • 92
              • 93
              • 94
              • 95
              • 96
              • 97
              • 98
              • 99
              • 100
              • 101
              • 102
              • 103
              • 104
              • 105
              • 106
              • 107
              • 108
              • 109
              • 110
              • 111
              • 112
              • 113
              • 114
              • 115
              • 116
              • 117
              • 118
              • 119
              • 120
              • 121
              • 122
              • 123
              • 124
              • 125
              • 126
              • 127
              • 128
              • 129
              • 130
              • 131
              • 132
              • 133
              • 134
              • 135
              • 136
              • 137
              • 138
              • 139
              • 140
              • 141
              • 142
              • 143
              • 144
              • 145
              • 146
              • 147
              • 148
              • 149
              • 150
              • 151
              • 152
              • 153
              • 154
              • 155
              • 156
              • 157
              • 158
              • 159
              • 160
              • 161
              • 162
              • 163
              • 164
              • 165
              • 166
              • 167
              • 168
              • 169
              • 170
              • 171
              • 172
              • 173
              • 174
              • 175
              • 176
              • 177
              • 178
              • 179
              • 180
              • 181
              • 182
              • 183
              • 184
              • 185
              • 186
              • 187
              • 188
              • 189
              • 190
              • 191
              • 192
              • 193
              • 194
              • 195
              • 196
              • 197
              • 198
              • 199
              • 200
              • 201
              • 202
              • 203
              • 204
              • 205
              • 206
              • 207
              • 208
              • 209
              • 210
              • 211
              • 212
              • 213
              • 214
              • 215
              • 216
              • 217
              • 218
              • 219
              • 220
              • 221
              • 222
              • 223
              • 224
              • 225
              • 226
              • 227
              • 228
              • 229
              • 230
              • 231
              • 232
              • 233
              • 234
              • 235
              • 236
              • 237
              • 238
              • 239
              • 240
              • 241
              • 242
              • 243
              • 244
              • 245
              • 246
              • 247
              • 248
              • 249
              • 250
              • 251
              • 252
              • 253
              • 254
              • 255
              • 256
              • 257
              • 258
              • 259
              • 260
              • 261
              • 262
              • 263
              • 264
              • 265
              • 266
              • 267
              • 268
              • 269
              • 270
              • 271
              • 272
              • 273
              • 274
              • 275
              • 276
              • 277
              • 278
              • 279
              • 280
              • 281
              • 282
              • 283
              • 284
              • 285
              • 286
              • 287
              • 288
              • 289
              • 290
              • 291
              • 292
              • 293
              • 294
              • 295
              • 296
              • 297
              • 298
              • 299
              • 300
              • 301
              • 302
              • 303
              • 304
              • 305
              • 306
              • 307
              • 308
              • 309
              • 310
              • 311
              • 312
              • 313
              • 314
              • 315
              • 316
              • 317
              • 318
              • 319
              • 320
              • 321
              • 322
              • 323
              • 324
              • 325
              • 326
              • 327
              • 328
              • 329
              • 330
              • 331
              • 332
              • 333
              • 334
              • 335
              • 336
              • 337
              • 338
              • 339
              • 340
              • 341
              • 342
              • 343
              • 344
              • 345
              • 346
              • 347
              • 348
              • 349
              • 350
              • 351
              • 352
              • 353
              • 354
              • 355
              • 356
              • 357
              • 358
              • 359
              • 360
              • 361
              • 362
              • 363
              • 364
              • 365
              • 366
              • 367
              • 368
              • 369
              • 370
              • 371
              • 372
              • 373
              • 374
              • 375
              • 376
              • 377
              • 378
              • 379
              • 380
              • 381
              • 382
              • 383
              • 384
              • 385
              • 386
              • 387
              • 388
              • 389
              • 390
              • 391
              • 392
              • 393
              • 394
              • 395
              • 396
              • 397
              • 398
              • 399
              • 400
              • 401
              • 402
              • 403
              • 404
              • 405
              • 406
              • 407
              • 408
              • 409
              • 410
              • 411
              • 412
              • 413
              • 414
              • 415
              • 416
              • 417
              • 418
              • 419
              • 420
              • 421
              • 422
              • 423
              • 424
              • 425
              • 426
              • 427
              • 428
              • 429
              • 430
              • 431
              • 432
              • 433
              • 434
              • 435
              • 436
              • 437
              • 438
              • 439
              • 440
              • 441
              • 442
              • 443
              • 444
              • 445
              • 446
              • 447
              • 448
              • 449
              • 450
              • 451
              • 452
              • 453
              • 454
              • 455
              • 456
              • 457
              • 458
              • 459
              • 460
              • 461
              • 462
              • 463
              • 464
              • 465
              • 466
              • 467
              • 468
              • 469
              • 470
              • 471
              • 472
              • 473
              • 474
              • 475
              • 476
              • 477
              • 478
              • 479
              • 480
              • 481
              • 482
              • 483
              • 484
              • 485
              • 486
              • 487
              • 488
              • 489
              • 490
              • 491
              • 492
              • 493
              • 494
              • 495
              • 496
              • 497
              • 498
              • 499
              • 500
              • 501
              • 502
              • 503
              • 504
              • 505
              • 506
              • 507
              • 508
              • 509
              • 510
              • 511
              • 512
              • 513
              • 514
              • 515
              • 516
              • 517
              • 518
              • 519
              • 520
              • 521
              • 522
              • 523
              • 524
              • 525
              • 526
              • 527
              • 528
              • 529
              • 530
              • 531
              • 532
              • 533
              • 534
              • 535
              • 536
              • 537
              • 538
              • 539
              • 540
              • 541
              • 542
              • 543
              • 544
              • 545
              • 546
              • 547
              • 548
              • 549
              • 550
              • 551
              • 552
              • 553
              • 554
              • 555
              • 556
              • 557
              • 558
              • 559
              • 560
              • 561
              • 562
              • 563
              • 564
              • 565
              • 566

              4. 使用 inquirer.js 构建交互式命令界面

              当你使用 vue-cli (现在以被create-vue取代,但仍使用 inquirer )等脚手架创建 vue 项目时,一定见到过很炫酷的交互式问答,以让脚手架了解到你的项目需求,比如是否需要使用 vue-routervuex(已被pinia替代),是否使用 CSS 预处理器(如lesssass),等等。这样的交互式问答功能就是由 使用 inquirer.js 所提供支持的。

              当你自己搭建脚手架项目时,如果你的脚手架也要动态地了解用户本次执行的需求,那么使用 inquirer.js 是相当快捷、易于上手的选择。本章的任务就是学习和掌握如何使用 inquirer.js 来构造我们自己的一个交互式命令界面。

              4.1 安装

              npm install inquirer
              # 或者
              yarn add inquirer
              
              • 1
              • 2
              • 3

              4.2 从一个示例开始

              4.2.1 创建示例项目

              在项目下安装依赖:

              npm install --save inquirer@^8.0.0
              
              • 1

              新建文件 eg3.js (如果你喜欢也可以用其它文件名),并添加一下内容:

              // eg3.js
              const inquirer = require('inquirer');
              
              console.log('你好,欢迎体验 inquirer 命令行交互界面!');
              console.log('下面我们将问你一些问题。。。');
              
              const questions = [
                {
                  type: 'confirm',
                  name: 'toBeDelivered',
                  message: '今天天气好吗?(Y:好,N:不好)',
                  default: true,
                },
                {
                  type: 'rawlist',
                  name: 'select_number',
                  message: '您是先生还是女士?(Y:先生,N:女士)',
                  choices: ['先生', '女士'],
                },
                {
                  type: 'input',
                  name: 'CSDN_name',
                  message: "您的CSDN用户名是?",
                },
                {
                  type: 'input',
                  name: 'phone',
                  message: "您的手机号码是?",
                  validate(value) {
                    const pass = value.match(
                      /^([01]{1})?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})\s?((?:#|ext\.?\s?|x\.?\s?){1}(?:\d+)?)?$/i
                    );
                    if (pass) {
                      return true;
                    }
                    return '请输入有效的电话号码!!';
                  },
                },
                {
                  type: 'list',
                  name: 'fv_food',
                  message: '您喜欢的食物类型是?',
                  choices: ['甜食', '辛辣', '烧烤', '我不挑食'],
                },
                {
                  type: 'expand',
                  name: 'select_key',
                  message: '请依据 key 指定一个选项:',
                  choices: [
                    {
                      key: 'a',
                      name: '选项1',
                      value: 'A',
                    },
                    {
                      key: 'b',
                      name: '选项2',
                      value: 'B',
                    },
                    {
                      key: 'c',
                      name: '选项3',
                      value: 'C',
                    },
                  ],
                },
                {
                  type: 'rawlist',
                  name: 'select_number',
                  message: '选择一个项目对应的编号',
                  choices: ['第一', '第二', '第三'],
                },
                {
                  type: 'input',
                  name: '评论',
                  message: '输入一段话',
                  default: '这是默认输入的一段话。',
                },
              ];
              
              inquirer.prompt(questions).then((answers) => {
                console.log('\n本次对话交互的信息如下:');
                console.log(JSON.stringify(answers, null, '  '));
              });
              
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6
              • 7
              • 8
              • 9
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15
              • 16
              • 17
              • 18
              • 19
              • 20
              • 21
              • 22
              • 23
              • 24
              • 25
              • 26
              • 27
              • 28
              • 29
              • 30
              • 31
              • 32
              • 33
              • 34
              • 35
              • 36
              • 37
              • 38
              • 39
              • 40
              • 41
              • 42
              • 43
              • 44
              • 45
              • 46
              • 47
              • 48
              • 49
              • 50
              • 51
              • 52
              • 53
              • 54
              • 55
              • 56
              • 57
              • 58
              • 59
              • 60
              • 61
              • 62
              • 63
              • 64
              • 65
              • 66
              • 67
              • 68
              • 69
              • 70
              • 71
              • 72
              • 73
              • 74
              • 75
              • 76
              • 77
              • 78
              • 79
              • 80
              • 81
              • 82
              • 83
              • 84

              运行效果如下:
              在这里插入图片描述

              4.2.2 Inquirer 的导入方法

              Inquirer v9 及更高版本是本机 esm 模块,这意味着您不能再使用 commonjs 语法require(‘inquirer’)。如果像本例中一样,使用传统 commonjs 语法,则需要像上面这样安装 Inquirer v8,否则需要使用import inquirer from 'inquirer'的写法。比如本例使用:

              npm install --save inquirer@^8.0.0
              # 或者
              yarn add inquirer@^8.0.0
              
              • 1
              • 2
              • 3

              则对应使用 commonjs 语法导入 inquirer:

              const inquirer = require('inquirer');
              
              • 1

              而安装 Inquirer v9 之后的版本则可以直接安装,就像靠头那样:

              npm install inquirer
              # 或者
              yarn add inquirer
              
              • 1
              • 2
              • 3

              导入语法也对应换成 ES module的语法:

              import inquirer from 'inquirer'
              
              • 1

              4.2.3 Inquirer 问题 与 question 对象

              启动提示界面(查询会话)

              inquirer.prompt(questions, answers) -> promise
              
              • 1

              4.2.4 Inquirer 回答(Answers)

              4.2.5 Inquirer 分隔符(Separator)

              4.2.6 Inquirer 问题提示类型(Separator)

              (1)列表 list

              例如:

              {
                type: 'list',
                name: 'fv_food',
                message: '您喜欢的食物类型是?',
                choices: ['甜食', '辛辣', '烧烤', '我不挑食'],
              },
              
              • 1
              • 2
              • 3
              • 4
              • 5
              • 6

              (2)原始列表 rawlist

                (3)扩展 expand

                  (4)选择框 checkbox

                  以下是一个使用了 checkbox 类型的完整示范:

                  const inquirer = require('inquirer');
                  
                  inquirer
                    .prompt([
                      {
                        type: 'checkbox',
                        message: '选择框',
                        name: 'ck',
                        choices: [
                          { name: 'option1' },
                          { name: 'option2'},
                          new inquirer.Separator('--- 下面的 option3 指定了默认被选中: ---'),
                          {
                            name: 'option3',
                            checked: true,
                          },
                          new inquirer.Separator('--- 下面的 option4 是一个被禁用的选项: ---'),
                          {
                            name: 'option4',
                            disabled: '禁用的选项',
                          },
                          { name: 'option5'},
                        ],
                        validate(answer) {
                          if (answer.length < 1) {
                            return '您必须选择至少一个选项。';
                          }
                  
                          return true;
                        },
                      },
                    ])
                    .then((answers) => {
                      console.log("以下是你的选择结果:");
                      console.log(JSON.stringify(answers, null, '  '));
                    });
                  
                  • 1
                  • 2
                  • 3
                  • 4
                  • 5
                  • 6
                  • 7
                  • 8
                  • 9
                  • 10
                  • 11
                  • 12
                  • 13
                  • 14
                  • 15
                  • 16
                  • 17
                  • 18
                  • 19
                  • 20
                  • 21
                  • 22
                  • 23
                  • 24
                  • 25
                  • 26
                  • 27
                  • 28
                  • 29
                  • 30
                  • 31
                  • 32
                  • 33
                  • 34
                  • 35
                  • 36

                  其效果如下:
                  在这里插入图片描述

                  (5)确认 confirm

                  例如:

                  {
                    type: 'confirm',
                    name: 'toBeDelivered',
                    message: '您的心情怎么样?(Y:好,N:不好)',
                    default: true,
                  },
                  
                  • 1
                  • 2
                  • 3
                  • 4
                  • 5
                  • 6

                  (6)输入 input

                  例如:
                  以下是引导脚手架用户输入手机号码,并在手机号码输入错误的时候提示“请输入合法的手机号码!”例子:

                  {
                    type: 'input',
                    name: 'phone',
                    message: "您的手机号码是?",
                    validate(value) {
                      const pass = value.match(
                        /^([01]{1})?[-.\s]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})\s?((?:#|ext\.?\s?|x\.?\s?){1}(?:\d+)?)?$/i
                      );
                      if (pass) {
                        return true;
                      }
                  
                      return '请输入合法的手机号码!';
                    },
                  },
                  
                  • 1
                  • 2
                  • 3
                  • 4
                  • 5
                  • 6
                  • 7
                  • 8
                  • 9
                  • 10
                  • 11
                  • 12
                  • 13
                  • 14
                  • 15

                  以下是另外一个完整的示例:

                  const inquirer = require('inquirer');
                  
                  inquirer
                    .prompt([
                      {
                        type: 'list',
                        name: 'drinks',
                        message: '😘你想来点什么呢?',
                        choices: [
                          '咖啡',
                          '奶茶'
                        ],
                      },
                      {
                        type: 'list',
                        name: 'size',
                        message: '😘我们有以下几种被型,您要哪种呢?',
                        choices: ['小杯(Small)', '中杯(Middle)', '大杯(Large)', '特大杯(Super Large)'],
                        filter(val) {
                          return val.toLowerCase();
                        },
                      },
                    ])
                    .then((answers) => {
                      console.log(`您要的是${answers.size}${answers.drinks}`);
                    });
                  
                  • 1
                  • 2
                  • 3
                  • 4
                  • 5
                  • 6
                  • 7
                  • 8
                  • 9
                  • 10
                  • 11
                  • 12
                  • 13
                  • 14
                  • 15
                  • 16
                  • 17
                  • 18
                  • 19
                  • 20
                  • 21
                  • 22
                  • 23
                  • 24
                  • 25
                  • 26

                  其运行效果如下:
                  在这里插入图片描述

                  (7)数字 number

                  例如:

                  
                  
                  • 1

                  (8)密码 password

                  例如:

                  const inquirer = require('inquirer');
                  
                  const requireLetterAndNumber = (value) => {
                    if (/\w/.test(value) && /\d/.test(value)) {
                      return true;
                    }
                  
                    return '错误:密码至少需要有一个字母和一个数字';
                  };
                  
                  inquirer
                    .prompt([
                      {
                        type: 'password',
                        message: '这是不显示任何字符的密码输入形式 - 请输入密码:',
                        name: 'psw1',
                        validate: requireLetterAndNumber,
                      },
                      {
                        type: 'password',
                        message: '这是使用屏蔽符显示密码输入形式 - 请输入密码:',
                        name: 'psw2',
                        mask: '*',
                        validate: requireLetterAndNumber,
                      },
                    ])
                    .then((answers) => {
                      console.log(`完全不显示输入的密码是:${answers.psw1},显示为屏蔽符输入的密码是:${answers.psw2}`);
                    });
                  
                  • 1
                  • 2
                  • 3
                  • 4
                  • 5
                  • 6
                  • 7
                  • 8
                  • 9
                  • 10
                  • 11
                  • 12
                  • 13
                  • 14
                  • 15
                  • 16
                  • 17
                  • 18
                  • 19
                  • 20
                  • 21
                  • 22
                  • 23
                  • 24
                  • 25
                  • 26
                  • 27
                  • 28
                  • 29

                  其效果如下:
                  在这里插入图片描述

                  (9)编辑器 editor

                  例如:

                  
                  
                  • 1

                  5. 奇技淫巧:让你的CLI更美观

                  5.1 通过kolorist 在终端中使用色彩

                  5.1.1 安装 kolorist

                  npm install --save-dev kolorist
                  # 或者
                  yarn add kolorist
                  
                  • 1
                  • 2
                  • 3

                  5.1.2 案例入门

                    5.2 通过boxen 在终端中创建方框

                    5.2.1 安装 kolorist

                    npm install boxen
                    # 或者
                    yarn add boxen
                    
                    • 1
                    • 2
                    • 3

                    5.2.2 案例入门

                    • 相关阅读:
                      Java私活500元,做个JavaWeb仓储管理网站(二)
                      Ceph(L版本)部署及相关概念
                      简单走近ChatGPT
                      Redis_三种集群模式
                      vector 用法 说明
                      05_用一个栈实现另一个栈的排序
                      如何在Python中操作MySQL?
                      go-micro集成链路跟踪的方法和中间件原理
                      npmp 的简单理解
                      JAVA要点
                    • 原文地址:https://blog.csdn.net/qq_28550263/article/details/126220698