From f8a7a1c1b2a19703b837a73592d7a37d944a2568 Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Thu, 19 Oct 2017 19:10:38 -0400 Subject: [PATCH] Handle node_modules without package.json This fixes running `npx` inside temporary projects (ones that have a `node_modules` but don't have a `package.json`). `getPrefix()` has useful cases: 1. Finds a path with a `package.json`, returns it. 2. Finds a path with a `node_modules`, returns it. 3. Finds nothing, returns the original path. Cases 1 and 2 return a path that's useful to `npx`, but case 3 doesn't. But, `localBinPath()` confused case 2 and 3 (by stating for a `package.json`), making `npx` only work with case 1. That's no good. This makes cases 1 and 2 distinct from case 3 (it just returns `false` now). And `localBinPath()` no longer has to do any stating to differentiate between paths and `false`, so it's happy. And now `npx` can run without a local `package.json`. Yay! Fixes https://github.com/zkat/npx/issues/104. Fixes https://github.com/babel/babel/issues/4066#issuecomment-336705199. --- get-prefix.js | 28 ++++++++++++++-------------- index.js | 6 +----- test/get-prefix.js | 15 ++------------- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/get-prefix.js b/get-prefix.js index 71bedff..3bb6780 100644 --- a/get-prefix.js +++ b/get-prefix.js @@ -6,20 +6,21 @@ const path = require('path') const statAsync = promisify(require('fs').stat) module.exports = getPrefix -function getPrefix (current, root) { - if (!root) { - const original = root = path.resolve(current) - while (path.basename(root) === 'node_modules') { - root = path.dirname(root) - } - if (original !== root) { - return Promise.resolve(root) - } else { - return getPrefix(root, root) - } +function getPrefix (root) { + const original = root = path.resolve(root) + while (path.basename(root) === 'node_modules') { + root = path.dirname(root) } - if (isRootPath(current, process.platform)) { + if (original !== root) { return Promise.resolve(root) + } else { + return Promise.resolve(getPrefixFromTree(root)) + } +} + +function getPrefixFromTree (current) { + if (isRootPath(current, process.platform)) { + return false } else { return Promise.all([ fileExists(path.join(current, 'package.json')), @@ -30,8 +31,7 @@ function getPrefix (current, root) { if (hasPkg || hasModules) { return current } else { - const parent = path.dirname(current) - return getPrefix(parent, root) + return getPrefixFromTree(path.dirname(current)) } }) } diff --git a/index.js b/index.js index 3033e9c..a1faee1 100644 --- a/index.js +++ b/index.js @@ -116,11 +116,7 @@ function npx (argv) { module.exports._localBinPath = localBinPath function localBinPath (cwd) { return require('./get-prefix.js')(cwd).then(prefix => { - const pkgjson = path.join(prefix, 'package.json') - return promisify(fs.stat)(pkgjson).then( - () => path.join(prefix, 'node_modules', '.bin'), - err => { if (err.code !== 'ENOENT') throw err } - ) + return prefix && path.join(prefix, 'node_modules', '.bin') }) } diff --git a/test/get-prefix.js b/test/get-prefix.js index df1dcd1..6fd71a2 100644 --- a/test/get-prefix.js +++ b/test/get-prefix.js @@ -40,11 +40,11 @@ test('detects if currently in an npm package using node_modules', t => { }) }) -test('returns the same path if no package was found in parent dirs', t => { +test('returns false if no package was found in parent dirs', t => { // Hopefully folks' tmpdir isn't inside an npm package ;) const tmp = os.tmpdir() return getPrefix(tmp).then(prefix => { - t.equal(prefix, tmp, 'returned the same path') + t.equal(prefix, false, 'returned the false') }) }) @@ -70,17 +70,6 @@ test('doesn\'t go too far while navigating up', t => { }) }) -test('returns root if we get there', t => { - let root = '/' - if (process.platform === 'win32') { - const currentDrive = process.cwd().match(/^([a-z]+):/i)[1] - root = `${currentDrive}:\\` - } - return getPrefix(root).then(prefix => { - t.equal(prefix, root, 'used the same root') - }) -}) - test('fileExists unit', t => { const fileExists = requireInject('../get-prefix.js', { fs: {