diff options
author | Kat Marchán <kzm@sykosomatic.org> | 2017-12-07 14:05:23 -0800 |
---|---|---|
committer | Myles Borins <mylesborins@google.com> | 2018-01-19 11:32:08 -0500 |
commit | d3b1c971bcf0177b17c649c3aeca1a94cbc3fff5 (patch) | |
tree | 321928c015be00cdbe11715297d2d2fc45802263 /deps/npm/lib/install | |
parent | bfe41fe88e7421f441067a79fb7512cf5935a2bb (diff) | |
download | android-node-v8-d3b1c971bcf0177b17c649c3aeca1a94cbc3fff5.tar.gz android-node-v8-d3b1c971bcf0177b17c649c3aeca1a94cbc3fff5.tar.bz2 android-node-v8-d3b1c971bcf0177b17c649c3aeca1a94cbc3fff5.zip |
deps: upgrade npm to 5.6.0
PR-URL: https://github.com/nodejs/node/pull/17777
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gibson Fahnestock <gibfahn@gmail.com>
Diffstat (limited to 'deps/npm/lib/install')
-rw-r--r-- | deps/npm/lib/install/action/build.js | 2 | ||||
-rw-r--r-- | deps/npm/lib/install/action/extract.js | 3 | ||||
-rw-r--r-- | deps/npm/lib/install/action/fetch.js | 6 | ||||
-rw-r--r-- | deps/npm/lib/install/action/finalize.js | 21 | ||||
-rw-r--r-- | deps/npm/lib/install/action/refresh-package-json.js | 2 | ||||
-rw-r--r-- | deps/npm/lib/install/actions.js | 13 | ||||
-rw-r--r-- | deps/npm/lib/install/deps.js | 63 | ||||
-rw-r--r-- | deps/npm/lib/install/diff-trees.js | 152 | ||||
-rw-r--r-- | deps/npm/lib/install/inflate-shrinkwrap.js | 7 | ||||
-rw-r--r-- | deps/npm/lib/install/realize-shrinkwrap-specifier.js | 3 | ||||
-rw-r--r-- | deps/npm/lib/install/save.js | 2 |
11 files changed, 175 insertions, 99 deletions
diff --git a/deps/npm/lib/install/action/build.js b/deps/npm/lib/install/action/build.js index f59b852e84..be2c141f0d 100644 --- a/deps/npm/lib/install/action/build.js +++ b/deps/npm/lib/install/action/build.js @@ -7,7 +7,7 @@ var packageId = require('../../utils/package-id.js') module.exports = function (staging, pkg, log, next) { log.silly('build', packageId(pkg)) chain([ - [build.linkStuff, pkg.package, pkg.path, npm.config.get('global'), true], + [build.linkStuff, pkg.package, pkg.path, npm.config.get('global')], [build.writeBuiltinConf, pkg.package, pkg.path] ], next) } diff --git a/deps/npm/lib/install/action/extract.js b/deps/npm/lib/install/action/extract.js index 8e80d4adda..6b827f36ea 100644 --- a/deps/npm/lib/install/action/extract.js +++ b/deps/npm/lib/install/action/extract.js @@ -16,6 +16,7 @@ let pacoteOpts const path = require('path') const localWorker = require('./extract-worker.js') const workerFarm = require('worker-farm') +const isRegistry = require('../../utils/is-registry.js') const WORKER_PATH = require.resolve('./extract-worker.js') let workers @@ -72,7 +73,7 @@ function extract (staging, pkg, log) { let msg = args const spec = typeof args[0] === 'string' ? npa(args[0]) : args[0] args[0] = spec.raw - if (ENABLE_WORKERS && (spec.registry || spec.type === 'remote')) { + if (ENABLE_WORKERS && (isRegistry(spec) || spec.type === 'remote')) { // We can't serialize these options opts.loglevel = opts.log.level opts.log = null diff --git a/deps/npm/lib/install/action/fetch.js b/deps/npm/lib/install/action/fetch.js index 474e00b05c..a4d760fe82 100644 --- a/deps/npm/lib/install/action/fetch.js +++ b/deps/npm/lib/install/action/fetch.js @@ -1,5 +1,8 @@ 'use strict' +const BB = require('bluebird') + +const finished = BB.promisify(require('mississippi').finished) const packageId = require('../../utils/package-id.js') const pacote = require('pacote') const pacoteOpts = require('../../config/pacote') @@ -8,5 +11,6 @@ module.exports = fetch function fetch (staging, pkg, log, next) { log.silly('fetch', packageId(pkg)) const opts = pacoteOpts({integrity: pkg.package._integrity}) - pacote.prefetch(pkg.package._requested, opts).then(() => next(), next) + return finished(pacote.tarball.stream(pkg.package._requested, opts)) + .then(() => next(), next) } diff --git a/deps/npm/lib/install/action/finalize.js b/deps/npm/lib/install/action/finalize.js index a50ec8a6bd..e46f1b9d83 100644 --- a/deps/npm/lib/install/action/finalize.js +++ b/deps/npm/lib/install/action/finalize.js @@ -7,11 +7,13 @@ const mkdirp = Bluebird.promisify(require('mkdirp')) const lstat = Bluebird.promisify(fs.lstat) const readdir = Bluebird.promisify(fs.readdir) const symlink = Bluebird.promisify(fs.symlink) -const gentlyRm = require('../../utils/gently-rm') +const gentlyRm = Bluebird.promisify(require('../../utils/gently-rm')) const moduleStagingPath = require('../module-staging-path.js') const move = require('move-concurrently') const moveOpts = {fs: fs, Promise: Bluebird, maxConcurrency: 4} const getRequested = require('../get-requested.js') +const log = require('npmlog') +const packageId = require('../../utils/package-id.js') module.exports = function (staging, pkg, log) { log.silly('finalize', pkg.realpath) @@ -88,8 +90,17 @@ module.exports = function (staging, pkg, log) { } } -module.exports.rollback = function (top, staging, pkg, next) { - const requested = pkg.package._requested || getRequested(pkg) - if (requested && requested.type === 'directory') return next() - gentlyRm(pkg.path, false, top, next) +module.exports.rollback = function (top, staging, pkg) { + return Bluebird.try(() => { + const requested = pkg.package._requested || getRequested(pkg) + if (requested && requested.type === 'directory') return Promise.resolve() + // strictly speaking rolling back a finalize should ONLY remove module that + // was being finalized, not any of the things under it. But currently + // those modules are guaranteed to be useless so we may as well remove them too. + // When/if we separate `commit` step and can rollback to previous versions + // of upgraded modules then we'll need to revisit this… + return gentlyRm(pkg.path, false, top).catch((err) => { + log.warn('rollback', `Rolling back ${packageId(pkg)} failed (this is probably harmless): ${err.message ? err.message : err}`) + }) + }) } diff --git a/deps/npm/lib/install/action/refresh-package-json.js b/deps/npm/lib/install/action/refresh-package-json.js index 42f8012100..32e6444444 100644 --- a/deps/npm/lib/install/action/refresh-package-json.js +++ b/deps/npm/lib/install/action/refresh-package-json.js @@ -14,7 +14,7 @@ module.exports = function (staging, pkg, log) { return readJson(path.join(pkg.path, 'package.json'), false).then((metadata) => { Object.keys(pkg.package).forEach(function (key) { - if (key !== 'dependencies' && !isEmpty(pkg.package[key])) { + if (key !== 'version' && key !== 'dependencies' && !isEmpty(pkg.package[key])) { metadata[key] = pkg.package[key] } }) diff --git a/deps/npm/lib/install/actions.js b/deps/npm/lib/install/actions.js index 9f0dcfa5dc..9608a943a5 100644 --- a/deps/npm/lib/install/actions.js +++ b/deps/npm/lib/install/actions.js @@ -83,10 +83,8 @@ function markAsFailed (pkg) { if (pkg.failed) return pkg.failed = true pkg.requires.forEach((req) => { - req.requiredBy = req.requiredBy.filter((reqReqBy) => { - return reqReqBy !== pkg - }) - if (req.requiredBy.length === 0 && !req.userRequired) { + var requiredBy = req.requiredBy.filter((reqReqBy) => !reqReqBy.failed) + if (requiredBy.length === 0 && !req.userRequired) { markAsFailed(req) } }) @@ -94,12 +92,7 @@ function markAsFailed (pkg) { function handleOptionalDepErrors (pkg, err) { markAsFailed(pkg) - var anyFatal = pkg.userRequired || pkg.isTop - for (var ii = 0; ii < pkg.requiredBy.length; ++ii) { - var parent = pkg.requiredBy[ii] - var isFatal = failedDependency(parent, pkg) - if (isFatal) anyFatal = true - } + var anyFatal = failedDependency(pkg) if (anyFatal) { throw err } else { diff --git a/deps/npm/lib/install/deps.js b/deps/npm/lib/install/deps.js index c93907a416..93c4adffd7 100644 --- a/deps/npm/lib/install/deps.js +++ b/deps/npm/lib/install/deps.js @@ -32,6 +32,7 @@ var reportOptionalFailure = require('./report-optional-failure.js') var getSaveType = require('./save.js').getSaveType var unixFormatPath = require('../utils/unix-format-path.js') var isExtraneous = require('./is-extraneous.js') +var isRegistry = require('../utils/is-registry.js') // The export functions in this module mutate a dependency tree, adding // items to them. @@ -121,7 +122,7 @@ function computeMetadata (tree, seen) { } } - tree.children.filter((child) => !child.removed && !child.failed).forEach((child) => computeMetadata(child, seen)) + tree.children.filter((child) => !child.removed).forEach((child) => computeMetadata(child, seen)) return tree } @@ -276,7 +277,7 @@ function isNotEmpty (value) { return value != null && value !== '' } -module.exports.computeVersionSpec = computeVersionSpec +exports.computeVersionSpec = computeVersionSpec function computeVersionSpec (tree, child) { validate('OO', arguments) var requested @@ -288,7 +289,7 @@ function computeVersionSpec (tree, child) { } else { requested = npa.resolve(child.package.name, child.package.version) } - if (requested.registry) { + if (isRegistry(requested)) { var version = child.package.version var rangeDescriptor = '' if (semver.valid(version, true) && @@ -308,10 +309,6 @@ function moduleNameMatches (name) { return function (child) { return moduleName(child) === name } } -function noModuleNameMatches (name) { - return function (child) { return moduleName(child) !== name } -} - // while this implementation does not require async calling, doing so // gives this a consistent interface with loadDeps et al exports.removeDeps = function (args, tree, saveToDependencies, next) { @@ -377,19 +374,12 @@ function isDepOptional (tree, name, pkg) { return false } -var failedDependency = exports.failedDependency = function (tree, name_pkg) { - var name - var pkg = {} - if (typeof name_pkg === 'string') { - name = name_pkg - } else { - pkg = name_pkg - name = moduleName(pkg) - } - tree.children = tree.children.filter(noModuleNameMatches(name)) - - if (isDepOptional(tree, name, pkg)) { - return false +exports.failedDependency = failedDependency +function failedDependency (tree, name, pkg) { + if (name) { + if (isDepOptional(tree, name, pkg || {})) { + return false + } } tree.failed = true @@ -398,17 +388,16 @@ var failedDependency = exports.failedDependency = function (tree, name_pkg) { if (tree.userRequired) return true - removeObsoleteDep(tree) - if (!tree.requiredBy) return false + let anyFailed = false for (var ii = 0; ii < tree.requiredBy.length; ++ii) { var requireParent = tree.requiredBy[ii] - if (failedDependency(requireParent, tree.package)) { - return true + if (failedDependency(requireParent, moduleName(tree), tree)) { + anyFailed = true } } - return false + return anyFailed } function andHandleOptionalErrors (log, tree, name, done) { @@ -418,7 +407,6 @@ function andHandleOptionalErrors (log, tree, name, done) { if (!er) return done(er, child, childLog) var isFatal = failedDependency(tree, name) if (er && !isFatal) { - tree.children = tree.children.filter(noModuleNameMatches(name)) reportOptionalFailure(tree, name, er) return done() } else { @@ -443,7 +431,7 @@ function prefetchDeps (tree, deps, log, next) { var allDependencies = Object.keys(deps).map((dep) => { return npa.resolve(dep, deps[dep]) }).filter((dep) => { - return dep.registry && + return isRegistry(dep) && !seen.has(dep.toString()) && !findRequirement(tree, dep.name, dep) }) @@ -601,8 +589,9 @@ function resolveWithNewModule (pkg, tree, log, next) { validate('OOOF', arguments) log.silly('resolveWithNewModule', packageId(pkg), 'checking installable status') - return isInstallable(pkg, iferr(next, function () { - addBundled(pkg, iferr(next, function () { + return isInstallable(pkg, (err) => { + let installable = !err + addBundled(pkg, (bundleErr) => { var parent = earliestInstallable(tree, tree, pkg) || tree var isLink = pkg._requested.type === 'directory' var child = createChild({ @@ -613,8 +602,9 @@ function resolveWithNewModule (pkg, tree, log, next) { children: pkg._bundled || [], isLink: isLink, isInLink: parent.isLink, - knownInstallable: true + knownInstallable: installable }) + if (!installable || bundleErr) child.failed = true delete pkg._bundled var hasBundled = child.children.length @@ -630,13 +620,14 @@ function resolveWithNewModule (pkg, tree, log, next) { } if (pkg._shrinkwrap && pkg._shrinkwrap.dependencies) { - return inflateShrinkwrap(child, pkg._shrinkwrap, function (er) { - next(er, child, log) + return inflateShrinkwrap(child, pkg._shrinkwrap, (swErr) => { + if (swErr) child.failed = true + next(err || bundleErr || swErr, child, log) }) } - next(null, child, log) - })) - })) + next(err || bundleErr, child, log) + }) + }) } var validatePeerDeps = exports.validatePeerDeps = function (tree, onInvalid) { @@ -669,7 +660,7 @@ var findRequirement = exports.findRequirement = function (tree, name, requested, validate('OSO', [tree, name, requested]) if (!requestor) requestor = tree var nameMatch = function (child) { - return moduleName(child) === name && child.parent && !child.removed && !child.failed + return moduleName(child) === name && child.parent && !child.removed } var versionMatch = function (child) { return doesChildVersionMatch(child, requested, requestor) diff --git a/deps/npm/lib/install/diff-trees.js b/deps/npm/lib/install/diff-trees.js index ac4f421a50..4316f351cc 100644 --- a/deps/npm/lib/install/diff-trees.js +++ b/deps/npm/lib/install/diff-trees.js @@ -6,41 +6,98 @@ var flattenTree = require('./flatten-tree.js') var isOnlyDev = require('./is-only-dev.js') var log = require('npmlog') var path = require('path') +var ssri = require('ssri') +var moduleName = require('../utils/module-name.js') -function nonRegistrySource (pkg) { - validate('O', arguments) - var requested = pkg._requested || (pkg._from && npa(pkg._from)) - if (!requested) return false +// we don't use get-requested because we're operating on files on disk, and +// we don't want to extropolate from what _should_ be there. +function pkgRequested (pkg) { + return pkg._requested || (pkg._resolved && npa(pkg._resolved)) || (pkg._from && npa(pkg._from)) +} - if (requested.type === 'hosted') return true - if (requested.type === 'file' || requested.type === 'directory') return true +function nonRegistrySource (requested) { + if (fromGit(requested)) return true + if (fromLocal(requested)) return true + if (fromRemote(requested)) return true return false } -function pkgAreEquiv (aa, bb) { - var aaSha = (aa.dist && aa.dist.integrity) || aa._integrity - var bbSha = (bb.dist && bb.dist.integrity) || bb._integrity - if (aaSha === bbSha) return true - if (aaSha || bbSha) return false - if (nonRegistrySource(aa) || nonRegistrySource(bb)) return false - if (aa.version === bb.version) return true +function fromRemote (requested) { + if (requested.type === 'remote') return true +} + +function fromLocal (requested) { + // local is an npm@3 type that meant "file" + if (requested.type === 'file' || requested.type === 'directory' || requested.type === 'local') return true + return false +} + +function fromGit (requested) { + if (requested.type === 'hosted' || requested.type === 'git') return true return false } -function getUniqueId (pkg) { - var versionspec = pkg._integrity +function pkgIntegrity (pkg) { + try { + // dist is provided by the registry + var sri = (pkg.dist && pkg.dist.integrity) || + // _integrity is provided by pacote + pkg._integrity || + // _shasum is legacy + (pkg._shasum && ssri.fromHex(pkg._shasum, 'sha1').toString()) + if (!sri) return + var integrity = ssri.parse(sri) + if (Object.keys(integrity).length === 0) return + return integrity + } catch (ex) { + return + } +} - if (!versionspec && nonRegistrySource(pkg)) { - if (pkg._requested) { - versionspec = pkg._requested.fetchSpec - } else if (pkg._from) { - versionspec = npa(pkg._from).fetchSpec +function sriMatch (aa, bb) { + if (!aa || !bb) return false + for (let algo of Object.keys(aa)) { + if (!bb[algo]) continue + for (let aaHash of aa[algo]) { + for (let bbHash of bb[algo]) { + return aaHash.digest === bbHash.digest + } } } - if (!versionspec) { - versionspec = pkg.version + return false +} + +function pkgAreEquiv (aa, bb) { + // coming in we know they share a path… + + // if they share package metadata _identity_, they're the same thing + if (aa.package === bb.package) return true + // if they share integrity information, they're the same thing + var aaIntegrity = pkgIntegrity(aa.package) + var bbIntegrity = pkgIntegrity(bb.package) + if (aaIntegrity || bbIntegrity) return sriMatch(aaIntegrity, bbIntegrity) + + // if they're links and they share the same target, they're the same thing + if (aa.isLink && bb.isLink) return aa.realpath === bb.realpath + + // if we can't determine both their sources then we have no way to know + // if they're the same thing, so we have to assume they aren't + var aaReq = pkgRequested(aa.package) + var bbReq = pkgRequested(bb.package) + if (!aaReq || !bbReq) return false + + if (fromGit(aaReq) && fromGit(bbReq)) { + // if both are git and share a _resolved specifier (one with the + // comittish replaced by a commit hash) then they're the same + return aa.package._resolved && bb.package._resolved && + aa.package._resolved === bb.package._resolved } - return pkg.name + '@' + versionspec + + // we have to give up trying to find matches for non-registry sources at this point… + if (nonRegistrySource(aaReq) || nonRegistrySource(bbReq)) return false + + // finally, if they ARE a registry source then version matching counts + return aa.package.version === bb.package.version } function pushAll (aa, bb) { @@ -118,41 +175,56 @@ var diffTrees = module.exports._diffTrees = function (oldTree, newTree) { var flatOldTree = flattenTree(oldTree) var flatNewTree = flattenTree(newTree) var toRemove = {} - var toRemoveByUniqueId = {} - // find differences + var toRemoveByName = {} + + // Build our tentative remove list. We don't add remove actions yet + // because we might resuse them as part of a move. Object.keys(flatOldTree).forEach(function (flatname) { + if (flatname === '/') return if (flatNewTree[flatname]) return var pkg = flatOldTree[flatname] if (pkg.isInLink && /^[.][.][/\\]/.test(path.relative(newTree.realpath, pkg.realpath))) return toRemove[flatname] = pkg - var pkgunique = getUniqueId(pkg.package) - if (!toRemoveByUniqueId[pkgunique]) toRemoveByUniqueId[pkgunique] = [] - toRemoveByUniqueId[pkgunique].push(flatname) + var name = moduleName(pkg) + if (!toRemoveByName[name]) toRemoveByName[name] = [] + toRemoveByName[name].push({flatname: flatname, pkg: pkg}) }) - Object.keys(flatNewTree).forEach(function (path) { - var pkg = flatNewTree[path] - pkg.oldPkg = flatOldTree[path] - if (pkg.oldPkg) { - if (!pkg.userRequired && pkgAreEquiv(pkg.oldPkg.package, pkg.package)) return + + // generate our add/update/move actions + Object.keys(flatNewTree).forEach(function (flatname) { + if (flatname === '/') return + var pkg = flatNewTree[flatname] + var oldPkg = pkg.oldPkg = flatOldTree[flatname] + if (oldPkg) { + // if the versions are equivalent then we don't need to update… unless + // the user explicitly asked us to. + if (!pkg.userRequired && pkgAreEquiv(oldPkg, pkg)) return setAction(differences, 'update', pkg) } else { - var vername = getUniqueId(pkg.package) - var removing = toRemoveByUniqueId[vername] && toRemoveByUniqueId[vername].length + var name = moduleName(pkg) + // find any packages we're removing that share the same name and are equivalent + var removing = (toRemoveByName[name] || []).filter((rm) => pkgAreEquiv(rm.pkg, pkg)) var bundlesOrFromBundle = pkg.fromBundle || pkg.package.bundleDependencies - if (removing && !bundlesOrFromBundle) { - var flatname = toRemoveByUniqueId[vername].shift() - pkg.fromPath = toRemove[flatname].path + // if we have any removes that match AND we're not working with a bundle then upgrade to a move + if (removing.length && !bundlesOrFromBundle) { + var toMv = removing.shift() + toRemoveByName[name] = toRemoveByName[name].filter((rm) => rm !== toMv) + pkg.fromPath = toMv.pkg.path setAction(differences, 'move', pkg) - delete toRemove[flatname] + delete toRemove[toMv.flatname] + // we don't generate add actions for things found in links (which already exist on disk) or + // for bundled modules (which will be installed when we install their parent) } else if (!(pkg.isInLink && pkg.fromBundle)) { setAction(differences, 'add', pkg) } } }) + + // finally generate our remove actions from any not consumed by moves Object .keys(toRemove) - .map((path) => toRemove[path]) + .map((flatname) => toRemove[flatname]) .forEach((pkg) => setAction(differences, 'remove', pkg)) const includeDev = npm.config.get('dev') || diff --git a/deps/npm/lib/install/inflate-shrinkwrap.js b/deps/npm/lib/install/inflate-shrinkwrap.js index 48be93d095..43ac9136f0 100644 --- a/deps/npm/lib/install/inflate-shrinkwrap.js +++ b/deps/npm/lib/install/inflate-shrinkwrap.js @@ -13,6 +13,7 @@ const npm = require('../npm.js') const realizeShrinkwrapSpecifier = require('./realize-shrinkwrap-specifier.js') const validate = require('aproba') const path = require('path') +const isRegistry = require('../utils/is-registry.js') module.exports = function (tree, sw, opts, finishInflating) { if (!fetchPackageMetadata) { @@ -147,7 +148,7 @@ function adaptResolved (requested, resolved) { const registry = requested.scope ? npm.config.get(`${requested.scope}:registry`) || npm.config.get('registry') : npm.config.get('registry') - if (!requested.registry || (resolved && resolved.indexOf(registry) === 0)) { + if (!isRegistry(requested) || (resolved && resolved.indexOf(registry) === 0)) { // Nothing to worry about here. Pass it through. return resolved } else { @@ -199,7 +200,7 @@ function childIsEquivalent (sw, requested, child) { if (child.isLink && requested.type === 'directory') return path.relative(child.realpath, requested.fetchSpec) === '' if (sw.resolved) return child.package._resolved === sw.resolved - if (!requested.registry && sw.from) return child.package._from === sw.from - if (!requested.registry && child.package._resolved) return sw.version === child.package._resolved + if (!isRegistry(requested) && sw.from) return child.package._from === sw.from + if (!isRegistry(requested) && child.package._resolved) return sw.version === child.package._resolved return child.package.version === sw.version } diff --git a/deps/npm/lib/install/realize-shrinkwrap-specifier.js b/deps/npm/lib/install/realize-shrinkwrap-specifier.js index ac700278ff..e4b14b1f0d 100644 --- a/deps/npm/lib/install/realize-shrinkwrap-specifier.js +++ b/deps/npm/lib/install/realize-shrinkwrap-specifier.js @@ -1,5 +1,6 @@ 'use strict' var npa = require('npm-package-arg') +const isRegistry = require('../utils/is-registry.js') module.exports = function (name, sw, where) { try { @@ -7,7 +8,7 @@ module.exports = function (name, sw, where) { return npa.resolve(name, sw.version, where) } else if (sw.from) { const spec = npa(sw.from, where) - if (spec.registry && sw.version) { + if (isRegistry(spec) && sw.version) { return npa.resolve(name, sw.version, where) } else if (!sw.resolved) { return spec diff --git a/deps/npm/lib/install/save.js b/deps/npm/lib/install/save.js index e1a94fcff7..f0c61f555d 100644 --- a/deps/npm/lib/install/save.js +++ b/deps/npm/lib/install/save.js @@ -39,6 +39,8 @@ function andWarnErrors (cb) { } } +exports.saveShrinkwrap = saveShrinkwrap + function saveShrinkwrap (tree, next) { validate('OF', arguments) if (!npm.config.get('shrinkwrap') || !npm.config.get('package-lock')) { |