Field options usually modify a command rather than selecting a separate command themselves. func parses and validates them for you; application code only needs to decorate the corresponding properties on the Command.
Choose the value shape
| User need | Example syntax | Decorator | Field value |
|---|---|---|---|
| A yes/no switch | --verbose or -v | @Flag() | boolean |
| One string or finite number | --port 3000 | @Value() | string | number |
| The same option more than once | --include src --include tests | @ArrayString() / @ArrayNumber() | string[] | number[] |
Boolean flags
@Flag() creates a boolean switch. Without the option, the property’s initializer is used. Passing the long name or any of its one-character aliases sets the field to true.
import { Command, Flag, Handler } from 'func'
@Command('serve')
export class ServeCommand {
@Flag({ alias: 'v', aliases: ['V'], description: 'Print request logs' })
verbose = false
@Flag({ aliases: ['c', 'C'], description: 'Use colored output', negatable: true })
color = true
@Handler()
run() {
console.log(this.verbose, this.color)
}
}ship serve --verbose, ship serve -v, and ship serve -V all produce this.verbose === true. Use aliases for several short forms. The singular alias is merged with that list and duplicates are removed.
ship serve --color=false still produces this.color === true: a flag is selected by presence, not by a value after =. Set negatable: true to add the generated --no-<name> form, so ship serve --no-color produces this.color === false. Every short alias remains positive: ship serve --no-color -C produces true. If positive and negative forms both occur, the last one wins. Without either form, the property initializer is preserved.
Use a field flag for data that modifies an action. If the option must select a different method, use a handler flag instead; see Commands.
Scalar values
@Value() consumes one value. func infers String and Number from emitted TypeScript decorator metadata, so every value field must explicitly declare its property as string or number; an initializer by itself is not enough. Boolean options must use @Flag() so their presence and negated forms have unambiguous CLI semantics.
import { Command, Handler, Value } from 'func'
@Command('serve')
export class ServeCommand {
@Value({ description: 'Interface to bind' })
host: string = 'localhost'
@Value({ aliases: ['p', 'P'], description: 'Port to listen on' })
port: number = 3000
@Value('config-file')
configFile?: string
@Handler()
run() {
console.log(this.host, this.port, this.configFile)
}
}ship serve --host 0.0.0.0 -P 4000 --config-file ./dev.json assigns a string, a number, and a string to the three fields. A property name is the default public option name. For camel-case fields that should use conventional kebab case, pass the name directly as shown by configFile.
The property initializer is the default when the user omits the option. That default directly affects application behavior.
To turn one string input into a URL, Date, or domain value, use a custom transformation function. For a reusable solution, create a field decorator with createValueDecorator(); see Custom decorators.
Repeated values
@ArrayString() and @ArrayNumber() collect repeated occurrences into string[] and number[] respectively. TypeScript decorator metadata exposes only Array, so the decorator name carries the element parser type while the property declaration confirms that the field is an array.
import { ArrayNumber, ArrayString, Command, Handler } from 'func'
@Command('build')
export class BuildCommand {
@ArrayString({ name: 'include', aliases: ['i', 'I'] })
includes: string[] = []
@ArrayNumber('port')
ports: number[] = []
@Handler()
run() {
console.log(this.includes, this.ports)
}
}ship build -i src -I tests --port 3000 --port -1.5 assigns ['src', 'tests'] to this.includes and [3000, -1.5] to this.ports. Repeated values preserve their input order.
Validators
Validators run after fields receive parsed or default values and before the Handler. A failure raises the corresponding input error, and the Handler does not run.
import { Command, DependsOn, Enum, Exclusive, Flag, Handler, Required, Value, ValueValidate } from 'func'
@Command('publish')
export class PublishCommand {
@Required()
@Enum(['dev', 'prod'])
@Value()
target?: string
@DependsOn(['token'])
@Value()
registry?: string
@Value()
token?: string
@Exclusive(['json'])
@Flag()
table = false
@Flag()
json = false
@ValueValidate((value) => Number(value) > 0 || 'retry must be positive')
@Value()
retry: number = 1
@Handler()
run() {}
}@Required()rejectsundefined. A defined property default satisfies it, so omit the default when the user must provide the option.@Enum(values)accepts only listed scalar values, or requires every repeated array item to be listed.@DependsOn(['token'])requires--tokenonly when the decorated option is explicitly supplied.@Exclusive(['json'])rejects an invocation that explicitly supplies both options.@ValueValidate(fn)receives the normalized value and all option values. Returnfalsefor a generic error, a string for a user-facing message, or nothing for success.
Names passed to dependency and exclusivity validators are public long option names without --, not TypeScript property names or short aliases.
Undeclared option-looking tokens are rejected. Use Parameters for remaining positional input and complete runtime context.
Deprecate an option
Place @Deprecated() beside @Flag, @Value, @ArrayString, or @ArrayNumber to keep an option working while marking it in structured help and printing a non-fatal stderr notice when the user explicitly supplies it. Pass a message such as @Deprecated('Use --format instead.') to include migration guidance. Defaults do not trigger notices, and placing the decorator on an ordinary property has no observable effect.
Module-scoped action options use the deprecated parameter on @OptionCommand instead.
Use options as commands
Options can also act as commands, but this is a specialized case. See Provide action options through a Module for the full model.