From abd6b122f44482ddea33be3e7c22312d07120d62 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 24 Mar 2020 21:30:12 +0100 Subject: [PATCH 01/24] Add isRequired option for flags --- fixture-required.js | 26 ++++++++++++++++++++++++++ index.js | 28 ++++++++++++++++++++++++++++ test.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100755 fixture-required.js diff --git a/fixture-required.js b/fixture-required.js new file mode 100755 index 0000000..a2f6b03 --- /dev/null +++ b/fixture-required.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +'use strict'; +const meow = require('.'); + +const cli = meow({ + description: 'Custom description', + help: ` + Usage + foo + `, + flags: { + test: { + type: 'string', + alias: 't', + isRequired: true + }, + number: { + type: 'number', + isRequired: true + }, + notRequired: { + type: 'string' + } + } +}); +console.log(`${cli.flags.test},${cli.flags.number}`); diff --git a/index.js b/index.js index 4b802db..bb5d51e 100644 --- a/index.js +++ b/index.js @@ -51,6 +51,16 @@ const meow = (helpText, options) => { options.flags ) : options.flags; + // Get a list of required flags + const requiredFlags = []; + if (typeof options.flags !== 'undefined') { + for (const flagName of Object.keys(options.flags)) { + if (options.flags[flagName].isRequired) { + requiredFlags.push(flagName); + } + } + } + let minimistoptions = { arguments: options.input, ...minimistFlags @@ -118,6 +128,24 @@ const meow = (helpText, options) => { } } + // Get a list of missing flags, that are required + const missing = []; + for (const requiredFlag of requiredFlags) { + if (typeof flags[requiredFlag] === 'undefined') { + missing.push({key: requiredFlag, ...options.flags[requiredFlag]}); + } + } + + // Print error message for missing flags + if (missing.length > 0) { + console.log(`Missing required option${missing.length > 1 ? 's' : ''}`); + for (const flag of missing) { + console.log(`\t--${flag.key}${flag.alias ? `, -${flag.alias}` : ''}`); + } + + process.exit(2); + } + return { input, flags, diff --git a/test.js b/test.js index d3e40ec..c402eaf 100644 --- a/test.js +++ b/test.js @@ -80,6 +80,51 @@ test('spawn cli and test input flag', async t => { t.is(stdout, 'bar'); }); +test('spawn cli and test required flag with missing args', async t => { + try { + await execa('./fixture-required.js', []); + } catch (error) { + const {stdout, message} = error; + t.regex(message, /Command failed with exit code 2/); + t.regex(stdout, /Missing required option/); + t.regex(stdout, /--test, -t/); + t.regex(stdout, /--number/); + t.notRegex(stdout, /--notRequired/); + } +}); + +test('spawn cli and test required flag with all args given', async t => { + const {stdout} = await execa('./fixture-required.js', [ + '-t', + 'test', + '--number', + '6' + ]); + t.is(stdout, 'test,6'); +}); + +test('spawn cli and test required flag with empty string', async t => { + try { + await execa('./fixture-required.js', ['--test', '']); + } catch (error) { + const {stdout, message} = error; + t.regex(message, /Command failed with exit code 2/); + t.regex(stdout, /Missing required option/); + t.notRegex(stdout, /--test, -t/); + } +}); + +test('spawn cli and test required flag with empty number', async t => { + try { + await execa('./fixture-required.js', ['--number']); + } catch (error) { + const {stdout, message} = error; + t.regex(message, /Command failed with exit code 2/); + t.regex(stdout, /Missing required option/); + t.regex(stdout, /--number/); + } +}); + test.serial('pkg.bin as a string should work', t => { meow({ pkg: { From 3d2f6c8effa32b40052bc92b3d5dcaed248ae39e Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 24 Mar 2020 21:55:35 +0100 Subject: [PATCH 02/24] Add ability to specify isRequired as a function --- fixture-required-function.js | 24 ++++++++++++++++++++++++ index.js | 8 +++++++- test.js | 21 +++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100755 fixture-required-function.js diff --git a/fixture-required-function.js b/fixture-required-function.js new file mode 100755 index 0000000..91bb757 --- /dev/null +++ b/fixture-required-function.js @@ -0,0 +1,24 @@ +#!/usr/bin/env node +'use strict'; +const meow = require('.'); + +const cli = meow({ + description: 'Custom description', + help: ` + Usage + foo + `, + flags: { + trigger: { + type: 'boolean', + alias: 't' + }, + withTrigger: { + type: 'string', + isRequired: (flags, _) => { + return flags.trigger; + } + } + } +}); +console.log(`${cli.flags.trigger},${cli.flags.withTrigger}`); diff --git a/index.js b/index.js index bb5d51e..6d223c5 100644 --- a/index.js +++ b/index.js @@ -131,7 +131,13 @@ const meow = (helpText, options) => { // Get a list of missing flags, that are required const missing = []; for (const requiredFlag of requiredFlags) { - if (typeof flags[requiredFlag] === 'undefined') { + let requiredByFunction = true; + + if (typeof options.flags[requiredFlag].isRequired === 'function') { + requiredByFunction = options.flags[requiredFlag].isRequired(flags, input); + } + + if (typeof flags[requiredFlag] === 'undefined' && requiredByFunction) { missing.push({key: requiredFlag, ...options.flags[requiredFlag]}); } } diff --git a/test.js b/test.js index c402eaf..b99bf61 100644 --- a/test.js +++ b/test.js @@ -125,6 +125,27 @@ test('spawn cli and test required flag with empty number', async t => { } }); +test('spawn cli and test required (specified as function) flag with without arguments', async t => { + const {stdout} = await execa('./fixture-required-function.js', []); + t.is(stdout, 'false,undefined'); +}); + +test('spawn cli and test required (specified as function) flag with trigger only', async t => { + try { + await execa('./fixture-required-function.js', ['--trigger']); + } catch (error) { + const {stdout, message} = error; + t.regex(message, /Command failed with exit code 2/); + t.regex(stdout, /Missing required option/); + t.regex(stdout, /--withTrigger/); + } +}); + +test('spawn cli and test required (specified as function) flag with trigger and dynamically required option', async t => { + const {stdout} = await execa('./fixture-required-function.js', ['--trigger', '--withTrigger', 'specified']); + t.is(stdout, 'true,specified'); +}); + test.serial('pkg.bin as a string should work', t => { meow({ pkg: { From 9b0c22ab9d208addb2d6b0879ba42e70296f985c Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 24 Mar 2020 22:05:54 +0100 Subject: [PATCH 03/24] Add type definition for isRequired --- index.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/index.d.ts b/index.d.ts index c4dce28..e49795e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,11 +2,13 @@ import {PackageJson} from 'type-fest'; declare namespace meow { type FlagType = 'string' | 'boolean' | 'number'; + type IsRequiredPredicate = (flags: AnyFlags, input: string[]) => boolean; interface Flag { readonly type?: Type; readonly alias?: string; readonly default?: Default; + readonly isRequired?: boolean | IsRequiredPredicate } type StringFlag = Flag<'string', string>; @@ -24,6 +26,7 @@ declare namespace meow { - `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`: Boolean or Function that specifies if this flag is required. @example ``` @@ -32,6 +35,9 @@ declare namespace meow { type: 'string', alias: 'u', default: 'rainbow' + isRequired: (flags, input) => { + if (flags.otherFlag) return true; + } } } ``` From 45fb69a74666d24c3b1f1f13655bc2382976de98 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 24 Mar 2020 22:08:32 +0100 Subject: [PATCH 04/24] Sync readme with typedef comments and examples --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 8071317..0defc54 100644 --- a/readme.md +++ b/readme.md @@ -97,6 +97,7 @@ 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`: Boolean or Function that specified is this flag is required. Example: @@ -106,6 +107,9 @@ flags: { type: 'string', alias: 'u', default: 'rainbow' + isRequired: (flags, input) => { + if (flags.otherFlag) return true; + } } } ``` From 6bde1705410730655407e5be4f011b86a86039f8 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 21:34:05 +0200 Subject: [PATCH 05/24] Use console.error, better variable naming --- index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 65739d1..bbc1f18 100644 --- a/index.js +++ b/index.js @@ -129,7 +129,7 @@ const meow = (helpText, options) => { } // Get a list of missing flags, that are required - const missing = []; + const missingRequiredFlags = []; for (const requiredFlag of requiredFlags) { let requiredByFunction = true; @@ -138,15 +138,15 @@ const meow = (helpText, options) => { } if (typeof flags[requiredFlag] === 'undefined' && requiredByFunction) { - missing.push({key: requiredFlag, ...options.flags[requiredFlag]}); + missingRequiredFlags.push({key: requiredFlag, ...options.flags[requiredFlag]}); } } // Print error message for missing flags - if (missing.length > 0) { - console.log(`Missing required option${missing.length > 1 ? 's' : ''}`); - for (const flag of missing) { - console.log(`\t--${flag.key}${flag.alias ? `, -${flag.alias}` : ''}`); + if (missingRequiredFlags.length > 0) { + console.error(`Missing required flag${missingRequiredFlags.length > 1 ? 's' : ''}`); + for (const flag of missingRequiredFlags) { + console.error(`\t--${flag.key}${flag.alias ? `, -${flag.alias}` : ''}`); } process.exit(2); From c09599074d64287e58cfcc575ff2c35a6aa49662 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 21:34:18 +0200 Subject: [PATCH 06/24] Update documentation --- index.d.ts | 7 ++++++- readme.md | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index e49795e..51ea4f3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -27,6 +27,9 @@ declare namespace meow { - `alias`: Usually used to define a short flag alias. - `default`: Default value when the flag is not specified. - `isRequired`: Boolean or Function that specifies if this flag is required. + Two arguments are passed to the function. + The first arguments is the flags object, it contains the flags converted to camelCase excluding aliases. + The second arugment is the input string array, it contains the non-flag arguments. @example ``` @@ -36,7 +39,9 @@ declare namespace meow { alias: 'u', default: 'rainbow' isRequired: (flags, input) => { - if (flags.otherFlag) return true; + if (flags.otherFlag) { + return true; + } } } } diff --git a/readme.md b/readme.md index 0defc54..15b9aa1 100644 --- a/readme.md +++ b/readme.md @@ -97,7 +97,10 @@ 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`: Boolean or Function that specified is this flag is required. +- `isRequired`: Boolean or Function that specifies if this flag is required. + Two arguments are passed to the function. + The first arguments is the **flags** object, it contains the flags converted to camelCase excluding aliases. + The second arugment is the **input** string array, it contains the non-flag arguments. Example: @@ -108,7 +111,9 @@ flags: { alias: 'u', default: 'rainbow' isRequired: (flags, input) => { - if (flags.otherFlag) return true; + if (flags.otherFlag) { + return true; + } } } } From 1d9fa7d4d133730fe2e351eeb1259fe878dd2dac Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 22:18:46 +0200 Subject: [PATCH 07/24] Use stderr, because missing flags are printed with console.error --- test.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test.js b/test.js index b99bf61..0170640 100644 --- a/test.js +++ b/test.js @@ -84,12 +84,12 @@ test('spawn cli and test required flag with missing args', async t => { try { await execa('./fixture-required.js', []); } catch (error) { - const {stdout, message} = error; + const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); - t.regex(stdout, /Missing required option/); - t.regex(stdout, /--test, -t/); - t.regex(stdout, /--number/); - t.notRegex(stdout, /--notRequired/); + t.regex(stderr, /Missing required flag/); + t.regex(stderr, /--test, -t/); + t.regex(stderr, /--number/); + t.notRegex(stderr, /--notRequired/); } }); @@ -107,10 +107,10 @@ test('spawn cli and test required flag with empty string', async t => { try { await execa('./fixture-required.js', ['--test', '']); } catch (error) { - const {stdout, message} = error; + const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); - t.regex(stdout, /Missing required option/); - t.notRegex(stdout, /--test, -t/); + t.regex(stderr, /Missing required flag/); + t.notRegex(stderr, /--test, -t/); } }); @@ -118,10 +118,10 @@ test('spawn cli and test required flag with empty number', async t => { try { await execa('./fixture-required.js', ['--number']); } catch (error) { - const {stdout, message} = error; + const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); - t.regex(stdout, /Missing required option/); - t.regex(stdout, /--number/); + t.regex(stderr, /Missing required flag/); + t.regex(stderr, /--number/); } }); @@ -134,10 +134,10 @@ test('spawn cli and test required (specified as function) flag with trigger only try { await execa('./fixture-required-function.js', ['--trigger']); } catch (error) { - const {stdout, message} = error; + const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); - t.regex(stdout, /Missing required option/); - t.regex(stdout, /--withTrigger/); + t.regex(stderr, /Missing required flag/); + t.regex(stderr, /--withTrigger/); } }); From 1f6423a2af31dec99597f267373647bb504c4cd4 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 22:19:23 +0200 Subject: [PATCH 08/24] Split out isRequired logic to separate methods --- index.js | 64 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index bbc1f18..36b5229 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,37 @@ const normalizePackageData = require('normalize-package-data'); delete require.cache[__filename]; const parentDir = path.dirname(module.parent.filename); +const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => { + const flag = definedFlags[flagName]; + let isFlagRequired = true; + + if (typeof flag.isRequired === 'function') { + isFlagRequired = flag.isRequired(receivedFlags, input); + } + + return typeof receivedFlags[flagName] === 'undefined' && isFlagRequired; +}; + +const getMissingRequiredFlags = (flags, receivedFlags, input) => { + const missingRequiredFlags = []; + if (typeof flags !== 'undefined') { + 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 meow = (helpText, options) => { if (typeof helpText !== 'string') { options = helpText; @@ -51,16 +82,6 @@ const meow = (helpText, options) => { options.flags ) : options.flags; - // Get a list of required flags - const requiredFlags = []; - if (typeof options.flags !== 'undefined') { - for (const flagName of Object.keys(options.flags)) { - if (options.flags[flagName].isRequired) { - requiredFlags.push(flagName); - } - } - } - let parserOptions = { arguments: options.input, ...parserFlags @@ -128,27 +149,12 @@ const meow = (helpText, options) => { } } - // Get a list of missing flags, that are required - const missingRequiredFlags = []; - for (const requiredFlag of requiredFlags) { - let requiredByFunction = true; + // Get a list of missing flags that are required + const missingRequiredFlags = getMissingRequiredFlags(options.flags, flags, input); - if (typeof options.flags[requiredFlag].isRequired === 'function') { - requiredByFunction = options.flags[requiredFlag].isRequired(flags, input); - } - - if (typeof flags[requiredFlag] === 'undefined' && requiredByFunction) { - missingRequiredFlags.push({key: requiredFlag, ...options.flags[requiredFlag]}); - } - } - - // Print error message for missing flags + // Print error message for missing flags that are required if (missingRequiredFlags.length > 0) { - console.error(`Missing required flag${missingRequiredFlags.length > 1 ? 's' : ''}`); - for (const flag of missingRequiredFlags) { - console.error(`\t--${flag.key}${flag.alias ? `, -${flag.alias}` : ''}`); - } - + reportMissingRequiredFlags(missingRequiredFlags); process.exit(2); } From a5c24a2486a0d5bb11a11947520ccf50dfda6875 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 22:33:27 +0200 Subject: [PATCH 09/24] Organize tests and fixtures, update affected paths --- package.json | 5 +++ .../fixtures/fixture-required-function.js | 2 +- .../fixtures/fixture-required.js | 2 +- fixture.js => test/fixtures/fixture.js | 2 +- test.js => test/test.js | 36 +++++++++---------- 5 files changed, 26 insertions(+), 21 deletions(-) rename fixture-required-function.js => test/fixtures/fixture-required-function.js (92%) rename fixture-required.js => test/fixtures/fixture-required.js (92%) rename fixture.js => test/fixtures/fixture.js (94%) rename test.js => test/test.js (83%) diff --git a/package.json b/package.json index 23e555a..b4d6a0a 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,10 @@ "rules": { "unicorn/no-process-exit": "off" } + }, + "ava": { + "files": [ + "test/*" + ] } } diff --git a/fixture-required-function.js b/test/fixtures/fixture-required-function.js similarity index 92% rename from fixture-required-function.js rename to test/fixtures/fixture-required-function.js index 91bb757..c862f47 100755 --- a/fixture-required-function.js +++ b/test/fixtures/fixture-required-function.js @@ -1,6 +1,6 @@ #!/usr/bin/env node 'use strict'; -const meow = require('.'); +const meow = require('../..'); const cli = meow({ description: 'Custom description', diff --git a/fixture-required.js b/test/fixtures/fixture-required.js similarity index 92% rename from fixture-required.js rename to test/fixtures/fixture-required.js index a2f6b03..dc87ec7 100755 --- a/fixture-required.js +++ b/test/fixtures/fixture-required.js @@ -1,6 +1,6 @@ #!/usr/bin/env node 'use strict'; -const meow = require('.'); +const meow = require('../..'); const cli = meow({ description: 'Custom description', diff --git a/fixture.js b/test/fixtures/fixture.js similarity index 94% rename from fixture.js rename to test/fixtures/fixture.js index c96580f..6917bae 100755 --- a/fixture.js +++ b/test/fixtures/fixture.js @@ -1,6 +1,6 @@ #!/usr/bin/env node 'use strict'; -const meow = require('.'); +const meow = require('../..'); const cli = meow({ description: 'Custom description', diff --git a/test.js b/test/test.js similarity index 83% rename from test.js rename to test/test.js index 0170640..424914d 100644 --- a/test.js +++ b/test/test.js @@ -1,8 +1,8 @@ import test from 'ava'; import indentString from 'indent-string'; import execa from 'execa'; -import pkg from './package.json'; -import meow from '.'; +import pkg from '../package.json'; +import meow from '..'; test('return object', t => { const cli = meow({ @@ -36,53 +36,53 @@ test('support help shortcut', t => { }); test('spawn cli and show version', async t => { - const {stdout} = await execa('./fixture.js', ['--version']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--version']); t.is(stdout, pkg.version); }); test('spawn cli and disabled autoVersion and autoHelp', async t => { - const {stdout} = await execa('./fixture.js', ['--version', '--help']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--version', '--help']); t.is(stdout, 'version\nhelp\nmeow\ncamelCaseOption'); }); test('spawn cli and disabled autoVersion', async t => { - const {stdout} = await execa('./fixture.js', ['--version', '--no-auto-version']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--version', '--no-auto-version']); t.is(stdout, 'version\nautoVersion\nmeow\ncamelCaseOption'); }); test('spawn cli and not show version', async t => { - const {stdout} = await execa('./fixture.js', ['--version=beta']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--version=beta']); t.is(stdout, 'version\nmeow\ncamelCaseOption'); }); test('spawn cli and show help screen', async t => { - const {stdout} = await execa('./fixture.js', ['--help']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--help']); t.is(stdout, indentString('\nCustom description\n\nUsage\n foo \n\n', 2)); }); test('spawn cli and disabled autoHelp', async t => { - const {stdout} = await execa('./fixture.js', ['--help', '--no-auto-help']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--help', '--no-auto-help']); t.is(stdout, 'help\nautoHelp\nmeow\ncamelCaseOption'); }); test('spawn cli and not show help', async t => { - const {stdout} = await execa('./fixture.js', ['--help=all']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--help=all']); t.is(stdout, 'help\nmeow\ncamelCaseOption'); }); test('spawn cli and test input', async t => { - const {stdout} = await execa('./fixture.js', ['-u', 'cat']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['-u', 'cat']); t.is(stdout, 'unicorn\nmeow\ncamelCaseOption'); }); test('spawn cli and test input flag', async t => { - const {stdout} = await execa('./fixture.js', ['--camel-case-option', 'bar']); + const {stdout} = await execa('./test/fixtures/fixture.js', ['--camel-case-option', 'bar']); t.is(stdout, 'bar'); }); test('spawn cli and test required flag with missing args', async t => { try { - await execa('./fixture-required.js', []); + await execa('./test/fixtures/fixture-required.js', []); } catch (error) { const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); @@ -94,7 +94,7 @@ test('spawn cli and test required flag with missing args', async t => { }); test('spawn cli and test required flag with all args given', async t => { - const {stdout} = await execa('./fixture-required.js', [ + const {stdout} = await execa('./test/fixtures/fixture-required.js', [ '-t', 'test', '--number', @@ -105,7 +105,7 @@ test('spawn cli and test required flag with all args given', async t => { test('spawn cli and test required flag with empty string', async t => { try { - await execa('./fixture-required.js', ['--test', '']); + await execa('./test/fixtures/fixture-required.js', ['--test', '']); } catch (error) { const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); @@ -116,7 +116,7 @@ test('spawn cli and test required flag with empty string', async t => { test('spawn cli and test required flag with empty number', async t => { try { - await execa('./fixture-required.js', ['--number']); + await execa('./test/fixtures/fixture-required.js', ['--number']); } catch (error) { const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); @@ -126,13 +126,13 @@ test('spawn cli and test required flag with empty number', async t => { }); test('spawn cli and test required (specified as function) flag with without arguments', async t => { - const {stdout} = await execa('./fixture-required-function.js', []); + const {stdout} = await execa('./test/fixtures/fixture-required-function.js', []); t.is(stdout, 'false,undefined'); }); test('spawn cli and test required (specified as function) flag with trigger only', async t => { try { - await execa('./fixture-required-function.js', ['--trigger']); + await execa('./test/fixtures/fixture-required-function.js', ['--trigger']); } catch (error) { const {stderr, message} = error; t.regex(message, /Command failed with exit code 2/); @@ -142,7 +142,7 @@ test('spawn cli and test required (specified as function) flag with trigger only }); test('spawn cli and test required (specified as function) flag with trigger and dynamically required option', async t => { - const {stdout} = await execa('./fixture-required-function.js', ['--trigger', '--withTrigger', 'specified']); + const {stdout} = await execa('./test/fixtures/fixture-required-function.js', ['--trigger', '--withTrigger', 'specified']); t.is(stdout, 'true,specified'); }); From bf0d816980996f556cd8b166aa97d434b569e813 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 22:54:31 +0200 Subject: [PATCH 10/24] Split out isRequired flag related tests to separate file, better test descriptions --- test/test-is-required-flag.js | 72 +++++++++++++++++++++++++++++++++++ test/test.js | 66 -------------------------------- 2 files changed, 72 insertions(+), 66 deletions(-) create mode 100644 test/test-is-required-flag.js diff --git a/test/test-is-required-flag.js b/test/test-is-required-flag.js new file mode 100644 index 0000000..c8dfa2d --- /dev/null +++ b/test/test-is-required-flag.js @@ -0,0 +1,72 @@ +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'); + +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'); +}); diff --git a/test/test.js b/test/test.js index 424914d..83e6799 100644 --- a/test/test.js +++ b/test/test.js @@ -80,72 +80,6 @@ test('spawn cli and test input flag', async t => { t.is(stdout, 'bar'); }); -test('spawn cli and test required flag with missing args', async t => { - try { - await execa('./test/fixtures/fixture-required.js', []); - } 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 required flag with all args given', async t => { - const {stdout} = await execa('./test/fixtures/fixture-required.js', [ - '-t', - 'test', - '--number', - '6' - ]); - t.is(stdout, 'test,6'); -}); - -test('spawn cli and test required flag with empty string', async t => { - try { - await execa('./test/fixtures/fixture-required.js', ['--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 required flag with empty number', async t => { - try { - await execa('./test/fixtures/fixture-required.js', ['--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 required (specified as function) flag with without arguments', async t => { - const {stdout} = await execa('./test/fixtures/fixture-required-function.js', []); - t.is(stdout, 'false,undefined'); -}); - -test('spawn cli and test required (specified as function) flag with trigger only', async t => { - try { - await execa('./test/fixtures/fixture-required-function.js', ['--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 required (specified as function) flag with trigger and dynamically required option', async t => { - const {stdout} = await execa('./test/fixtures/fixture-required-function.js', ['--trigger', '--withTrigger', 'specified']); - t.is(stdout, 'true,specified'); -}); - test.serial('pkg.bin as a string should work', t => { meow({ pkg: { From 8b03f9f18af010d63c2adc152dfd78e0682e8af2 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 22:58:02 +0200 Subject: [PATCH 11/24] Specify fixture path better --- test/test.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/test.js b/test/test.js index 83e6799..15306be 100644 --- a/test/test.js +++ b/test/test.js @@ -3,6 +3,9 @@ import indentString from 'indent-string'; import execa from 'execa'; import pkg from '../package.json'; import meow from '..'; +const path = require('path'); + +const fixturePath = path.join(__dirname, 'fixtures', 'fixture.js'); test('return object', t => { const cli = meow({ @@ -36,47 +39,47 @@ test('support help shortcut', t => { }); test('spawn cli and show version', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--version']); + const {stdout} = await execa(fixturePath, ['--version']); t.is(stdout, pkg.version); }); test('spawn cli and disabled autoVersion and autoHelp', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--version', '--help']); + const {stdout} = await execa(fixturePath, ['--version', '--help']); t.is(stdout, 'version\nhelp\nmeow\ncamelCaseOption'); }); test('spawn cli and disabled autoVersion', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--version', '--no-auto-version']); + const {stdout} = await execa(fixturePath, ['--version', '--no-auto-version']); t.is(stdout, 'version\nautoVersion\nmeow\ncamelCaseOption'); }); test('spawn cli and not show version', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--version=beta']); + const {stdout} = await execa(fixturePath, ['--version=beta']); t.is(stdout, 'version\nmeow\ncamelCaseOption'); }); test('spawn cli and show help screen', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--help']); + const {stdout} = await execa(fixturePath, ['--help']); t.is(stdout, indentString('\nCustom description\n\nUsage\n foo \n\n', 2)); }); test('spawn cli and disabled autoHelp', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--help', '--no-auto-help']); + const {stdout} = await execa(fixturePath, ['--help', '--no-auto-help']); t.is(stdout, 'help\nautoHelp\nmeow\ncamelCaseOption'); }); test('spawn cli and not show help', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--help=all']); + const {stdout} = await execa(fixturePath, ['--help=all']); t.is(stdout, 'help\nmeow\ncamelCaseOption'); }); test('spawn cli and test input', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['-u', 'cat']); + const {stdout} = await execa(fixturePath, ['-u', 'cat']); t.is(stdout, 'unicorn\nmeow\ncamelCaseOption'); }); test('spawn cli and test input flag', async t => { - const {stdout} = await execa('./test/fixtures/fixture.js', ['--camel-case-option', 'bar']); + const {stdout} = await execa(fixturePath, ['--camel-case-option', 'bar']); t.is(stdout, 'bar'); }); From 854e2941261aad0e888b85cfd82ac2b497a04a94 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Tue, 28 Apr 2020 23:04:23 +0200 Subject: [PATCH 12/24] Add isRequired (as function) return value to documentation --- index.d.ts | 1 + readme.md | 1 + 2 files changed, 2 insertions(+) diff --git a/index.d.ts b/index.d.ts index 51ea4f3..eb4275e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -30,6 +30,7 @@ declare namespace meow { Two arguments are passed to the function. The first arguments is the flags object, it contains the flags converted to camelCase excluding aliases. The second arugment is the input string array, it contains the non-flag arguments. + The function should return a Boolean, true if the flag is requried, otherwise false. @example ``` diff --git a/readme.md b/readme.md index 15b9aa1..7dc808d 100644 --- a/readme.md +++ b/readme.md @@ -101,6 +101,7 @@ The key is the flag name and the value is an object with any of: Two arguments are passed to the function. The first arguments is the **flags** object, it contains the flags converted to camelCase excluding aliases. The second arugment is the **input** string array, it contains the non-flag arguments. + The function should return a Boolean, true if the flag is requried, otherwise false. Example: From c54437d4999377d121d72c46afff6926e77a5699 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 17:02:50 +0200 Subject: [PATCH 13/24] Fix broken state after merge --- index.d.ts | 4 ++-- index.js | 2 ++ package.json | 8 ++++---- test/test.js | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/index.d.ts b/index.d.ts index dfb9157..be77dcf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,13 +2,13 @@ import {PackageJson} from 'type-fest'; declare namespace meow { type FlagType = 'string' | 'boolean' | 'number'; - type IsRequiredPredicate = (flags: AnyFlags, input: string[]) => boolean; + type IsRequiredPredicate = (flags: Readonly, input: readonly string[]) => boolean; interface Flag { readonly type?: Type; readonly alias?: string; readonly default?: Default; - readonly isRequired?: boolean | IsRequiredPredicate + readonly isRequired?: boolean | IsRequiredPredicate; readonly isMultiple?: boolean; } diff --git a/index.js b/index.js index 0ae68f3..8e7871d 100644 --- a/index.js +++ b/index.js @@ -43,6 +43,8 @@ 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}) => Object.entries(flags).reduce((parserFlags, [flagKey, flagValue]) => { diff --git a/package.json b/package.json index 791199f..428dae1 100644 --- a/package.json +++ b/package.json @@ -64,14 +64,14 @@ "rules": { "unicorn/no-process-exit": "off", "node/no-unsupported-features/es-syntax": "off" - } + }, + "ignores": [ + "estest/index.js" + ] }, "ava": { "files": [ "test/*" - }, - "ignores": [ - "estest/index.js" ] } } diff --git a/test/test.js b/test/test.js index e26bac6..dbf8961 100644 --- a/test/test.js +++ b/test/test.js @@ -2,8 +2,8 @@ import test from 'ava'; import indentString from 'indent-string'; import execa from 'execa'; import path from 'path'; -import pkg from './package.json'; -import meow from '.'; +import pkg from '../package.json'; +import meow from '..'; const fixturePath = path.join(__dirname, 'fixtures', 'fixture.js'); const NODE_MAJOR_VERSION = process.versions.node.split('.')[0]; From 80cc74090fbb25c0811e772908ce13f725b78501 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 17:53:02 +0200 Subject: [PATCH 14/24] Improve documentation --- index.d.ts | 16 +++++++++++----- readme.md | 10 ++++++---- ...t-is-required-flag.js => is-required-flag.js} | 0 3 files changed, 17 insertions(+), 9 deletions(-) rename test/{test-is-required-flag.js => is-required-flag.js} (100%) diff --git a/index.d.ts b/index.d.ts index be77dcf..fd382d3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,6 +2,14 @@ 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 camelCase excluding aliases. + @param input - Contains the non-flag arguments. + + @returns True if the flag is required, otherwise false. + */ type IsRequiredPredicate = (flags: Readonly, input: readonly string[]) => boolean; interface Flag { @@ -27,11 +35,8 @@ declare namespace meow { - `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`: Boolean or Function that specifies if this flag is required. - Two arguments are passed to the function. - The first arguments is the flags object, it contains the flags converted to camelCase excluding aliases. - The second arugment is the input string array, it contains the non-flag arguments. - The function should return a Boolean, true if the flag is requried, otherwise false. + - `isRequired`: Determine if the flag is required. + If it's only known at runtime whether the flag is requried 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) @example @@ -46,6 +51,7 @@ declare namespace meow { if (flags.otherFlag) { return true; } + return false; } } } diff --git a/readme.md b/readme.md index 0c39d17..b298219 100644 --- a/readme.md +++ b/readme.md @@ -137,11 +137,12 @@ 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`: Boolean or Function that specifies if this flag is required. +- `isRequired`: Determine if the flag is required. + If it's only known at runtime whether the flag is requried 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 arguments is the **flags** object, it contains the flags converted to camelCase excluding aliases. - The second arugment is the **input** string array, it contains the non-flag arguments. - The function should return a Boolean, true if the flag is requried, otherwise false. + The first argument is the **flags** object, it contains the flags converted to camelCase excluding aliases. + The second argument is the **input** string array, it 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) Example: @@ -157,6 +158,7 @@ flags: { if (flags.otherFlag) { return true; } + return false; } } } diff --git a/test/test-is-required-flag.js b/test/is-required-flag.js similarity index 100% rename from test/test-is-required-flag.js rename to test/is-required-flag.js From 019dd3221dda90e5b7b61e10a71e6f8461c80feb Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 17:59:47 +0200 Subject: [PATCH 15/24] Simplify code, check isRequired callback return value type --- index.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 8e7871d..8d805e5 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,9 @@ const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => { 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.`); + } } return typeof receivedFlags[flagName] === 'undefined' && isFlagRequired; @@ -28,11 +31,13 @@ const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => { const getMissingRequiredFlags = (flags, receivedFlags, input) => { const missingRequiredFlags = []; - if (typeof flags !== 'undefined') { - for (const flagName of Object.keys(flags)) { - if (flags[flagName].isRequired && isFlagMissing(flagName, flags, receivedFlags, input)) { - missingRequiredFlags.push({key: flagName, ...flags[flagName]}); - } + 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]}); } } From 940fb9613ca1a024d4be9a353c983ec125b5bb6a Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 18:08:40 +0200 Subject: [PATCH 16/24] Add test for isRequired callback return value validation --- test/fixtures/fixture-required-function.js | 14 ++++++++++++++ test/is-required-flag.js | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/fixtures/fixture-required-function.js b/test/fixtures/fixture-required-function.js index c862f47..ac7de69 100755 --- a/test/fixtures/fixture-required-function.js +++ b/test/fixtures/fixture-required-function.js @@ -18,6 +18,20 @@ const cli = meow({ isRequired: (flags, _) => { return flags.trigger; } + }, + allowError: { + type: 'boolean', + alias: 'a' + }, + shouldError: { + type: 'boolean', + isRequired: (flags, _) => { + if (flags.allowError) { + return 'should error'; + } + + return false; + } } } }); diff --git a/test/is-required-flag.js b/test/is-required-flag.js index c8dfa2d..58e67a4 100644 --- a/test/is-required-flag.js +++ b/test/is-required-flag.js @@ -70,3 +70,13 @@ test('spawn cli and test setting isRequired as a function and specifying both th 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./); + } +}); From 3c1c71061f8657cb2745ff53743a9b9293e18875 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 18:41:05 +0200 Subject: [PATCH 17/24] Add handling of isMultiple flags to the isRequired logic --- index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 8d805e5..559366d 100644 --- a/index.js +++ b/index.js @@ -26,7 +26,11 @@ const isFlagMissing = (flagName, definedFlags, receivedFlags, input) => { } } - return typeof receivedFlags[flagName] === 'undefined' && isFlagRequired; + if (typeof receivedFlags[flagName] === 'undefined') { + return isFlagRequired; + } + + return flag.isMultiple && receivedFlags[flagName].length === 0; }; const getMissingRequiredFlags = (flags, receivedFlags, input) => { From 22f0a77832fd0f6e8e4ce8cd8997ca3a307c3bc8 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 18:42:15 +0200 Subject: [PATCH 18/24] Add tests for isRequired and isMultiple options set at the same time --- test/fixtures/fixture-required-multiple.js | 20 +++++++++++++ test/is-required-flag.js | 33 ++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100755 test/fixtures/fixture-required-multiple.js diff --git a/test/fixtures/fixture-required-multiple.js b/test/fixtures/fixture-required-multiple.js new file mode 100755 index 0000000..f02777e --- /dev/null +++ b/test/fixtures/fixture-required-multiple.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node +'use strict'; +const meow = require('../..'); + +const cli = meow({ + description: 'Custom description', + help: ` + Usage + foo + `, + flags: { + test: { + type: 'number', + alias: 't', + isRequired: true, + isMultiple: true + } + } +}); +console.log(cli.flags.test); diff --git a/test/is-required-flag.js b/test/is-required-flag.js index 58e67a4..66907fb 100644 --- a/test/is-required-flag.js +++ b/test/is-required-flag.js @@ -4,6 +4,7 @@ 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 { @@ -80,3 +81,35 @@ test('spawn cli and test setting isRequired as a function and check if returning 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 a multiple values', async t => { + const {stdout} = await execa(fixtureRequiredMultiplePath, ['--test', '1', '2', '3']); + t.is(stdout, '[ 1, 2, 3 ]'); +}); + +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/); + } +}); From e11a0adc0b2ccec57ef164f13ed357cd5b5daaa7 Mon Sep 17 00:00:00 2001 From: sbencoding Date: Wed, 6 May 2020 18:49:26 +0200 Subject: [PATCH 19/24] Fix estest path --- test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index dbf8961..946a069 100644 --- a/test/test.js +++ b/test/test.js @@ -486,7 +486,7 @@ if (NODE_MAJOR_VERSION >= 14) { test('supports es modules', async t => { try { const {stdout} = await execa('node', ['index.js', '--version'], { - cwd: path.join(__dirname, 'estest') + cwd: path.join(__dirname, '..', 'estest') }); t.regex(stdout, /1.2.3/); } catch (error) { From e5dac8bc2ef5510dfeaef20b0b209b59c1fb8757 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 May 2020 13:20:44 +0800 Subject: [PATCH 20/24] Update index.d.ts --- index.d.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index fd382d3..e92d14a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,10 +2,11 @@ import {PackageJson} from 'type-fest'; declare namespace meow { type FlagType = 'string' | 'boolean' | 'number'; + /** - Callback function to determine if a flag is required during runtime + Callback function to determine if a flag is required during runtime. - @param flags - Contains the flags converted to camelCase excluding aliases. + @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. @@ -51,6 +52,7 @@ declare namespace meow { if (flags.otherFlag) { return true; } + return false; } } From 1e04bdc390d0b3b3fcfa1a48374d6d582c75cb8e Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 May 2020 13:24:44 +0800 Subject: [PATCH 21/24] Update readme.md --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index b298219..4f3fdf2 100644 --- a/readme.md +++ b/readme.md @@ -137,12 +137,11 @@ 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 requried 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, it contains the flags converted to camelCase excluding aliases. - The second argument is the **input** string array, it contains the non-flag arguments. - The function should return a Boolean, true if the flag is required, otherwise false. +- `isRequired`: Determine if the flag is required. (Default: false) + - If it's only known at runtime whether the flag is requried 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) Example: @@ -158,6 +157,7 @@ flags: { if (flags.otherFlag) { return true; } + return false; } } From 59de8b8f6e2fe5f1d6ccbc09d1df4c67585e7035 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 May 2020 13:25:13 +0800 Subject: [PATCH 22/24] Update fixture-required-function.js --- test/fixtures/fixture-required-function.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/fixtures/fixture-required-function.js b/test/fixtures/fixture-required-function.js index ac7de69..ef3c443 100755 --- a/test/fixtures/fixture-required-function.js +++ b/test/fixtures/fixture-required-function.js @@ -35,4 +35,5 @@ const cli = meow({ } } }); + console.log(`${cli.flags.trigger},${cli.flags.withTrigger}`); From 3dee2378f65cbaf46b77f389b2601a8e6ef5b75f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 May 2020 13:25:37 +0800 Subject: [PATCH 23/24] Update fixture-required-multiple.js --- test/fixtures/fixture-required-multiple.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/fixtures/fixture-required-multiple.js b/test/fixtures/fixture-required-multiple.js index f02777e..fa424e2 100755 --- a/test/fixtures/fixture-required-multiple.js +++ b/test/fixtures/fixture-required-multiple.js @@ -17,4 +17,5 @@ const cli = meow({ } } }); + console.log(cli.flags.test); From 4b7a7acb137a7a8379b11feacf471d12a8c57096 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 7 May 2020 13:25:51 +0800 Subject: [PATCH 24/24] Update fixture-required.js --- test/fixtures/fixture-required.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/fixtures/fixture-required.js b/test/fixtures/fixture-required.js index dc87ec7..e34577b 100755 --- a/test/fixtures/fixture-required.js +++ b/test/fixtures/fixture-required.js @@ -23,4 +23,5 @@ const cli = meow({ } } }); + console.log(`${cli.flags.test},${cli.flags.number}`);