参数与 Flag
命令在终端中的参数就是 execute 方法或函数的普通参数,它们会自动映射到命令行参数。
如果将某个参数标记为可选,则无需传递它。如果参数有默认值,同样无需传递。
根据类型(string、number、union 等)的不同,传入的值会被自动反序列化并验证。
import { cli } from '@deepkit/app'; //函数式 new App().command('test', (name: string) => { console.log('Hello', name); }); //类 @cli.controller('test') class TestCommand { async execute(name: string) { console.log('Hello', name); } }
如果现在执行此命令而不指定 name 参数,将会报错:
$ ts-node app.ts test RequiredArgsError: Missing 1 required arg: name
使用 --help 可以获取有关必需参数的更多信息:
$ ts-node app.ts test --help USAGE $ ts-node-script app.ts test NAME
一旦将 name 作为参数传入,命令就会被执行,并且 name 会被正确传递。
$ ts-node app.ts test "beautiful world" Hello beautiful world
每种原始参数类型,例如 string、number、boolean、字符串字面量、它们的 union 以及它们的数组,都会自动用作 CLI 参数, 并被自动验证和反序列化。参数的顺序决定了 CLI 参数的顺序。你可以添加任意数量的参数。
一旦定义了复杂对象(interface、class、对象字面量),它就会被视为服务依赖, 依赖注入容器会尝试解析它。更多信息请参阅 依赖注入 章节。
多个值
类型为数组的参数会接收所有剩余的位置参数。每个值都会根据元素类型进行反序列化和验证。
数组之后的参数会保留各自的值,因此像 cp <files...> <target> 这样的命令可以按预期工作。
new App().command('copy', (files: string[], target: string) => { console.log('copy', files, 'to', target); }); new App().command('delete', (ids: number[]) => { console.log('delete', ids); });
$ ts-node app.ts copy a.txt b.txt backup/ copy [ 'a.txt', 'b.txt' ] to backup/ $ ts-node app.ts delete 1 2 3 delete [ 1, 2, 3 ] $ ts-node app.ts delete 1 two Invalid value for argument ids: 1,two. Cannot convert two to number [type]
必需的数组参数至少需要一个值。如果有默认值,例如 ids: number[] = [],则它是可选的。
Flag
Flag 是向命令传递值的另一种方式。它们大多是可选的,但并非必须如此。使用 Flag 类型修饰的参数可以通过 --name value 或 --name=value 传递。
import { Flag } from '@deepkit/app'; //函数式 new App().command('test', (id: number & Flag) => { console.log('id', name); }); //类 class TestCommand { async execute(id: number & Flag) { console.log('id', id); } }
$ ts-node app.ts test --help USAGE $ ts-node app.ts test OPTIONS --id=id (required)
在帮助视图的 "OPTIONS" 中可以看到,--id flag 是必需的。如果正确输入此 flag,命令将接收到该值。
$ ts-node app.ts test --id 23 id 23 $ ts-node app.ts test --id=23 id 23
Boolean Flag
Flag 的优点在于它们也可以用作无值 flag,例如用于激活某种行为。一旦参数被标记为可选的 Boolean,就会启用此行为。
import { Flag } from '@deepkit/app'; //函数式 new App().command('test', (remove: boolean & Flag = false) => { console.log('delete?', remove); }); //类 class TestCommand { async execute(remove: boolean & Flag = false) { console.log('delete?', remove); } }
$ ts-node app.ts test delete? false $ ts-node app.ts test --remove delete? true
多个 Flag
要向同一个 flag 传递多个值,可以将 flag 标记为数组。
import { Flag } from '@deepkit/app'; //函数式 new App().command('test', (id: number[] & Flag = []) => { console.log('ids', id); }); //类 class TestCommand { async execute(id: number[] & Flag = []) { console.log('ids', id); } }
$ ts-node app.ts test ids: [] $ ts-node app.ts test --id 12 ids: [12] $ ts-node app.ts test --id 12 --id 23 ids: [12, 23]
单字符 Flag
要允许 flag 也以单个字符的形式传递,可以使用 Flag<{char: 'x'}>。
import { Flag } from '@deepkit/app'; //函数式 new App().command('test', (output: string & Flag<{char: 'o'}>) => { console.log('output: ', output); }); //类 class TestCommand { async execute(output: string & Flag<{char: 'o'}>) { console.log('output: ', output); } }
$ ts-node app.ts test --help USAGE $ ts-node app.ts test OPTIONS -o, --output=output (required) $ ts-node app.ts test --output test.txt output: test.txt $ ts-node app.ts test -o test.txt output: test.txt
可选 / 默认值
方法/函数的签名定义了哪些参数或 flag 是可选的。如果参数在类型系统中是可选的,用户就不必提供它。
//函数式 new App().command('test', (name?: string) => { console.log('Hello', name || 'nobody'); }); //类 class TestCommand { async execute(name?: string) { console.log('Hello', name || 'nobody'); } }
$ ts-node app.ts test
Hello nobody带有默认值的参数也是如此:
//函数式 new App().command('test', (name: string = 'body') => { console.log('Hello', name); }); //类 class TestCommand { async execute(name: string = 'body') { console.log('Hello', name); } }
$ ts-node app.ts test
Hello nobody这同样适用于 flag。
序列化 / 验证
所有参数和 flag 都会根据其类型自动反序列化、验证,并且可以附加额外的约束。
因此,即使命令行界面基于文本(即字符串),定义为 number 的参数在控制器中也始终保证是真正的数字。
//函数式 new App().command('test', (id: number) => { console.log('id', id, typeof id); }); //类 class TestCommand { async execute(id: number) { console.log('id', id, typeof id); } }
$ ts-node app.ts test 123 id 123 number
可以使用 @deepkit/type 中的类型注解定义额外的约束。
import { Positive } from '@deepkit/type'; //函数式 new App().command('test', (id: number & Positive) => { console.log('id', id, typeof id); }); //类 class TestCommand { async execute(id: number & Positive) { console.log('id', id, typeof id); } }
id 中的 Postive 类型表示只允许正数。如果用户现在传入负数,代码将根本不会执行,并会显示一条错误信息。
$ ts-node app.ts test -123 Validation error in id: Number needs to be positive [positive]
这种非常容易实现的额外验证,使命令在面对错误输入时更加健壮。更多信息请参阅 验证 章节。
描述
要描述 flag 或参数,请使用 @description 注释装饰器。
import { Positive } from '@deepkit/type'; class TestCommand { async execute( /** @description The users identifier */ id: number & Positive, /** @description Delete the user? */ remove: boolean = false ) { console.log('id', id, typeof id); } }
在帮助视图中,此描述会显示在 flag 或参数之后:
$ ts-node app.ts test --help USAGE $ ts-node app.ts test ID ARGUMENTS ID The users identifier OPTIONS --remove Delete the user?