diff options
Diffstat (limited to 'deps/npm/lib/shrinkwrap.js')
-rw-r--r-- | deps/npm/lib/shrinkwrap.js | 92 |
1 files changed, 73 insertions, 19 deletions
diff --git a/deps/npm/lib/shrinkwrap.js b/deps/npm/lib/shrinkwrap.js index c6a41842fc..02481a235e 100644 --- a/deps/npm/lib/shrinkwrap.js +++ b/deps/npm/lib/shrinkwrap.js @@ -15,11 +15,13 @@ var npm = require('./npm.js') var recalculateMetadata = require('./install/deps.js').recalculateMetadata var validatePeerDeps = require('./install/deps.js').validatePeerDeps var isExtraneous = require('./install/is-extraneous.js') -var isOnlyDev = require('./install/is-dev.js').isOnlyDev var packageId = require('./utils/package-id.js') var moduleName = require('./utils/module-name.js') var output = require('./utils/output.js') var lifecycle = require('./utils/lifecycle.js') +var isDevDep = require('./install/is-dev-dep.js') +var isProdDep = require('./install/is-prod-dep.js') +var isOptDep = require('./install/is-opt-dep.js') shrinkwrap.usage = 'npm shrinkwrap' @@ -35,25 +37,31 @@ function shrinkwrap (args, silent, cb) { var dir = path.resolve(npm.dir, '..') var packagePath = path.join(npm.localPrefix, 'package.json') - npm.config.set('production', true) - - readPackageJson(packagePath, iferr(cb, function (data) { - lifecycle(data, 'preshrinkwrap', function () { - readPackageTree(dir, andRecalculateMetadata(iferr(cb, function (tree) { - var pkginfo = treeToShrinkwrap(tree, !!npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('also'))) - - chain([ - [lifecycle, tree.package, 'shrinkwrap'], - [shrinkwrap_, pkginfo, silent], - [lifecycle, tree.package, 'postshrinkwrap'] - ], iferr(cb, function (data) { - cb(null, data[0]) - })) - }))) - }) + var dev = !!npm.config.get('dev') || /^dev(elopment)?$/.test(npm.config.get('also')) + + readPackageJson(packagePath, iferr(cb, function (pkg) { + createShrinkwrap(dir, pkg, dev, silent, cb) })) } +module.exports.createShrinkwrap = createShrinkwrap + +function createShrinkwrap (dir, pkg, dev, silent, cb) { + lifecycle(pkg, 'preshrinkwrap', function () { + readPackageTree(dir, andRecalculateMetadata(iferr(cb, function (tree) { + var pkginfo = treeToShrinkwrap(tree, dev) + + chain([ + [lifecycle, tree.package, 'shrinkwrap'], + [shrinkwrap_, pkginfo, silent], + [lifecycle, tree.package, 'postshrinkwrap'] + ], iferr(cb, function (data) { + cb(null, data[0]) + })) + }))) + }) +} + function andRecalculateMetadata (next) { validate('F', arguments) return function (er, tree) { @@ -92,14 +100,17 @@ function shrinkwrapDeps (dev, problems, deps, tree, seen) { } }) tree.children.sort(function (aa, bb) { return moduleName(aa).localeCompare(moduleName(bb)) }).forEach(function (child) { - if (!dev && isOnlyDev(child)) { - log.warn('shrinkwrap', 'Excluding devDependency: %s', packageId(child), child.parent.package.dependencies) + var childIsOnlyDev = isOnlyDev(child) + if (!dev && childIsOnlyDev) { + log.warn('shrinkwrap', 'Excluding devDependency: %s', child.location) return } var pkginfo = deps[moduleName(child)] = {} pkginfo.version = child.package.version pkginfo.from = child.package._from pkginfo.resolved = child.package._resolved + if (dev && childIsOnlyDev) pkginfo.dev = true + if (isOptional(child)) pkginfo.optional = true if (isExtraneous(child)) { problems.push('extraneous: ' + child.package._id + ' ' + child.path) } @@ -143,3 +154,46 @@ function save (pkginfo, silent, cb) { cb(null, pkginfo) }) } + +// Returns true if the module `node` is only required direcctly as a dev +// dependency of the top level or transitively _from_ top level dev +// dependencies. +// Dual mode modules (that are both dev AND prod) should return false. +function isOnlyDev (node, seen) { + if (!seen) seen = {} + return node.requiredBy.length && node.requiredBy.every(andIsOnlyDev(moduleName(node), seen)) +} + +// There is a known limitation with this implementation: If a dependency is +// ONLY required by cycles that are detached from the top level then it will +// ultimately return ture. +// +// This is ok though: We don't allow shrinkwraps with extraneous deps and +// these situation is caught by the extraneous checker before we get here. +function andIsOnlyDev (name, seen) { + return function (req) { + var isDev = isDevDep(req, name) + var isProd = isProdDep(req, name) + if (req.isTop) { + return isDev && !isProd + } else { + if (seen[req.path]) return true + seen[req.path] = true + return isOnlyDev(req, seen) + } + } +} + +function isOptional (node, seen) { + if (!seen) seen = {} + // If a node is not required by anything, then we've reached + // the top level package. + if (seen[node.path] || node.requiredBy.length === 0) { + return false + } + seen[node.path] = true + + return node.requiredBy.every(function (req) { + return isOptDep(req, node.package.name) || isOptional(req, seen) + }) +} |