File Explorer

/proc/4/task/7/root/var/lang/lib/node_modules/npm/node_modules/@npmcli/arborist/lib

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

can-place-dep.js14.6 KB · 450 lines
// Internal methods used by buildIdealTree.// Answer the question: "can I put this dep here?"//// IMPORTANT: *nothing* in this class should *ever* modify or mutate the tree// at all.  The contract here is strictly limited to read operations.  We call// this in the process of walking through the ideal tree checking many// different potential placement targets for a given node.  If a change is made// to the tree along the way, that can cause serious problems!//// In order to enforce this restriction, in debug mode, canPlaceDep() will// snapshot the tree at the start of the process, and then at the end, will// verify that it still matches the snapshot, and throw an error if any changes// occurred.//// The algorithm is roughly like this:// - check the node itself://   - if there is no version present, and no conflicting edges from target,//     OK, provided all peers can be placed at or above the target.//   - if the current version matches, KEEP//   - if there is an older version present, which can be replaced, then//     - if satisfying and preferDedupe? KEEP//     - else: REPLACE//   - if there is a newer version present, and preferDedupe, REPLACE//   - if there is a newer vulnerable version present during an audit fix,//     and the candidate is older and not vulnerable, REPLACE//   - if the version present satisfies the edge, KEEP//   - else: CONFLICT// - if the node is not in conflict, check each of its peers://   - if the peer can be placed in the target, continue//   - else if the peer can be placed in a parent, and there is no other//     conflicting version shadowing it, continue//   - else CONFLICT// - If the peers are not in conflict, return the original node's value//// An exception to this logic is that if the target is the deepest location// that a node can be placed, and the conflicting node can be placed deeper,// then we will return REPLACE rather than CONFLICT, and Arborist will queue// the replaced node for resolution elsewhere. const localeCompare = require('@isaacs/string-locale-compare')('en')const semver = require('semver')const debug = require('./debug.js')const peerEntrySets = require('./peer-entry-sets.js')const deepestNestingTarget = require('./deepest-nesting-target.js') const CONFLICT = Symbol('CONFLICT')const OK = Symbol('OK')const REPLACE = Symbol('REPLACE')const KEEP = Symbol('KEEP') class CanPlaceDep {  // dep is a dep that we're trying to place.  it should already live in  // a virtual tree where its peer set is loaded as children of the root.  // target is the actual place where we're trying to place this dep  // in a node_modules folder.  // edge is the edge that we're trying to satisfy with this placement.  // parent is the CanPlaceDep object of the entry node when placing a peer.  constructor (options) {    const {      dep,      target,      edge,      preferDedupe,      parent = null,      peerPath = [],      explicitRequest = false,      auditReport = null,    } = options     debug(() => {      if (!dep) {        throw new Error('no dep provided to CanPlaceDep')      }       if (!target) {        throw new Error('no target provided to CanPlaceDep')      }       if (!edge) {        throw new Error('no edge provided to CanPlaceDep')      }       this._treeSnapshot = JSON.stringify([...target.root.inventory.entries()]        .map(([loc, { packageName, version, resolved }]) => {          return [loc, packageName, version, resolved]        }).sort(([a], [b]) => localeCompare(a, b)))    })     // the result of whether we can place it or not    this.canPlace = null    // if peers conflict, but this one doesn't, then that is useful info    this.canPlaceSelf = null     this.dep = dep    this.target = target    this.edge = edge    this.explicitRequest = explicitRequest    this.auditReport = auditReport     // preventing cycles when we check peer sets    this.peerPath = peerPath    // we always prefer to dedupe peers, because they are trying    // a bit harder to be singletons.    this.preferDedupe = !!preferDedupe || edge.peer    this.parent = parent    this.children = []     this.isSource = target === this.peerSetSource    this.name = edge.name    this.current = target.children.get(this.name)    this.targetEdge = target.edgesOut.get(this.name)    this.conflicts = new Map()     // check if this dep was already subject to a peerDep override while    // building the peerSet.    this.edgeOverride = !dep.satisfies(edge)     this.canPlace = this.checkCanPlace()    if (!this.canPlaceSelf) {      this.canPlaceSelf = this.canPlace    }     debug(() => {      const treeSnapshot = JSON.stringify([...target.root.inventory.entries()]        .map(([loc, { packageName, version, resolved }]) => {          return [loc, packageName, version, resolved]        }).sort(([a], [b]) => localeCompare(a, b)))      /* istanbul ignore if */      if (this._treeSnapshot !== treeSnapshot) {        throw Object.assign(new Error('tree changed in CanPlaceDep'), {          expect: this._treeSnapshot,          actual: treeSnapshot,        })      }    })  }   checkCanPlace () {    const { target, targetEdge, current, dep } = this     // if the dep failed to load, we're going to fail the build or    // prune it out anyway, so just move forward placing/replacing it.    if (dep.errors.length) {      return current ? REPLACE : OK    }     // cannot place peers inside their dependents, except for tops    if (targetEdge && targetEdge.peer && !target.isTop) {      return CONFLICT    }     // skip this test if there's a current node, because we might be able    // to dedupe against it anyway    if (!current &&        targetEdge &&        !dep.satisfies(targetEdge) &&        targetEdge !== this.edge) {      return CONFLICT    }     return current ? this.checkCanPlaceCurrent() : this.checkCanPlaceNoCurrent()  }   // we know that the target has a dep by this name in its node_modules  // already.  Can return KEEP, REPLACE, or CONFLICT.  checkCanPlaceCurrent () {    const { preferDedupe, explicitRequest, current, target, edge, dep } = this     if (dep.matches(current)) {      if (current.satisfies(edge) || this.edgeOverride) {        return explicitRequest ? REPLACE : KEEP      }    }     const { version: curVer } = current    const { version: newVer } = dep    // Audit fix may select an older safe version within the declared range.    // This only makes the candidate eligible; canReplace() and peer checks    // below still enforce placement compatibility.    const auditFixDowngrade = curVer && newVer &&      semver.lt(newVer, curVer) &&      this.auditReport?.isVulnerable(current) &&      !this.auditReport.isVulnerable(dep)    const tryReplace = curVer && newVer &&      (semver.gte(newVer, curVer) || auditFixDowngrade)    if (tryReplace && dep.canReplace(current)) {      // It's extremely rare that a replaceable node would be a conflict, if      // the current one wasn't a conflict, but it is theoretically possible      // if peer deps are pinned.  In that case we treat it like any other      // conflict, and keep trying.      const cpp = this.canPlacePeers(REPLACE)      if (cpp !== CONFLICT) {        return cpp      }    }     // ok, can't replace the current with new one, but maybe current is ok?    if (current.satisfies(edge) && (!explicitRequest || preferDedupe)) {      return KEEP    }     // if we prefer deduping, then try replacing newer with older    if (preferDedupe && !tryReplace && dep.canReplace(current)) {      const cpp = this.canPlacePeers(REPLACE)      if (cpp !== CONFLICT) {        return cpp      }    }     // Check for interesting cases!    // First, is this the deepest place that this thing can go, and NOT the    // deepest place where the conflicting dep can go?  If so, replace it,    // and let it re-resolve deeper in the tree.    const myDeepest = this.deepestNestingTarget     // ok, i COULD be placed deeper, so leave the current one alone.    if (target !== myDeepest) {      return CONFLICT    }     // if we are not checking a peerDep, then we MUST place it here, in the    // target that has a non-peer dep on it.    if (!edge.peer && target === edge.from) {      return this.canPlacePeers(REPLACE)    }     // if we aren't placing a peer in a set, then we're done here.    // This is ignored because it SHOULD be redundant, as far as I can tell,    // with the deepest target and target===edge.from tests.  But until we    // can prove that isn't possible, this condition is here for safety.    /* istanbul ignore if - allegedly impossible */    if (!this.parent && !edge.peer) {      return CONFLICT    }     // check the deps in the peer group for each edge into that peer group    // if ALL of them can be pushed deeper, or if it's ok to replace its    // members with the contents of the new peer group, then we're good.    let canReplace = true    for (const [entryEdge, currentPeers] of peerEntrySets(current)) {      if (entryEdge === this.edge || entryEdge === this.peerEntryEdge) {        continue      }       // First, see if it's ok to just replace the peerSet entirely.      // we do this by walking out from the entryEdge, because in a case like      // this:      //      // v -> PEER(a@1||2)      // a@1 -> PEER(b@1)      // a@2 -> PEER(b@2)      // b@1 -> PEER(a@1)      // b@2 -> PEER(a@2)      //      // root      // +-- v      // +-- a@2      // +-- b@2      //      // Trying to place a peer group of (a@1, b@1) would fail to note that      // they can be replaced, if we did it by looping 1 by 1.  If we are      // replacing something, we don't have to check its peer deps, because      // the peerDeps in the placed peerSet will presumably satisfy.      const entryNode = entryEdge.to      const entryRep = dep.parent.children.get(entryNode.name)      if (entryRep) {        if (entryRep.canReplace(entryNode, dep.parent.children.keys())) {          continue        }      }       let canClobber = !entryRep      if (!entryRep) {        const peerReplacementWalk = new Set([entryNode])        OUTER: for (const currentPeer of peerReplacementWalk) {          for (const edge of currentPeer.edgesOut.values()) {            if (!edge.peer || !edge.valid) {              continue            }            const rep = dep.parent.children.get(edge.name)            if (!rep) {              if (edge.to) {                peerReplacementWalk.add(edge.to)              }              continue            }            if (!rep.satisfies(edge)) {              canClobber = false              break OUTER            }          }        }      }      if (canClobber) {        continue      }       // ok, we can't replace, but maybe we can nest the current set deeper?      let canNestCurrent = true      for (const currentPeer of currentPeers) {        if (!canNestCurrent) {          break        }         // still possible to nest this peerSet        const curDeep = deepestNestingTarget(entryEdge.from, currentPeer.name)        if (curDeep === target || target.isDescendantOf(curDeep)) {          canNestCurrent = false          canReplace = false        }        if (canNestCurrent) {          continue        }      }    }     // if we can nest or replace all the current peer groups, we can replace.    if (canReplace) {      return this.canPlacePeers(REPLACE)    }     return CONFLICT  }   checkCanPlaceNoCurrent () {    const { target, peerEntryEdge, dep, name } = this     // check to see what that name resolves to here, and who may depend on    // being able to reach it by crawling up past the parent.  we know    // that it's not the target's direct child node, and if it was a direct    // dep of the target, we would have conflicted earlier.    const current = target !== peerEntryEdge.from && target.resolve(name)    if (current) {      for (const edge of current.edgesIn.values()) {        if (edge.from.isDescendantOf(target) && edge.valid) {          if (!dep.satisfies(edge)) {            return CONFLICT          }        }      }    }     // no objections, so this is fine as long as peers are ok here.    return this.canPlacePeers(OK)  }   get deepestNestingTarget () {    const start = this.parent ? this.parent.deepestNestingTarget      : this.edge.from    return deepestNestingTarget(start, this.name)  }   get conflictChildren () {    return this.allChildren.filter(c => c.canPlace === CONFLICT)  }   get allChildren () {    const set = new Set(this.children)    for (const child of set) {      for (const grandchild of child.children) {        set.add(grandchild)      }    }    return [...set]  }   get top () {    return this.parent ? this.parent.top : this  }   // check if peers can go here.  returns state or CONFLICT  canPlacePeers (state) {    this.canPlaceSelf = state    if (this._canPlacePeers) {      return this._canPlacePeers    }     // TODO: represent peerPath in ERESOLVE error somehow?    const peerPath = [...this.peerPath, this.dep]    let sawConflict = false    for (const peerEdge of this.dep.edgesOut.values()) {      if (!peerEdge.peer || !peerEdge.to || peerPath.includes(peerEdge.to)) {        continue      }      const peer = peerEdge.to      // it may be the case that the *initial* dep can be nested, but a peer      // of that dep needs to be placed shallower, because the target has      // a peer dep on the peer as well.      const target = deepestNestingTarget(this.target, peer.name)      const cpp = new CanPlaceDep({        dep: peer,        target,        parent: this,        edge: peerEdge,        peerPath,        auditReport: this.auditReport,        // always place peers in preferDedupe mode        preferDedupe: true,      })      /* istanbul ignore next */      debug(() => {        if (this.children.some(c => c.dep === cpp.dep)) {          throw new Error('checking same dep repeatedly')        }      })      this.children.push(cpp)       if (cpp.canPlace === CONFLICT) {        sawConflict = true      }    }     this._canPlacePeers = sawConflict ? CONFLICT : state    return this._canPlacePeers  }   // what is the node that is causing this peerSet to be placed?  get peerSetSource () {    return this.parent ? this.parent.peerSetSource : this.edge.from  }   get peerEntryEdge () {    return this.top.edge  }   static get CONFLICT () {    return CONFLICT  }   static get OK () {    return OK  }   static get REPLACE () {    return REPLACE  }   static get KEEP () {    return KEEP  }   get description () {    const { canPlace } = this    return canPlace && canPlace.description ||    /* istanbul ignore next - old node affordance */ canPlace  }} module.exports = CanPlaceDep