Repository: yargs/yargs (js)

Issue:
.commandDir(dir) does not work when the yargs definition script is `require`-ed from other file
While I'm building a tool with `.commandDir(dir)`, an error occured when I require `lib/cli.js` and execute `bin/tool.js`.

`node lib/cli.js` works properly.

Directory tree:
```
.
├── bin
│   └── tool.js
├── lib
│   ├── cli.js
│   └── commands
│       └── foo.js
└── package.json

```

bin/tool.js:
```
#!/usr/bin/env node
require('../lib/cli.js');
```

lib/cli.js:
```
'use strict';
require('yargs')
.commandDir('commands')
.argv;
```

lib/commands/foo.js:
```
'use strict';
exports.command = 'foo';
exports.describe = 'description';
exports.builder = {};
exports.handler = function (argv) { console.log('foo') };

```

The error message is:
```
fs.js:945
  return binding.readdir(pathModule._makeLong(path), options.encoding);
                 ^

Error: ENOENT: no such file or directory, scandir '/Users/popkirby/Documents/yargs-bug/bin/commands'
    at Error (native)
    at Object.fs.readdirSync (fs.js:945:18)
    at requireDirectory (/Users/popkirby/Documents/yargs-bug/node_modules/require-directory/index.js:59:6)
    at Object.self.addDirectory (/Users/popkirby/Documents/yargs-bug/node_modules/yargs/lib/command.js:72:5)
    at Object.Yargs.self.commandDir (/Users/popkirby/Documents/yargs-bug/node_modules/yargs/yargs.js:211:13)
    at Object.<anonymous> (/Users/popkirby/Documents/yargs-bug/lib/cli.js:4:2)
    at Module._compile (module.js:541:32)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
```

Pull request:
fix(commandDir): make dir relative to caller instead of require.main.filename
Pull in [`get-caller-file`](https://github.com/stefanpenner/get-caller-file) to always interpret the directory given to `.commandDir()` as relative to the caller. This actually helps simplify the logic a good bit since I no longer need to keep track of a command-to-dir mapping between calls.

The `require` is deliberately lazy, so we only load the dependency if `.commandDir()` is called.

There are a few different packages that provide the "get filename of caller" functionality, but I chose `get-caller-file` because it was the only one that already had passing tests for the range of Node we need and includes Windows tests ([`caller`](https://github.com/totherik/caller) seems to be popular but is lacking the fuller range of testing, and [`caller-path` seems to be broken on Node 4+](https://github.com/sindresorhus/caller-path/issues/1)). An added benefit is that `get-caller-file` also seems to be the smallest and only pulls in one dependency.

Fixes #529.

Patch:
diff --git a/lib/command.js b/lib/command.js
index 1dc8af9..da3dff6 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -42,10 +42,8 @@ module.exports = function (yargs, usage, validation) {
     }
   }
 
-  self.addDirectory = function (dir, context, req, mainFilename, opts) {
+  self.addDirectory = function (dir, context, req, callerFile, opts) {
     opts = opts || {}
-    // dir should be relative to the command module
-    dir = path.join(context.dirs[context.commands.join('|')] || '', dir)
     // disable recursion to support nested directories of subcommands
     if (typeof opts.recurse !== 'boolean') opts.recurse = false
     // exclude 'json', 'coffee' from require-directory defaults
@@ -62,14 +60,11 @@ module.exports = function (yargs, usage, validation) {
         if (~context.files.indexOf(joined)) return visited
         // keep track of visited files in context.files
         context.files.push(joined)
-        // map "command path" to the directory path it came from
-        // so that dir can be relative in the API
-        context.dirs[context.commands.concat(parseCommand(visited.command || commandFromFilename(filename)).cmd).join('|')] = dir
         self.addHandler(visited)
       }
       return visited
     }
-    requireDirectory({ require: req, filename: mainFilename }, dir, opts)
+    requireDirectory({ require: req, filename: callerFile }, dir, opts)
   }
 
   // lookup module object from require()d command and derive name
diff --git a/package.json b/package.json
index 9f280c1..623c7e6 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
   "dependencies": {
     "cliui": "^3.2.0",
     "decamelize": "^1.1.1",
+    "get-caller-file": "^1.0.1",
     "lodash.assign": "^4.0.3",
     "os-locale": "^1.4.0",
     "read-pkg-up": "^1.0.1",
@@ -29,7 +30,7 @@
   "devDependencies": {
     "chai": "^3.4.1",
     "chalk": "^1.1.3",
-    "coveralls": "^2.11.9",
+    "coveralls": "^2.11.11",
     "cpr": "^1.0.0",
     "cross-spawn-async": "^2.2.1",
     "es6-promise": "^3.0.2",
diff --git a/yargs.js b/yargs.js
index 494811b..8d90028 100644
--- a/yargs.js
+++ b/yargs.js
@@ -49,7 +49,7 @@ function Yargs (processArgs, cwd, parentRequire) {
 
   // use context object to keep track of resets, subcommand execution, etc
   // submodules should modify and check the state of context as necessary
-  const context = { resets: -1, commands: [], dirs: {}, files: [] }
+  const context = { resets: -1, commands: [], files: [] }
   self.getContext = function () {
     return context
   }
@@ -207,7 +207,7 @@ function Yargs (processArgs, cwd, parentRequire) {
 
   self.commandDir = function (dir, opts) {
     const req = parentRequire || require
-    command.addDirectory(dir, self.getContext(), req, requireMainFilename(req), opts)
+    command.addDirectory(dir, self.getContext(), req, require('get-caller-file')(), opts)
     return self
   }
 
@@ -355,9 +355,10 @@ function Yargs (processArgs, cwd, parentRequire) {
     return self
   }
 
-  var pkg = null
+  var pkgs = {}
   function pkgUp (path) {
-    if (pkg && !path) return pkg
+    var npath = path || '*'
+    if (pkgs[npath]) return pkgs[npath]
     const readPkgUp = require('read-pkg-up')
 
     var obj = {}
@@ -367,8 +368,8 @@ function Yargs (processArgs, cwd, parentRequire) {
       })
     } catch (noop) {}
 
-    if (obj.pkg) pkg = obj.pkg
-    return pkg || {}
+    pkgs[npath] = obj.pkg || {}
+    return pkgs[npath]
   }
 
   self.parse = function (args, shortCircuit) {


Test patch:
diff --git a/test/command.js b/test/command.js
index b28baf4..f7729d6 100644
--- a/test/command.js
+++ b/test/command.js
@@ -344,8 +344,7 @@ describe('Command', function () {
     it('supports relative dirs', function () {
       var r = checkOutput(function () {
         return yargs('--help').help().wrap(null)
-          // assumes cwd is node_modules/mocha/bin
-          .commandDir('../../../test/fixtures/cmddir')
+          .commandDir('fixtures/cmddir')
           .argv
       })
       r.should.have.property('exit').and.be.true
@@ -363,8 +362,7 @@ describe('Command', function () {
     it('supports nested subcommands', function () {
       var r = checkOutput(function () {
         return yargs('dream --help').help().wrap(null)
-          // assumes cwd is node_modules/mocha/bin
-          .commandDir('../../../test/fixtures/cmddir')
+          .commandDir('fixtures/cmddir')
           .argv
       }, [ './command' ])
       r.should.have.property('exit').and.be.true
@@ -386,8 +384,7 @@ describe('Command', function () {
     it('supports a "recurse" boolean option', function () {
       var r = checkOutput(function () {
         return yargs('--help').help().wrap(null)
-          // assumes cwd is node_modules/mocha/bin
-          .commandDir('../../../test/fixtures/cmddir', { recurse: true })
+          .commandDir('fixtures/cmddir', { recurse: true })
           .argv
       })
       r.should.have.property('exit').and.be.true
@@ -411,8 +408,7 @@ describe('Command', function () {
       var filename
       var r = checkOutput(function () {
         return yargs('--help').help().wrap(null)
-          // assumes cwd is node_modules/mocha/bin
-          .commandDir('../../../test/fixtures/cmddir', {
+          .commandDir('fixtures/cmddir', {
             visit: function (_commandObject, _pathToFile, _filename) {
               commandObject = _commandObject
               pathToFile = _pathToFile
@@ -441,8 +437,7 @@ describe('Command', function () {
     it('detects and ignores cyclic dir references', function () {
       var r = checkOutput(function () {
         return yargs('cyclic --help').help().wrap(null)
-          // assumes cwd is node_modules/mocha/bin
-          .commandDir('../../../test/fixtures/cmddir_cyclic')
+          .commandDir('fixtures/cmddir_cyclic')
           .argv
       }, [ './command' ])
       r.should.have.property('exit').and.be.true
@@ -459,8 +454,7 @@ describe('Command', function () {
     it('derives \'command\' string from filename when not exported', function () {
       var r = checkOutput(function () {
         return yargs('--help').help().wrap(null)
-          // assumes cwd is node_modules/mocha/bin
-          .commandDir('../../../test/fixtures/cmddir_noname')
+          .commandDir('fixtures/cmddir_noname')
           .argv
       })
       r.should.have.property('exit').and.be.true
diff --git a/test/fixtures/package.json b/test/fixtures/package.json
index 808a84b..20be857 100644
--- a/test/fixtures/package.json
+++ b/test/fixtures/package.json
@@ -1,5 +1,8 @@
 {
   "version": "9.9.9",
+  "repository": {
+    "type": "svn"
+  },
   "yargs": {
     "dot-notation": false
   }
diff --git a/test/yargs.js b/test/yargs.js
index 382a67d..aacf7b8 100644
--- a/test/yargs.js
+++ b/test/yargs.js
@@ -934,7 +934,7 @@ describe('yargs dsl tests', function () {
 
       argv.b.should.equal(99)
       argv.foo.should.equal('a')
-      expect(argv.git).to.equal(undefined)
+      expect(argv.type).to.equal(undefined)
     })
 
     it('allows an alternative cwd to be specified', function () {
@@ -946,6 +946,22 @@ describe('yargs dsl tests', function () {
       argv.dotNotation.should.equal(false)
     })
 
+    it('doesn\'t mess up other pkg lookups when cwd is specified', function () {
+      var r = checkOutput(function () {
+        return yargs('--version')
+          .pkgConf('repository', './test/fixtures')
+          .version()
+          .argv
+      })
+      const options = yargs.getOptions()
+
+      // assert pkgConf lookup (test/fixtures/package.json)
+      options.configObjects.should.deep.equal([{ type: 'svn' }])
+      // assert parseArgs and guessVersion lookup (package.json)
+      expect(options.configuration['dot-notation']).to.be.undefined
+      r.logs[0].should.not.equal('9.9.9') // breaks when yargs gets to this version
+    })
+
     // see https://github.com/yargs/yargs/issues/485
     it('handles an invalid package.json', function () {
       var argv = yargs('--foo a')