File Explorer

/proc/self/task/4/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 /.

edge.js9.8 KB · 365 lines
// An edge in the dependency graph// Represents a dependency relationship of some kind const util = require('node:util')const npa = require('npm-package-arg')const depValid = require('./dep-valid.js')const OverrideSet = require('./override-set.js') class ArboristEdge {  constructor (edge) {    this.name = edge.name    this.spec = edge.spec    this.type = edge.type     const edgeFrom = edge.from?.location    const edgeTo = edge.to?.location    const override = edge.overrides?.value     if (edgeFrom != null) {      this.from = edgeFrom    }    if (edgeTo) {      this.to = edgeTo    }    if (edge.error) {      this.error = edge.error    }    if (edge.peerConflicted) {      this.peerConflicted = true    }    if (override) {      this.overridden = override    }  }} class Edge {  #accept  #error  #explanation  #from  #name  #spec  #to  #type   static types = Object.freeze([    'prod',    'dev',    'optional',    'peer',    'peerOptional',    'workspace',  ])   // XXX where is this used?  static errors = Object.freeze([    'DETACHED',    'MISSING',    'PEER LOCAL',    'INVALID',  ])   constructor (options) {    const { type, name, spec, accept, from, overrides } = options     // XXX are all of these error states even possible?    if (typeof spec !== 'string') {      throw new TypeError('must provide string spec')    }    if (!Edge.types.includes(type)) {      throw new TypeError(`invalid type: ${type}\n(valid types are: ${Edge.types.join(', ')})`)    }    if (type === 'workspace' && npa(spec).type !== 'directory') {      throw new TypeError('workspace edges must be a symlink')    }    if (typeof name !== 'string') {      throw new TypeError('must provide dependency name')    }    if (!from) {      throw new TypeError('must provide "from" node')    }    if (accept !== undefined) {      if (typeof accept !== 'string') {        throw new TypeError('accept field must be a string if provided')      }      this.#accept = accept || '*'    }    if (overrides !== undefined) {      this.overrides = overrides    }     this.#name = name    this.#type = type    this.#spec = spec    this.#explanation = null    this.#from = from     from.edgesOut.get(this.#name)?.detach()    from.addEdgeOut(this)     this.reload(true)    this.peerConflicted = false  }   satisfiedBy (node) {    if (node.name !== this.#name || !this.#from) {      return false    }     // NOTE: this condition means we explicitly do not support overriding    // bundled or shrinkwrapped dependencies    if (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle) {      return depValid(node, this.rawSpec, this.#accept, this.#from)    }     // If there's no override we just use the spec.    if (!this.overrides?.keySpec) {      return depValid(node, this.spec, this.#accept, this.#from)    }    // There's some override. If the target node satisfies the overriding spec    // then it's okay.    if (depValid(node, this.spec, this.#accept, this.#from)) {      return true    }    // If it doesn't, then it should at least satisfy the original spec.    if (!depValid(node, this.rawSpec, this.#accept, this.#from)) {      return false    }    // It satisfies the original spec, not the overriding spec. We need to make    // sure it doesn't use the overridden spec.    // For example:    //   we might have an ^8.0.0 rawSpec, and an override that makes    //   keySpec=8.23.0 and the override value spec=9.0.0.    //   If the node is 9.0.0, then it's okay because it's consistent with spec.    //   If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.    //   If the node is 8.23.0, then it's not okay because even though it's consistent    //   with the rawSpec, it's also consistent with the keySpec.    //   So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.    return !depValid(node, this.overrides.keySpec, this.#accept, this.#from)  }   // return the edge data, and an explanation of how that edge came to be here  explain (seen = []) {    if (!this.#explanation) {      const explanation = {        type: this.#type,        name: this.#name,        spec: this.spec,      }      if (this.rawSpec !== this.spec) {        explanation.rawSpec = this.rawSpec        explanation.overridden = true      }      if (this.bundled) {        explanation.bundled = this.bundled      }      if (this.error) {        explanation.error = this.error      }      if (this.#from) {        explanation.from = this.#from.explain(null, seen)      }      this.#explanation = explanation    }    return this.#explanation  }   get bundled () {    return !!this.#from?.package?.bundleDependencies?.includes(this.#name)  }   get workspace () {    return this.#type === 'workspace'  }   get prod () {    return this.#type === 'prod'  }   get dev () {    return this.#type === 'dev'  }   get optional () {    return this.#type === 'optional' || this.#type === 'peerOptional'  }   get peer () {    return this.#type === 'peer' || this.#type === 'peerOptional'  }   get type () {    return this.#type  }   get name () {    return this.#name  }   get rawSpec () {    return this.#spec  }   get spec () {    if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.#name) {      if (this.overrides.value.startsWith('$')) {        const ref = this.overrides.value.slice(1)        let pkg = this.#from?.sourceReference          ? this.#from?.sourceReference.root.package          : this.#from?.root?.package         let specValue = this.#calculateReferentialOverrideSpec(ref, pkg)         // If the package isn't found in the root package, fall back to the local package.        if (!specValue) {          pkg = this.#from?.package          specValue = this.#calculateReferentialOverrideSpec(ref, pkg)        }         if (specValue) {          return specValue        }        throw new Error(`Unable to resolve reference ${this.overrides.value}`)      }      return this.overrides.value    }    return this.#spec  }   #calculateReferentialOverrideSpec (ref, pkg) {    if (pkg.devDependencies?.[ref]) {      return pkg.devDependencies[ref]    }    if (pkg.optionalDependencies?.[ref]) {      return pkg.optionalDependencies[ref]    }    if (pkg.dependencies?.[ref]) {      return pkg.dependencies[ref]    }    if (pkg.peerDependencies?.[ref]) {      return pkg.peerDependencies[ref]    }  }   get accept () {    return this.#accept  }   get valid () {    return !this.error  }   get missing () {    return this.error === 'MISSING'  }   get invalid () {    return this.error === 'INVALID'  }   get peerLocal () {    return this.error === 'PEER LOCAL'  }   get error () {    if (!this.#error) {      if (!this.#to) {        if (this.optional) {          this.#error = null        } else {          this.#error = 'MISSING'        }      } else if (this.peer && this.#from === this.#to.parent && !this.#from?.isTop) {        this.#error = 'PEER LOCAL'      } else if (!this.satisfiedBy(this.#to)) {        this.#error = 'INVALID'      } else if (this.overrides && this.#to.overrides && this.#to.edgesOut.size && OverrideSet.doOverrideSetsConflict(this.overrides, this.#to.overrides)) {        // Check for conflicts between the edge's override set and the target node's override set.        // This catches cases where different parts of the tree have genuinely incompatible        // version requirements for the same package.        // The improved conflict detection uses semantic comparison (checking for incompatible        // version ranges) rather than pure structural equality, avoiding false positives from:        // - Reference overrides ($syntax) that resolve to compatible versions        // - Peer dependencies with different but compatible override contexts        // Note: We only check if the target has dependencies (edgesOut.size > 0), since        // override conflicts are only relevant if the target has its own dependencies.        this.#error = 'INVALID'      } else {        this.#error = 'OK'      }    }    if (this.#error === 'OK') {      return null    }    return this.#error  }   reload (hard = false) {    this.#explanation = null     let needToUpdateOverrideSet = false    let newOverrideSet    let oldOverrideSet    if (this.#from?.overrides) {      newOverrideSet = this.#from.overrides.getEdgeRule(this)      if (newOverrideSet && !newOverrideSet.isEqual(this.overrides)) {        // If there's a new different override set we need to propagate it to the nodes.        // If we're deleting the override set then there's no point propagating it right now since it will be filled with another value later.        needToUpdateOverrideSet = true        oldOverrideSet = this.overrides        this.overrides = newOverrideSet      }    } else {      delete this.overrides    }    const newTo = this.#from?.resolve(this.#name)    if (newTo !== this.#to) {      if (this.#to) {        this.#to.deleteEdgeIn(this)      }      this.#to = newTo      this.#error = null      if (this.#to) {        this.#to.addEdgeIn(this)      }    } else if (hard) {      this.#error = null    } else if (needToUpdateOverrideSet && this.#to) {      // Propagate the new override set to the target node.      this.#to.updateOverridesEdgeInRemoved(oldOverrideSet)      this.#to.updateOverridesEdgeInAdded(newOverrideSet)    }  }   detach () {    this.#explanation = null    if (this.#to) {      this.#to.deleteEdgeIn(this)    }    this.#from?.edgesOut.delete(this.#name)    this.#to = null    this.#error = 'DETACHED'    this.#from = null  }   get from () {    return this.#from  }   get to () {    return this.#to  }   toJSON () {    return new ArboristEdge(this)  }   [util.inspect.custom] () {    return this.toJSON()  }} module.exports = Edge