| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 | 'use strict'var npm = require('../npm.js')var validate = require('aproba')var npa = require('npm-package-arg')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')var isOnlyOptional = require('./is-only-optional.js')// we don't use get-requested because we're operating on files on disk, and// we don't want to extrapolate from what _should_ be there.function pkgRequested (pkg) {  return pkg._requested || (pkg._resolved && npa(pkg._resolved)) || (pkg._from && npa(pkg._from))}function nonRegistrySource (requested) {  if (fromGit(requested)) return true  if (fromLocal(requested)) return true  if (fromRemote(requested)) return true  return false}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 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) {  }}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      }    }  }  return false}function pkgAreEquiv (aa, bb) {  // coming in we know they share a path…  // if one is inside a link and the other is not, then they are not equivalent  // this happens when we're replacing a linked dep with a non-linked version  if (aa.isInLink !== bb.isInLink) return false  // 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  }  // 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) {  Array.prototype.push.apply(aa, bb)}module.exports = function (oldTree, newTree, differences, log, next) {  validate('OOAOF', arguments)  pushAll(differences, sortActions(diffTrees(oldTree, newTree)))  log.finish()  next()}function isNotTopOrExtraneous (node) {  return !node.isTop && !node.userRequired && !node.existing}var sortActions = module.exports.sortActions = function (differences) {  var actions = {}  differences.forEach(function (action) {    var child = action[1]    actions[child.location] = action  })  var sorted = []  var added = {}  var sortedlocs = Object.keys(actions).sort(sortByLocation)  // We're going to sort the actions taken on top level dependencies first, before  // considering the order of transitive deps. Because we're building our list  // from the bottom up, this means we will return a list with top level deps LAST.  // This is important in terms of keeping installations as consistent as possible  // as folks add new dependencies.  var toplocs = sortedlocs.filter(function (location) {    var mod = actions[location][1]    if (!mod.requiredBy) return true    // If this module is required by any non-top level module    // or by any extraneous module, eg user requested or existing    // then we don't want to give this priority sorting.    return !mod.requiredBy.some(isNotTopOrExtraneous)  })  toplocs.concat(sortedlocs).forEach(function (location) {    sortByDeps(actions[location])  })  function sortByLocation (aa, bb) {    return bb.localeCompare(aa)  }  function sortModuleByLocation (aa, bb) {    return sortByLocation(aa && aa.location, bb && bb.location)  }  function sortByDeps (action) {    var mod = action[1]    if (added[mod.location]) return    added[mod.location] = action    if (!mod.requiredBy) mod.requiredBy = []    mod.requiredBy.sort(sortModuleByLocation).forEach(function (mod) {      if (actions[mod.location]) sortByDeps(actions[mod.location])    })    sorted.unshift(action)  }  // safety net, anything excluded above gets tacked on the end  differences.forEach((_) => {    if (sorted.indexOf(_) === -1) sorted.push(_)  })  return sorted}function setAction (differences, action, pkg) {  differences.push([action, pkg])}var diffTrees = module.exports._diffTrees = function (oldTree, newTree) {  validate('OO', arguments)  var differences = []  var flatOldTree = flattenTree(oldTree)  var flatNewTree = flattenTree(newTree)  var toRemove = {}  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 name = moduleName(pkg)    if (!toRemoveByName[name]) toRemoveByName[name] = []    toRemoveByName[name].push({flatname: flatname, pkg: pkg})  })  // 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 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 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[toMv.flatname]      // we don't generate add actions for things found in links (which already exist on disk)      } else if (!pkg.isInLink || !(pkg.fromBundle && pkg.fromBundle.isLink)) {        setAction(differences, 'add', pkg)      }    }  })  // finally generate our remove actions from any not consumed by moves  Object    .keys(toRemove)    .map((flatname) => toRemove[flatname])    .forEach((pkg) => setAction(differences, 'remove', pkg))  return filterActions(differences)}function filterActions (differences) {  const includeOpt = npm.config.get('optional')  const includeDev = npm.config.get('dev') ||    (!/^prod(uction)?$/.test(npm.config.get('only')) && !npm.config.get('production')) ||    /^dev(elopment)?$/.test(npm.config.get('only')) ||    /^dev(elopment)?$/.test(npm.config.get('also'))  const includeProd = !/^dev(elopment)?$/.test(npm.config.get('only'))  if (includeProd && includeDev && includeOpt) return differences  log.silly('diff-trees', 'filtering actions:', 'includeDev', includeDev, 'includeProd', includeProd, 'includeOpt', includeOpt)  return differences.filter((diff) => {    const pkg = diff[1]    const pkgIsOnlyDev = isOnlyDev(pkg)    const pkgIsOnlyOpt = isOnlyOptional(pkg)    if (!includeProd && pkgIsOnlyDev) return true    if (includeDev && pkgIsOnlyDev) return true    if (includeProd && !pkgIsOnlyDev && (includeOpt || !pkgIsOnlyOpt)) return true    return false  })}
 |