Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
node.js1632 linesDownload Raw Back to lib
1// inventory, path, realpath, root, and parent
2//
3// node.root is a reference to the root module in the tree (ie, typically the
4// cwd project folder)
5//
6// node.location is the /-delimited path from the root module to the node.  In
7// the case of link targets that may be outside of the root's package tree,
8// this can include some number of /../ path segments.  The location of the
9// root module is always '.'.  node.location thus never contains drive letters
10// or absolute paths, and is portable within a given project, suitable for
11// inclusion in lockfiles and metadata.
12//
13// node.path is the path to the place where this node lives on disk.  It is
14// system-specific and absolute.
15//
16// node.realpath is the path to where the module actually resides on disk.  In
17// the case of non-link nodes, node.realpath is equivalent to node.path.  In
18// the case of link nodes, it is equivalent to node.target.path.
19//
20// Setting node.parent will set the node's root to the parent's root, as well
21// as updating edgesIn and edgesOut to reload dependency resolutions as needed,
22// and setting node.path to parent.path/node_modules/name.
23//
24// node.inventory is a Map of name to a Set() of all the nodes under a given
25// root by that name.  It's empty for non-root nodes, and changing the root
26// reference will remove it from the old root's inventory and add it to the new
27// one.  This map is useful for cases like `npm update foo` or `npm ls foo`
28// where we need to quickly find all instances of a given package name within a
29// tree.
30
31const PackageJson = require('@npmcli/package-json')
32const nameFromFolder = require('@npmcli/name-from-folder')
33const npa = require('npm-package-arg')
34const semver = require('semver')
35const util = require('node:util')
36const { getPaths: getBinPaths } = require('bin-links')
37const { log } = require('proc-log')
38const { resolve, relative, dirname, basename } = require('node:path')
39const { walkUp } = require('walk-up-path')
40
41const CaseInsensitiveMap = require('./case-insensitive-map.js')
42const Edge = require('./edge.js')
43const Inventory = require('./inventory.js')
44const OverrideSet = require('./override-set.js')
45const consistentResolve = require('./consistent-resolve.js')
46const debug = require('./debug.js')
47const gatherDepSet = require('./gather-dep-set.js')
48const printableTree = require('./printable.js')
49const querySelectorAll = require('./query-selector-all.js')
50const relpath = require('./relpath.js')
51const treeCheck = require('./tree-check.js')
52
53const _package = Symbol('_package')
54const _parent = Symbol('_parent')
55const _target = Symbol.for('_target')
56const _fsParent = Symbol('_fsParent')
57const _reloadNamedEdges = Symbol('_reloadNamedEdges')
58// overridden by Link class
59const _loadDeps = Symbol.for('Arborist.Node._loadDeps')
60const _refreshLocation = Symbol.for('_refreshLocation')
61const _changePath = Symbol.for('_changePath')
62// used by Link class as well
63const _delistFromMeta = Symbol.for('_delistFromMeta')
64const _explain = Symbol('_explain')
65const _explanation = Symbol('_explanation')
66
67class Node {
68  #global
69  #meta
70  #root
71  #workspaces
72
73  constructor (options) {
74    // NB: path can be null if it's a link target
75    const {
76      children,
77      dev = true,
78      devOptional = true,
79      dummy = false,
80      error,
81      extraneous = true,
82      fsChildren,
83      fsParent,
84      global = false,
85      hasShrinkwrap,
86      inert = false,
87      installLinks = false,
88      integrity,
89      isInStore = false,
90      legacyPeerDeps = false,
91      linksIn,
92      loadOverrides = false,
93      meta,
94      name, // allow setting name explicitly when we haven't set a path yet
95      optional = true,
96      overrides,
97      parent,
98      path,
99      peer = true,
100      realpath,
101      resolved,
102      root,
103      sourceReference = null,
104    } = options
105    // this object gives querySelectorAll somewhere to stash context about a node
106    // while processing a query
107    this.queryContext = {}
108
109    // true if part of a global install
110    this.#global = global
111
112    this.#workspaces = null
113
114    this.errors = error ? [error] : []
115    this.isInStore = isInStore
116
117    // this will usually be null, except when modeling a
118    // package's dependencies in a virtual root.
119    this.sourceReference = sourceReference
120
121    // have to set the internal package ref before assigning the parent, because this.package is read when adding to inventory
122    if (sourceReference) {
123      this[_package] = sourceReference.package
124    } else {
125      // TODO if this came from pacote.manifest we don't have to do this, we can be told to skip this step
126      const pkg = new PackageJson()
127      let content = {}
128      // TODO this is overly guarded.  If pkg is not an object we should not allow it at all.
129      if (options.pkg && typeof options.pkg === 'object') {
130        content = options.pkg
131      }
132      pkg.fromContent(content)
133      pkg.syncNormalize()
134      this[_package] = pkg.content
135    }
136
137    this.name = name ||
138      nameFromFolder(path || this.package.name || realpath) ||
139      this.package.name ||
140      null
141
142    // should be equal if not a link
143    this.path = path ? resolve(path) : null
144
145    if (!this.name && (!this.path || this.path !== dirname(this.path))) {
146      throw new TypeError('could not detect node name from path or package')
147    }
148
149    this.realpath = !this.isLink ? this.path : resolve(realpath)
150
151    this.resolved = resolved || null
152    if (!this.resolved) {
153      // note: this *only* works for non-file: deps, so we avoid even
154      // trying here.
155      // file: deps are tracked in package.json will _resolved set to the
156      // full path to the tarball or link target.  However, if the package
157      // is checked into git or moved to another location, that's 100% not
158      // portable at all!  The _where and _location don't provide much help,
159      // since _location is just where the module ended up in the tree,
160      // and _where can be different than the actual root if it's a
161      // meta-dep deeper in the dependency graph.
162      //
163      // If we don't have the other oldest indicators of legacy npm, then it's
164      // probably what we're getting from pacote, which IS trustworthy.
165      //
166      // Otherwise, hopefully a shrinkwrap will help us out.
167      const resolved = consistentResolve(this.package._resolved)
168      if (resolved && !(/^file:/.test(resolved) && this.package._where)) {
169        this.resolved = resolved
170      }
171    }
172    this.integrity = integrity || this.package._integrity || null
173    this.hasShrinkwrap = hasShrinkwrap || this.package._hasShrinkwrap || false
174    this.installLinks = installLinks
175    this.legacyPeerDeps = legacyPeerDeps
176
177    this.children = new CaseInsensitiveMap()
178    this.fsChildren = new Set()
179    this.inventory = new Inventory()
180    this.tops = new Set()
181    this.linksIn = new Set(linksIn || [])
182
183    // these three are set by an Arborist taking a catalog
184    // after the tree is built.  We don't get this along the way,
185    // because they have a tendency to change as new children are
186    // added, especially when they're deduped.  Eg, a dev dep may be
187    // a 3-levels-deep dependency of a non-dev dep.  If we calc the
188    // flags along the way, then they'll tend to be invalid  by the
189    // time we need to look at them.
190    if (!dummy) {
191      this.dev = dev
192      this.optional = optional
193      this.devOptional = devOptional
194      this.peer = peer
195      this.extraneous = extraneous
196      this.dummy = false
197    } else {
198      // true if this is a placeholder for the purpose of serving as a
199      // fsParent to link targets that get their deps resolved outside
200      // the root tree folder.
201      this.dummy = true
202      this.dev = false
203      this.optional = false
204      this.devOptional = false
205      this.peer = false
206      this.extraneous = false
207    }
208
209    this.inert = inert
210
211    this.edgesIn = new Set()
212    this.edgesOut = new CaseInsensitiveMap()
213
214    if (overrides) {
215      this.overrides = overrides
216    } else if (loadOverrides) {
217      const overrides = this.package.overrides || {}
218      if (Object.keys(overrides).length > 0) {
219        this.overrides = new OverrideSet({
220          overrides: this.package.overrides,
221        })
222      }
223    }
224
225    // only relevant for the root and top nodes
226    this.meta = meta
227
228    // Note: this is _slightly_ less efficient for the initial tree
229    // building than it could be, but in exchange, it's a much simpler
230    // algorithm.
231    // If this node has a bunch of children, and those children satisfy
232    // its various deps, then we're going to _first_ create all the
233    // edges, and _then_ assign the children into place, re-resolving
234    // them all in _reloadNamedEdges.
235    // A more efficient, but more complicated, approach would be to
236    // flag this node as being a part of a tree build, so it could
237    // hold off on resolving its deps until its children are in place.
238
239    // call the parent setter
240    // Must be set prior to calling _loadDeps, because top-ness is relevant
241
242    // will also assign root if present on the parent
243    this[_parent] = null
244    this.parent = parent || null
245
246    this[_fsParent] = null
247    this.fsParent = fsParent || null
248
249    // see parent/root setters below.
250    // root is set to parent's root if we have a parent; otherwise, if it's
251    // null, then it's set to the node itself.
252    if (!parent && !fsParent) {
253      this.root = root || null
254    }
255
256    // mostly a convenience for testing, but also a way to create
257    // trees in a more declarative way than setting parent on each
258    if (children) {
259      for (const c of children) {
260        new Node({ ...c, parent: this })
261      }
262    }
263    if (fsChildren) {
264      for (const c of fsChildren) {
265        new Node({ ...c, fsParent: this })
266      }
267    }
268
269    // now load all the dep edges
270    this[_loadDeps]()
271  }
272
273  get meta () {
274    return this.#meta
275  }
276
277  set meta (meta) {
278    this.#meta = meta
279    if (meta) {
280      meta.add(this)
281    }
282  }
283
284  get global () {
285    if (this.#root === this) {
286      return this.#global
287    }
288    return this.#root.global
289  }
290
291  // true for packages installed directly in the global node_modules folder
292  get globalTop () {
293    return this.global && this.parent && this.parent.isProjectRoot
294  }
295
296  get workspaces () {
297    return this.#workspaces
298  }
299
300  set workspaces (workspaces) {
301    // deletes edges if they already exists
302    if (this.#workspaces) {
303      for (const name of this.#workspaces.keys()) {
304        if (!workspaces.has(name)) {
305          this.edgesOut.get(name).detach()
306        }
307      }
308    }
309
310    this.#workspaces = workspaces
311    this.#loadWorkspaces()
312    this[_loadDeps]()
313  }
314
315  get binPaths () {
316    if (!this.parent) {
317      return []
318    }
319
320    return getBinPaths({
321      pkg: this.package,
322      path: this.path,
323      global: this.global,
324      top: this.globalTop,
325    })
326  }
327
328  get hasInstallScript () {
329    const { hasInstallScript, scripts } = this.package
330    const { install, preinstall, postinstall } = scripts || {}
331    return !!(hasInstallScript || install || preinstall || postinstall)
332  }
333
334  get version () {
335    return this.package.version || ''
336  }
337
338  get packageName () {
339    return this.package.name || null
340  }
341
342  get pkgid () {
343    const { name = '', version = '' } = this.package
344    // root package will prefer package name over folder name,
345    // and never be called an alias.
346    const { isProjectRoot } = this
347    const myname = isProjectRoot ? name || this.name
348      : this.name
349    const alias = !isProjectRoot && name && myname !== name ? `npm:${name}@`
350      : ''
351    return `${myname}@${alias}${version}`
352  }
353
354  get overridden () {
355    if (!this.overrides) {
356      return false
357    }
358    if (!this.overrides.value) {
359      return false
360    }
361    if (this.overrides.name !== this.name) {
362      return false
363    }
364
365    // The overrides rule is for a package with this name, but some override rules only apply to specific
366    // versions. To make sure this package was actually overridden, we check whether any edge going in
367    // had the rule applied to it, in which case its overrides set is different than its source node.
368    for (const edge of this.edgesIn) {
369      if (edge.overrides && edge.overrides.name === this.name && edge.overrides.value === this.version) {
370        if (!edge.overrides.isEqual(edge.from.overrides)) {
371          return true
372        }
373      }
374    }
375
376    return false
377  }
378
379  get package () {
380    return this[_package]
381  }
382
383  set package (pkg) {
384    // just detach them all.  we could make this _slightly_ more efficient
385    // by only detaching the ones that changed, but we'd still have to walk
386    // them all, and the comparison logic gets a bit tricky.  we generally
387    // only do this more than once at the root level, so the resolve() calls
388    // are only one level deep, and there's not much to be saved, anyway.
389    // simpler to just toss them all out.
390    for (const edge of this.edgesOut.values()) {
391      edge.detach()
392    }
393
394    this[_explanation] = null
395    /* istanbul ignore next - should be impossible */
396    if (!pkg || typeof pkg !== 'object') {
397      debug(() => {
398        throw new Error('setting Node.package to non-object')
399      })
400      pkg = {}
401    }
402    this[_package] = pkg
403    this.#loadWorkspaces()
404    this[_loadDeps]()
405    // do a hard reload, since the dependents may now be valid or invalid
406    // as a result of the package change.
407    this.edgesIn.forEach(edge => edge.reload(true))
408  }
409
410  // node.explain(nodes seen already, edge we're trying to satisfy
411  // if edge is not specified, it lists every edge into the node.
412  explain (edge = null, seen = []) {
413    if (this[_explanation]) {
414      return this[_explanation]
415    }
416
417    return this[_explanation] = this[_explain](edge, seen)
418  }
419
420  [_explain] (edge, seen) {
421    if (this.isProjectRoot && !this.sourceReference) {
422      return {
423        location: this.path,
424      }
425    }
426
427    const why = {
428      name: this.isProjectRoot || this.isTop ? this.packageName : this.name,
429      version: this.package.version,
430    }
431    if (this.errors.length || !this.packageName || !this.package.version) {
432      why.errors = this.errors.length ? this.errors : [
433        new Error('invalid package: lacks name and/or version'),
434      ]
435      why.package = this.package
436    }
437
438    if (this.root.sourceReference) {
439      const { name, version } = this.root.package
440      why.whileInstalling = {
441        name,
442        version,
443        path: this.root.sourceReference.path,
444      }
445    }
446
447    if (this.sourceReference) {
448      return this.sourceReference.explain(edge, seen)
449    }
450
451    if (seen.includes(this)) {
452      return why
453    }
454
455    why.location = this.location
456    why.isWorkspace = this.isWorkspace
457
458    // make a new list each time.  we can revisit, but not loop.
459    seen = seen.concat(this)
460
461    why.dependents = []
462    if (edge) {
463      why.dependents.push(edge.explain(seen))
464    } else {
465      // ignore invalid edges, since those aren't satisfied by this thing,
466      // and are not keeping it held in this spot anyway.
467      const edges = []
468      for (const edge of this.edgesIn) {
469        if (!edge.valid && !edge.from.isProjectRoot) {
470          continue
471        }
472
473        edges.push(edge)
474      }
475      for (const edge of edges) {
476        why.dependents.push(edge.explain(seen))
477      }
478    }
479
480    if (this.linksIn.size) {
481      why.linksIn = [...this.linksIn].map(link => link[_explain](edge, seen))
482    }
483
484    return why
485  }
486
487  isDescendantOf (node) {
488    for (let p = this; p; p = p.resolveParent) {
489      if (p === node) {
490        return true
491      }
492    }
493    return false
494  }
495
496  shouldOmit (omitSet) {
497    if (!omitSet.size) {
498      return false
499    }
500
501    const { top } = this
502
503    // if the top is not the root or workspace then we do not want to omit it
504    if (!top.isProjectRoot && !top.isWorkspace) {
505      return false
506    }
507
508    // omit node if the dep type matches any omit flags that were set
509    return (
510      this.peer && omitSet.has('peer') ||
511      this.dev && omitSet.has('dev') ||
512      this.optional && omitSet.has('optional') ||
513      this.devOptional && omitSet.has('optional') && omitSet.has('dev')
514    )
515  }
516
517  getBundler (path = []) {
518    // made a cycle, definitely not bundled!
519    if (path.includes(this)) {
520      return null
521    }
522
523    path.push(this)
524
525    const parent = this[_parent]
526    if (!parent) {
527      return null
528    }
529
530    const pBundler = parent.getBundler(path)
531    if (pBundler) {
532      return pBundler
533    }
534
535    const ppkg = parent.package
536    const bd = ppkg && ppkg.bundleDependencies
537    // explicit bundling
538    if (Array.isArray(bd) && bd.includes(this.name)) {
539      return parent
540    }
541
542    // deps that are deduped up to the bundling level are bundled.
543    // however, if they get their dep met further up than that,
544    // then they are not bundled.  Ie, installing a package with
545    // unmet bundled deps will not cause your deps to be bundled.
546    for (const edge of this.edgesIn) {
547      const eBundler = edge.from.getBundler(path)
548      if (!eBundler) {
549        continue
550      }
551
552      if (eBundler === parent) {
553        return eBundler
554      }
555    }
556
557    return null
558  }
559
560  get inBundle () {
561    return !!this.getBundler()
562  }
563
564  // when reifying, if a package is technically in a bundleDependencies list,
565  // but that list is the root project, we still have to install it.  This
566  // getter returns true if it's in a dependency's bundle list, not the root's.
567  get inDepBundle () {
568    const bundler = this.getBundler()
569    return !!bundler && bundler !== this.root
570  }
571
572  get isWorkspace () {
573    if (this.isProjectRoot) {
574      return false
575    }
576    const { root } = this
577    const { type, to } = root.edgesOut.get(this.packageName) || {}
578    return type === 'workspace' && to && (to.target === this || to === this)
579  }
580
581  get isRoot () {
582    return this === this.root
583  }
584
585  get isProjectRoot () {
586    // only treat as project root if it's the actual link that is the root, or the target of the root link, but NOT if it's another link to the same root that happens to be somewhere else.
587    return this === this.root || this === this.root.target
588  }
589
590  get isRegistryDependency () {
591    if (this.edgesIn.size === 0) {
592      return false
593    }
594    for (const edge of this.edgesIn) {
595      if (!npa(edge.spec).registry) {
596        return false
597      }
598    }
599    return true
600  }
601
602  * ancestry () {
603    for (let anc = this; anc; anc = anc.resolveParent) {
604      yield anc
605    }
606  }
607
608  set root (root) {
609    // setting to null means this is the new root
610    // should only ever be one step
611    while (root && root.root !== root) {
612      root = root.root
613    }
614
615    root = root || this
616
617    // delete from current root inventory
618    this[_delistFromMeta]()
619
620    // can't set the root (yet) if there's no way to determine location
621    // this allows us to do new Node({...}) and then set the root later.
622    // just make the assignment so we don't lose it, and move on.
623    if (!this.path || !root.realpath || !root.path) {
624      this.#root = root
625      return
626    }
627
628    // temporarily become a root node
629    this.#root = this
630
631    // break all linksIn, we're going to re-set them if needed later
632    for (const link of this.linksIn) {
633      link[_target] = null
634      this.linksIn.delete(link)
635    }
636
637    // temporarily break this link as well, we'll re-set if possible later
638    const { target } = this
639    if (this.isLink) {
640      if (target) {
641        target.linksIn.delete(this)
642        if (target.root === this) {
643          target[_delistFromMeta]()
644        }
645      }
646      this[_target] = null
647    }
648
649    // if this is part of a cascading root set, then don't do this bit
650    // but if the parent/fsParent is in a different set, we have to break
651    // that reference before proceeding
652    if (this.parent && this.parent.root !== root) {
653      this.parent.children.delete(this.name)
654      this[_parent] = null
655    }
656    if (this.fsParent && this.fsParent.root !== root) {
657      this.fsParent.fsChildren.delete(this)
658      this[_fsParent] = null
659    }
660
661    if (root === this) {
662      this[_refreshLocation]()
663    } else {
664      // setting to some different node.
665      const loc = relpath(root.realpath, this.path)
666      const current = root.inventory.get(loc)
667
668      // clobber whatever is there now
669      if (current) {
670        current.root = null
671      }
672
673      this.#root = root
674      // set this.location and add to inventory
675      this[_refreshLocation]()
676
677      // try to find our parent/fsParent in the new root inventory
678      for (const p of walkUp(dirname(this.path))) {
679        if (p === this.path) {
680          continue
681        }
682        const ploc = relpath(root.realpath, p)
683        const parent = root.inventory.get(ploc)
684        if (parent) {
685          /* istanbul ignore next - impossible */
686          if (parent.isLink) {
687            debug(() => {
688              throw Object.assign(new Error('assigning parentage to link'), {
689                path: this.path,
690                parent: parent.path,
691                parentReal: parent.realpath,
692              })
693            })
694            continue
695          }
696          const childLoc = `${ploc}${ploc ? '/' : ''}node_modules/${this.name}`
697          const isParent = this.location === childLoc
698          if (isParent) {
699            const oldChild = parent.children.get(this.name)
700            if (oldChild && oldChild !== this) {
701              oldChild.root = null
702            }
703            if (this.parent) {
704              this.parent.children.delete(this.name)
705              this.parent[_reloadNamedEdges](this.name)
706            }
707            parent.children.set(this.name, this)
708            this[_parent] = parent
709            // don't do it for links, because they don't have a target yet
710            // we'll hit them up a bit later on.
711            if (!this.isLink) {
712              parent[_reloadNamedEdges](this.name)
713            }
714          } else {
715            /* istanbul ignore if - should be impossible, since we break
716             * all fsParent/child relationships when moving? */
717            if (this.fsParent) {
718              this.fsParent.fsChildren.delete(this)
719            }
720            parent.fsChildren.add(this)
721            this[_fsParent] = parent
722          }
723          break
724        }
725      }
726
727      // if it doesn't have a parent, it's a top node
728      if (!this.parent) {
729        root.tops.add(this)
730      } else {
731        root.tops.delete(this)
732      }
733
734      // assign parentage for any nodes that need to have this as a parent
735      // this can happen when we have a node at nm/a/nm/b added *before*
736      // the node at nm/a, which might have the root node as a fsParent.
737      // we can't rely on the public setter here, because it calls into
738      // this function to set up these references!
739      // check dirname so that /foo isn't treated as the fsparent of /foo-bar
740      const nmloc = `${this.location}${this.location ? '/' : ''}node_modules/`
741      // only walk top nodes, since anything else already has a parent.
742      for (const child of root.tops) {
743        const isChild = child.location === nmloc + child.name
744        const isFsChild =
745          dirname(child.path).startsWith(this.path) &&
746          child !== this &&
747          !child.parent &&
748          (
749            !child.fsParent ||
750            child.fsParent === this ||
751            dirname(this.path).startsWith(child.fsParent.path)
752          )
753
754        if (!isChild && !isFsChild) {
755          continue
756        }
757
758        // set up the internal parentage links
759        if (this.isLink) {
760          child.root = null
761        } else {
762          // can't possibly have a parent, because it's in tops
763          if (child.fsParent) {
764            child.fsParent.fsChildren.delete(child)
765          }
766          child[_fsParent] = null
767          if (isChild) {
768            this.children.set(child.name, child)
769            child[_parent] = this
770            root.tops.delete(child)
771          } else {
772            this.fsChildren.add(child)
773            child[_fsParent] = this
774          }
775        }
776      }
777
778      // look for any nodes with the same realpath.  either they're links
779      // to that realpath, or a thing at that realpath if we're adding a link
780      // (if we're adding a regular node, we already deleted the old one)
781      for (const node of root.inventory.query('realpath', this.realpath)) {
782        if (node === this) {
783          continue
784        }
785
786        /* istanbul ignore next - should be impossible */
787        debug(() => {
788          if (node.root !== root) {
789            throw new Error('inventory contains node from other root')
790          }
791        })
792
793        if (this.isLink) {
794          const target = node.target
795          this[_target] = target
796          this[_package] = target.package
797          target.linksIn.add(this)
798          // reload edges here, because now we have a target
799          if (this.parent) {
800            this.parent[_reloadNamedEdges](this.name)
801          }
802          break
803        } else {
804          /* istanbul ignore else - should be impossible */
805          if (node.isLink) {
806            node[_target] = this
807            node[_package] = this.package
808            this.linksIn.add(node)
809            if (node.parent) {
810              node.parent[_reloadNamedEdges](node.name)
811            }
812          } else {
813            debug(() => {
814              throw Object.assign(new Error('duplicate node in root setter'), {
815                path: this.path,
816                realpath: this.realpath,
817                root: root.realpath,
818              })
819            })
820          }
821        }
822      }
823    }
824
825    // reload all edgesIn where the root doesn't match, so we don't have
826    // cross-tree dependency graphs
827    for (const edge of this.edgesIn) {
828      if (edge.from.root !== root) {
829        edge.reload()
830      }
831    }
832    // reload all edgesOut where root doesn't match, or is missing, since
833    // it might not be missing in the new tree
834    for (const edge of this.edgesOut.values()) {
835      if (!edge.to || edge.to.root !== root) {
836        edge.reload()
837      }
838    }
839
840    // now make sure our family comes along for the ride!
841    const family = new Set([
842      ...this.fsChildren,
843      ...this.children.values(),
844      ...this.inventory.values(),
845    ].filter(n => n !== this))
846
847    for (const child of family) {
848      if (child.root !== root) {
849        child[_delistFromMeta]()
850        child[_parent] = null
851        this.children.delete(child.name)
852        child[_fsParent] = null
853        this.fsChildren.delete(child)
854        for (const l of child.linksIn) {
855          l[_target] = null
856          child.linksIn.delete(l)
857        }
858      }
859    }
860    for (const child of family) {
861      if (child.root !== root) {
862        child.root = root
863      }
864    }
865
866    // if we had a target, and didn't find one in the new root, then bring
867    // it over as well, but only if we're setting the link into a new root,
868    // as we don't want to lose the target any time we remove a link.
869    if (this.isLink && target && !this.target && root !== this) {
870      target.root = root
871    }
872
873    // tree should always be valid upon root setter completion.
874    treeCheck(this)
875    if (this !== root) {
876      treeCheck(root)
877    }
878  }
879
880  get root () {
881    return this.#root || this
882  }
883
884  #loadWorkspaces () {
885    if (!this.#workspaces) {
886      return
887    }
888
889    for (const [name, path] of this.#workspaces.entries()) {
890      new Edge({ from: this, name, spec: `file:${path}`, type: 'workspace' })
891    }
892  }
893
894  [_loadDeps] () {
895    // Caveat!  Order is relevant!
896    // Packages in optionalDependencies are optional.
897    // Packages in both deps and devDeps are required.
898    // Note the subtle breaking change from v6: it is no longer possible
899    // to have a different spec for a devDep than production dep.
900
901    // Linked targets that are disconnected from the tree are tops,
902    // but don't have a 'path' field, only a 'realpath', because we
903    // don't know their canonical location. We don't need their devDeps.
904    const pd = this.package.peerDependencies
905    const ad = this.package.acceptDependencies || {}
906    if (pd && typeof pd === 'object' && !this.legacyPeerDeps) {
907      const pm = this.package.peerDependenciesMeta || {}
908      const peerDependencies = {}
909      const peerOptional = {}
910      for (const [name, dep] of Object.entries(pd)) {
911        if (pm[name]?.optional) {
912          peerOptional[name] = dep
913        } else {
914          peerDependencies[name] = dep
915        }
916      }
917      this.#loadDepType(peerDependencies, 'peer', ad)
918      this.#loadDepType(peerOptional, 'peerOptional', ad)
919    }
920
921    this.#loadDepType(this.package.dependencies, 'prod', ad)
922    this.#loadDepType(this.package.optionalDependencies, 'optional', ad)
923
924    const { globalTop, isTop, path, sourceReference } = this
925    const {
926      globalTop: srcGlobalTop,
927      isTop: srcTop,
928      path: srcPath,
929    } = sourceReference || {}
930    const thisDev = isTop && !globalTop && path
931    const srcDev = !sourceReference || srcTop && !srcGlobalTop && srcPath
932    if (thisDev && srcDev) {
933      this.#loadDepType(this.package.devDependencies, 'dev', ad)
934    }
935  }
936
937  #loadDepType (deps, type, ad) {
938    // Because of the order in which _loadDeps runs, we always want to
939    // prioritize a new edge over an existing one
940    for (const [name, spec] of Object.entries(deps || {})) {
941      const current = this.edgesOut.get(name)
942      if (!current || current.type !== 'workspace') {
943        new Edge({ from: this, name, spec, accept: ad[name], type })
944      }
945    }
946  }
947
948  get fsParent () {
949    // in debug setter prevents fsParent from being this
950    return this[_fsParent]
951  }
952
953  set fsParent (fsParent) {
954    if (!fsParent) {
955      if (this[_fsParent]) {
956        this.root = null
957      }
958      return
959    }
960
961    debug(() => {
962      if (fsParent === this) {
963        throw new Error('setting node to its own fsParent')
964      }
965
966      if (fsParent.realpath === this.realpath) {
967        throw new Error('setting fsParent to same path')
968      }
969
970      // the initial set MUST be an actual walk-up from the realpath
971      // subsequent sets will re-root on the new fsParent's path.
972      if (!this[_fsParent] && this.realpath.indexOf(fsParent.realpath) !== 0) {
973        throw Object.assign(new Error('setting fsParent improperly'), {
974          path: this.path,
975          realpath: this.realpath,
976          fsParent: {
977            path: fsParent.path,
978            realpath: fsParent.realpath,
979          },
980        })
981      }
982    })
983
984    if (fsParent.isLink) {
985      fsParent = fsParent.target
986    }
987
988    // setting a thing to its own fsParent is not normal, but no-op for safety
989    if (this === fsParent || fsParent.realpath === this.realpath) {
990      return
991    }
992
993    // nothing to do
994    if (this[_fsParent] === fsParent) {
995      return
996    }
997
998    const oldFsParent = this[_fsParent]
999    const newPath = !oldFsParent ? this.path
1000      : resolve(fsParent.path, relative(oldFsParent.path, this.path))
1001    const nmPath = resolve(fsParent.path, 'node_modules', this.name)
1002
1003    // this is actually the parent, set that instead
1004    if (newPath === nmPath) {
1005      this.parent = fsParent
1006      return
1007    }
1008
1009    const pathChange = newPath !== this.path
1010
1011    // remove from old parent/fsParent
1012    const oldParent = this.parent
1013    const oldName = this.name
1014    if (this.parent) {
1015      this.parent.children.delete(this.name)
1016      this[_parent] = null
1017    }
1018    if (this.fsParent) {
1019      this.fsParent.fsChildren.delete(this)
1020      this[_fsParent] = null
1021    }
1022
1023    // update this.path/realpath for this and all children/fsChildren
1024    if (pathChange) {
1025      this[_changePath](newPath)
1026    }
1027
1028    if (oldParent) {
1029      oldParent[_reloadNamedEdges](oldName)
1030    }
1031
1032    // clobbers anything at that path, resets all appropriate references
1033    this.root = fsParent.root
1034  }
1035
1036  // is it safe to replace one node with another?  check the edges to
1037  // make sure no one will get upset.  Note that the node might end up
1038  // having its own unmet dependencies, if the new node has new deps.
1039  // Note that there are cases where Arborist will opt to insert a node
1040  // into the tree even though this function returns false!  This is
1041  // necessary when a root dependency is added or updated, or when a
1042  // root dependency brings peer deps along with it.  In that case, we
1043  // will go ahead and create the invalid state, and then try to resolve
1044  // it with more tree construction, because it's a user request.
1045  canReplaceWith (node, ignorePeers) {
1046    if (node.name !== this.name) {
1047      return false
1048    }
1049
1050    if (node.packageName !== this.packageName) {
1051      return false
1052    }
1053
1054    // If this node has no dependencies, then it's irrelevant to check the override
1055    // rules of the replacement node.
1056    if (this.edgesOut.size) {
1057      // XXX need to check for two root nodes?
1058      if (node.overrides) {
1059        if (!node.overrides.isEqual(this.overrides)) {
1060          return false
1061        }
1062      } else {
1063        if (this.overrides) {
1064          return false
1065        }
1066      }
1067    }
1068
1069    ignorePeers = new Set(ignorePeers)
1070
1071    // gather up all the deps of this node and that are only depended
1072    // upon by deps of this node.  those ones don't count, since
1073    // they'll be replaced if this node is replaced anyway.
1074    const depSet = gatherDepSet([this], e => e.to !== this && e.valid)
1075
1076    for (const edge of this.edgesIn) {
1077      // when replacing peer sets, we need to be able to replace the entire
1078      // peer group, which means we ignore incoming edges from other peers
1079      // within the replacement set.
1080      if (!this.isTop &&
1081        edge.from.parent === this.parent &&
1082        edge.peer &&
1083        ignorePeers.has(edge.from.name)) {
1084        continue
1085      }
1086
1087      // only care about edges that don't originate from this node
1088      if (!depSet.has(edge.from) && !edge.satisfiedBy(node)) {
1089        return false
1090      }
1091    }
1092
1093    return true
1094  }
1095
1096  canReplace (node, ignorePeers) {
1097    return node.canReplaceWith(this, ignorePeers)
1098  }
1099
1100  // return true if it's safe to remove this node, because anything that
1101  // is depending on it would be fine with the thing that they would resolve
1102  // to if it was removed, or nothing is depending on it in the first place.
1103  canDedupe (preferDedupe = false, explicitRequest = false) {
1104    // not allowed to mess with shrinkwraps or bundles
1105    if (this.inDepBundle || this.inShrinkwrap) {
1106      return false
1107    }
1108
1109    // it's a top level pkg, or a dep of one
1110    if (!this.resolveParent || !this.resolveParent.resolveParent) {
1111      return false
1112    }
1113
1114    // no one wants it, remove it
1115    if (this.edgesIn.size === 0) {
1116      return true
1117    }
1118
1119    const other = this.resolveParent.resolveParent.resolve(this.name)
1120
1121    // nothing else, need this one
1122    if (!other) {
1123      return false
1124    }
1125
1126    // if it's the same thing, then always fine to remove
1127    if (other.matches(this)) {
1128      return true
1129    }
1130
1131    // if the other thing can't replace this, then skip it
1132    if (!other.canReplace(this)) {
1133      return false
1134    }
1135
1136    // if we prefer dedupe, or if the version is equal, take the other
1137    if (preferDedupe || semver.eq(other.version, this.version)) {
1138      return true
1139    }
1140
1141    // if our current version isn't the result of an override, then prefer to take the greater version
1142    if (!this.overridden && semver.gt(other.version, this.version)) {
1143      return true
1144    }
1145
1146    // if the other version was an explicit request, then prefer to take the other version
1147    if (explicitRequest) {
1148      return true
1149    }
1150
1151    return false
1152  }
1153
1154  satisfies (requested) {
1155    if (requested instanceof Edge) {
1156      return this.name === requested.name && requested.satisfiedBy(this)
1157    }
1158
1159    const parsed = npa(requested)
1160    const { name = this.name, rawSpec: spec } = parsed
1161    return this.name === name && this.satisfies(new Edge({
1162      from: new Node({ path: this.root.realpath }),
1163      type: 'prod',
1164      name,
1165      spec,
1166    }))
1167  }
1168
1169  matches (node) {
1170    // if the nodes are literally the same object, obviously a match.
1171    if (node === this) {
1172      return true
1173    }
1174
1175    // if the names don't match, they're different things, even if
1176    // the package contents are identical.
1177    if (node.name !== this.name) {
1178      return false
1179    }
1180
1181    // if they're links, they match if the targets match
1182    if (this.isLink) {
1183      return node.isLink && this.target.matches(node.target)
1184    }
1185
1186    // if they're two project root nodes, they're different if the paths differ
1187    if (this.isProjectRoot && node.isProjectRoot) {
1188      return this.path === node.path
1189    }
1190
1191    // if the integrity matches, then they're the same.
1192    if (this.integrity && node.integrity) {
1193      return this.integrity === node.integrity
1194    }
1195
1196    // if no integrity, check resolved
1197    if (this.resolved && node.resolved) {
1198      return this.resolved === node.resolved
1199    }
1200

Showing the first 1,200 of 1632 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai