diff --git a/index.d.ts b/index.d.ts index df3f10e..ad9bf13 100644 --- a/index.d.ts +++ b/index.d.ts @@ -199,6 +199,13 @@ declare namespace meow { @default true */ readonly hardRejection?: boolean; + + /** + Whether to allow unknown flags or not. + + @default true + */ + readonly allowUnknownFlags?: boolean; } type TypedFlag = diff --git a/index.js b/index.js index dccbcb5..f651900 100644 --- a/index.js +++ b/index.js @@ -54,6 +54,13 @@ const reportMissingRequiredFlags = missingRequiredFlags => { } }; +const reportUnknownFlags = unknownFlags => { + console.error(`Unknown flag${unknownFlags.length > 1 ? 's' : ''}`); + for (const flag of unknownFlags) { + console.error(flag); + } +}; + const buildParserFlags = ({flags, booleanDefault}) => { const parserFlags = {}; @@ -110,6 +117,7 @@ const meow = (helpText, options) => { autoVersion: true, booleanDefault: false, hardRejection: true, + allowUnknownFlags: true, ...options }; @@ -139,6 +147,11 @@ const meow = (helpText, options) => { parserOptions.configuration['populate--'] = true; } + if (!options.allowUnknownFlags) { + // Collect unknown options in `argv._` to be checked later. + parserOptions.configuration['unknown-options-as-args'] = true; + } + const {pkg} = options; const argv = parseArguments(options.argv, parserOptions); let help = redent(trimNewlines((options.help || '').replace(/\t+\n*$/, '')), 2); @@ -177,6 +190,14 @@ const meow = (helpText, options) => { const input = argv._; delete argv._; + if (!options.allowUnknownFlags) { + const unknownFlags = input.filter(item => typeof item === 'string' && item.startsWith('-')); + if (unknownFlags.length > 0) { + reportUnknownFlags(unknownFlags); + process.exit(2); + } + } + const flags = camelCaseKeys(argv, {exclude: ['--', /^\w$/]}); const unnormalizedFlags = {...flags}; diff --git a/readme.md b/readme.md index 1c547dc..7440c32 100644 --- a/readme.md +++ b/readme.md @@ -314,6 +314,13 @@ Default: `true` 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. +#### allowUnknownFlags + +Type `boolean`\ +Default: `true` + +Whether to allow unknown flags or not. + ## 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. diff --git a/test/allow-unkonwn-flags.js b/test/allow-unkonwn-flags.js new file mode 100644 index 0000000..5780e73 --- /dev/null +++ b/test/allow-unkonwn-flags.js @@ -0,0 +1,23 @@ +import test from 'ava'; +import execa from 'execa'; +const path = require('path'); + +const fixtureAllowUnknownFlags = path.join(__dirname, 'fixtures', 'fixture-allow-unknown-flags.js'); + +test('spawn cli and test specifying unknown flags', async t => { + try { + await execa(fixtureAllowUnknownFlags, ['--foo', 'bar', '--unspecified-a', '--unspecified-b', 'input-is-allowed']); + } catch (error) { + const {stderr, message} = error; + t.regex(message, /Command failed with exit code 2/); + t.regex(stderr, /Unknown flag/); + t.regex(stderr, /--unspecified-a/); + t.regex(stderr, /--unspecified-b/); + t.notRegex(stderr, /input-is-allowed/); + } +}); + +test('spawn cli and test specifying known flags', async t => { + const {stdout} = await execa(fixtureAllowUnknownFlags, ['--foo', 'bar']); + t.is(stdout, 'bar'); +}); diff --git a/test/fixtures/fixture-allow-unknown-flags.js b/test/fixtures/fixture-allow-unknown-flags.js new file mode 100755 index 0000000..131e9e9 --- /dev/null +++ b/test/fixtures/fixture-allow-unknown-flags.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +'use strict'; +const meow = require('../..'); + +const cli = meow({ + description: 'Custom description', + help: ` + Usage + foo + `, + allowUnknownFlags: false, + flags: { + foo: { + type: 'string' + } + } +}); + +console.log(cli.flags.foo);