Repository: yargs/yargs
Pull request #1061 (2018-01-27 09:38:50)

feat: --show-hidden
Adds a `--show-hidden` flag to show options defined with `hidden: true` property

Recap: (#962) Earlier, a fix was made to address the issue where `.options({...})` without a `description` property did not show up up in `--help`. The fix introduced a new `hidden: true` property to truly hide an option from `--help`, and to show all other options (even without `description` property)

Issue with the fix: There was no way to show options defined with `hidden:true` property (they were hidden permanently)

Solution (this patch): This introduces a new flag: `--show-hidden` (pre-configured) which can be used to show those hidden options.

The default key for this new flag may also be custom configured with a new function property `.showHidden('custom-show-hidden')`

Morishiri: The code looks fine, but maybe you could write some UT for that?
laggingreflex: @Morishiri done
laggingreflex: @bcoe [done](https://github.com/laggingreflex/yargs/blob/af62bb6af12b00d8fd1b4e7be78fe60feb325433/docs/api.md#showHidden)

Patch:
diff --git a/docs/api.md b/docs/api.md
index 193056b..afd779d 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -576,6 +576,11 @@ Describe a `key` for the generated usage information.
 
 Optionally `.describe()` can take an object that maps keys to descriptions.
 
+<a name="hide"></a>.hide(key)
+--------------------
+
+Hides a `key` from the generated usage information. Unless a `--show-hidden` option is also passed with `--help` (see [`showHidden()`](#showHidden)).
+
 .detectLocale(boolean)
 -----------
 
@@ -798,6 +803,29 @@ var yargs = require("yargs")(['--info'])
   .argv
 ```
 
+<a name="showHidden"></a>.showHidden()
+-----------------------------------------
+.showHidden([option | boolean])
+-----------------------------------------
+.showHidden([option, [description]])
+-----------------------------------------
+
+Configure the `--show-hidden` option that displays the hidden keys (see [`hide()`](#hide)).
+
+If the first argument is a boolean, it enables/disables this option altogether. I.e. hidden keys will be permanently hidden if first argument is `false`.
+
+If the first argument is a string it changes the key name ("--show-hidden").
+
+Second argument changes the default description ("Show hidden options")
+
+Example:
+
+```js
+var yargs = require("yargs")(['--help'])
+  .showHidden('show-hidden', 'Show hidden options')
+  .argv
+```
+
 <a name="implies"></a>.implies(x, y)
 --------------
 
diff --git a/example/cmds/init.js b/example/cmds/init.js
new file mode 100644
index 0000000..ecfd314
--- /dev/null
+++ b/example/cmds/init.js
@@ -0,0 +1,10 @@
+exports.command = 'init [dir]'
+exports.desc = 'Create an empty repo'
+exports.builder = {
+  dir: {
+    default: '.'
+  }
+}
+exports.handler = function (argv) {
+  console.log('init called for dir', argv.dir)
+}
diff --git a/example/cmds/remote.js b/example/cmds/remote.js
new file mode 100644
index 0000000..e0ea5dc
--- /dev/null
+++ b/example/cmds/remote.js
@@ -0,0 +1,6 @@
+exports.command = 'remote <command>'
+exports.description = 'Manage set of tracked repos'
+exports.builder = function (yargs) {
+  return yargs.commandDir('remote_cmds')
+}
+exports.handler = function (argv) {}
diff --git a/example/cmds/remote_cmds/add.js b/example/cmds/remote_cmds/add.js
new file mode 100644
index 0000000..4f3b0e3
--- /dev/null
+++ b/example/cmds/remote_cmds/add.js
@@ -0,0 +1,6 @@
+exports.command = 'add <name> <url>'
+exports.desc = 'Add remote named <name> for repo at url <url>'
+exports.builder = {}
+exports.handler = function (argv) {
+  console.log('adding remote %s at url %s', argv.name, argv.url)
+}
diff --git a/example/cmds/remote_cmds/prune.js b/example/cmds/remote_cmds/prune.js
new file mode 100644
index 0000000..b791da8
--- /dev/null
+++ b/example/cmds/remote_cmds/prune.js
@@ -0,0 +1,6 @@
+exports.command = 'prune <name> [names..]'
+exports.desc = 'Delete tracked branches gone stale for remotes'
+exports.builder = {}
+exports.handler = function (argv) {
+  console.log('pruning remotes %s', [].concat(argv.name).concat(argv.names).join(', '))
+}
diff --git a/example/command_hierarchy.js b/example/command_hierarchy.js
new file mode 100644
index 0000000..850d4ba
--- /dev/null
+++ b/example/command_hierarchy.js
@@ -0,0 +1,6 @@
+#!/usr/bin/env node
+require('yargs')
+  .commandDir('cmds')
+  .demandCommand()
+  .help()
+  .argv
diff --git a/lib/apply-extends.js b/lib/apply-extends.js
index 530b022..1436b65 100644
--- a/lib/apply-extends.js
+++ b/lib/apply-extends.js
@@ -21,7 +21,7 @@ function applyExtends (config, cwd) {
 
   if (config.hasOwnProperty('extends')) {
     if (typeof config.extends !== 'string') return defaultConfig
-    const isPath = /\.json$/.test(config.extends)
+    const isPath = /\.json|\..*rc$/.test(config.extends)
     let pathToDefault = null
     if (!isPath) {
       try {
diff --git a/lib/usage.js b/lib/usage.js
index bd0906a..c323c1b 100644
--- a/lib/usage.js
+++ b/lib/usage.js
@@ -152,16 +152,23 @@ module.exports = function usage (yargs, y18n) {
     const demandedCommands = yargs.getDemandedCommands()
     const groups = yargs.getGroups()
     const options = yargs.getOptions()
-    let keys = Object.keys(
-      Object.keys(descriptions)
-      .concat(Object.keys(demandedOptions))
-      .concat(Object.keys(demandedCommands))
-      .concat(Object.keys(options.default))
-      .reduce((acc, key) => {
-        if (key !== '_') acc[key] = true
-        return acc
-      }, {})
-    )
+
+    let keys = []
+    keys = keys.concat(Object.keys(descriptions))
+    keys = keys.concat(Object.keys(demandedOptions))
+    keys = keys.concat(Object.keys(demandedCommands))
+    keys = keys.concat(Object.keys(options.default))
+    keys = keys.filter(key => {
+      if (options.hiddenOptions.indexOf(key) < 0) {
+        return true
+      } else if (yargs.parsed.argv[options.showHiddenOpt]) {
+        return true
+      }
+    })
+    keys = Object.keys(keys.reduce((acc, key) => {
+      if (key !== '_') acc[key] = true
+      return acc
+    }, {}))
 
     const theWrap = getWrap()
     const ui = require('cliui')({
diff --git a/yargs.js b/yargs.js
index 38f1b2e..d9d34a4 100644
--- a/yargs.js
+++ b/yargs.js
@@ -93,7 +93,8 @@ function Yargs (processArgs, cwd, parentRequire) {
 
     const arrayOptions = [
       'array', 'boolean', 'string', 'skipValidation',
-      'count', 'normalize', 'number'
+      'count', 'normalize', 'number',
+      'hiddenOptions'
     ]
 
     const objectOptions = [
@@ -657,8 +658,9 @@ function Yargs (processArgs, cwd, parentRequire) {
       }
 
       const desc = opt.describe || opt.description || opt.desc
-      if (!opt.hidden) {
-        self.describe(key, desc)
+      self.describe(key, desc)
+      if (opt.hidden) {
+        self.hide(key)
       }
 
       if (opt.requiresArg) {
@@ -824,6 +826,28 @@ function Yargs (processArgs, cwd, parentRequire) {
     return self
   }
 
+  const defaultShowHiddenOpt = 'show-hidden'
+  options.showHiddenOpt = defaultShowHiddenOpt
+  self.addShowHiddenOpt = self.showHidden = function addShowHiddenOpt (opt, msg) {
+    argsert('[string|boolean] [string]', [opt, msg], arguments.length)
+
+    if (arguments.length === 1) {
+      if (opt === false) return self
+    }
+
+    const showHiddenOpt = typeof opt === 'string' ? opt : defaultShowHiddenOpt
+    self.boolean(showHiddenOpt)
+    self.describe(showHiddenOpt, msg || usage.deferY18nLookup('Show hidden options'))
+    options.showHiddenOpt = showHiddenOpt
+    return self
+  }
+
+  self.hide = function hide (key) {
+    argsert('<string|object>', [key], arguments.length)
+    options.hiddenOptions.push(key)
+    return self
+  }
+
   self.showHelpOnFail = function showHelpOnFail (enabled, message) {
     argsert('[boolean|string] [string]', [enabled, message], arguments.length)
     usage.showHelpOnFail(enabled, message)


Test patch:
diff --git a/test/fixtures/extends/.myotherrc b/test/fixtures/extends/.myotherrc
new file mode 100644
index 0000000..8c686ca
--- /dev/null
+++ b/test/fixtures/extends/.myotherrc
@@ -0,0 +1,4 @@
+{
+    "extends": "./config_1.json",
+    "c": 201
+}
\ No newline at end of file
diff --git a/test/fixtures/extends/.myrc b/test/fixtures/extends/.myrc
new file mode 100644
index 0000000..0d45cf9
--- /dev/null
+++ b/test/fixtures/extends/.myrc
@@ -0,0 +1,4 @@
+{
+    "extends": "./.myotherrc",
+    "a": 5
+}
\ No newline at end of file
diff --git a/test/usage.js b/test/usage.js
index e1ce066..611f72e 100644
--- a/test/usage.js
+++ b/test/usage.js
@@ -2887,5 +2887,83 @@ describe('usage tests', () => {
         ''
       ])
     })
+    it('--help should display all options (including hidden ones) with --show-hidden', () => {
+      const r = checkUsage(() => yargs('--help --show-hidden --mama ama')
+          .options({
+            foo: {
+              describe: 'FOO'
+            },
+            bar: {},
+            baz: {
+              describe: 'BAZ',
+              hidden: true
+            }
+          })
+          .argv
+        )
+
+      r.logs[0].split('\n').should.deep.equal([
+        'Options:',
+        '  --help     Show help                                                 [boolean]',
+        '  --version  Show version number                                       [boolean]',
+        '  --foo      FOO',
+        '  --bar',
+        '  --baz      BAZ',
+        ''
+      ])
+    })
+    it('--help should display --custom-show-hidden', () => {
+      const r = checkUsage(() => yargs('--help')
+          .options({
+            foo: {
+              describe: 'FOO'
+            },
+            bar: {},
+            baz: {
+              describe: 'BAZ',
+              hidden: true
+            }
+          })
+          .showHidden('custom-show-hidden')
+          .argv
+        )
+
+      r.logs[0].split('\n').should.deep.equal([
+        'Options:',
+        '  --help                Show help                                      [boolean]',
+        '  --version             Show version number                            [boolean]',
+        '  --foo                 FOO',
+        '  --bar',
+        '  --custom-show-hidden  Show hidden options                            [boolean]',
+        ''
+      ])
+    })
+    it('--help should display all options with --custom-show-hidden', () => {
+      const r = checkUsage(() => yargs('--help --custom-show-hidden')
+          .options({
+            foo: {
+              describe: 'FOO'
+            },
+            bar: {},
+            baz: {
+              describe: 'BAZ',
+              hidden: true
+            }
+          })
+          .showHidden('custom-show-hidden')
+          .argv
+        )
+
+      r.logs[0].split('\n').should.deep.equal([
+        'Options:',
+        '  --help                Show help                                      [boolean]',
+        '  --version             Show version number                            [boolean]',
+        '  --foo                 FOO',
+        '  --bar',
+        '  --baz                 BAZ',
+        '  --custom-show-hidden  Show hidden options                            [boolean]',
+        ''
+      ])
+    })
   })
 })
diff --git a/test/yargs.js b/test/yargs.js
index 94f4018..70438c3 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -129,6 +129,42 @@ describe('yargs dsl tests', () => {
     expect(r.errors).to.deep.equal([])
   })
 
+  describe('hide', () => {
+    it('should add the key to hiddenOptions', () => {
+      const options = yargs('')
+        .hide('someKey')
+        .getOptions()
+      options.should.have.property('hiddenOptions')
+      options.hiddenOptions.should.include('someKey')
+    })
+  })
+
+  describe('showHidden', () => {
+    it('should have a default show-hidden private option pre-configured', () => {
+      const options = yargs('').getOptions()
+      options.should.have.property('showHiddenOpt')
+      options.showHiddenOpt.should.eql('show-hidden')
+    })
+    it('should not have show-hidden as an actual option described by default', () => {
+      const options = yargs('').getOptions()
+      options.key.should.not.have.property('show-hidden')
+    })
+    it('should set show-hidden option', () => {
+      const options = yargs('')
+        .showHidden()
+        .getOptions()
+      options.key.should.have.property('show-hidden')
+    })
+    it('should set custom-show-hidden option', () => {
+      const options = yargs('')
+        .showHidden('custom-show-hidden')
+        .getOptions()
+      options.key.should.have.property('custom-show-hidden')
+      options.should.have.property('showHiddenOpt')
+      options.showHiddenOpt.should.eql('custom-show-hidden')
+    })
+  })
+
   describe('showHelpOnFail', () => {
     it('should display custom failure message, if string is provided as first argument', () => {
       const r = checkOutput(() => yargs([])
@@ -233,6 +269,7 @@ describe('yargs dsl tests', () => {
         config: {},
         configObjects: [],
         envPrefix: 'YARGS', // preserved as global
+        hiddenOptions: [],
         demandedCommands: {},
         demandedOptions: {},
         local: [
@@ -1284,6 +1321,20 @@ describe('yargs dsl tests', () => {
         argv.a.should.equal(2)
         argv.extends.should.equal('batman')
       })
+
+      it('allows files with .*rc extension to be extended', () => {
+        const argv = yargs()
+          .config({
+            extends: './test/fixtures/extends/.myrc',
+            a: 3
+          })
+          .argv
+
+        argv.a.should.equal(3)
+        argv.b.should.equal(22)
+        argv.c.should.equal(201)
+        argv.z.should.equal(15)
+      })
     })
   })