2023-01-脚手架开发yargs和command
1. 场景需求
开发脚手架,框架有两个
2. yargs
2.1 简单示例
bin/index.js
nginx
#! /usr/bin/env node
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
const arg = hideBin(process.argv);
yargs(arg).argv
package.json
nginx
{
  "name": "yargs",
  "version": "1.0.0",
  "bin": {
    "qingfengmy-yargs": "bin/index.js"
  },
  "license": "MIT",
  "dependencies": {
    "yargs": "^17.6.2"
  }
}
执行命令
nginx
zhang@zhangMacBook-Pro yargs % npm link
added 1 package in 638ms
zhang@zhangMacBook-Pro yargs % qingfengmy-yargs --help
选项:
  --help     显示帮助信息                                                 [布尔]
  --version  显示版本号                                                   [布尔]
2.2 原理
命令行里执行qingfengmy-yargs的时候,会去环境变量中找,我们电脑已经安装了node,node的环境变量位置
nginx
zhang@zhangMacBook-Pro yargs % where qingfengmy-yargs
/Users/zhang/.nvm/versions/node/v16.17.0/bin/qingfengmy-yargs
而执行npm link就是把软连接放到node/bin目录下 
那么命令行里执行qingfengmy-yargs就是执行index.js文件。但是我们一般不是需要node index.js这样去执行吗?现在怎么能直接执行index.js呢? index.js改成
nginx
#! /usr/bin/env node
console.log('sss');
然后直接执行bin/index.js,发现也是可以直接执行的。
nginx
zhang@zhangMacBook-Pro yargs % bin/index.js
sss
原因就是这行代码
nginx
#! /usr/bin/env node
#! 是标注文件可以当做脚本运行的,后面表示用node运行,node在env环境变量中去找。
2.3 复杂示例
javascript
#! /usr/bin/env node
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
// 文字顶格
const dedent = require('dedent');
const pkg = require('../package.json');
const arg = hideBin(process.argv);
const cli = yargs(arg);
// 添加参数
const argv = process.argv.slice(2);
const context = {
  qingfengmyVersion: pkg.version,
}
cli.strict()
  // 用法说明
  .usage('这是用法说明:qingfengmy-yargs <command> [options]')
  // 最少参数数量
  .demandCommand(1, '最少输入1个参数')
  // 别名
  .alias('h', "help")
  // 命令行宽度
  .wrap(cli.terminalWidth())
  // 页脚
  .epilogue(dedent`   这是
     页脚
     显示`)
  // 多个选项
  .options({
    debug: {
      type: 'boolean',
      description: '是否开启调试',
      alias: 'd',
    }
  })
  // 单个选项
  .option('log', {
    type: 'boolean',
    description: '是否打开日志',
    alias: 'l'
  })
  // 选项分组
  .group(['debug'], 'dev')
  // 子命令第一种写法
  .command(
    'toUpper [upper]',
    '变大写',
    (yargs) => {
      return yargs
        .positional('upper', {
          describe: '输入的参数',
          default: ''
        })
    },
    (argv) => {
      if (argv.upper) {
        console.log(argv.upper.toUpperCase())
      } else {
        console.log('你没有输入参数')
      }
    })
  // 子命令第二种写法
  .command({
    command: 'toLower [lower]',
    alias: ['tl', 'low'],
    description: '变小写',
    builder: (yargs) => {
      return yargs
        .positional('lower', {
          describe: '输入的参数',
          default: ''
        })
    },
    handler: (argv) => {
      // 这里可以拿到子命令参数和总的参数
      console.log(argv);
      if (argv.lower) {
        console.log(argv.lower.toLowerCase())
      } else {
        console.log('你没有输入参数')
      }
    }
  })
  // 推荐命令
  .recommendCommands()
  // 错误时候的提醒
  .fail((err, msg) => {
    console.log(err);
  })
  .parse(argv, context);
3. commander
3.1 简单示例
javascript
#! /usr/bin/env node
const commander = require('commander');
const pkg = require('../package.json')
// 单例
// const { program } = commander;
const program = new commander.Command();
program
  .version(pkg.version)
  .parse(process.argv);
3.2 复杂示例
javascript
#! /usr/bin/env node
const commander = require('commander');
const pkg = require('../package.json')
const program = new commander.Command();
// command必须写在program.parse前面
const toUpper = program.command('toUpper <upper>');
toUpper
  .description('变大写')
  .option('--log', '是否输出', false)
  .action((upper, toUpper) => {
    if (upper && toUpper.log) {
      console.log(upper.toUpperCase())
    }
  })
program
  .name('kobe')
  .version(pkg.version)
  .usage('用法说明:[option] <command>')
  .option('--debug [debug]', '是否开启调试', false)
  .parse(process.argv);
注意带参和不带参的区别
javascript
zhang@zhangMacBook-Pro commander % qingfengmy-commander toUpper --log a
A
3.3 其他高级用法
3.3.1 命令的子命令
javascript
const service = program.command('service');
service
  .command('start [port]')
  .description('启动服务器')
  .action((port) => {
    console.log('service start' + port)
  })
service
  .command('stop [port]')
  .description('停止服务器')
  .action((port) => {
    console.log('service stop' + port)
  })
javascript
zhang@zhangMacBook-Pro commander % qingfengmy-commander service start 2222
service start 2222
zhang@zhangMacBook-Pro commander % qingfengmy-commander service stop 2222
service stop 2222
3.3.2 参数说明
javascript
service.argument('<cmd> [options]')
  .description('参数说明', {
    start: '启动',
    'stop': '停止'
  })
javascript
zhang@zhangMacBook-Pro commander % qingfengmy-commander service  -h
Usage: kobe service [options] [command] <cmd> [options>
参数说明
Options:
  -h, --help      display help for command
Commands:
  start [port]    启动服务器
  stop [port]     停止服务器
  help [command]  display help for command
zhang@zhangMacBook-Pro commander %
3.3.3 和别的命令交互
javascript
const init = program
  .command('init [name]', '初始化')
  .action((name) => {
    console.log('name', name);
  })
command多了第二个参数,导致执行qingfengmy-command init aaa时,变成了kobe-init aaa
javascript
zhang@zhangMacBook-Pro commander % qingfengmy-commander init aaa
/Users/zhang/demos/demo-git/commander/node_modules/commander/lib/command.js:1030
        throw new Error(executableMissing);
        ^
Error: 'kobe-init' does not exist
3.3.4 拦截所有的未定义命令
javascript
program.on('command:*', (obj) => {
  console.log('未知命令', obj);
})
4. 命令行交互实现
https://www.npmjs.com/package/inquirerhttps://juejin.cn/post/6981736393458319397
需要注意的是v9以上只支持es module,不支持common,我们这里选择v8版本
javascript
const inquirer = require('inquirer');
inquirer
  .prompt([
    {
      type: 'input',
      name: 'project',
      message: '项目名称',
      default: 'copyLeft',
    },
    {
      type: 'list',
      name: 'type',
      message: '项目类型',
      default: 'vue',
      choices: [
        { name: 'vue', value: 'vue' },
        { name: 'react', value: 'react' },
        { name: 'jq', value: 'jq' },
      ]
    }
  ])
  .then((answers) => {
    console.log('答案: ', answers)
  })
  .catch((error) => {
    console.log(`错误: ${error}`)
  });
5. inquirer和yargs结合使用
javascript
const inquirer = require('inquirer');
const { hideBin } = require('yargs/helpers');
const yargs = require('yargs/yargs');
const options = {
  name: {
    // inquirer
    message: 'What is your name?',
    name: 'name',
    // yargs
    demandOption: true,
    describe: 'Your name',
    // shared
    type: 'string',
  },
};
(async () => {
  const answers = await inquirer.prompt(Object.values(options));
  Object.entries(answers).forEach(([key, value]) => {
    value && process.argv.push(`--${key}`, value);
  });
  const argv = yargs(hideBin(process.argv)).options(options).parseSync();
  console.log(`Hello, ${argv.name}!`);
})();
5. 命令行其他实现
- ora: loading
 - chalk: 文字加颜色