Skip to content
This repository has been archived by the owner on Jul 6, 2019. It is now read-only.

Commit

Permalink
fix(prefix): Handle node_modules without package.json (#128)
Browse files Browse the repository at this point in the history
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: #104
Fixes: babel/babel#4066 (comment)
  • Loading branch information
jridgewell authored and zkat committed Nov 6, 2017
1 parent d2f035e commit f64ae43
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 32 deletions.
28 changes: 14 additions & 14 deletions get-prefix.js
Expand Up @@ -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')),
Expand All @@ -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))
}
})
}
Expand Down
6 changes: 1 addition & 5 deletions index.js
Expand Up @@ -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')
})
}

Expand Down
15 changes: 2 additions & 13 deletions test/get-prefix.js
Expand Up @@ -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')
})
})

Expand All @@ -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: {
Expand Down

0 comments on commit f64ae43

Please sign in to comment.