• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const relpath = require('./relpath.js')
2const Node = require('./node.js')
3const _loadDeps = Symbol.for('Arborist.Node._loadDeps')
4const _target = Symbol.for('_target')
5const { dirname } = require('path')
6// defined by Node class
7const _delistFromMeta = Symbol.for('_delistFromMeta')
8const _refreshLocation = Symbol.for('_refreshLocation')
9class Link extends Node {
10  constructor (options) {
11    const { root, realpath, target, parent, fsParent, isStoreLink } = options
12
13    if (!realpath && !(target && target.path)) {
14      throw new TypeError('must provide realpath for Link node')
15    }
16
17    super({
18      ...options,
19      realpath: realpath || target.path,
20      root: root || (parent ? parent.root
21      : fsParent ? fsParent.root
22      : target ? target.root
23      : null),
24    })
25
26    this.isStoreLink = isStoreLink || false
27
28    if (target) {
29      this.target = target
30    } else if (this.realpath === this.root.path) {
31      this.target = this.root
32    } else {
33      this.target = new Node({
34        ...options,
35        path: realpath,
36        parent: null,
37        fsParent: null,
38        root: this.root,
39      })
40    }
41  }
42
43  get version () {
44    return this.target ? this.target.version : this.package.version || ''
45  }
46
47  get target () {
48    return this[_target]
49  }
50
51  set target (target) {
52    const current = this[_target]
53    if (target === current) {
54      return
55    }
56
57    if (!target) {
58      if (current && current.linksIn) {
59        current.linksIn.delete(this)
60      }
61      if (this.path) {
62        this[_delistFromMeta]()
63        this[_target] = null
64        this.package = {}
65        this[_refreshLocation]()
66      } else {
67        this[_target] = null
68      }
69      return
70    }
71
72    if (!this.path) {
73      // temp node pending assignment to a tree
74      // we know it's not in the inventory yet, because no path.
75      if (target.path) {
76        this.realpath = target.path
77      } else {
78        target.path = target.realpath = this.realpath
79      }
80      target.root = this.root
81      this[_target] = target
82      target.linksIn.add(this)
83      this.package = target.package
84      return
85    }
86
87    // have to refresh metadata, because either realpath or package
88    // is very likely changing.
89    this[_delistFromMeta]()
90    this.package = target.package
91    this.realpath = target.path
92    this[_refreshLocation]()
93
94    target.root = this.root
95  }
96
97  // a link always resolves to the relative path to its target
98  get resolved () {
99    // the path/realpath guard is there for the benefit of setting
100    // these things in the "wrong" order
101    return this.path && this.realpath
102      ? `file:${relpath(dirname(this.path), this.realpath).replace(/#/g, '%23')}`
103      : null
104  }
105
106  set resolved (r) {}
107
108  // deps are resolved on the target, not the Link
109  // so this is a no-op
110  [_loadDeps] () {}
111
112  // links can't have children, only their targets can
113  // fix it to an empty list so that we can still call
114  // things that iterate over them, just as a no-op
115  get children () {
116    return new Map()
117  }
118
119  set children (c) {}
120
121  get isLink () {
122    return true
123  }
124}
125
126module.exports = Link
127