318 lines
7.9 KiB
TypeScript
318 lines
7.9 KiB
TypeScript
|
import {PackageJson} from 'type-fest';
|
||
|
|
||
|
export type FlagType = 'string' | 'boolean' | 'number';
|
||
|
|
||
|
/**
|
||
|
Callback function to determine if a flag is required during runtime.
|
||
|
|
||
|
@param flags - Contains the flags converted to camel-case excluding aliases.
|
||
|
@param input - Contains the non-flag arguments.
|
||
|
|
||
|
@returns True if the flag is required, otherwise false.
|
||
|
*/
|
||
|
export type IsRequiredPredicate = (flags: Readonly<AnyFlags>, input: readonly string[]) => boolean;
|
||
|
|
||
|
export interface Flag<Type extends FlagType, Default, IsMultiple = false> {
|
||
|
readonly type?: Type;
|
||
|
readonly alias?: string;
|
||
|
readonly default?: Default;
|
||
|
readonly isRequired?: boolean | IsRequiredPredicate;
|
||
|
readonly isMultiple?: IsMultiple;
|
||
|
}
|
||
|
|
||
|
type StringFlag = Flag<'string', string> | Flag<'string', string[], true>;
|
||
|
type BooleanFlag = Flag<'boolean', boolean> | Flag<'boolean', boolean[], true>;
|
||
|
type NumberFlag = Flag<'number', number> | Flag<'number', number[], true>;
|
||
|
type AnyFlag = StringFlag | BooleanFlag | NumberFlag;
|
||
|
type AnyFlags = Record<string, AnyFlag>;
|
||
|
|
||
|
export interface Options<Flags extends AnyFlags> {
|
||
|
/**
|
||
|
Pass in [`import.meta`](https://nodejs.org/dist/latest/docs/api/esm.html#esm_import_meta). This is used to find the correct package.json file.
|
||
|
*/
|
||
|
readonly importMeta: ImportMeta;
|
||
|
|
||
|
/**
|
||
|
Define argument flags.
|
||
|
|
||
|
The key is the flag name in camel-case and the value is an object with any of:
|
||
|
|
||
|
- `type`: Type of value. (Possible values: `string` `boolean` `number`)
|
||
|
- `alias`: Usually used to define a short flag alias.
|
||
|
- `default`: Default value when the flag is not specified.
|
||
|
- `isRequired`: Determine if the flag is required.
|
||
|
If it's only known at runtime whether the flag is required or not you can pass a Function instead of a boolean, which based on the given flags and other non-flag arguments should decide if the flag is required.
|
||
|
- `isMultiple`: Indicates a flag can be set multiple times. Values are turned into an array. (Default: false)
|
||
|
Multiple values are provided by specifying the flag multiple times, for example, `$ foo -u rainbow -u cat`. Space- or comma-separated values are *not* supported.
|
||
|
|
||
|
Note that flags are always defined using a camel-case key (`myKey`), but will match arguments in kebab-case (`--my-key`).
|
||
|
|
||
|
@example
|
||
|
```
|
||
|
flags: {
|
||
|
unicorn: {
|
||
|
type: 'string',
|
||
|
alias: 'u',
|
||
|
default: ['rainbow', 'cat'],
|
||
|
isMultiple: true,
|
||
|
isRequired: (flags, input) => {
|
||
|
if (flags.otherFlag) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
*/
|
||
|
readonly flags?: Flags;
|
||
|
|
||
|
/**
|
||
|
Description to show above the help text. Default: The package.json `"description"` property.
|
||
|
|
||
|
Set it to `false` to disable it altogether.
|
||
|
*/
|
||
|
readonly description?: string | false;
|
||
|
|
||
|
/**
|
||
|
The help text you want shown.
|
||
|
|
||
|
The input is reindented and starting/ending newlines are trimmed which means you can use a [template literal](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/template_strings) without having to care about using the correct amount of indent.
|
||
|
|
||
|
The description will be shown above your help text automatically.
|
||
|
|
||
|
Set it to `false` to disable it altogether.
|
||
|
*/
|
||
|
readonly help?: string | false;
|
||
|
|
||
|
/**
|
||
|
Set a custom version output. Default: The package.json `"version"` property.
|
||
|
|
||
|
Set it to `false` to disable it altogether.
|
||
|
*/
|
||
|
readonly version?: string | false;
|
||
|
|
||
|
/**
|
||
|
Automatically show the help text when the `--help` flag is present. Useful to set this value to `false` when a CLI manages child CLIs with their own help text.
|
||
|
|
||
|
This option is only considered when there is only one argument in `process.argv`.
|
||
|
*/
|
||
|
readonly autoHelp?: boolean;
|
||
|
|
||
|
/**
|
||
|
Automatically show the version text when the `--version` flag is present. Useful to set this value to `false` when a CLI manages child CLIs with their own version text.
|
||
|
|
||
|
This option is only considered when there is only one argument in `process.argv`.
|
||
|
*/
|
||
|
readonly autoVersion?: boolean;
|
||
|
|
||
|
/**
|
||
|
`package.json` as an `Object`. Default: Closest `package.json` upwards.
|
||
|
|
||
|
_You most likely don't need this option._
|
||
|
*/
|
||
|
readonly pkg?: Record<string, unknown>;
|
||
|
|
||
|
/**
|
||
|
Custom arguments object.
|
||
|
|
||
|
@default process.argv.slice(2)
|
||
|
*/
|
||
|
readonly argv?: readonly string[];
|
||
|
|
||
|
/**
|
||
|
Infer the argument type.
|
||
|
|
||
|
By default, the argument `5` in `$ foo 5` becomes a string. Enabling this would infer it as a number.
|
||
|
|
||
|
@default false
|
||
|
*/
|
||
|
readonly inferType?: boolean;
|
||
|
|
||
|
/**
|
||
|
Value of `boolean` flags not defined in `argv`.
|
||
|
|
||
|
If set to `undefined`, the flags not defined in `argv` will be excluded from the result. The `default` value set in `boolean` flags take precedence over `booleanDefault`.
|
||
|
|
||
|
_Note: If used in conjunction with `isMultiple`, the default flag value is set to `[]`._
|
||
|
|
||
|
__Caution: Explicitly specifying `undefined` for `booleanDefault` has different meaning from omitting key itself.__
|
||
|
|
||
|
@example
|
||
|
```
|
||
|
import meow from 'meow';
|
||
|
|
||
|
const cli = meow(`
|
||
|
Usage
|
||
|
$ foo
|
||
|
|
||
|
Options
|
||
|
--rainbow, -r Include a rainbow
|
||
|
--unicorn, -u Include a unicorn
|
||
|
--no-sparkles Exclude sparkles
|
||
|
|
||
|
Examples
|
||
|
$ foo
|
||
|
🌈 unicorns✨🌈
|
||
|
`, {
|
||
|
importMeta: import.meta,
|
||
|
booleanDefault: undefined,
|
||
|
flags: {
|
||
|
rainbow: {
|
||
|
type: 'boolean',
|
||
|
default: true,
|
||
|
alias: 'r'
|
||
|
},
|
||
|
unicorn: {
|
||
|
type: 'boolean',
|
||
|
default: false,
|
||
|
alias: 'u'
|
||
|
},
|
||
|
cake: {
|
||
|
type: 'boolean',
|
||
|
alias: 'c'
|
||
|
},
|
||
|
sparkles: {
|
||
|
type: 'boolean',
|
||
|
default: true
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
//{
|
||
|
// flags: {
|
||
|
// rainbow: true,
|
||
|
// unicorn: false,
|
||
|
// sparkles: true
|
||
|
// },
|
||
|
// unnormalizedFlags: {
|
||
|
// rainbow: true,
|
||
|
// r: true,
|
||
|
// unicorn: false,
|
||
|
// u: false,
|
||
|
// sparkles: true
|
||
|
// },
|
||
|
// …
|
||
|
//}
|
||
|
```
|
||
|
*/
|
||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||
|
readonly booleanDefault?: boolean | null | undefined;
|
||
|
|
||
|
/**
|
||
|
Whether to use [hard-rejection](https://github.com/sindresorhus/hard-rejection) or not. Disabling this can be useful if you need to handle `process.on('unhandledRejection')` yourself.
|
||
|
|
||
|
@default true
|
||
|
*/
|
||
|
readonly hardRejection?: boolean;
|
||
|
|
||
|
/**
|
||
|
Whether to allow unknown flags or not.
|
||
|
|
||
|
@default true
|
||
|
*/
|
||
|
readonly allowUnknownFlags?: boolean;
|
||
|
}
|
||
|
|
||
|
type TypedFlag<Flag extends AnyFlag> =
|
||
|
Flag extends {type: 'number'}
|
||
|
? number
|
||
|
: Flag extends {type: 'string'}
|
||
|
? string
|
||
|
: Flag extends {type: 'boolean'}
|
||
|
? boolean
|
||
|
: unknown;
|
||
|
|
||
|
type PossiblyOptionalFlag<Flag extends AnyFlag, FlagType> =
|
||
|
Flag extends {isRequired: true}
|
||
|
? FlagType
|
||
|
: Flag extends {default: any}
|
||
|
? FlagType
|
||
|
: FlagType | undefined;
|
||
|
|
||
|
export type TypedFlags<Flags extends AnyFlags> = {
|
||
|
[F in keyof Flags]: Flags[F] extends {isMultiple: true}
|
||
|
? PossiblyOptionalFlag<Flags[F], Array<TypedFlag<Flags[F]>>>
|
||
|
: PossiblyOptionalFlag<Flags[F], TypedFlag<Flags[F]>>
|
||
|
};
|
||
|
|
||
|
export interface Result<Flags extends AnyFlags> {
|
||
|
/**
|
||
|
Non-flag arguments.
|
||
|
*/
|
||
|
input: string[];
|
||
|
|
||
|
/**
|
||
|
Flags converted to camelCase excluding aliases.
|
||
|
*/
|
||
|
flags: TypedFlags<Flags> & Record<string, unknown>;
|
||
|
|
||
|
/**
|
||
|
Flags converted camelCase including aliases.
|
||
|
*/
|
||
|
unnormalizedFlags: TypedFlags<Flags> & Record<string, unknown>;
|
||
|
|
||
|
/**
|
||
|
The `package.json` object.
|
||
|
*/
|
||
|
pkg: PackageJson;
|
||
|
|
||
|
/**
|
||
|
The help text used with `--help`.
|
||
|
*/
|
||
|
help: string;
|
||
|
|
||
|
/**
|
||
|
Show the help text and exit with code.
|
||
|
|
||
|
@param exitCode - The exit code to use. Default: `2`.
|
||
|
*/
|
||
|
showHelp: (exitCode?: number) => never;
|
||
|
|
||
|
/**
|
||
|
Show the version text and exit.
|
||
|
*/
|
||
|
showVersion: () => void;
|
||
|
}
|
||
|
/**
|
||
|
@param helpMessage - Shortcut for the `help` option.
|
||
|
|
||
|
@example
|
||
|
```
|
||
|
#!/usr/bin/env node
|
||
|
import meow from 'meow';
|
||
|
import foo from './index.js';
|
||
|
|
||
|
const cli = meow(`
|
||
|
Usage
|
||
|
$ foo <input>
|
||
|
|
||
|
Options
|
||
|
--rainbow, -r Include a rainbow
|
||
|
|
||
|
Examples
|
||
|
$ foo unicorns --rainbow
|
||
|
🌈 unicorns 🌈
|
||
|
`, {
|
||
|
importMeta: import.meta,
|
||
|
flags: {
|
||
|
rainbow: {
|
||
|
type: 'boolean',
|
||
|
alias: 'r'
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
//{
|
||
|
// input: ['unicorns'],
|
||
|
// flags: {rainbow: true},
|
||
|
// ...
|
||
|
//}
|
||
|
|
||
|
foo(cli.input[0], cli.flags);
|
||
|
```
|
||
|
*/
|
||
|
export default function meow<Flags extends AnyFlags>(helpMessage: string, options?: Options<Flags>): Result<Flags>;
|
||
|
export default function meow<Flags extends AnyFlags>(options?: Options<Flags>): Result<Flags>;
|