The context object is the single argument passed to every command's run
function. It's the only thing a handler reads, and run() is the only thing
that writes it, and that separation is what makes commands easy to test.
interface Context<F = Record<string, unknown>, C extends Config = Config> {
flags: F
positionals: string[]
route: string[]
cwd: string
env: Record<string, string | undefined>
config: C
io: Io
logger: Logger
}Parsed and coerced according to the command's flags spec. See
Flag Parsing:
export default command({
flags: {
verbose: { type: 'boolean', short: 'v' },
count: { type: 'number', default: 1 },
},
run(ctx) {
ctx.flags.verbose // boolean
ctx.flags.count // number
},
})Non-flag arguments left over after routing, in order, always string[]. See
Positional Arguments.
The matched route as an array, e.g. ['db', 'migrate'] for app db migrate:
run(ctx) {
ctx.io.write(ctx.route.join(' ')) // 'db migrate'
}Useful for logging which command ran, or for a command that needs to know its own path.
run(ctx) {
ctx.io.write(`Running in ${ctx.cwd}`)
const apiKey = ctx.env.API_KEY
}env is process.env, typed as Record<string, string | undefined>.
The Config object passed to run():
interface Config {
name: string
version: string
commandsDir?: string
targets?: string[]
bin?: string
manifest?: Manifest
}run(ctx) {
ctx.io.write(`${ctx.config.name} v${ctx.config.version}`)
}Extend Config with your own properties (an API base URL, feature flags,
whatever your commands need); concise-ti only reads the fields above.
interface ApiClientConfig extends Config {
apiVersion: string
}
const config: ApiClientConfig = { name: 'api-client', version: '1.0.0', apiVersion: 'v2' }
void run<ApiClientConfig>(config, import.meta)Passing ApiClientConfig as run's type argument makes ctx.config typed as
ApiClientConfig in every command, once that command opts in with the same
type on command():
export default command<Record<string, unknown>, ApiClientConfig>({
run(ctx) {
ctx.config.apiVersion // string, not a cast
},
})Without the second command() type argument, ctx.config stays typed as the
base Config. See Type safety with generics
below and demos/api-client.
run(ctx) {
ctx.io.write('Output')
ctx.io.writeError('Error')
ctx.logger.debug('Starting operation')
}See I/O System and Logger.
Context<F, C> carries your flag shape and your Config shape:
interface DeployFlags {
environment: string
force: boolean
}
export default command<DeployFlags>({
flags: {
environment: { type: 'string', default: 'staging' },
force: { type: 'boolean' },
},
run(ctx) {
ctx.flags.environment // string, not unknown
ctx.flags.force // boolean, not unknown
},
})C works the same way for an extended Config (default Config if omitted):
export default command<DeployFlags, DeployConfig>({
run(ctx) {
ctx.config.someExtendedField // typed, not a cast
},
})