UNPKG

npm

Version:

a package manager for JavaScript

163 lines (142 loc) 4.38 kB
const relpath = require('./relpath.js') const Node = require('./node.js') const _loadDeps = Symbol.for('Arborist.Node._loadDeps') const _target = Symbol.for('_target') const { dirname } = require('node:path') // defined by Node class const _delistFromMeta = Symbol.for('_delistFromMeta') const _refreshLocation = Symbol.for('_refreshLocation') class Link extends Node { constructor (options) { const { root, realpath, target, parent, fsParent, isStoreLink } = options if (!realpath && !(target && target.path)) { throw new TypeError('must provide realpath for Link node') } super({ ...options, realpath: realpath || target.path, root: root || (parent ? parent.root : fsParent ? fsParent.root : target ? target.root : null), }) this.isStoreLink = isStoreLink || false if (target) { this.target = target } else if (this.realpath === this.root.path) { this.target = this.root } else { this.target = new Node({ ...options, path: realpath, parent: null, fsParent: null, root: this.root, }) } } get version () { return this.target ? this.target.version : this.package.version || '' } get target () { return this[_target] } set target (target) { const current = this[_target] if (target === current) { return } if (!target) { if (current && current.linksIn) { current.linksIn.delete(this) } if (this.path) { this[_delistFromMeta]() this[_target] = null this.package = {} this[_refreshLocation]() } else { this[_target] = null } return } if (!this.path) { // temp node pending assignment to a tree // we know it's not in the inventory yet, because no path. if (target.path) { this.realpath = target.path } else { target.path = target.realpath = this.realpath } target.root = this.root this[_target] = target target.linksIn.add(this) this.package = target.package return } // have to refresh metadata, because either realpath or package // is very likely changing. this[_delistFromMeta]() this.package = target.package this.realpath = target.path this[_refreshLocation]() target.root = this.root } // a link always resolves to the relative path to its target get resolved () { // the path/realpath guard is there for the benefit of setting // these things in the "wrong" order return this.path && this.realpath ? `file:${relpath(dirname(this.path), this.realpath)}` : null } set resolved (r) {} // deps are resolved on the target, not the Link // so this is a no-op [_loadDeps] () {} // Forward overrides to the target only when a rule names a dep it needs, directly or transitively (npm/cli#9357, #9619). recalculateOutEdgesOverrides () { if (!this.target || !this.overrides) { return } if (!overrideMatchesSubtree(this.overrides, this.target)) { return } this.target.updateOverridesEdgeInAdded(this.overrides) } // links can't have children, only their targets can // fix it to an empty list so that we can still call // things that iterate over them, just as a no-op get children () { return new Map() } set children (c) {} get isLink () { return true } } // True when an override rule applies to an edge reachable from the target at any depth. // getEdgeRule matches on name and spec, returning the set itself when nothing applies, so a non-applicable version-qualified rule doesn't flip an intermediate node to "has overrides" (npm/cli#9357). // Not a method: runs from the Node super-constructor, before subclass private methods exist. const overrideMatchesSubtree = (overrides, target) => { const seen = new Set() const stack = [target] while (stack.length) { const node = stack.pop() const resolved = node.isLink ? node.target : node if (seen.has(resolved)) { continue } seen.add(resolved) for (const edge of resolved.edgesOut.values()) { if (overrides.getEdgeRule(edge).name === edge.name) { return true } if (edge.to) { stack.push(edge.to) } } } return false } module.exports = Link