diff --git a/lib/command.ts b/lib/command.ts index ff9da5b1b..42c0e570b 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -593,19 +593,20 @@ export class CommandInstance { positionalKeys.push(...parsed.aliases[key]); }); - const defaults = yargs.getOptions().default; Object.keys(parsed.argv).forEach(key => { if (positionalKeys.includes(key)) { // any new aliases need to be placed in positionalMap, which // is used for validation. if (!positionalMap[key]) positionalMap[key] = parsed.argv[key]; // Addresses: https://github.com/yargs/yargs/issues/1637 - // If both positionals/options provided, no default was set, + // If both positionals/options provided, + // and no default or config values were set for that key, // and if at least one is an array: don't overwrite, combine. if ( - !Object.hasOwnProperty.call(defaults, key) && - Object.hasOwnProperty.call(argv, key) && - Object.hasOwnProperty.call(parsed.argv, key) && + !this.isInConfigs(yargs, key) && + !this.isDefaulted(yargs, key) && + Object.prototype.hasOwnProperty.call(argv, key) && + Object.prototype.hasOwnProperty.call(parsed.argv, key) && (Array.isArray(argv[key]) || Array.isArray(parsed.argv[key])) ) { argv[key] = ([] as string[]).concat(argv[key], parsed.argv[key]); @@ -616,6 +617,27 @@ export class CommandInstance { }); } } + // Check defaults for key (and camel case version of key) + isDefaulted(yargs: YargsInstance, key: string): boolean { + const {default: defaults} = yargs.getOptions(); + return ( + Object.prototype.hasOwnProperty.call(defaults, key) || + Object.prototype.hasOwnProperty.call( + defaults, + this.shim.Parser.camelCase(key) + ) + ); + } + // Check each config for key (and camel case version of key) + isInConfigs(yargs: YargsInstance, key: string): boolean { + const {configObjects} = yargs.getOptions(); + return ( + configObjects.some(c => Object.prototype.hasOwnProperty.call(c, key)) || + configObjects.some(c => + Object.prototype.hasOwnProperty.call(c, this.shim.Parser.camelCase(key)) + ) + ); + } runDefaultBuilderOn(yargs: YargsInstance): unknown | Promise { if (!this.defaultCommand) return; if (this.shouldUpdateUsage(yargs)) { diff --git a/lib/platform-shims/esm.mjs b/lib/platform-shims/esm.mjs index 652d2e10a..7cddfdb45 100644 --- a/lib/platform-shims/esm.mjs +++ b/lib/platform-shims/esm.mjs @@ -22,7 +22,7 @@ try { } catch (e) { __dirname = process.cwd(); } -const mainFilename = __dirname.split('node_modules')[0]; +const mainFilename = __dirname.substring(0, __dirname.lastIndexOf('node_modules')); export default { assert: { diff --git a/lib/yargs-factory.ts b/lib/yargs-factory.ts index 279d8bcce..c3f88348f 100644 --- a/lib/yargs-factory.ts +++ b/lib/yargs-factory.ts @@ -871,7 +871,7 @@ export class YargsInstance { } locale(locale?: string): YargsInstance | string { argsert('[string]', [locale], arguments.length); - if (!locale) { + if (locale === undefined) { this[kGuessLocale](); return this.#shim.y18n.getLocale(); } diff --git a/locales/ru.json b/locales/ru.json index 5f7f76810..d5c9e323b 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -42,5 +42,10 @@ "Path to JSON config file": "Путь к файлу конфигурации JSON", "Show help": "Показать помощь", "Show version number": "Показать номер версии", - "Did you mean %s?": "Вы имели в виду %s?" + "Did you mean %s?": "Вы имели в виду %s?", + "Arguments %s and %s are mutually exclusive": "Аргументы %s и %s являются взаимоисключающими", + "Positionals:": "Позиционные аргументы:", + "command": "команда", + "deprecated": "устар.", + "deprecated: %s": "устар.: %s" } diff --git a/test/command.cjs b/test/command.cjs index 867f8f9b5..d5f94b8cb 100644 --- a/test/command.cjs +++ b/test/command.cjs @@ -264,6 +264,31 @@ describe('Command', () => { .parse('cmd apples cherries grapes'); }); + it('does not combine config values and provided values', () => { + yargs('foo bar baz qux') + .command({ + command: '$0 [arg-2] [arg-3..]', + desc: 'default description', + builder: yargs => + yargs + .option('arg-1', {type: 'string'}) + .option('arg-2', {type: 'string'}) + .option('arg-3', {type: 'string'}) + .config({ + arg2: 'bar', + arg3: ['baz', 'qux'], + }), + handler: argv => { + argv.arg1.should.equal('foo'); + argv.arg2.should.equal('bar'); + argv.arg3.should.deep.equal(['baz', 'qux']); + argv['arg-3'].should.deep.equal(['baz', 'qux']); + }, + }) + .strict() + .parse(); + }); + it('does not overwrite options in argv if variadic and preserves falsy values', () => { yargs .command({ diff --git a/test/yargs.cjs b/test/yargs.cjs index 2b4f81ca4..390f2359a 100644 --- a/test/yargs.cjs +++ b/test/yargs.cjs @@ -757,6 +757,38 @@ describe('yargs dsl tests', () => { r.logs.join(' ').should.match(/Parlay this here code of conduct/); }); + // Addresses: https://github.com/yargs/yargs/issues/2178 + it('does not enter infinite loop when locale is invalid', () => { + // Save env vars + const lcAll = process.env.LC_ALL; + const lcMessages = process.env.LC_MESSAGES; + const lang = process.env.LANG; + const language = process.env.LANGUAGE; + // Change + delete process.env.LC_ALL; + delete process.env.LC_MESSAGES; + process.env.LANG = '.UTF-8'; + delete process.env.LANGUAGE; + try { + yargs + .command({ + command: 'cmd1', + desc: 'cmd1 desc', + builder: () => {}, + handler: _argv => {}, + }) + .parse(); + } catch { + expect.fail(); + } finally { + // Restore + process.env.LC_ALL = lcAll; + process.env.LC_MESSAGES = lcMessages; + process.env.LANG = lang; + process.env.LANGUAGE = language; + } + }); + describe('updateLocale', () => { it('allows you to override the default locale strings', () => { const r = checkOutput(() => {