Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sindresorhus/meow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: e04760557cca1200af91d96a60072f47d6a06aa1
Choose a base ref
...
head repository: sindresorhus/meow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a1f5f0fb16057e137259607565a319e5d3e2152b
Choose a head ref

Commits on May 22, 2018

  1. Document the --no- prefix (#87)

    zeakd authored and sindresorhus committed May 22, 2018
    Copy the full SHA
    258659a View commit details

Commits on Sep 20, 2018

  1. Fix readme example (#99)

    chinanf-boy authored and sindresorhus committed Sep 20, 2018
    Copy the full SHA
    59773ee View commit details

Commits on Nov 5, 2018

  1. Copy the full SHA
    d2e0e1e View commit details
  2. Minor code tweaks

    sindresorhus committed Nov 5, 2018
    Copy the full SHA
    89f8983 View commit details
  3. Switch from loud-rejection to hard-rejection

    The difference is that now it will crash the process right away on unhandled promises instead of waiting until the process exits.
    
    Fixes #93
    sindresorhus committed Nov 5, 2018
    Copy the full SHA
    f60c26e View commit details
  4. Add hardRejection option

    Fixes #94
    sindresorhus committed Nov 5, 2018
    Copy the full SHA
    2bcfee7 View commit details
  5. Require Node.js 8

    sindresorhus committed Nov 5, 2018
    Copy the full SHA
    cd635d4 View commit details
  6. Copy the full SHA
    439ac9b View commit details
  7. Fix Travis

    sindresorhus committed Nov 5, 2018
    Copy the full SHA
    646f30b View commit details

Commits on Feb 20, 2019

  1. Copy the full SHA
    f36715c View commit details

Commits on Mar 19, 2019

  1. Copy the full SHA
    f1036df View commit details
  2. Update dependencies

    sindresorhus committed Mar 19, 2019
    Copy the full SHA
    fd537b8 View commit details

Commits on Mar 31, 2019

  1. Copy the full SHA
    167d1ec View commit details

Commits on Apr 27, 2019

  1. Copy the full SHA
    927e6e8 View commit details

Commits on May 28, 2019

  1. Create funding.yml

    sindresorhus authored May 28, 2019
    Copy the full SHA
    47fe20f View commit details

Commits on May 31, 2019

  1. Tidelift tasks

    sindresorhus committed May 31, 2019
    Copy the full SHA
    54e1f22 View commit details

Commits on Jun 12, 2019

  1. Only consider enabling autoHelp/autoVersion in case there is only one…

    … argument in `process.argv` (#114)
    
    Co-Authored-By: Sindre Sorhus <sindresorhus@gmail.com>
    LitoMore and sindresorhus committed Jun 12, 2019
    Copy the full SHA
    cd29865 View commit details

Commits on Jun 13, 2019

  1. Fix typo (#121)

    LitoMore authored and sindresorhus committed Jun 13, 2019
    Copy the full SHA
    8e5248e View commit details

Commits on Nov 17, 2019

  1. Copy the full SHA
    5ef9478 View commit details
  2. Update dependencies

    sindresorhus committed Nov 17, 2019
    Copy the full SHA
    499d186 View commit details

Commits on Nov 18, 2019

  1. Copy the full SHA
    3e05a2e View commit details

Commits on Dec 7, 2019

  1. 6.0.0

    sindresorhus committed Dec 7, 2019
    Copy the full SHA
    5975fe6 View commit details

Commits on Feb 14, 2020

  1. Replace minimist-options types with own ones (#135)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    ybiquitous and sindresorhus authored Feb 14, 2020
    Copy the full SHA
    16640f1 View commit details
  2. 6.0.1

    sindresorhus committed Feb 14, 2020
    Copy the full SHA
    f17525e View commit details

Commits on Mar 19, 2020

  1. Copy the full SHA
    4527b45 View commit details
  2. Meta tweaks

    sindresorhus committed Mar 19, 2020
    Copy the full SHA
    3f331d9 View commit details
  3. 6.1.0

    sindresorhus committed Mar 19, 2020
    Copy the full SHA
    2954ed2 View commit details

Commits on Apr 16, 2020

  1. Code style tweak (#145)

    ulken authored Apr 16, 2020
    Copy the full SHA
    3571757 View commit details

Commits on Apr 27, 2020

  1. Copy the full SHA
    c67d9f4 View commit details

Commits on May 2, 2020

  1. Update dependencies

    Closes #148
    sindresorhus committed May 2, 2020
    Copy the full SHA
    f85b546 View commit details
  2. 6.1.1

    sindresorhus committed May 2, 2020
    Copy the full SHA
    3c23328 View commit details

Commits on May 3, 2020

  1. Fix Travis

    sindresorhus committed May 3, 2020
    Copy the full SHA
    43e9f39 View commit details
  2. Document ES Modules usage (#147)

    jimthedev authored and sindresorhus committed May 3, 2020
    Copy the full SHA
    d9d42a2 View commit details

Commits on May 5, 2020

  1. Add isMultiple option for flags (#143)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    ulken and sindresorhus authored May 5, 2020
    Copy the full SHA
    c4c5ee2 View commit details

Commits on May 7, 2020

  1. Add isRequired flag option (#141)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    sbencoding and sindresorhus authored May 7, 2020
    Copy the full SHA
    1eede6a View commit details
  2. Require Node.js 10

    sindresorhus committed May 7, 2020
    Copy the full SHA
    ea3fd99 View commit details
  3. 7.0.0

    sindresorhus committed May 7, 2020
    Copy the full SHA
    e3301ed View commit details

Commits on May 11, 2020

  1. Copy the full SHA
    e08eb4d View commit details
  2. 7.0.1

    sindresorhus committed May 11, 2020
    Copy the full SHA
    1f265e4 View commit details

Commits on May 12, 2020

  1. Rename camelcase to camelCase (#151)

    * Rename `camelcase` to `camelCase`
    
    * Remove meaningless comments
    ulken authored May 12, 2020
    Copy the full SHA
    20f6e85 View commit details
  2. Rename yargs to parseArguments (#149)

    ulken authored May 12, 2020
    Copy the full SHA
    1c251e8 View commit details

Commits on May 17, 2020

  1. Copy the full SHA
    629af48 View commit details

Commits on Jul 6, 2020

  1. Fix typo

    Closes #153
    sindresorhus committed Jul 6, 2020
    Copy the full SHA
    fa2a374 View commit details

Commits on Aug 10, 2020

  1. Improve flags types to acknowledge isMultiple and isRequired opti…

    …ons (#154)
    
    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    voxpelli and sindresorhus authored Aug 10, 2020
    Copy the full SHA
    e38789f View commit details
  2. 7.1.0

    sindresorhus committed Aug 10, 2020
    Copy the full SHA
    ebe00a1 View commit details

Commits on Aug 29, 2020

  1. Copy the full SHA
    71d640e View commit details
  2. 7.1.1

    sindresorhus committed Aug 29, 2020
    Copy the full SHA
    dc7dae4 View commit details

Commits on Oct 19, 2020

  1. Default isMultiple to empty array (#163)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    ulken and sindresorhus authored Oct 19, 2020
    Copy the full SHA
    14924de View commit details

Commits on Oct 28, 2020

  1. Make isMultiple non-greedy (#162)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    ulken and sindresorhus authored Oct 28, 2020
    Copy the full SHA
    49ce74d View commit details
  2. Gracefully handle package.json not being found (#167)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    ryanrhee and sindresorhus authored Oct 28, 2020
    Copy the full SHA
    ef7ae5d View commit details
Showing with 1,477 additions and 285 deletions.
  1. +1 −2 .gitattributes
  2. +4 −0 .github/funding.yml
  3. +3 −0 .github/security.md
  4. +2 −2 .travis.yml
  5. +24 −0 estest/index.js
  6. +5 −0 estest/package.json
  7. +306 −0 index.d.ts
  8. +138 −44 index.js
  9. +67 −0 index.test-d.ts
  10. +1 −1 license
  11. +34 −20 package.json
  12. +134 −37 readme.md
  13. +0 −176 test.js
  14. +39 −0 test/fixtures/fixture-required-function.js
  15. +21 −0 test/fixtures/fixture-required-multiple.js
  16. +27 −0 test/fixtures/fixture-required.js
  17. +3 −3 { → test/fixtures}/fixture.js
  18. +115 −0 test/is-required-flag.js
  19. +534 −0 test/test.js
  20. +19 −0 tsconfig.json
3 changes: 1 addition & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
* text=auto
*.js text eol=lf
* text=auto eol=lf
4 changes: 4 additions & 0 deletions .github/funding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github: sindresorhus
open_collective: sindresorhus
tidelift: npm/meow
custom: https://sindresorhus.com/donate
3 changes: 3 additions & 0 deletions .github/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Security Policy

To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: node_js
node_js:
- '14'
- '12'
- '10'
- '8'
- '6'
24 changes: 24 additions & 0 deletions estest/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {createRequire} from 'module';

const meow = createRequire(import.meta.url)('../index.js');

meow(`
Usage
$ estest <input>
Options
--rainbow, -r Include a rainbow
Examples
$ estest unicorns --rainbow
🌈 unicorns 🌈
`,
{
flags: {
rainbow: {
type: 'boolean',
alias: 'r'
}
}
}
);
5 changes: 5 additions & 0 deletions estest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "estest",
"type": "module",
"version": "1.2.3"
}
306 changes: 306 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import {PackageJson} from 'type-fest';

declare namespace meow {
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.
*/
type IsRequiredPredicate = (flags: Readonly<AnyFlags>, input: readonly string[]) => boolean;

interface Flag<Type extends FlagType, Default> {
readonly type?: Type;
readonly alias?: string;
readonly default?: Default;
readonly isRequired?: boolean | IsRequiredPredicate;
readonly isMultiple?: boolean;
}

type StringFlag = Flag<'string', string>;
type BooleanFlag = Flag<'boolean', boolean>;
type NumberFlag = Flag<'number', number>;

type AnyFlag = StringFlag | BooleanFlag | NumberFlag;
type AnyFlags = Record<string, AnyFlag>;

interface Options<Flags extends AnyFlags> {
/**
Define argument flags.
The key is the flag name 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.
@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 = require('meow');
const cli = meow(`
Usage
$ foo
Options
--rainbow, -r Include a rainbow
--unicorn, -u Include a unicorn
--no-sparkles Exclude sparkles
Examples
$ foo
🌈 unicorns✨🌈
`, {
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
// },
// …
//}
```
*/
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;
}

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;

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]>>
};

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) => void;

/**
Show the version text and exit.
*/
showVersion: () => void;
}
}
/**
@param helpMessage - Shortcut for the `help` option.
@example
```
#!/usr/bin/env node
'use strict';
import meow = require('meow');
import foo = require('.');
const cli = meow(`
Usage
$ foo <input>
Options
--rainbow, -r Include a rainbow
Examples
$ foo unicorns --rainbow
🌈 unicorns 🌈
`, {
flags: {
rainbow: {
type: 'boolean',
alias: 'r'
}
}
});
//{
// input: ['unicorns'],
// flags: {rainbow: true},
// ...
//}
foo(cli.input[0], cli.flags);
```
*/
declare function meow<Flags extends meow.AnyFlags>(helpMessage: string, options?: meow.Options<Flags>): meow.Result<Flags>;
declare function meow<Flags extends meow.AnyFlags>(options?: meow.Options<Flags>): meow.Result<Flags>;

export = meow;
182 changes: 138 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,146 @@
'use strict';
const path = require('path');
const buildMinimistOptions = require('minimist-options');
const yargs = require('yargs-parser');
const camelcaseKeys = require('camelcase-keys');
const buildParserOptions = require('minimist-options');
const parseArguments = require('yargs-parser');
const camelCaseKeys = require('camelcase-keys');
const decamelizeKeys = require('decamelize-keys');
const trimNewlines = require('trim-newlines');
const redent = require('redent');
const readPkgUp = require('read-pkg-up');
const loudRejection = require('loud-rejection');
const hardRejection = require('hard-rejection');
const normalizePackageData = require('normalize-package-data');

// Prevent caching of this module so module.parent is always accurate
delete require.cache[__filename];
const parentDir = path.dirname(module.parent.filename);
const parentDir = path.dirname(module.parent && module.parent.filename ? module.parent.filename : '.');

module.exports = (helpMessage, options) => {
loudRejection();
const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => {
const flag = definedFlags[flagName];
let isFlagRequired = true;

if (typeof helpMessage === 'object' && !Array.isArray(helpMessage)) {
options = helpMessage;
helpMessage = '';
if (typeof flag.isRequired === 'function') {
isFlagRequired = flag.isRequired(receivedFlags, input);
if (typeof isFlagRequired !== 'boolean') {
throw new TypeError(`Return value for isRequired callback should be of type boolean, but ${typeof isFlagRequired} was returned.`);
}
}

options = Object.assign({
pkg: readPkgUp.sync({
cwd: parentDir,
normalize: false
}).pkg || {},
if (typeof receivedFlags[flagName] === 'undefined') {
return isFlagRequired;
}

return flag.isMultiple && receivedFlags[flagName].length === 0;
};

const getMissingRequiredFlags = (flags, receivedFlags, input) => {
const missingRequiredFlags = [];
if (typeof flags === 'undefined') {
return [];
}

for (const flagName of Object.keys(flags)) {
if (flags[flagName].isRequired && isFlagMissing(flagName, flags, receivedFlags, input)) {
missingRequiredFlags.push({key: flagName, ...flags[flagName]});
}
}

return missingRequiredFlags;
};

const reportMissingRequiredFlags = missingRequiredFlags => {
console.error(`Missing required flag${missingRequiredFlags.length > 1 ? 's' : ''}`);
for (const flag of missingRequiredFlags) {
console.error(`\t--${flag.key}${flag.alias ? `, -${flag.alias}` : ''}`);
}
};

const buildParserFlags = ({flags, booleanDefault}) => {
const parserFlags = {};

for (const [flagKey, flagValue] of Object.entries(flags)) {
const flag = {...flagValue};

if (
typeof booleanDefault !== 'undefined' &&
flag.type === 'boolean' &&
!Object.prototype.hasOwnProperty.call(flag, 'default')
) {
flag.default = flag.isMultiple ? [booleanDefault] : booleanDefault;
}

if (flag.isMultiple) {
flag.type = flag.type ? `${flag.type}-array` : 'array';
flag.default = flag.default || [];
delete flag.isMultiple;
}

parserFlags[flagKey] = flag;
}

return parserFlags;
};

const validateFlags = (flags, options) => {
for (const [flagKey, flagValue] of Object.entries(options.flags)) {
if (flagKey !== '--' && !flagValue.isMultiple && Array.isArray(flags[flagKey])) {
throw new Error(`The flag --${flagKey} can only be set once.`);
}
}
};

const meow = (helpText, options) => {
if (typeof helpText !== 'string') {
options = helpText;
helpText = '';
}

const foundPkg = readPkgUp.sync({
cwd: parentDir,
normalize: false
});

options = {
pkg: foundPkg ? foundPkg.packageJson : {},
argv: process.argv.slice(2),
flags: {},
inferType: false,
input: 'string',
help: helpMessage,
help: helpText,
autoHelp: true,
autoVersion: true,
booleanDefault: false
}, options);

const minimistFlags = options.flags && typeof options.booleanDefault !== 'undefined' ? Object.keys(options.flags).reduce(
(flags, flag) => {
if (flags[flag].type === 'boolean' && !Object.prototype.hasOwnProperty.call(flags[flag], 'default')) {
flags[flag].default = options.booleanDefault;
}
booleanDefault: false,
hardRejection: true,
...options
};

return flags;
},
options.flags
) : options.flags;
if (options.hardRejection) {
hardRejection();
}

let minimistoptions = Object.assign({
arguments: options.input
}, minimistFlags);
let parserOptions = {
arguments: options.input,
...buildParserFlags(options)
};

minimistoptions = decamelizeKeys(minimistoptions, '-', {exclude: ['stopEarly', '--']});
parserOptions = decamelizeKeys(parserOptions, '-', {exclude: ['stopEarly', '--']});

if (options.inferType) {
delete minimistoptions.arguments;
delete parserOptions.arguments;
}

minimistoptions = buildMinimistOptions(minimistoptions);
parserOptions = buildParserOptions(parserOptions);

parserOptions.configuration = {
...parserOptions.configuration,
'greedy-arrays': false
};

if (minimistoptions['--']) {
minimistoptions.configuration = Object.assign({}, minimistoptions.configuration, {'populate--': true});
if (parserOptions['--']) {
parserOptions.configuration['populate--'] = true;
}

const {pkg} = options;
const argv = yargs(options.argv, minimistoptions);
const argv = parseArguments(options.argv, parserOptions);
let help = redent(trimNewlines((options.help || '').replace(/\t+\n*$/, '')), 2);

normalizePackageData(pkg);
@@ -85,28 +161,46 @@ module.exports = (helpMessage, options) => {

const showVersion = () => {
console.log(typeof options.version === 'string' ? options.version : pkg.version);
process.exit();
process.exit(0);
};

if (argv.version && options.autoVersion) {
showVersion();
}
if (argv._.length === 0 && options.argv.length === 1) {
if (argv.version === true && options.autoVersion) {
showVersion();
}

if (argv.help && options.autoHelp) {
showHelp(0);
if (argv.help === true && options.autoHelp) {
showHelp(0);
}
}

const input = argv._;
delete argv._;

const flags = camelcaseKeys(argv, {exclude: ['--', /^\w$/]});
const flags = camelCaseKeys(argv, {exclude: ['--', /^\w$/]});
const unnormalizedFlags = {...flags};

validateFlags(flags, options);

for (const flagValue of Object.values(options.flags)) {
delete flags[flagValue.alias];
}

const missingRequiredFlags = getMissingRequiredFlags(options.flags, flags, input);
if (missingRequiredFlags.length > 0) {
reportMissingRequiredFlags(missingRequiredFlags);
process.exit(2);
}

return {
input,
flags,
unnormalizedFlags,
pkg,
help,
showHelp,
showVersion
};
};

module.exports = meow;
67 changes: 67 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {expectAssignable, expectType} from 'tsd';
import {PackageJson} from 'type-fest';
import meow = require('.');
import {Result} from '.';

expectType<Result<never>>(meow('Help text'));
expectType<Result<never>>(meow('Help text', {hardRejection: false}));
expectAssignable<{flags: {foo: number}}>(
meow({flags: {foo: {type: 'number', isRequired: true}}})
);
expectAssignable<{flags: {foo: string}}>(
meow({flags: {foo: {type: 'string', isRequired: true}}})
);
expectAssignable<{flags: {foo: boolean}}>(
meow({flags: {foo: {type: 'boolean', isRequired: true}}})
);
expectAssignable<{flags: {foo: number | undefined}}>(
meow({flags: {foo: {type: 'number'}}})
);
expectAssignable<{flags: {foo: string | undefined}}>(
meow({flags: {foo: {type: 'string'}}})
);
expectAssignable<{flags: {foo: boolean | undefined}}>(
meow({flags: {foo: {type: 'boolean'}}})
);
expectType<Result<never>>(meow({description: 'foo'}));
expectType<Result<never>>(meow({description: false}));
expectType<Result<never>>(meow({help: 'foo'}));
expectType<Result<never>>(meow({help: false}));
expectType<Result<never>>(meow({version: 'foo'}));
expectType<Result<never>>(meow({version: false}));
expectType<Result<never>>(meow({autoHelp: false}));
expectType<Result<never>>(meow({autoVersion: false}));
expectType<Result<never>>(meow({pkg: {foo: 'bar'}}));
expectType<Result<never>>(meow({argv: ['foo', 'bar']}));
expectType<Result<never>>(meow({inferType: true}));
expectType<Result<never>>(meow({booleanDefault: true}));
expectType<Result<never>>(meow({booleanDefault: null}));
expectType<Result<never>>(meow({booleanDefault: undefined}));
expectType<Result<never>>(meow({hardRejection: false}));

const result = meow('Help text', {
flags: {
foo: {type: 'boolean', alias: 'f'},
'foo-bar': {type: 'number'},
bar: {type: 'string', default: ''},
abc: {type: 'string', isMultiple: true}
}
});

expectType<string[]>(result.input);
expectType<PackageJson>(result.pkg);
expectType<string>(result.help);

expectType<boolean | undefined>(result.flags.foo);
expectType<unknown>(result.flags.fooBar);
expectType<string>(result.flags.bar);
expectType<string[] | undefined>(result.flags.abc);
expectType<boolean | undefined>(result.unnormalizedFlags.foo);
expectType<unknown>(result.unnormalizedFlags.f);
expectType<number | undefined>(result.unnormalizedFlags['foo-bar']);
expectType<string>(result.unnormalizedFlags.bar);
expectType<string[] | undefined>(result.unnormalizedFlags.abc);

result.showHelp();
result.showHelp(1);
result.showVersion();
2 changes: 1 addition & 1 deletion license
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

54 changes: 34 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
{
"name": "meow",
"version": "5.0.0",
"version": "8.0.0",
"description": "CLI app helper",
"license": "MIT",
"repository": "sindresorhus/meow",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
"url": "https://sindresorhus.com"
},
"engines": {
"node": ">=6"
"node": ">=10"
},
"scripts": {
"test": "xo && ava"
"test": "xo && ava && tsd"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"cli",
@@ -38,25 +40,37 @@
"console"
],
"dependencies": {
"camelcase-keys": "^4.0.0",
"decamelize-keys": "^1.0.0",
"loud-rejection": "^1.0.0",
"minimist-options": "^3.0.1",
"normalize-package-data": "^2.3.4",
"read-pkg-up": "^3.0.0",
"redent": "^2.0.0",
"trim-newlines": "^2.0.0",
"yargs-parser": "^10.0.0"
"@types/minimist": "^1.2.0",
"camelcase-keys": "^6.2.2",
"decamelize-keys": "^1.1.0",
"hard-rejection": "^2.1.0",
"minimist-options": "4.1.0",
"normalize-package-data": "^3.0.0",
"read-pkg-up": "^7.0.1",
"redent": "^3.0.0",
"trim-newlines": "^3.0.0",
"type-fest": "^0.18.0",
"yargs-parser": "^20.2.3"
},
"devDependencies": {
"ava": "*",
"execa": "^0.10.0",
"indent-string": "^3.0.0",
"xo": "*"
"ava": "^2.4.0",
"execa": "^4.1.0",
"indent-string": "^4.0.0",
"tsd": "^0.13.1",
"xo": "^0.34.1"
},
"xo": {
"rules": {
"unicorn/no-process-exit": "off"
}
"unicorn/no-process-exit": "off",
"node/no-unsupported-features/es-syntax": "off"
},
"ignores": [
"estest/index.js"
]
},
"ava": {
"files": [
"test/*"
]
}
}
171 changes: 134 additions & 37 deletions readme.md
Original file line number Diff line number Diff line change
@@ -4,30 +4,30 @@
![](meow.gif)


## Features

- Parses arguments
- Converts flags to [camelCase](https://github.com/sindresorhus/camelcase)
- Negates flags when using the `--no-` prefix
- Outputs version when `--version`
- Outputs description and supplied help text when `--help`
- Makes unhandled rejected promises [fail loudly](https://github.com/sindresorhus/loud-rejection) instead of the default silent fail
- Makes unhandled rejected promises [fail hard](https://github.com/sindresorhus/hard-rejection) instead of the default silent fail
- Sets the process title to the binary name defined in package.json


## Install

```
$ npm install meow
```


## Usage

```
$ ./foo-app.js unicorns --rainbow
```

**CommonJS**

```js
#!/usr/bin/env node
'use strict';
@@ -63,37 +63,87 @@ const cli = meow(`
foo(cli.input[0], cli.flags);
```

**ES Modules**

```js
#!/usr/bin/env node
import {createRequire} from 'module';
import foo from './lib/index.js';

const meow = createRequire(import.meta.url)('meow');

const cli = meow(`
Usage
$ foo <input>
Options
--rainbow, -r Include a rainbow
Examples
$ foo unicorns --rainbow
🌈 unicorns 🌈
`, {
flags: {
rainbow: {
type: 'boolean',
alias: 'r'
}
}
});
/*
{
input: ['unicorns'],
flags: {rainbow: true},
...
}
*/

foo(cli.input[0], cli.flags);
```
## API
### meow(options, [minimistOptions])
### meow(helpText, options?)
### meow(options)
Returns an `Object` with:
Returns an `object` with:
- `input` *(Array)* - Non-flag arguments
- `flags` *(Object)* - Flags converted to camelCase
- `flags` *(Object)* - Flags converted to camelCase excluding aliases
- `unnormalizedFlags` *(Object)* - Flags converted to camelCase including aliases
- `pkg` *(Object)* - The `package.json` object
- `help` *(string)* - The help text used with `--help`
- `showHelp([code=2])` *(Function)* - Show the help text and exit with `code`
- `showHelp([exitCode=2])` *(Function)* - Show the help text and exit with `exitCode`
- `showVersion()` *(Function)* - Show the version text and exit
#### options
#### helpText
Type: `string`
Shortcut for the `help` option.
Type: `Object` `Array` `string`
#### options
Can either be a string/array that is the `help` or an options object.
Type: `object`
##### flags
Type: `Object`
Type: `object`
Define argument flags.
The key is the flag name and the value is an object with any of:
- `type`: Type of value. (Possible values: `string` `boolean`)
- `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. (Default: false)
- 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. Two arguments are passed to the function:
- The first argument is the **flags** object, which contains the flags converted to camel-case excluding aliases.
- The second argument is the **input** string array, which contains the non-flag arguments.
- The function should return a `boolean`, true if the flag is required, otherwise false.
- `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 [currently *not* supported](https://github.com/sindresorhus/meow/issues/164).
Example:
@@ -102,15 +152,22 @@ flags: {
unicorn: {
type: 'string',
alias: 'u',
default: 'rainbow'
default: ['rainbow', 'cat'],
isMultiple: true,
isRequired: (flags, input) => {
if (flags.otherFlag) {
return true;
}

return false;
}
}
}
```

##### description
Type: `string` `boolean`<br>
Type: `string | boolean`\
Default: The package.json `"description"` property
Description to show above the help text.
@@ -119,7 +176,7 @@ Set it to `false` to disable it altogether.
##### help
Type: `string` `boolean`
Type: `string | boolean`
The help text you want shown.
@@ -129,44 +186,48 @@ The description will be shown above your help text automatically.
##### version
Type: `string` `boolean`<br>
Type: `string | boolean`\
Default: The package.json `"version"` property
Set a custom version output.
##### autoHelp
Type: `boolean`<br>
Type: `boolean`\
Default: `true`
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`.
##### autoVersion
Type: `boolean`<br>
Type: `boolean`\
Default: `true`
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`.
##### pkg
Type: `Object`<br>
Type: `object`\
Default: Closest package.json upwards
package.json as an `Object`.
package.json as an `object`.
*You most likely don't need this option.*
##### argv
Type: `Array`<br>
Type: `string[]`\
Default: `process.argv.slice(2)`
Custom arguments object.
##### inferType
Type: `boolean`<br>
Type: `boolean`\
Default: `false`
Infer the argument type.
@@ -175,58 +236,87 @@ By default, the argument `5` in `$ foo 5` becomes a string. Enabling this would
##### booleanDefault
Type: `boolean` `null` `undefined`<br>
Type: `boolean | null | undefined`\
Default: `false`
Value of `boolean` flags not defined in `argv`.
If set to `undefined` the flags not defined in `argv` will be excluded from the result.
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:
```js
const meow = require('meow');

const cli = meow(`
Usage
$ foo
Options
--rainbow, -r Include a rainbow
--unicorn, -r Include a unicorn
--unicorn, -u Include a unicorn
--no-sparkles Exclude sparkles
Examples
$ foo
🌈 unicorns 🌈
🌈 unicorns🌈
`, {
booleanDefault: undefined,
flags: {
rainbow: {
type: 'boolean',
default: true
default: true,
alias: 'r'
},
unicorn: {
type: 'boolean',
default: false
default: false,
alias: 'u'
},
cake: {
type: 'boolean',
alias: 'c'
},
sparkles: {
type: 'boolean',
default: true
}
}
});
/*
{
flags: {rainbow: true, unicorn: false},
flags: {
rainbow: true,
unicorn: false,
sparkles: true
},
unnormalizedFlags: {
rainbow: true,
r: true,
unicorn: false,
u: false,
sparkles: true
},
}
*/
```
## Promises
##### hardRejection
Type: `boolean`\
Default: `true`
Meow will make unhandled rejected promises [fail loudly](https://github.com/sindresorhus/loud-rejection) instead of the default silent fail. Meaning you don't have to manually `.catch()` promises used in your CLI.
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.
## Promises
Meow will make unhandled rejected promises [fail hard](https://github.com/sindresorhus/hard-rejection) instead of the default silent fail. Meaning you don't have to manually `.catch()` promises used in your CLI.
## Tips
@@ -240,7 +330,14 @@ See [`update-notifier`](https://github.com/yeoman/update-notifier) if you want u
[More useful CLI utilities…](https://github.com/sindresorhus/awesome-nodejs#command-line-utilities)

## License

MIT © [Sindre Sorhus](https://sindresorhus.com)
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-meow?utm_source=npm-meow&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
176 changes: 0 additions & 176 deletions test.js

This file was deleted.

39 changes: 39 additions & 0 deletions test/fixtures/fixture-required-function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env node
'use strict';
const meow = require('../..');

const cli = meow({
description: 'Custom description',
help: `
Usage
foo <input>
`,
flags: {
trigger: {
type: 'boolean',
alias: 't'
},
withTrigger: {
type: 'string',
isRequired: (flags, _) => {
return flags.trigger;
}
},
allowError: {
type: 'boolean',
alias: 'a'
},
shouldError: {
type: 'boolean',
isRequired: (flags, _) => {
if (flags.allowError) {
return 'should error';
}

return false;
}
}
}
});

console.log(`${cli.flags.trigger},${cli.flags.withTrigger}`);
21 changes: 21 additions & 0 deletions test/fixtures/fixture-required-multiple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
'use strict';
const meow = require('../..');

const cli = meow({
description: 'Custom description',
help: `
Usage
foo <input>
`,
flags: {
test: {
type: 'number',
alias: 't',
isRequired: true,
isMultiple: true
}
}
});

console.log(cli.flags.test);
27 changes: 27 additions & 0 deletions test/fixtures/fixture-required.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env node
'use strict';
const meow = require('../..');

const cli = meow({
description: 'Custom description',
help: `
Usage
foo <input>
`,
flags: {
test: {
type: 'string',
alias: 't',
isRequired: true
},
number: {
type: 'number',
isRequired: true
},
notRequired: {
type: 'string'
}
}
});

console.log(`${cli.flags.test},${cli.flags.number}`);
6 changes: 3 additions & 3 deletions fixture.js → test/fixtures/fixture.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/usr/bin/env node
'use strict';
const meow = require('.');
const meow = require('../..');

const cli = meow({
description: 'Custom description',
help: `
Usage
foo <input>
`,
autoVersion: process.argv.indexOf('--no-auto-version') === -1,
autoHelp: process.argv.indexOf('--no-auto-help') === -1,
autoVersion: !process.argv.includes('--no-auto-version'),
autoHelp: !process.argv.includes('--no-auto-help'),
flags: {
unicorn: {alias: 'u'},
meow: {default: 'dog'},
115 changes: 115 additions & 0 deletions test/is-required-flag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import test from 'ava';
import execa from 'execa';
const path = require('path');

const fixtureRequiredPath = path.join(__dirname, 'fixtures', 'fixture-required.js');
const fixtureRequiredFunctionPath = path.join(__dirname, 'fixtures', 'fixture-required-function.js');
const fixtureRequiredMultiplePath = path.join(__dirname, 'fixtures', 'fixture-required-multiple.js');

test('spawn cli and test not specifying required flags', async t => {
try {
await execa(fixtureRequiredPath, []);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 2/);
t.regex(stderr, /Missing required flag/);
t.regex(stderr, /--test, -t/);
t.regex(stderr, /--number/);
t.notRegex(stderr, /--notRequired/);
}
});

test('spawn cli and test specifying all required flags', async t => {
const {stdout} = await execa(fixtureRequiredPath, [
'-t',
'test',
'--number',
'6'
]);
t.is(stdout, 'test,6');
});

test('spawn cli and test specifying required string flag with an empty string as value', async t => {
try {
await execa(fixtureRequiredPath, ['--test', '']);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 2/);
t.regex(stderr, /Missing required flag/);
t.notRegex(stderr, /--test, -t/);
}
});

test('spawn cli and test specifying required number flag without a number', async t => {
try {
await execa(fixtureRequiredPath, ['--number']);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 2/);
t.regex(stderr, /Missing required flag/);
t.regex(stderr, /--number/);
}
});

test('spawn cli and test setting isRequired as a function and not specifying any flags', async t => {
const {stdout} = await execa(fixtureRequiredFunctionPath, []);
t.is(stdout, 'false,undefined');
});

test('spawn cli and test setting isRequired as a function and specifying only the flag that activates the isRequired condition for the other flag', async t => {
try {
await execa(fixtureRequiredFunctionPath, ['--trigger']);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 2/);
t.regex(stderr, /Missing required flag/);
t.regex(stderr, /--withTrigger/);
}
});

test('spawn cli and test setting isRequired as a function and specifying both the flags', async t => {
const {stdout} = await execa(fixtureRequiredFunctionPath, ['--trigger', '--withTrigger', 'specified']);
t.is(stdout, 'true,specified');
});

test('spawn cli and test setting isRequired as a function and check if returning a non-boolean value throws an error', async t => {
try {
await execa(fixtureRequiredFunctionPath, ['--allowError', '--shouldError', 'specified']);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 1/);
t.regex(stderr, /Return value for isRequired callback should be of type boolean, but string was returned./);
}
});

test('spawn cli and test isRequired with isMultiple giving a single value', async t => {
const {stdout} = await execa(fixtureRequiredMultiplePath, ['--test', '1']);
t.is(stdout, '[ 1 ]');
});

test('spawn cli and test isRequired with isMultiple giving multiple values', async t => {
const {stdout} = await execa(fixtureRequiredMultiplePath, ['--test', '1', '--test', '2']);
t.is(stdout, '[ 1, 2 ]');
});

test('spawn cli and test isRequired with isMultiple giving no values, but flag is given', async t => {
try {
await execa(fixtureRequiredMultiplePath, ['--test']);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 2/);
t.regex(stderr, /Missing required flag/);
t.regex(stderr, /--test/);
}
});

test('spawn cli and test isRequired with isMultiple giving no values, but flag is not given', async t => {
try {
await execa(fixtureRequiredMultiplePath, []);
} catch (error) {
const {stderr, message} = error;
t.regex(message, /Command failed with exit code 2/);
t.regex(stderr, /Missing required flag/);
t.regex(stderr, /--test/);
}
});
534 changes: 534 additions & 0 deletions test/test.js

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"files": [
"index.d.ts",
"index.test-d.ts",
],
"compilerOptions": {
"strict": true,
"jsx": "react",
"target": "es2018",
"lib": [
"es2018"
],
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true
}
}