Typed & tiny CLI argument parser.
- Strongly typed Parse argvs with confidence!
- Custom types & validation Pass in any type function and the flag type will be inferred!
- Array types Accept multiple flag values!
- Configurable defaults Set better defaults to avoid checking whether a flag was passed in!
- Tiny 1.3 kB minified + gzipped!
🔥 Looking for something more robust?
Checkout Cleye, a CLI development tool powered by type-flag. It comes with argument parsing and a beautiful
--help
documentation generator.
Support this project by ⭐️ starring and sharing it. Follow me to see what other cool projects I'm working on! ❤️
npm i type-flag
Here's a simple example file cli.ts
:
import typeFlag from 'type-flag'
// Pass in flag schemas and automatically parse process.argv!
const parsed = typeFlag({
// Define flags here...
someString: String,
someBoolean: {
type: Boolean,
alias: 'b'
},
someNumber: {
type: Number,
alias: 'n',
default: 2
},
// Wrap type with an array to indicate accept multiple flags
stringArray: [String],
numberArray: {
type: [Number]
}
})
console.log(parsed.flags)
When you execute the file in the command-line, you can see the argvs parsed:
$ node ./cli --some-string 'hello' --some-boolean --some-number 3
> {
someString: 'hello',
someBoolean: true,
someNumber: 3,
stringArray: [],
numberArray: []
}
parsed
will have the following type:
type Parsed = {
flags: {
someString: string | undefined
someBoolean: boolean
someNumber: number
stringArray: string[]
numberArray: number[]
}
unknownFlags: {
[flagName: string]: (string | boolean)[]
}
_: string[] & {
'--': string[]
}
}
Pass in an object where the key is the flag name and the value is the flag type—a parser function that takes in a string and parses it to that type. Default JavaScript constructors should be able to cover most use-cases: String, Number, Boolean, etc.
The value can also be an object with the type
property as the flag type.
typeFlag({
// Short-hand: immediately set the type
stringFlag: String,
numberFlag: Number,
booleanFlag: Boolean,
// Object syntax:
stringFlag: {
type: String
}
})
To accept multiple values of a flag, use an array type by simply wrapping the type in an array:
const parsed = typeFlag({
stringFlag: [String]
})
This allows usage like this:
$ node ./cli --string-flag A --string-flag B
# > parsed.flags.stringFlag = ['A', 'B']
Flags are often given single-character aliases for shorthand usage (eg. --help
to -h
). To give a flag an alias, use the object syntax and set the alias
property to a single-character name.
typeFlag({
stringFlag: {
type: String,
alias: 's'
}
})
This allows usage like this:
$ node ./cli -s A
# > parsed.flags.stringFlag = 'A'
Flags that are not passed in will default to being undefined
. To set a different default value, use the object syntax and pass in a value as the default
property. When a default is provided, the flag type will include that instead of undefined
.
When using mutable values (eg. objects/arrays) as a default, pass in a function that creates it to avoid mutation-related bugs.
const parsed = typeFlag({
someNumber: {
type: Number,
default: 1
},
manyNumbers: {
type: [Number],
// Use a function to return an object or array
default: () => [1, 2, 3]
}
})
To get undefined
in the parsed flag type, make sure strict
or strictNullChecks
is enabled.
When passing in the flags, they can be in kebab-case and will automatically map to the camelCase equivalent.
# These two map to the same flag
$ node ./cli --someString hello --some-string world
When unrecognized flags are passed in, they are either interpreted as a string or boolean depending on usage. Unknown flags are not converted to camelCase to allow for accurate error handling.
$ node ./cli --unknown-flag --unknown-flag 2
This outputs the following:
{
unknownFlags: {
'unknown-flag': [true, '2']
},
// ...
}
Sometimes it may be undesirable to parse out unknown flags. For example, when an argument follows an unknown boolean flag, type-flag may assume that the argument was passed into the flag.
In these cases, you can ignore them so they're treated as arguments.
const parsed = typeFlag({}, {
ignoreUnknown: true
})
// $ node ./cli --unknown hello
console.log(parsed._) // => ['--unknown', 'hello]
All argument values are stored in the _
property.
Everything after --
(end-of-flags) is treated as arguments and will be stored in the _['--']
property.
$ node ./cli --boolean value --string "hello world" "another value" -- --string "goodbye world"
This outputs the following:
{
_: [
'value',
'another value',
'--string',
'goodbye world',
'--': [
'--string',
'goodbye world'
]
]
// ...
}
Note: value
after --boolean
is parsed as an argument because the boolean flag doesn't accept a value.
The characters =
, :
and .
are reserved for delimiting the value from the flag.
$ node ./cli --flag=value --flag:value --flag.value
This allows for usage like --flag:key=value
or --flag.property=value
to be possible.
Basic types can be set using built-in functions in JavaScript, but sometimes you want to a new type, narrow the type, or add validation.
To create a new type, simply declare a function that accepts a string argument and returns the parsed value with the expected type.
In this example, the size
flag is enforced to be either small
, medium
or large
.
const possibleSizes = ['small', 'medium', 'large'] as const
type Sizes = typeof possibleSizes[number]
function Size(size: Sizes) {
if (!possibleSizes.includes(size)) {
throw new Error(`Invalid size: "${size}"`)
}
return size
}
const parsed = typeFlag({
size: Size
})
parsed
resolves to the following type:
type Parsed = {
flags: {
size: 'small' | 'medium' | 'large' | undefined
}
// ...
}
It's common to have flags that act as a boolean when no value is passed in. This can be done by creating a custom type that returns both types.
function OptionalString(value: string) {
if (!value) {
return true
}
return value
}
const parsed = typeFlag({
optionalString: OptionalString
})
// $ node ./cli --optional-string
parsed.flags.optionalString // => true
// $ node ./cli --optional-string hello
parsed.flags.optionalString // => 'hello'
In use-cases where flag values contain =
, you can use :
instead. This allows flags like --define:K=V
.
$ node ./cli --define:key=value
const parsed = typeFlag({
define: String
})
console.log(parsed.flags.define) // => 'key=value'
$ node ./cli --env.TOKEN=123 --env.CI
type Environment = {
TOKEN?: string
CI?: boolean
}
function EnvironmentObject(value: string): Environment {
const [propertyName, propertyValue] = value.split('=')
return {
[propertyName]: propertyValue || true
}
}
const parsed = typeFlag({
env: [EnvironmentObject]
})
const environment = parsed.flags.env.reduce((agg, next) => Object.assign(agg, next), {})
console.log(environment) // { TOKEN: 123, CI: true }
To invert a boolean flag, false
must be passed in with the =
operator (or any other value delimiters).
$ node ./cli --boolean-flag=false
Note, without the =
, the false
will be parsed as a separate argument.
$ node ./cli --boolean-flag false
{
flags: {
booleanFlag: true
},
_: ['false']
}
To create an API where passing in a flag multiple times increases a count (a pretty common one is -vvv
), you can use an array-boolean type and count the size of the array:
const parsed = typeFlag({
verbose: {
type: [Boolean],
alias: 'v'
}
})
console.log(parsed.flags.verbose.length)
$ node ./cli -vvv # logs 3
Returns an object with the shape:
type Parsed = {
flags: {
[flagName: string]: InferredType
}
unknownFlags: {
[flagName: string]: (string | boolean)[]
}
_: string[]
}
Type:
type TypeFunction = (argvValue: any) => any
type FlagSchema = {
[flagName: string]: TypeFunction | [TypeFunction] | {
type: TypeFunction | [TypeFunction]
alias?: string
default?: any
}
}
An object containing flag schema definitions. Where the key is the flag name, and the value is either the type function or an object containing the type function and/or alias.
Type: string[]
Default: process.argv.slice(2)
The argv array to parse.
Type:
type Options = {
// Whether not to parse unknown flags (treat as arguments)
ignoreUnknown?: boolean
}