diff --git a/lib/install/diff-trees.js b/lib/install/diff-trees.js index 346846fdc0ffe..7ff2cb89df539 100644 --- a/lib/install/diff-trees.js +++ b/lib/install/diff-trees.js @@ -4,6 +4,7 @@ var validate = require('aproba') var npa = require('npm-package-arg') var flattenTree = require('./flatten-tree.js') var isOnlyDev = require('./is-only-dev.js') +var isDev = require('./is-dev.js') var log = require('npmlog') var path = require('path') var ssri = require('ssri') @@ -250,10 +251,11 @@ function filterActions (differences) { log.silly('diff-trees', 'filtering actions:', 'includeDev', includeDev, 'includeProd', includeProd, 'includeOpt', includeOpt) return differences.filter((diff) => { const pkg = diff[1] + const pkgIsDev = isDev(pkg) const pkgIsOnlyDev = isOnlyDev(pkg) const pkgIsOnlyOpt = isOnlyOptional(pkg) if (!includeProd && pkgIsOnlyDev) return true - if (includeDev && pkgIsOnlyDev) return true + if (includeDev && pkgIsDev) return true // if we used isOnlyDev here we'd exclude mixed prod/dev packages if (includeProd && !pkgIsOnlyDev && (includeOpt || !pkgIsOnlyOpt)) return true return false }) diff --git a/lib/install/is-dev.js b/lib/install/is-dev.js new file mode 100644 index 0000000000000..e2197d373f3e5 --- /dev/null +++ b/lib/install/is-dev.js @@ -0,0 +1,18 @@ +'use strict' +module.exports = isDev + +// Returns true if the module `node` is a dev dependency itself or a +// dependency of some dev dependency higher in the hierarchy. +function isDev (node, seen) { + if (!seen) seen = new Set() + if (seen.has(node.package.name)) return true + + seen.add(node.package.name) + if (!node.requiredBy || node.requiredBy.length === 0 || node.package._development || node.isTop) { + return !!node.package._development + } + + return node.requiredBy.some((parent) => { + return isDev(parent, seen) + }) +} diff --git a/test/tap/install-cli-only-development.js b/test/tap/install-cli-only-development.js index ff9d05f547f26..21ce2665c0009 100644 --- a/test/tap/install-cli-only-development.js +++ b/test/tap/install-cli-only-development.js @@ -28,12 +28,24 @@ var json = { var dependency = { name: 'dependency', description: 'fixture', - version: '0.0.0' + version: '0.0.0', + dependencies: { + 'sub-dependency': 'file:../sub-dependency' + } } var devDependency = { name: 'dev-dependency', description: 'fixture', + version: '0.0.0', + dependencies: { + 'sub-dependency': 'file:../sub-dependency' + } +} + +var subDependency = { + name: 'sub-dependency', + description: 'fixture', version: '0.0.0' } @@ -57,6 +69,12 @@ test('\'npm install --only=development\' should only install devDependencies', f existsSync(path.resolve(pkg, 'node_modules/dependency/package.json')), 'dependency was NOT installed' ) + t.ok( + JSON.parse(fs.readFileSync( + path.resolve(pkg, 'node_modules/sub-dependency/package.json'), 'utf8') + ), + 'subDependency was installed' + ) t.end() }) }) @@ -101,6 +119,12 @@ function setup () { JSON.stringify(devDependency, null, 2) ) + mkdirp.sync(path.join(pkg, 'sub-dependency')) + fs.writeFileSync( + path.join(pkg, 'sub-dependency', 'package.json'), + JSON.stringify(subDependency, null, 2) + ) + mkdirp.sync(path.join(pkg, 'node_modules')) fs.writeFileSync( path.join(pkg, 'package.json'),