命令参数

本指南介绍在自定义命令中定义命令参数。你将了解以下主题:

  • 定义位置参数
  • 使参数变为可选
  • 接受多个值
  • 转换参数值

概述

参数是用户在执行命令时在命令名称之后提供的位置值。与可以按任意顺序指定的标志不同,参数必须严格按照它们在你的命令类中定义的顺序提供。

例如,在命令 node ace make:controller users --resource 中,单词 users 是一个参数,而 --resource 是一个标志。参数非常适合具有自然顺序的必需输入值,例如文件名、资源名称或实体标识符。

定义你的第一个参数

你将命令参数定义为带有 @args 装饰器的类属性。Ace 会按照与你的类中相同的顺序接受参数,因此属性的顺序很重要。

最常见的参数类型是字符串参数,它接受任何文本值。使用 @args.string 装饰器来定义字符串参数。

commands/greet.ts
import { BaseCommand, args } from '@adonisjs/core/ace'

export default class GreetCommand extends BaseCommand {
  static commandName = 'greet'
  static description = 'Greet a user by name'

  /**
   * 定义一个必需的字符串参数
   */
  @args.string()
  declare name: string

  async run() {
    this.logger.info(`Hello, ${this.name}!`)
  }
}

用户现在可以通过为 name 参数提供值来运行你的命令。

node ace greet John

# 输出:Hello, John!

如果用户忘记提供必需的参数,Ace 会显示一条错误消息,指出缺少哪个参数。

接受多个值

某些命令需要为单个参数接受多个值。例如,处理多个文件的命令可能接受任意数量的文件名。

使用 @args.spread 装饰器来接受多个值。spread 参数必须是你的命令中的最后一个参数,因为它会捕获所有剩余的值。

commands/greet.ts
import { BaseCommand, args } from '@adonisjs/core/ace'

export default class GreetCommand extends BaseCommand {
  static commandName = 'greet'
  static description = 'Greet multiple users by name'

  /**
   * 将多个名称接受为数组
   */
  @args.spread()
  declare names: string[]

  async run() {
    this.names.forEach((name) => {
      this.logger.info(`Hello, ${name}!`)
    })
  }
}

用户现在可以在运行命令时提供任意数量的值。

node ace greet John Jane Bob

# 输出:
# Hello, John!
# Hello, Jane!
# Hello, Bob!

自定义参数名称和描述

参数名称会出现在帮助界面和错误消息中。默认情况下,Ace 使用你的属性名的短横线形式(dashed-case)作为参数名称。例如,名为 userName 的属性在帮助输出中会变成 user-name

你可以使用 argumentName 选项来自定义参数名称。

commands/greet.ts
@args.string({
  argumentName: 'user-name'
})
declare name: string

添加描述有助于用户理解他们应该提供什么值。描述会在用户运行 node ace greet --help 时出现在帮助界面中。

commands/greet.ts
@args.string({
  argumentName: 'user-name',
  description: 'Name of the user to greet'
})
declare name: string

使参数变为可选

默认情况下,所有参数都是必需的,以确保用户在执行命令之前提供必要的输入。但是,你可以通过将 required 选项设为 false 来使参数变为可选。

可选参数必须位于所有必需参数之后。这个排序要求避免了参数解析中的歧义。

commands/greet.ts
import { BaseCommand, args } from '@adonisjs/core/ace'

export default class GreetCommand extends BaseCommand {
  static commandName = 'greet'

  @args.string({
    description: 'Name of the user to greet'
  })
  declare name: string

  /**
   * 带有自定义措辞的可选问候消息
   */
  @args.string({
    description: 'Custom greeting message',
    required: false,
  })
  declare message?: string

  async run() {
    const greeting = this.message || 'Hello'
    this.logger.info(`${greeting}, ${this.name}!`)
  }
}

现在用户可以在有或没有第二个参数的情况下运行命令。

node ace greet John
# 输出:Hello, John!

node ace greet John "Good morning"
# 输出:Good morning, John!

提供默认值

你可以使用 default 属性为可选参数指定默认值。当用户不提供值时,Ace 会使用默认值代替。

commands/greet.ts
@args.string({
  description: 'Name of the user to greet',
  required: false,
  default: 'guest'
})
declare name: string

有了默认值,参数就变为可选的,但你的代码始终可以期望一个字符串值,而无需处理 undefined。

node ace greet
# 使用默认值 "guest"
# 输出:Hello, guest!

转换参数值

parse 方法允许你在将参数值赋给类属性之前对其进行转换或校验。这对于规范化输入、转换类型或执行校验很有用。

parse 方法接收来自命令行的原始字符串值,并且必须返回转换后的值。

commands/greet.ts
@args.string({
  argumentName: 'user-name',
  description: 'Name of the user to greet',
  parse(value) {
    /**
     * 将名称转换为大写
     */
    return value ? value.toUpperCase() : value
  }
})
declare name: string

现在,当用户提供名称时,它会在你的命令的 run 方法执行之前自动转换为大写。

node ace greet john
# 名称被转换为 "JOHN"
# 输出:Hello, JOHN!

你也可以使用 parse 方法进行校验,在值无效时抛出错误。

commands/create_user.ts
@args.string({
  description: 'Email address of the user',
  parse(value) {
    if (!value.includes('@')) {
      throw new Error('Please provide a valid email address')
    }
    return value.toLowerCase()
  }
})
declare email: string

访问所有参数

你可以使用 this.parsed.args 属性访问用户提供的所有参数,包括它们的原始值。这对于调试,或当你需要检查完整的参数列表时很有用。

commands/greet.ts
import { BaseCommand, args } from '@adonisjs/core/ace'

export default class GreetCommand extends BaseCommand {
  static commandName = 'greet'
  static description = 'Greet a user by name'

  @args.string()
  declare name: string

  async run() {
    /**
     * 以键值对象形式访问所有参数
     */
    console.log(this.parsed.args)
    // 输出:{ name: 'John' }

    this.logger.info(`Hello, ${this.name}!`)
  }
}