Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/privatenumber/type-flag
⛳️ Typed command-line arguments parser for Node.js
https://github.com/privatenumber/type-flag
argument-parser argv cli cli-flags command-line flag minimist node nodejs parse parser typed typescript yargs
Last synced: 4 days ago
JSON representation
⛳️ Typed command-line arguments parser for Node.js
- Host: GitHub
- URL: https://github.com/privatenumber/type-flag
- Owner: privatenumber
- License: mit
- Created: 2021-08-15T03:14:41.000Z (about 3 years ago)
- Default Branch: develop
- Last Pushed: 2024-06-23T03:47:53.000Z (4 months ago)
- Last Synced: 2024-10-19T01:11:25.309Z (16 days ago)
- Topics: argument-parser, argv, cli, cli-flags, command-line, flag, minimist, node, nodejs, parse, parser, typed, typescript, yargs
- Language: TypeScript
- Homepage:
- Size: 1.08 MB
- Stars: 167
- Watchers: 4
- Forks: 3
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
type-flag
Strongly typed command-line arguments parser.
No dependencies & tree-shakable (Max 1.4 kB).
→ [Try it out online](https://stackblitz.com/edit/type-flag-demo?devtoolsheight=50&file=src/type-flag.ts&view=editor)
> [!TIP]
> **Looking for something more robust? 👀**
>
> Try [**Cleye**](https://github.com/privatenumber/cleye)—a CLI development tool powered by _type-flag_.
>
> In addition to flag parsing, it supports argument parsing and has a beautiful `--help` documentation generator.Already a sponsor? Join the discussion in the Development repo!
## 🚀 Install
```bash
npm i type-flag
```## 🚦 Quick start
Let's say you want to create a script with the following usage:
```
$ my-script --name John --age 20
```### typeFlag
Here's how easy it is with _type-flag_:
```ts
import { typeFlag } from 'type-flag'const parsed = typeFlag({
name: String,
age: {
type: Number,
alias: 'a'
}
})console.log(parsed.flags.name) // 'John'
console.log(parsed.flags.age) // 20
```You can also get unknown flags and arguments from the `parsed` object:
```ts
// object of unknown flags passed in
console.log(parsed.unknownFlags)// arguments
console.log(parsed._)
```### getFlag
_Want something even simpler?_
_type-flag_ also exports a `getFlag` function that returns a single flag value.
```ts
import { getFlag } from 'type-flag'const name = getFlag('--name', String)
const age = getFlag('-a,--age', Number)console.log(name) // 'John'
console.log(age) // 20
```These are quick demos but _type-flag_ can do so much more:
- Accept multiple flag values
- Flag operators (e.g. `=`) for explicitly passing in a value
- Parse unknown flags
- Parse alias groups (e.g. `-abc`)Keep reading to learn more!
## 🧑💻 Usage
### Defining flags
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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/String), [Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/Number), [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/Boolean), etc.The value can also be an object with the `type` property as the flag type.
```ts
typeFlag({
// Short-hand
stringFlag: String,
numberFlag: Number,
booleanFlag: Boolean,// Object syntax:
stringFlag: {
type: String
}
})
```#### Array type
To accept multiple values of a flag, wrap the type with an array:```ts
const parsed = typeFlag({
myFlag: [String]
})// $ node ./cli --my-flag A --my-flag B
parsed.flags.myFlag // => ['A', 'B']
```#### Aliases
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.```ts
typeFlag({
myFlag: {
type: String,
alias: 'm'
}
})// $ node ./cli -m hello
parsed.flags.myFlag // => 'hello'
```#### Default values
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 return type will reflect 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.
```ts
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`](https://www.typescriptlang.org/tsconfig/#strict) or [`strictNullChecks`](https://www.typescriptlang.org/tsconfig#strictNullChecks) is enabled.
### kebab-case flags mapped to camelCase
When passing in the flags, they can be in kebab-case and will automatically map to the camelCase equivalent.
```ts
const parsed = typeFlag({
someString: [String]
})// $ node ./cli --someString hello --some-string world
parsed.flags.someString // => ['hello', 'world']
```### Unknown flags
When unrecognized flags are passed in, they are interpreted as a boolean, or a string if explicitly passed in. Unknown flags are not converted to camelCase to allow for accurate error handling.```ts
const parsed = typeFlag({})// $ node ./cli --some-flag --some-flag=1234
parsed.unknownFlags // => { 'some-flag': [true, '1234'] }
```### Arguments
Arguments are values passed in that are not associated with any flags. All arguments are stored in the `_` property.Everything after `--` (end-of-flags) is treated as an argument (including flags) and will be stored in the `_['--']` property.
```ts
const parsed = typeFlag({
myFlag: [String]
})// $ node ./cli --my-flag value arg1 -- --my-flag world
parsed.flags.myFlag // => ['value']
parsed._ // => ['arg1', '--my-flag', 'world']
parsed._['--'] // => ['--my-flag', 'world']
```### Flag value delimiters
The characters `=`, `:` and `.` are reserved for delimiting the value from the flag.```sh
$ node ./cli --flag=value --flag:value --flag.value
```This allows for usage like `--flag:key=value` or `--flag.property=value` to be possible.
### Mutated argv array
When `type-flag` iterates over the argv array, it removes the tokens it parses out via mutation.
By default, `type-flag` works on a new copy of `process.argv.slice(2)` so this doesn't have any side-effects. But if you want to leverage this behavior to extract certain flags and arguments, you can pass in your own copy of `process.argv.slice(2)`.
This may be useful for filtering out certain flags before passing down the `argv` to a child process.
#### Ignoring unknown flags
Sometimes it may be undesirable to parse unknown flags. In these cases, you can ignore them so they're left unparsed in the `argv` array.```ts
const argv = process.argv.slice(2)
const parsed = typeFlag(
{},
argv,
{
ignore: type => type === 'unknown-flag'
}
)// $ node ./cli --unknown=hello
parsed._ // => []
argv // => ['--unknown=hello']
```#### Ignoring everything after the first argument
Similarly to how Node.js only reads flags passed in before the first argument, _type-flag_ can be configured to ignore everything after the first argument.
```ts
const argv = process.argv.slice(2)let stopParsing = false
const parsed = typeFlag(
{
myFlag: [Boolean]
},
argv,
{
ignore: (type) => {
if (stopParsing) {
return true
}
const isArgument = type === 'argument'
if (isArgument) {
stopParsing = isArgument
return stopParsing
}
}
}
)// $ node ./cli --my-flag ./file.js --my-flag
parsed.flags.myFlag // => [true]
argv // => ['./file.js', '--my-flag']
```## 👨🏻🏫 Examples
### Custom flag type
Basic types can be set using [built-in functions in JavaScript](https://www.w3schools.com/js/js_object_constructors.asp#:~:text=Built-in%20JavaScript%20Constructors), 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`.
```ts
const possibleSizes = ['small', 'medium', 'large'] as consttype Sizes = typeof possibleSizes[number]
const 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:
```ts
type Parsed = {
flags: {
size: 'small' | 'medium' | 'large' | undefined
}
// ...
}
```### Optional value flag
To create a string flag that acts as a boolean when nothing is passed in, create a custom type that returns both types.
```ts
const OptionalString = (value: string) => {
if (!value) {
return true
}return value
}const parsed = typeFlag({
string: OptionalString
})// $ node ./cli --string
parsed.flags.string // => true// $ node ./cli --string hello
parsed.flags.string // => 'hello'
```### Accepting flag values with `=` in it
In use-cases where flag values contain `=`, you can use `:` instead. This allows flags like `--define:K=V`.```ts
const parsed = typeFlag({
define: String
})// $ node ./cli --define:key=value
parsed.flags.define // => 'key=value'
```### Dot-nested flags
```ts
type Environment = {
TOKEN?: string
CI?: boolean
}const EnvironmentObject = (value: string): Environment => {
const [propertyName, propertyValue] = value.split('=')
return {
[propertyName]: propertyValue || true
}
}const parsed = typeFlag({
env: [EnvironmentObject]
})const env = parsed.flags.env.reduce(
(agg, next) => Object.assign(agg, next),
{}
)// $ node ./cli --env.TOKEN=123 --env.CI
env // => { TOKEN: 123, CI: true }
```### Inverting a boolean
To invert a boolean flag, `false` must be passed in with the `=` operator (or any other value delimiters).```ts
const parsed = typeFlag({
booleanFlag: Boolean
})// $ node ./cli --boolean-flag=false
parsed.flags.booleanFlag // => false
```Without explicitly specfying the flag value via `=`, the `false` will be parsed as a separate argument.
```ts
// $ node ./cli --boolean-flag false
parsed.flags.booleanFlag // => true
parsed._ // => ['false']
```### Counting flags
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:```ts
const parsed = typeFlag({
verbose: {
type: [Boolean],
alias: 'v'
}
})// $ node ./cli -vvv
parsed.flags.verbose.length // => 3
```## ⚙️ API
### typeFlag(flagSchema, argv, options)
Returns an object with the shape:
```ts
type Parsed = {
flags: {
[flagName: string]: InferredType
}
unknownFlags: {
[flagName: string]: (string | boolean)[]
}
_: string[]
}
```#### flagSchema
Type:
```ts
type TypeFunction = (...args: any[]) => unknowntype FlagSchema = {
[flagName: string]: TypeFunction | [TypeFunction] | {
type: TypeFunction | [TypeFunction]
alias?: string
default?: unknown
}
}
```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.
#### argv
Type: `string[]`Default: `process.argv.slice(2)`
The argv array to parse. The array is mutated to remove the parsed flags.
#### options
Type:
```ts
type Options = {
// Callback to skip parsing on certain argv tokens
ignore?: (
type: 'known-flag' | 'unknown-flag' | 'argument',
flagOrArgv: string,
value: string | undefined,
) => boolean | void
}
```---
### getFlag(flagNames, flagType, argv)
#### flagNames
Type: `string`A comma-separated list of flag names to parse.
#### flagType
Type:
```ts
type TypeFunction = (...args: any[]) => unknowntype FlagType = TypeFunction | [TypeFunction]
```A function to parse the flag value. Wrap the function in an array to retrieve all values.
#### argv
Type: `string[]`Default: `process.argv.slice(2)`
The argv array to parse. The array is mutated to remove the parsed flags.
## Sponsors