@npmcli/arborist 2.7.1 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/bin/actual.js +4 -2
  2. package/bin/audit.js +12 -6
  3. package/bin/dedupe.js +49 -0
  4. package/bin/funding.js +4 -2
  5. package/bin/ideal.js +2 -1
  6. package/bin/lib/logging.js +4 -3
  7. package/bin/lib/options.js +14 -12
  8. package/bin/lib/timers.js +6 -3
  9. package/bin/license.js +9 -5
  10. package/bin/prune.js +6 -3
  11. package/bin/reify.js +6 -3
  12. package/bin/virtual.js +4 -2
  13. package/lib/add-rm-pkg-deps.js +25 -14
  14. package/lib/arborist/audit.js +2 -1
  15. package/lib/arborist/build-ideal-tree.js +246 -757
  16. package/lib/arborist/deduper.js +2 -1
  17. package/lib/arborist/index.js +8 -4
  18. package/lib/arborist/load-actual.js +32 -15
  19. package/lib/arborist/load-virtual.js +34 -18
  20. package/lib/arborist/load-workspaces.js +4 -2
  21. package/lib/arborist/rebuild.js +31 -16
  22. package/lib/arborist/reify.js +332 -119
  23. package/lib/audit-report.js +42 -22
  24. package/lib/calc-dep-flags.js +18 -9
  25. package/lib/can-place-dep.js +430 -0
  26. package/lib/case-insensitive-map.js +50 -0
  27. package/lib/consistent-resolve.js +2 -1
  28. package/lib/deepest-nesting-target.js +18 -0
  29. package/lib/dep-valid.js +8 -4
  30. package/lib/diff.js +74 -22
  31. package/lib/edge.js +29 -14
  32. package/lib/gather-dep-set.js +2 -1
  33. package/lib/inventory.js +12 -6
  34. package/lib/link.js +14 -9
  35. package/lib/node.js +269 -118
  36. package/lib/optional-set.js +4 -2
  37. package/lib/peer-entry-sets.js +77 -0
  38. package/lib/place-dep.js +578 -0
  39. package/lib/printable.js +48 -18
  40. package/lib/realpath.js +12 -6
  41. package/lib/shrinkwrap.js +168 -91
  42. package/lib/signal-handling.js +6 -3
  43. package/lib/spec-from-lock.js +7 -4
  44. package/lib/tracker.js +24 -18
  45. package/lib/tree-check.js +12 -6
  46. package/lib/version-from-tgz.js +4 -2
  47. package/lib/vuln.js +28 -16
  48. package/lib/yarn-lock.js +27 -15
  49. package/package.json +9 -13
  50. package/lib/peer-set.js +0 -25
@@ -0,0 +1,50 @@
1
+ // package children are represented with a Map object, but many file systems
2
+ // are case-insensitive and unicode-normalizing, so we need to treat
3
+ // node.children.get('FOO') and node.children.get('foo') as the same thing.
4
+
5
+ const _keys = Symbol('keys')
6
+ const _normKey = Symbol('normKey')
7
+ const normalize = s => s.normalize('NFKD').toLowerCase()
8
+ const OGMap = Map
9
+ module.exports = class Map extends OGMap {
10
+ constructor (items = []) {
11
+ super()
12
+ this[_keys] = new OGMap()
13
+ for (const [key, val] of items) {
14
+ this.set(key, val)
15
+ }
16
+ }
17
+
18
+ [_normKey] (key) {
19
+ return typeof key === 'string' ? normalize(key) : key
20
+ }
21
+
22
+ get (key) {
23
+ const normKey = this[_normKey](key)
24
+ return this[_keys].has(normKey) ? super.get(this[_keys].get(normKey))
25
+ : undefined
26
+ }
27
+
28
+ set (key, val) {
29
+ const normKey = this[_normKey](key)
30
+ if (this[_keys].has(normKey)) {
31
+ super.delete(this[_keys].get(normKey))
32
+ }
33
+ this[_keys].set(normKey, key)
34
+ return super.set(key, val)
35
+ }
36
+
37
+ delete (key) {
38
+ const normKey = this[_normKey](key)
39
+ if (this[_keys].has(normKey)) {
40
+ const prevKey = this[_keys].get(normKey)
41
+ this[_keys].delete(normKey)
42
+ return super.delete(prevKey)
43
+ }
44
+ }
45
+
46
+ has (key) {
47
+ const normKey = this[_normKey](key)
48
+ return this[_keys].has(normKey) && super.has(this[_keys].get(normKey))
49
+ }
50
+ }
@@ -5,8 +5,9 @@
5
5
  const npa = require('npm-package-arg')
6
6
  const relpath = require('./relpath.js')
7
7
  const consistentResolve = (resolved, fromPath, toPath, relPaths = false) => {
8
- if (!resolved)
8
+ if (!resolved) {
9
9
  return null
10
+ }
10
11
 
11
12
  try {
12
13
  const hostedOpt = { noCommittish: false }
@@ -0,0 +1,18 @@
1
+ // given a starting node, what is the *deepest* target where name could go?
2
+ // This is not on the Node class for the simple reason that we sometimes
3
+ // need to check the deepest *potential* target for a Node that is not yet
4
+ // added to the tree where we are checking.
5
+ const deepestNestingTarget = (start, name) => {
6
+ for (const target of start.ancestry()) {
7
+ // note: this will skip past the first target if edge is peer
8
+ if (target.isProjectRoot || !target.resolveParent || target.globalTop) {
9
+ return target
10
+ }
11
+ const targetEdge = target.edgesOut.get(name)
12
+ if (!targetEdge || !targetEdge.peer) {
13
+ return target
14
+ }
15
+ }
16
+ }
17
+
18
+ module.exports = deepestNestingTarget
package/lib/dep-valid.js CHANGED
@@ -44,8 +44,9 @@ const depValid = (child, requested, requestor) => {
44
44
 
45
45
  switch (requested.type) {
46
46
  case 'range':
47
- if (requested.fetchSpec === '*')
47
+ if (requested.fetchSpec === '*') {
48
48
  return true
49
+ }
49
50
  // fallthrough
50
51
  case 'version':
51
52
  // if it's a version or a range other than '*', semver it
@@ -108,17 +109,20 @@ const depValid = (child, requested, requestor) => {
108
109
  }
109
110
 
110
111
  const tarballValid = (child, requested, requestor) => {
111
- if (child.isLink)
112
+ if (child.isLink) {
112
113
  return false
114
+ }
113
115
 
114
- if (child.resolved)
116
+ if (child.resolved) {
115
117
  return child.resolved.replace(/\\/g, '/') === `file:${requested.fetchSpec.replace(/\\/g, '/')}`
118
+ }
116
119
 
117
120
  // if we have a legacy mutated package.json file. we can't be 100%
118
121
  // sure that it resolved to the same file, but if it was the same
119
122
  // request, that's a pretty good indicator of sameness.
120
- if (child.package._requested)
123
+ if (child.package._requested) {
121
124
  return child.package._requested.saveSpec === requested.saveSpec
125
+ }
122
126
 
123
127
  // ok, we're probably dealing with some legacy cruft here, not much
124
128
  // we can do at this point unfortunately.
package/lib/diff.js CHANGED
@@ -31,7 +31,12 @@ class Diff {
31
31
  this.removed = []
32
32
  }
33
33
 
34
- static calculate ({actual, ideal, filterNodes = [], shrinkwrapInflated = new Set()}) {
34
+ static calculate ({
35
+ actual,
36
+ ideal,
37
+ filterNodes = [],
38
+ shrinkwrapInflated = new Set(),
39
+ }) {
35
40
  // if there's a filterNode, then:
36
41
  // - get the path from the root to the filterNode. The root or
37
42
  // root.target should have an edge either to the filterNode or
@@ -43,8 +48,9 @@ class Diff {
43
48
  const extraneous = new Set()
44
49
  for (const filterNode of filterNodes) {
45
50
  const { root } = filterNode
46
- if (root !== ideal && root !== actual)
51
+ if (root !== ideal && root !== actual) {
47
52
  throw new Error('invalid filterNode: outside idealTree/actualTree')
53
+ }
48
54
  const rootTarget = root.target
49
55
  const edge = [...rootTarget.edgesOut.values()].filter(e => {
50
56
  return e.to && (e.to === filterNode || e.to.target === filterNode)
@@ -73,8 +79,9 @@ class Diff {
73
79
  : [...actualNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
74
80
  if (actualNode) {
75
81
  for (const child of actualNode.children.values()) {
76
- if (child.extraneous)
82
+ if (child.extraneous) {
77
83
  extraneous.add(child)
84
+ }
78
85
  }
79
86
  }
80
87
 
@@ -82,8 +89,9 @@ class Diff {
82
89
  },
83
90
  })
84
91
  }
85
- for (const extra of extraneous)
92
+ for (const extra of extraneous) {
86
93
  filterSet.add(extra)
94
+ }
87
95
 
88
96
  return depth({
89
97
  tree: new Diff({actual, ideal, filterSet, shrinkwrapInflated}),
@@ -94,23 +102,27 @@ class Diff {
94
102
  }
95
103
 
96
104
  const getAction = ({actual, ideal}) => {
97
- if (!ideal)
105
+ if (!ideal) {
98
106
  return 'REMOVE'
107
+ }
99
108
 
100
109
  // bundled meta-deps are copied over to the ideal tree when we visit it,
101
110
  // so they'll appear to be missing here. There's no need to handle them
102
111
  // in the diff, though, because they'll be replaced at reify time anyway
103
112
  // Otherwise, add the missing node.
104
- if (!actual)
113
+ if (!actual) {
105
114
  return ideal.inDepBundle ? null : 'ADD'
115
+ }
106
116
 
107
117
  // always ignore the root node
108
- if (ideal.isRoot && actual.isRoot)
118
+ if (ideal.isRoot && actual.isRoot) {
109
119
  return null
120
+ }
110
121
 
111
122
  // if the versions don't match, it's a change no matter what
112
- if (ideal.version !== actual.version)
123
+ if (ideal.version !== actual.version) {
113
124
  return 'CHANGE'
125
+ }
114
126
 
115
127
  const binsExist = ideal.binPaths.every((path) => existsSync(path))
116
128
 
@@ -125,33 +137,38 @@ const getAction = ({actual, ideal}) => {
125
137
  const noIntegrity = !ideal.integrity && !actual.integrity
126
138
  const noResolved = !ideal.resolved && !actual.resolved
127
139
  const resolvedMatch = ideal.resolved && ideal.resolved === actual.resolved
128
- if (noIntegrity && binsExist && (resolvedMatch || noResolved))
140
+ if (noIntegrity && binsExist && (resolvedMatch || noResolved)) {
129
141
  return null
142
+ }
130
143
 
131
144
  // otherwise, verify that it's the same bits
132
145
  // note that if ideal has integrity, and resolved doesn't, we treat
133
146
  // that as a 'change', so that it gets re-fetched and locked down.
134
147
  const integrityMismatch = !ideal.integrity || !actual.integrity ||
135
148
  !ssri.parse(ideal.integrity).match(actual.integrity)
136
- if (integrityMismatch || !binsExist)
149
+ if (integrityMismatch || !binsExist) {
137
150
  return 'CHANGE'
151
+ }
138
152
 
139
153
  return null
140
154
  }
141
155
 
142
156
  const allChildren = node => {
143
- if (!node)
157
+ if (!node) {
144
158
  return new Map()
159
+ }
145
160
 
146
161
  // if the node is root, and also a link, then what we really
147
162
  // want is to traverse the target's children
148
- if (node.isRoot && node.isLink)
163
+ if (node.isRoot && node.isLink) {
149
164
  return allChildren(node.target)
165
+ }
150
166
 
151
167
  const kids = new Map()
152
168
  for (const n of [node, ...node.fsChildren]) {
153
- for (const kid of n.children.values())
169
+ for (const kid of n.children.values()) {
154
170
  kids.set(kid.path, kid)
171
+ }
155
172
  }
156
173
  return kids
157
174
  }
@@ -160,7 +177,14 @@ const allChildren = node => {
160
177
  // to create the diff tree
161
178
  const getChildren = diff => {
162
179
  const children = []
163
- const {actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated} = diff
180
+ const {
181
+ actual,
182
+ ideal,
183
+ unchanged,
184
+ removed,
185
+ filterSet,
186
+ shrinkwrapInflated,
187
+ } = diff
164
188
 
165
189
  // Note: we DON'T diff fsChildren themselves, because they are either
166
190
  // included in the package contents, or part of some other project, and
@@ -182,26 +206,45 @@ const getChildren = diff => {
182
206
  for (const path of paths) {
183
207
  const actual = actualKids.get(path)
184
208
  const ideal = idealKids.get(path)
185
- diffNode(actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated)
209
+ diffNode({
210
+ actual,
211
+ ideal,
212
+ children,
213
+ unchanged,
214
+ removed,
215
+ filterSet,
216
+ shrinkwrapInflated,
217
+ })
186
218
  }
187
219
 
188
- if (diff.leaves && !children.length)
220
+ if (diff.leaves && !children.length) {
189
221
  diff.leaves.push(diff)
222
+ }
190
223
 
191
224
  return children
192
225
  }
193
226
 
194
- const diffNode = (actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated) => {
195
- if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual)))
227
+ const diffNode = ({
228
+ actual,
229
+ ideal,
230
+ children,
231
+ unchanged,
232
+ removed,
233
+ filterSet,
234
+ shrinkwrapInflated,
235
+ }) => {
236
+ if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual))) {
196
237
  return
238
+ }
197
239
 
198
240
  const action = getAction({actual, ideal})
199
241
 
200
242
  // if it's a match, then get its children
201
243
  // otherwise, this is the child diff node
202
244
  if (action || (!shrinkwrapInflated.has(ideal) && ideal.hasShrinkwrap)) {
203
- if (action === 'REMOVE')
245
+ if (action === 'REMOVE') {
204
246
  removed.push(actual)
247
+ }
205
248
  children.push(new Diff({actual, ideal, filterSet, shrinkwrapInflated}))
206
249
  } else {
207
250
  unchanged.push(ideal)
@@ -227,13 +270,22 @@ const diffNode = (actual, ideal, children, unchanged, removed, filterSet, shrink
227
270
  if (actual && bd && bd.length) {
228
271
  const bundledChildren = []
229
272
  for (const node of actual.children.values()) {
230
- if (node.inBundle)
273
+ if (node.inBundle) {
231
274
  bundledChildren.push(node)
275
+ }
232
276
  }
233
- for (const node of bundledChildren)
277
+ for (const node of bundledChildren) {
234
278
  node.parent = ideal
279
+ }
235
280
  }
236
- children.push(...getChildren({actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated}))
281
+ children.push(...getChildren({
282
+ actual,
283
+ ideal,
284
+ unchanged,
285
+ removed,
286
+ filterSet,
287
+ shrinkwrapInflated,
288
+ }))
237
289
  }
238
290
  }
239
291
 
package/lib/edge.js CHANGED
@@ -37,6 +37,7 @@ const printableEdge = (edge) => {
37
37
  ...(edgeFrom != null ? { from: edgeFrom } : {}),
38
38
  ...(edgeTo ? { to: edgeTo } : {}),
39
39
  ...(edge.error ? { error: edge.error } : {}),
40
+ ...(edge.overridden ? { overridden: true } : {}),
40
41
  })
41
42
  }
42
43
 
@@ -44,22 +45,26 @@ class Edge {
44
45
  constructor (options) {
45
46
  const { type, name, spec, accept, from } = options
46
47
 
47
- if (typeof spec !== 'string')
48
+ if (typeof spec !== 'string') {
48
49
  throw new TypeError('must provide string spec')
50
+ }
49
51
 
50
- if (type === 'workspace' && npa(spec).type !== 'directory')
52
+ if (type === 'workspace' && npa(spec).type !== 'directory') {
51
53
  throw new TypeError('workspace edges must be a symlink')
54
+ }
52
55
 
53
56
  this[_spec] = spec
54
57
 
55
58
  if (accept !== undefined) {
56
- if (typeof accept !== 'string')
59
+ if (typeof accept !== 'string') {
57
60
  throw new TypeError('accept field must be a string if provided')
61
+ }
58
62
  this[_accept] = accept || '*'
59
63
  }
60
64
 
61
- if (typeof name !== 'string')
65
+ if (typeof name !== 'string') {
62
66
  throw new TypeError('must provide dependency name')
67
+ }
63
68
  this[_name] = name
64
69
 
65
70
  if (!types.has(type)) {
@@ -68,19 +73,23 @@ class Edge {
68
73
  `(valid types are: ${Edge.types.join(', ')})`)
69
74
  }
70
75
  this[_type] = type
71
- if (!from)
76
+ if (!from) {
72
77
  throw new TypeError('must provide "from" node')
78
+ }
73
79
  this[_setFrom](from)
74
80
  this[_error] = this[_loadError]()
81
+ this.overridden = false
75
82
  }
76
83
 
77
84
  satisfiedBy (node) {
78
- return depValid(node, this.spec, this.accept, this.from)
85
+ return node.name === this.name &&
86
+ depValid(node, this.spec, this.accept, this.from)
79
87
  }
80
88
 
81
89
  explain (seen = []) {
82
- if (this[_explanation])
90
+ if (this[_explanation]) {
83
91
  return this[_explanation]
92
+ }
84
93
 
85
94
  return this[_explanation] = this[_explain](seen)
86
95
  }
@@ -99,8 +108,9 @@ class Edge {
99
108
  }
100
109
 
101
110
  get bundled () {
102
- if (!this.from)
111
+ if (!this.from) {
103
112
  return false
113
+ }
104
114
  const { package: { bundleDependencies = [] } } = this.from
105
115
  return bundleDependencies.includes(this.name)
106
116
  }
@@ -165,7 +175,7 @@ class Edge {
165
175
  [_loadError] () {
166
176
  return !this[_to] ? (this.optional ? null : 'MISSING')
167
177
  : this.peer && this.from === this.to.parent && !this.from.isTop ? 'PEER LOCAL'
168
- : !depValid(this.to, this.spec, this.accept, this.from) ? 'INVALID'
178
+ : !this.satisfiedBy(this.to) ? 'INVALID'
169
179
  : 'OK'
170
180
  }
171
181
 
@@ -173,20 +183,24 @@ class Edge {
173
183
  this[_explanation] = null
174
184
  const newTo = this[_from].resolve(this.name)
175
185
  if (newTo !== this[_to]) {
176
- if (this[_to])
186
+ if (this[_to]) {
177
187
  this[_to].edgesIn.delete(this)
188
+ }
178
189
  this[_to] = newTo
179
190
  this[_error] = this[_loadError]()
180
- if (this[_to])
191
+ if (this[_to]) {
181
192
  this[_to].addEdgeIn(this)
182
- } else if (hard)
193
+ }
194
+ } else if (hard) {
183
195
  this[_error] = this[_loadError]()
196
+ }
184
197
  }
185
198
 
186
199
  detach () {
187
200
  this[_explanation] = null
188
- if (this[_to])
201
+ if (this[_to]) {
189
202
  this[_to].edgesIn.delete(this)
203
+ }
190
204
  this[_from].edgesOut.delete(this.name)
191
205
  this[_to] = null
192
206
  this[_error] = 'DETACHED'
@@ -196,8 +210,9 @@ class Edge {
196
210
  [_setFrom] (node) {
197
211
  this[_explanation] = null
198
212
  this[_from] = node
199
- if (node.edgesOut.has(this.name))
213
+ if (node.edgesOut.has(this.name)) {
200
214
  node.edgesOut.get(this.name).detach()
215
+ }
201
216
  node.addEdgeOut(this)
202
217
  this.reload()
203
218
  }
@@ -14,8 +14,9 @@ const gatherDepSet = (set, edgeFilter) => {
14
14
  // as the deps set increases in size.
15
15
  for (const node of deps) {
16
16
  for (const edge of node.edgesOut.values()) {
17
- if (edge.to && edgeFilter(edge))
17
+ if (edge.to && edgeFilter(edge)) {
18
18
  deps.add(edge.to)
19
+ }
19
20
  }
20
21
  }
21
22
 
package/lib/inventory.js CHANGED
@@ -13,11 +13,13 @@ const debug = require('./debug.js')
13
13
  const getLicense = pkg => {
14
14
  if (pkg) {
15
15
  const lic = pkg.license || pkg.licence
16
- if (lic)
16
+ if (lic) {
17
17
  return lic
18
+ }
18
19
  const lics = pkg.licenses || pkg.licences
19
- if (Array.isArray(lics))
20
+ if (Array.isArray(lics)) {
20
21
  return lics[0]
22
+ }
21
23
  }
22
24
  }
23
25
 
@@ -42,8 +44,9 @@ class Inventory extends Map {
42
44
 
43
45
  * filter (fn) {
44
46
  for (const node of this.values()) {
45
- if (fn(node))
47
+ if (fn(node)) {
46
48
  yield node
49
+ }
47
50
  }
48
51
  }
49
52
 
@@ -62,8 +65,9 @@ class Inventory extends Map {
62
65
 
63
66
  const current = super.get(node[this.primaryKey])
64
67
  if (current) {
65
- if (current === node)
68
+ if (current === node) {
66
69
  return
70
+ }
67
71
  this.delete(current)
68
72
  }
69
73
  super.set(node[this.primaryKey], node)
@@ -85,8 +89,9 @@ class Inventory extends Map {
85
89
  }
86
90
 
87
91
  delete (node) {
88
- if (!this.has(node))
92
+ if (!this.has(node)) {
89
93
  return
94
+ }
90
95
 
91
96
  super.delete(node[this.primaryKey])
92
97
  for (const [key, map] of this[_index].entries()) {
@@ -95,8 +100,9 @@ class Inventory extends Map {
95
100
  const set = map.get(val)
96
101
  if (set) {
97
102
  set.delete(node)
98
- if (set.size === 0)
103
+ if (set.size === 0) {
99
104
  map.delete(node[key])
105
+ }
100
106
  }
101
107
  }
102
108
  }
package/lib/link.js CHANGED
@@ -11,8 +11,9 @@ class Link extends Node {
11
11
  constructor (options) {
12
12
  const { root, realpath, target, parent, fsParent } = options
13
13
 
14
- if (!realpath && !(target && target.path))
14
+ if (!realpath && !(target && target.path)) {
15
15
  throw new TypeError('must provide realpath for Link node')
16
+ }
16
17
 
17
18
  super({
18
19
  ...options,
@@ -23,11 +24,11 @@ class Link extends Node {
23
24
  : null),
24
25
  })
25
26
 
26
- if (target)
27
+ if (target) {
27
28
  this.target = target
28
- else if (this.realpath === this.root.path)
29
+ } else if (this.realpath === this.root.path) {
29
30
  this.target = this.root
30
- else {
31
+ } else {
31
32
  this.target = new Node({
32
33
  ...options,
33
34
  path: realpath,
@@ -48,8 +49,9 @@ class Link extends Node {
48
49
 
49
50
  set target (target) {
50
51
  const current = this[_target]
51
- if (target === current)
52
+ if (target === current) {
52
53
  return
54
+ }
53
55
 
54
56
  if (current && current.then) {
55
57
  debug(() => {
@@ -72,25 +74,28 @@ class Link extends Node {
72
74
  }
73
75
 
74
76
  if (!target) {
75
- if (current && current.linksIn)
77
+ if (current && current.linksIn) {
76
78
  current.linksIn.delete(this)
79
+ }
77
80
  if (this.path) {
78
81
  this[_delistFromMeta]()
79
82
  this[_target] = null
80
83
  this.package = {}
81
84
  this[_refreshLocation]()
82
- } else
85
+ } else {
83
86
  this[_target] = null
87
+ }
84
88
  return
85
89
  }
86
90
 
87
91
  if (!this.path) {
88
92
  // temp node pending assignment to a tree
89
93
  // we know it's not in the inventory yet, because no path.
90
- if (target.path)
94
+ if (target.path) {
91
95
  this.realpath = target.path
92
- else
96
+ } else {
93
97
  target.path = target.realpath = this.realpath
98
+ }
94
99
  target.root = this.root
95
100
  this[_target] = target
96
101
  target.linksIn.add(this)