脚手架开发之命令行参数解析

脚手架开发必知必会,这篇文章介绍了本来开发脚手架的流程,以及命令行输入命令如何查找并执行对应的脚手架。

以下这篇文章带你更进一步了解脚手架如何解析命令参数从而执行对应的复杂命令的。市面上yargs, commander这两个库是比较流行的,下面我们一起来看看吧。

yargs

API介绍

js 复制代码
#!/usr/bin/env node

const yargs = require("yargs")
// 解析参数
const {hideBin} = require("yargs/helpers")
/**
 *   ./src/index.js  zh llm
 *   [ 'zh', 'llm' ]
 */
// process.argv 中前两个元素分别是:  当前node程序文件位置,当前终端执行文件夹(pwd)
const argv = hideBin(process.argv) // 解析参数,只保留控制台输入的参数
const cli = yargs(arg)

// 处理命令提示
cli
.usage("Usage: $0 <command> [options]") // 脚手架通用模板格式。显示在开头位置 $0表示脚手架名称
.argv

如果不指定严格模式,输入错误的命令将没有任何错误反馈。

js 复制代码
// 严格模式。让输入不正确的命令或者参数出现错误提示。而不是没有任何反馈。
.strict()

约束最少输入的命令,不包括主命令

js 复制代码
// 约束最少输入的命令
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")

配置option别名

js 复制代码
// 别名
.alias("h", "help")
.alias("v", "version")

修改终端信息展示宽度,让其有更好看的样式。cli.terminalWidth()表示整个终端宽度。

js 复制代码
// 修改终端信息展示宽度
.wrap(cli.terminalWidth())

help中定义结尾内容。

js 复制代码
.epilogue(`
When a command fails, all logs are written to lerna-debug.log in the current working directory.

For more information, check out the docs at <工具官网>
`)

增加全局的option选项,对所有命令都生效。

js 复制代码
// 配置全局option(配置多个)
.options({
  debug: {
    type: "boolean",
    describe: "dev debug",
    default: true,
    alias: "d",
    // 表示隐藏命令,用于内部开发使用。他不会显示在options列表中,但是可以被使用。
    hidden: true
  }
})
// 配置全局option(配置一个)
.option("name", {
  type: "string",
  describe: "project name",
  alias: "n"
})

进行options分组。

js 复制代码
.group(["debug", "name"], "dev options")
.group(['version', 'help'], "global options")

定义子命令。两种方式

  • 命令格式
  • 描述
  • builder 定义当前命令options
  • handler 指定当前命令对应的脚本
js 复制代码
// 方式一
.command({
  command: "init [name]", // 命令名 option
  aliases: ["i"], // 可配置多个别名
  describe: "List local packages",
  // 执行这个命令之前回调, 比如定义当前命令参数
  builder(yargs) {
    yargs.option("name", {
      type: "string",
      describe: "a project name",
      // 脚手架中的别名不能重复
      alias: "n1"
    })
  },
  // 执行当前命令脚本
  handler(argv) {
    console.log("argv", argv)
  },
})
//  方式二
.command('serve [port]', 'start the server', (yargs) => {
  return yargs
    .(positional / option)('port', {
      describe: 'port to bind on',
      default: 5000
    })
}, (argv) => {
  if (argv.verbose) console.info(`start server on :${argv.port}`)
  serve(argv.port)
})

错误命令名称,给出错误提示,匹配相近的命令提示用户。 会打印help信息,然后给出一个建议。我们也可以通过fail来定义自己的建议内容给。

js 复制代码
// 输入错误命令,提示用户
.recommendCommands()
// 定制自己的错误提示信息
.fail((msg, err) => {
  console.log(msg)
})

向参数列表中注入参数。我们不再通过hideBin来解析参数。

js 复制代码
const { version } = require("../package.json")
const context = {
  yargsStudy: version
}
const cli = yargs()

cli
.parse(process.argv.slice(2), context)

完整的yargs demo代码

js 复制代码
#!/usr/bin/env node

const yargs = require("yargs")
// 解析参数
// const {hideBin} = require("yargs/helpers")
// 移除文本首行空格
// const dedent = require("dedent")
/**
 *   ./src/index.js  zh llm
 *   [ 'zh', 'llm' ]
 */
// const arg = hideBin(process.argv) // 解析参数,只保留控制台输入的参数
// const cli = yargs(arg)

const { version } = require("../package.json")
const context = {
  yargsStudy: version
}
const cli = yargs()

cli
// 脚手架通用模板格式。$0表示脚手架名称
.usage("Usage: $0 <command> [options]")
// 输入错误命令,提示用户
.recommendCommands()
// 定制自己的错误提示信息
.fail((msg, err) => {
  console.log(msg)
})
// 严格模式。让输入不正确的命令或者参数出现错误提示。而不是没有任何反馈。
.strict()
// 约束最少输入的命令
.demandCommand(1, "A command is required. Pass --help to see all available commands and options.")
// 别名
.alias("h", "help")
.alias("v", "version")
// 修改终端信息展示宽度
.wrap(cli.terminalWidth())
// 结尾输出内容
.epilogue(`
When a command fails, all logs are written to lerna-debug.log in the current working directory.

For more information, check out the docs at https://lerna.js.org/docs/introduction
`)
// 配置全局option(配置多个)
.options({
  debug: {
    type: "boolean",
    describe: "dev debug",
    default: true,
    alias: "d",
    // 表示隐藏命令,用于内部开发使用
    // hidden: true
  }
})
// 配置全局option(配置一个)
.option("name", {
  type: "string",
  describe: "project name",
  alias: "n"
})
.group(["debug", "name"], "dev options")
.group(['version', 'help'], "global options")
.command({
  command: "init [name]",
  aliases: ["i"],
  describe: "List local packages",
  // 执行这个命令之前回调,一般定义一些options
  builder(yargs) {
    yargs.option("name", {
      type: "string",
      describe: "a project name",
      // 别名不能重复
      alias: "n1"
    })
  },
  handler(argv) {
    console.log("argv", argv)
  },
})
// 定义命令 方式二
.command('serve [port]', 'start the server', (yargs) => {
  return yargs
    .positional('port', {
      describe: 'port to bind on',
      default: 5000
    })
}, (argv) => {
  if (argv.verbose) console.info(`start server on :${argv.port}`)
  // serve(argv.port)
})
// .argv
.parse(process.argv.slice(2), context)

总结

Yargs常用API

  • Yargs.usage(提示脚手架用法)
  • Yargs.strict(开启以后可以报错提示)
  • Yargs.demandCommand(规定最少传几个command)
  • Yargs.recommendCommands(在输入错误command以后可以给你推荐最接近的正确的command)
  • Yargs.alias(起别名)
  • Yargs.options(定义多个option)
  • Yargs.option(定义option)
  • Yargs.fail(错误处理方法)
  • Yargs.group(给option分组)
  • Yargs.wrap(命令行工具的宽度)
  • Yargs.epilogue(命令行工具底部的提示)

Yargs开发流程

  • 脚手架初始化(将process.argv当参数传递给Yargs())
  • 脚手架命令注册(Yargs.command)
  • 脚手架参数解析(Yargs.parse)

commander

API介绍

commander提供类和单例(Command, program)来供我们调用

js 复制代码
const { program } = require('commander');

// 或者
const { Command } = require('commander');
const program = new Command();

处理命令提示,如果直接指定那么它将会把当前脚手架入口文件名连接上, 所以我们应该通过name()修改其名称。

js 复制代码
// 错误
.usage(`${Object.keys(config.bin)[0]} command [options]`)

// 正确
.name(Object.keys(config.bin)[0])
.usage(`command [options]`)

通过version()指定版本号

js 复制代码
.version(config.version)

// 或者传入详细信息
program.version('0.0.1', '-v, --vers', '选项描述');

使用.option()方法来定义选项

选项及其选项参数可以用空格分隔,也可以组合成同一个参数。选项参数可以直接跟在短选项之后,也可以在长选项后面加上 =

js 复制代码
serve -p 80
serve -p80
serve --port 80
serve --port=80

解析后的选项可以通过Command对象上的.opts()方法获取,同时会被传递给命令处理函数。

选项可以设置必填,可以通过[]来指定选项参数是否可选(如果指定就必须进行传入),并且可以指定默认值(使用时在不指定选项的时候生效)。

js 复制代码
program
  // 必传选项
  .requiredOption("-r --require <params>", "必传选项")
  // 选项可选参数,通过[]定义
  .option("-z --zh [params]", "描述选项", "默认值")
program.parse();

通过.command().addCommand()可以配置命令。

js 复制代码
// 返回新生成的命令(即该子命令)以供继续配置,并不返回program
program
  // 指定必填参数和可选参数
  .command('clone <source> [destination]')
  .description('命令描述')
  .option("-s -sonOption <reason>", "命令选项描述")
  .action((source, destination, options) => {
    console.log('当前命令执行需逻辑', source, destination, options);
  });

我们可以通过addCommand注册子命令的子命令,类似于命令分组。

js 复制代码
const serve = new Command("serve")
serve.command("start <port>")
.description("启动服务")
.action((port) => {
  console.log("启动服务", port);
});

program.addCommand(serve)

并且我们可以通过arguments设置通用的匹配命令,处理通过command, addCommand定义的命令外,其他命令都会命中arguments的定义。

js 复制代码
program
.arguments("<cmd> <options>")
.description("通用命令描述")
.action((cmd, options) => {
  console.log("通用命令执行逻辑", cmd, options);
});

.command()带有描述参数时,就意味着使用独立的可执行文件作为子命令, 即组合命令。 在控制台中声明command-test install时,其实是查找command-test-install脚手架。所以我们可以设置executableFile来修改执行的脚手架。

js 复制代码
program
.command("currentInstall", "联合命令")
.command('install [name]', '联合命令,关联vu-cli脚手架', { executableFile: 'vue-cli' })
.command('list', '设置isDefault指定脚手架默认命令, 并且隐藏命令', { isDefault: true, hidden: true })

program.parse(process.argv);

完整配置

js 复制代码
#! /usr/bin/env node
const  {program, Command} = require("commander") 
const config  = require( "../package.json")

program
.name(Object.keys(config.bin)[0])
.usage(`command [options]`)
.version(config.version)
// .usage(`${Object.keys(config.bin)[0]} command [options]`)
// .requiredOption("-r --require <params>", "必传选项")
.option("-l --llm <params>", "描述选项", "默认值")
.option("-z --zh [params]", "描述选项", "默认值")

// 子命令
program
  // 指定必填参数和可选参数
  .command('clone <source> [destination]')
  .description('命令描述')
  .option("-s --sonOption <reason>", "命令选项描述")
  .action((source, destination, options) => {
    console.log('当前命令执行需逻辑', source, destination, options);
  });

// 分组命令
const serve = new Command("serve")
serve.command("start <port>")
.description("启动服务")
.action((port) => {
  console.log("启动服务", port);
});
program.addCommand(serve)

// 通用命令
program
.arguments("<cmd> <options>")
.description("通用命令描述")
.action((cmd, options) => {
  console.log("通用命令执行逻辑", cmd, options);
});

// 关联命令
program
.command("currentInstall", "联合命令")
.command('install [name]', '联合命令,关联vu-cli脚手架', { executableFile: 'vue-cli' })
.command('list', '设置isDefault指定脚手架默认命令', { isDefault: true, hidden: true })

console.log("options", program.opts())
program.parse(process.argv)

yargs和commander区别

  • yargs
    • 采用链式方法调用设计(如 .command().option().action() 连续调用)
    • 更灵活,支持多种配置方式
  • commander
    • 注册子命令时会返回新的命令实例(不通过链式返回自身)
    • 更结构化,强调清晰的命令分层(addCommand

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注 、👍点赞 、⭐收藏✍️评论, 支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!

相关推荐
Zero1017131 小时前
【详解pnpm、npm、yarn区别】
前端·react.js·前端框架
&白帝&2 小时前
vue右键显示菜单
前端·javascript·vue.js
Wannaer2 小时前
从 Vue3 回望 Vue2:事件总线的前世今生
前端·javascript·vue.js
羽球知道2 小时前
在Spark搭建YARN
前端·javascript·ajax
光影少年2 小时前
vue中,created和mounted两个钩子之间调用时差值受什么影响
前端·javascript·vue.js
青苔猿猿3 小时前
node版本.node版本、npm版本和pnpm版本对应
前端·npm·node.js·pnpm
一只码代码的章鱼3 小时前
Spring的 @Validate注解详细分析
前端·spring boot·算法
zimoyin4 小时前
Kotlin 协程实战:实现异步值加载委托,对值进行异步懒初始化
java·前端·kotlin
恋猫de小郭4 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin
大学生小郑4 小时前
Go语言八股之Mysql基础详解
mysql·面试