@npmcli/arborist 2.8.2 → 2.9.0
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.
- package/bin/actual.js +4 -2
- package/bin/audit.js +12 -6
- package/bin/dedupe.js +6 -3
- package/bin/funding.js +4 -2
- package/bin/ideal.js +2 -1
- package/bin/lib/logging.js +4 -3
- package/bin/lib/options.js +14 -12
- package/bin/lib/timers.js +6 -3
- package/bin/license.js +9 -5
- package/bin/prune.js +6 -3
- package/bin/reify.js +6 -3
- package/bin/virtual.js +4 -2
- package/lib/add-rm-pkg-deps.js +28 -15
- package/lib/arborist/audit.js +2 -1
- package/lib/arborist/build-ideal-tree.js +139 -72
- package/lib/arborist/deduper.js +2 -1
- package/lib/arborist/index.js +8 -4
- package/lib/arborist/load-actual.js +28 -13
- package/lib/arborist/load-virtual.js +37 -20
- package/lib/arborist/load-workspaces.js +4 -2
- package/lib/arborist/rebuild.js +34 -17
- package/lib/arborist/reify.js +153 -76
- package/lib/audit-report.js +44 -23
- package/lib/calc-dep-flags.js +18 -9
- package/lib/can-place-dep.js +59 -30
- package/lib/case-insensitive-map.js +4 -2
- package/lib/consistent-resolve.js +2 -1
- package/lib/deepest-nesting-target.js +4 -2
- package/lib/dep-valid.js +8 -4
- package/lib/diff.js +74 -22
- package/lib/edge.js +26 -13
- package/lib/gather-dep-set.js +2 -1
- package/lib/inventory.js +12 -6
- package/lib/link.js +14 -9
- package/lib/node.js +216 -113
- package/lib/optional-set.js +4 -2
- package/lib/peer-entry-sets.js +10 -5
- package/lib/place-dep.js +111 -37
- package/lib/printable.js +46 -25
- package/lib/realpath.js +12 -6
- package/lib/shrinkwrap.js +164 -90
- package/lib/signal-handling.js +6 -3
- package/lib/spec-from-lock.js +7 -4
- package/lib/tracker.js +24 -18
- package/lib/tree-check.js +12 -6
- package/lib/version-from-tgz.js +4 -2
- package/lib/vuln.js +44 -22
- package/lib/yarn-lock.js +34 -21
- package/package.json +8 -10
package/lib/tracker.js
CHANGED
|
@@ -11,35 +11,37 @@ module.exports = cls => class Tracker extends cls {
|
|
|
11
11
|
|
|
12
12
|
addTracker (section, subsection = null, key = null) {
|
|
13
13
|
// TrackerGroup type object not found
|
|
14
|
-
if (!this.log.newGroup)
|
|
14
|
+
if (!this.log.newGroup) {
|
|
15
15
|
return
|
|
16
|
+
}
|
|
16
17
|
|
|
17
|
-
if (section === null || section === undefined)
|
|
18
|
+
if (section === null || section === undefined) {
|
|
18
19
|
this[_onError](`Tracker can't be null or undefined`)
|
|
20
|
+
}
|
|
19
21
|
|
|
20
|
-
if (key === null)
|
|
22
|
+
if (key === null) {
|
|
21
23
|
key = subsection
|
|
24
|
+
}
|
|
22
25
|
|
|
23
26
|
const hasTracker = this[_progress].has(section)
|
|
24
27
|
const hasSubtracker = this[_progress].has(`${section}:${key}`)
|
|
25
28
|
|
|
26
|
-
if (hasTracker && subsection === null)
|
|
29
|
+
if (hasTracker && subsection === null) {
|
|
27
30
|
// 0. existing tracker, no subsection
|
|
28
31
|
this[_onError](`Tracker "${section}" already exists`)
|
|
29
|
-
|
|
30
|
-
else if (!hasTracker && subsection === null) {
|
|
32
|
+
} else if (!hasTracker && subsection === null) {
|
|
31
33
|
// 1. no existing tracker, no subsection
|
|
32
34
|
// Create a new tracker from this.log
|
|
33
35
|
// starts progress bar
|
|
34
|
-
if (this[_progress].size === 0)
|
|
36
|
+
if (this[_progress].size === 0) {
|
|
35
37
|
this.log.enableProgress()
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
this[_progress].set(section, this.log.newGroup(section))
|
|
38
|
-
} else if (!hasTracker && subsection !== null)
|
|
41
|
+
} else if (!hasTracker && subsection !== null) {
|
|
39
42
|
// 2. no parent tracker and subsection
|
|
40
43
|
this[_onError](`Parent tracker "${section}" does not exist`)
|
|
41
|
-
|
|
42
|
-
else if (!hasTracker || !hasSubtracker) {
|
|
44
|
+
} else if (!hasTracker || !hasSubtracker) {
|
|
43
45
|
// 3. existing parent tracker, no subsection tracker
|
|
44
46
|
// Create a new subtracker in this[_progress] from parent tracker
|
|
45
47
|
this[_progress].set(`${section}:${key}`,
|
|
@@ -52,14 +54,17 @@ module.exports = cls => class Tracker extends cls {
|
|
|
52
54
|
|
|
53
55
|
finishTracker (section, subsection = null, key = null) {
|
|
54
56
|
// TrackerGroup type object not found
|
|
55
|
-
if (!this.log.newGroup)
|
|
57
|
+
if (!this.log.newGroup) {
|
|
56
58
|
return
|
|
59
|
+
}
|
|
57
60
|
|
|
58
|
-
if (section === null || section === undefined)
|
|
61
|
+
if (section === null || section === undefined) {
|
|
59
62
|
this[_onError](`Tracker can't be null or undefined`)
|
|
63
|
+
}
|
|
60
64
|
|
|
61
|
-
if (key === null)
|
|
65
|
+
if (key === null) {
|
|
62
66
|
key = subsection
|
|
67
|
+
}
|
|
63
68
|
|
|
64
69
|
const hasTracker = this[_progress].has(section)
|
|
65
70
|
const hasSubtracker = this[_progress].has(`${section}:${key}`)
|
|
@@ -71,8 +76,9 @@ module.exports = cls => class Tracker extends cls {
|
|
|
71
76
|
// not have any remaining children
|
|
72
77
|
const keys = this[_progress].keys()
|
|
73
78
|
for (const key of keys) {
|
|
74
|
-
if (key.match(new RegExp(section + ':')))
|
|
79
|
+
if (key.match(new RegExp(section + ':'))) {
|
|
75
80
|
this.finishTracker(section, key)
|
|
81
|
+
}
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
// remove parent tracker
|
|
@@ -81,13 +87,13 @@ module.exports = cls => class Tracker extends cls {
|
|
|
81
87
|
|
|
82
88
|
// remove progress bar if all
|
|
83
89
|
// trackers are finished
|
|
84
|
-
if (this[_progress].size === 0)
|
|
90
|
+
if (this[_progress].size === 0) {
|
|
85
91
|
this.log.disableProgress()
|
|
86
|
-
|
|
92
|
+
}
|
|
93
|
+
} else if (!hasTracker && subsection === null) {
|
|
87
94
|
// 1. no existing parent tracker, no subsection
|
|
88
95
|
this[_onError](`Tracker "${section}" does not exist`)
|
|
89
|
-
|
|
90
|
-
else if (!hasTracker || hasSubtracker) {
|
|
96
|
+
} else if (!hasTracker || hasSubtracker) {
|
|
91
97
|
// 2. subtracker exists
|
|
92
98
|
// Finish subtracker and remove from this[_progress]
|
|
93
99
|
this[_progress].get(`${section}:${key}`).finish()
|
package/lib/tree-check.js
CHANGED
|
@@ -5,8 +5,9 @@ const checkTree = (tree, checkUnreachable = true) => {
|
|
|
5
5
|
|
|
6
6
|
// this can only happen in tests where we have a "tree" object
|
|
7
7
|
// that isn't actually a tree.
|
|
8
|
-
if (!tree.root || !tree.root.inventory)
|
|
8
|
+
if (!tree.root || !tree.root.inventory) {
|
|
9
9
|
return tree
|
|
10
|
+
}
|
|
10
11
|
|
|
11
12
|
const { inventory } = tree.root
|
|
12
13
|
const seen = new Set()
|
|
@@ -21,8 +22,9 @@ const checkTree = (tree, checkUnreachable = true) => {
|
|
|
21
22
|
'root=' + !!(node && node.isRoot),
|
|
22
23
|
])
|
|
23
24
|
|
|
24
|
-
if (!node || seen.has(node) || node.then)
|
|
25
|
+
if (!node || seen.has(node) || node.then) {
|
|
25
26
|
return
|
|
27
|
+
}
|
|
26
28
|
|
|
27
29
|
seen.add(node)
|
|
28
30
|
|
|
@@ -116,14 +118,18 @@ const checkTree = (tree, checkUnreachable = true) => {
|
|
|
116
118
|
check(fsParent, node, 'fsParent')
|
|
117
119
|
check(target, node, 'target')
|
|
118
120
|
log.push(['CHILDREN', node.location, ...node.children.keys()])
|
|
119
|
-
for (const kid of node.children.values())
|
|
121
|
+
for (const kid of node.children.values()) {
|
|
120
122
|
check(kid, node, 'children')
|
|
121
|
-
|
|
123
|
+
}
|
|
124
|
+
for (const kid of node.fsChildren) {
|
|
122
125
|
check(kid, node, 'fsChildren')
|
|
123
|
-
|
|
126
|
+
}
|
|
127
|
+
for (const link of node.linksIn) {
|
|
124
128
|
check(link, node, 'linksIn')
|
|
125
|
-
|
|
129
|
+
}
|
|
130
|
+
for (const top of node.tops) {
|
|
126
131
|
check(top, node, 'tops')
|
|
132
|
+
}
|
|
127
133
|
log.push(['DONE', node.location])
|
|
128
134
|
}
|
|
129
135
|
check(tree)
|
package/lib/version-from-tgz.js
CHANGED
|
@@ -4,8 +4,9 @@ const {basename} = require('path')
|
|
|
4
4
|
const {parse} = require('url')
|
|
5
5
|
module.exports = (name, tgz) => {
|
|
6
6
|
const base = basename(tgz)
|
|
7
|
-
if (!base.endsWith('.tgz'))
|
|
7
|
+
if (!base.endsWith('.tgz')) {
|
|
8
8
|
return null
|
|
9
|
+
}
|
|
9
10
|
|
|
10
11
|
const u = parse(tgz)
|
|
11
12
|
if (/^https?:/.test(u.protocol)) {
|
|
@@ -35,8 +36,9 @@ module.exports = (name, tgz) => {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
const versionFromBaseScopeName = (base, scope, name) => {
|
|
38
|
-
if (!base.startsWith(name + '-'))
|
|
39
|
+
if (!base.startsWith(name + '-')) {
|
|
39
40
|
return null
|
|
41
|
+
}
|
|
40
42
|
|
|
41
43
|
const parsed = semver.parse(base.substring(name.length + 1, base.length - 4))
|
|
42
44
|
return parsed ? {
|
package/lib/vuln.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const {satisfies, simplifyRange} = require('semver')
|
|
15
15
|
const semverOpt = { loose: true, includePrerelease: true }
|
|
16
16
|
|
|
17
|
+
const localeCompare = require('@isaacs/string-locale-compare')('en')
|
|
17
18
|
const npa = require('npm-package-arg')
|
|
18
19
|
const _range = Symbol('_range')
|
|
19
20
|
const _simpleRange = Symbol('_simpleRange')
|
|
@@ -28,8 +29,9 @@ const severities = new Map([
|
|
|
28
29
|
[null, -1],
|
|
29
30
|
])
|
|
30
31
|
|
|
31
|
-
for (const [name, val] of severities.entries())
|
|
32
|
+
for (const [name, val] of severities.entries()) {
|
|
32
33
|
severities.set(val, name)
|
|
34
|
+
}
|
|
33
35
|
|
|
34
36
|
class Vuln {
|
|
35
37
|
constructor ({ name, advisory }) {
|
|
@@ -43,7 +45,7 @@ class Vuln {
|
|
|
43
45
|
this[_simpleRange] = null
|
|
44
46
|
this.nodes = new Set()
|
|
45
47
|
// assume a fix is available unless it hits a top node
|
|
46
|
-
// that locks it in place, setting this
|
|
48
|
+
// that locks it in place, setting this false or {isSemVerMajor, version}.
|
|
47
49
|
this[_fixAvailable] = true
|
|
48
50
|
this.addAdvisory(advisory)
|
|
49
51
|
this.packument = advisory.packument
|
|
@@ -65,39 +67,55 @@ class Vuln {
|
|
|
65
67
|
// - true: fix does not require -f
|
|
66
68
|
for (const v of this.via) {
|
|
67
69
|
// don't blow up on loops
|
|
68
|
-
if (v.fixAvailable === f)
|
|
70
|
+
if (v.fixAvailable === f) {
|
|
69
71
|
continue
|
|
72
|
+
}
|
|
70
73
|
|
|
71
|
-
if (f === false)
|
|
74
|
+
if (f === false) {
|
|
72
75
|
v.fixAvailable = f
|
|
73
|
-
else if (v.fixAvailable === true)
|
|
76
|
+
} else if (v.fixAvailable === true) {
|
|
74
77
|
v.fixAvailable = f
|
|
75
|
-
else if (typeof f === 'object' && (
|
|
76
|
-
typeof v.fixAvailable !== 'object' || !v.fixAvailable.isSemVerMajor))
|
|
78
|
+
} else if (typeof f === 'object' && (
|
|
79
|
+
typeof v.fixAvailable !== 'object' || !v.fixAvailable.isSemVerMajor)) {
|
|
77
80
|
v.fixAvailable = f
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
get isDirect () {
|
|
86
|
+
for (const node of this.nodes.values()) {
|
|
87
|
+
for (const edge of node.edgesIn) {
|
|
88
|
+
if (edge.from.isProjectRoot || edge.from.isWorkspace) {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
testSpec (spec) {
|
|
82
97
|
const specObj = npa(spec)
|
|
83
|
-
if (!specObj.registry)
|
|
98
|
+
if (!specObj.registry) {
|
|
84
99
|
return true
|
|
100
|
+
}
|
|
85
101
|
|
|
86
|
-
if (specObj.subSpec)
|
|
102
|
+
if (specObj.subSpec) {
|
|
87
103
|
spec = specObj.subSpec.rawSpec
|
|
104
|
+
}
|
|
88
105
|
|
|
89
106
|
for (const v of this.versions) {
|
|
90
|
-
if (satisfies(v, spec) && !satisfies(v, this.range, semverOpt))
|
|
107
|
+
if (satisfies(v, spec) && !satisfies(v, this.range, semverOpt)) {
|
|
91
108
|
return false
|
|
109
|
+
}
|
|
92
110
|
}
|
|
93
111
|
return true
|
|
94
112
|
}
|
|
95
113
|
|
|
96
114
|
toJSON () {
|
|
97
|
-
// sort so that they're always in a consistent order
|
|
98
115
|
return {
|
|
99
116
|
name: this.name,
|
|
100
117
|
severity: this.severity,
|
|
118
|
+
isDirect: this.isDirect,
|
|
101
119
|
// just loop over the advisories, since via is only Vuln objects,
|
|
102
120
|
// and calculated advisories have all the info we need
|
|
103
121
|
via: [...this.advisories].map(v => v.type === 'metavuln' ? v.dependency : {
|
|
@@ -106,12 +124,10 @@ class Vuln {
|
|
|
106
124
|
vulnerableVersions: undefined,
|
|
107
125
|
id: undefined,
|
|
108
126
|
}).sort((a, b) =>
|
|
109
|
-
String(a.source || a)
|
|
110
|
-
effects: [...this.effects].map(v => v.name)
|
|
111
|
-
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b, 'en')),
|
|
127
|
+
localeCompare(String(a.source || a), String(b.source || b))),
|
|
128
|
+
effects: [...this.effects].map(v => v.name).sort(localeCompare),
|
|
112
129
|
range: this.simpleRange,
|
|
113
|
-
nodes: [...this.nodes].map(n => n.location)
|
|
114
|
-
.sort(/* istanbul ignore next */(a, b) => a.localeCompare(b, 'en')),
|
|
130
|
+
nodes: [...this.nodes].map(n => n.location).sort(localeCompare),
|
|
115
131
|
fixAvailable: this[_fixAvailable],
|
|
116
132
|
}
|
|
117
133
|
}
|
|
@@ -135,14 +151,16 @@ class Vuln {
|
|
|
135
151
|
this[_range] = null
|
|
136
152
|
this[_simpleRange] = null
|
|
137
153
|
// refresh severity
|
|
138
|
-
for (const advisory of this.advisories)
|
|
154
|
+
for (const advisory of this.advisories) {
|
|
139
155
|
this.addAdvisory(advisory)
|
|
156
|
+
}
|
|
140
157
|
|
|
141
158
|
// remove any effects that are no longer relevant
|
|
142
159
|
const vias = new Set([...this.advisories].map(a => a.dependency))
|
|
143
160
|
for (const via of this.via) {
|
|
144
|
-
if (!vias.has(via.name))
|
|
161
|
+
if (!vias.has(via.name)) {
|
|
145
162
|
this.deleteVia(via)
|
|
163
|
+
}
|
|
146
164
|
}
|
|
147
165
|
}
|
|
148
166
|
|
|
@@ -151,8 +169,9 @@ class Vuln {
|
|
|
151
169
|
const sev = severities.get(advisory.severity)
|
|
152
170
|
this[_range] = null
|
|
153
171
|
this[_simpleRange] = null
|
|
154
|
-
if (sev > severities.get(this.severity))
|
|
172
|
+
if (sev > severities.get(this.severity)) {
|
|
155
173
|
this.severity = advisory.severity
|
|
174
|
+
}
|
|
156
175
|
}
|
|
157
176
|
|
|
158
177
|
get range () {
|
|
@@ -161,8 +180,9 @@ class Vuln {
|
|
|
161
180
|
}
|
|
162
181
|
|
|
163
182
|
get simpleRange () {
|
|
164
|
-
if (this[_simpleRange] && this[_simpleRange] === this[_range])
|
|
183
|
+
if (this[_simpleRange] && this[_simpleRange] === this[_range]) {
|
|
165
184
|
return this[_simpleRange]
|
|
185
|
+
}
|
|
166
186
|
|
|
167
187
|
const versions = [...this.advisories][0].versions
|
|
168
188
|
const range = this.range
|
|
@@ -171,12 +191,14 @@ class Vuln {
|
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
isVulnerable (node) {
|
|
174
|
-
if (this.nodes.has(node))
|
|
194
|
+
if (this.nodes.has(node)) {
|
|
175
195
|
return true
|
|
196
|
+
}
|
|
176
197
|
|
|
177
198
|
const { version } = node.package
|
|
178
|
-
if (!version)
|
|
199
|
+
if (!version) {
|
|
179
200
|
return false
|
|
201
|
+
}
|
|
180
202
|
|
|
181
203
|
for (const v of this.advisories) {
|
|
182
204
|
if (v.testVersion(version)) {
|
package/lib/yarn-lock.js
CHANGED
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
// is an impenetrable 10kloc of webpack flow output, which is overkill
|
|
29
29
|
// for something relatively simple and tailored to Arborist's use case.
|
|
30
30
|
|
|
31
|
+
const localeCompare = require('@isaacs/string-locale-compare')('en')
|
|
31
32
|
const consistentResolve = require('./consistent-resolve.js')
|
|
32
33
|
const {dirname} = require('path')
|
|
33
34
|
const {breadth} = require('treeverse')
|
|
34
35
|
|
|
35
36
|
// sort a key/value object into a string of JSON stringified keys and vals
|
|
36
37
|
const sortKV = obj => Object.keys(obj)
|
|
37
|
-
.sort(
|
|
38
|
+
.sort(localeCompare)
|
|
38
39
|
.map(k => ` ${JSON.stringify(k)} ${JSON.stringify(obj[k])}`)
|
|
39
40
|
.join('\n')
|
|
40
41
|
|
|
@@ -82,13 +83,15 @@ class YarnLock {
|
|
|
82
83
|
const linere = /([^\r\n]*)\r?\n/gm
|
|
83
84
|
let match
|
|
84
85
|
let lineNum = 0
|
|
85
|
-
if (!/\n$/.test(data))
|
|
86
|
+
if (!/\n$/.test(data)) {
|
|
86
87
|
data += '\n'
|
|
88
|
+
}
|
|
87
89
|
while (match = linere.exec(data)) {
|
|
88
90
|
const line = match[1]
|
|
89
91
|
lineNum++
|
|
90
|
-
if (line.charAt(0) === '#')
|
|
92
|
+
if (line.charAt(0) === '#') {
|
|
91
93
|
continue
|
|
94
|
+
}
|
|
92
95
|
if (line === '') {
|
|
93
96
|
this.endCurrent()
|
|
94
97
|
continue
|
|
@@ -117,8 +120,9 @@ class YarnLock {
|
|
|
117
120
|
const metadata = this.splitQuoted(line.trimLeft(), ' ')
|
|
118
121
|
if (metadata.length === 2) {
|
|
119
122
|
// strip off the legacy shasum hashes
|
|
120
|
-
if (metadata[0] === 'resolved')
|
|
123
|
+
if (metadata[0] === 'resolved') {
|
|
121
124
|
metadata[1] = metadata[1].replace(/#.*/, '')
|
|
125
|
+
}
|
|
122
126
|
this.current[metadata[0]] = metadata[1]
|
|
123
127
|
continue
|
|
124
128
|
}
|
|
@@ -141,9 +145,9 @@ class YarnLock {
|
|
|
141
145
|
let o = 0
|
|
142
146
|
for (let i = 0; i < split.length; i++) {
|
|
143
147
|
const chunk = split[i]
|
|
144
|
-
if (/^".*"$/.test(chunk))
|
|
148
|
+
if (/^".*"$/.test(chunk)) {
|
|
145
149
|
out[o++] = chunk.trim().slice(1, -1)
|
|
146
|
-
else if (/^"/.test(chunk)) {
|
|
150
|
+
} else if (/^"/.test(chunk)) {
|
|
147
151
|
let collect = chunk.trimLeft().slice(1)
|
|
148
152
|
while (++i < split.length) {
|
|
149
153
|
const n = split[i]
|
|
@@ -152,12 +156,14 @@ class YarnLock {
|
|
|
152
156
|
if (/[^\\](\\\\)*"$/.test(n)) {
|
|
153
157
|
collect += n.trimRight().slice(0, -1)
|
|
154
158
|
break
|
|
155
|
-
} else
|
|
159
|
+
} else {
|
|
156
160
|
collect += n
|
|
161
|
+
}
|
|
157
162
|
}
|
|
158
163
|
out[o++] = collect
|
|
159
|
-
} else
|
|
164
|
+
} else {
|
|
160
165
|
out[o++] = chunk.trim()
|
|
166
|
+
}
|
|
161
167
|
}
|
|
162
168
|
return out
|
|
163
169
|
}
|
|
@@ -165,7 +171,7 @@ class YarnLock {
|
|
|
165
171
|
toString () {
|
|
166
172
|
return prefix + [...new Set([...this.entries.values()])]
|
|
167
173
|
.map(e => e.toString())
|
|
168
|
-
.sort(
|
|
174
|
+
.sort(localeCompare).join('\n\n') + '\n'
|
|
169
175
|
}
|
|
170
176
|
|
|
171
177
|
fromTree (tree) {
|
|
@@ -175,7 +181,7 @@ class YarnLock {
|
|
|
175
181
|
tree,
|
|
176
182
|
visit: node => this.addEntryFromNode(node),
|
|
177
183
|
getChildren: node => [...node.children.values(), ...node.fsChildren]
|
|
178
|
-
.sort((a, b) => a.depth - b.depth || a.name
|
|
184
|
+
.sort((a, b) => a.depth - b.depth || localeCompare(a.name, b.name)),
|
|
179
185
|
})
|
|
180
186
|
return this
|
|
181
187
|
}
|
|
@@ -183,7 +189,7 @@ class YarnLock {
|
|
|
183
189
|
addEntryFromNode (node) {
|
|
184
190
|
const specs = [...node.edgesIn]
|
|
185
191
|
.map(e => `${node.name}@${e.spec}`)
|
|
186
|
-
.sort(
|
|
192
|
+
.sort(localeCompare)
|
|
187
193
|
|
|
188
194
|
// Note:
|
|
189
195
|
// yarn will do excessive duplication in a case like this:
|
|
@@ -226,17 +232,19 @@ class YarnLock {
|
|
|
226
232
|
// no previous entry for this spec at all, so it's new
|
|
227
233
|
if (!prev) {
|
|
228
234
|
// if we saw a match already, then assign this spec to it as well
|
|
229
|
-
if (priorEntry)
|
|
235
|
+
if (priorEntry) {
|
|
230
236
|
priorEntry.addSpec(s)
|
|
231
|
-
else
|
|
237
|
+
} else {
|
|
232
238
|
newSpecs.push(s)
|
|
239
|
+
}
|
|
233
240
|
continue
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
const m = match(prev, n)
|
|
237
244
|
// there was a prior entry, but a different thing. skip this one
|
|
238
|
-
if (!m)
|
|
245
|
+
if (!m) {
|
|
239
246
|
continue
|
|
247
|
+
}
|
|
240
248
|
|
|
241
249
|
// previous matches, but first time seeing it, so already has this spec.
|
|
242
250
|
// go ahead and add all the previously unseen specs, though
|
|
@@ -259,8 +267,9 @@ class YarnLock {
|
|
|
259
267
|
// if we never found a matching prior, then this is a whole new thing
|
|
260
268
|
if (!priorEntry) {
|
|
261
269
|
const entry = Object.assign(new YarnLockEntry(newSpecs), n)
|
|
262
|
-
for (const s of newSpecs)
|
|
270
|
+
for (const s of newSpecs) {
|
|
263
271
|
this.entries.set(s, entry)
|
|
272
|
+
}
|
|
264
273
|
} else {
|
|
265
274
|
// pick up any new info that we got for this node, so that we can
|
|
266
275
|
// decorate with integrity/resolved/etc.
|
|
@@ -270,12 +279,15 @@ class YarnLock {
|
|
|
270
279
|
|
|
271
280
|
entryDataFromNode (node) {
|
|
272
281
|
const n = {}
|
|
273
|
-
if (node.package.dependencies)
|
|
282
|
+
if (node.package.dependencies) {
|
|
274
283
|
n.dependencies = node.package.dependencies
|
|
275
|
-
|
|
284
|
+
}
|
|
285
|
+
if (node.package.optionalDependencies) {
|
|
276
286
|
n.optionalDependencies = node.package.optionalDependencies
|
|
277
|
-
|
|
287
|
+
}
|
|
288
|
+
if (node.version) {
|
|
278
289
|
n.version = node.version
|
|
290
|
+
}
|
|
279
291
|
if (node.resolved) {
|
|
280
292
|
n.resolved = consistentResolve(
|
|
281
293
|
node.resolved,
|
|
@@ -284,8 +296,9 @@ class YarnLock {
|
|
|
284
296
|
true
|
|
285
297
|
)
|
|
286
298
|
}
|
|
287
|
-
if (node.integrity)
|
|
299
|
+
if (node.integrity) {
|
|
288
300
|
n.integrity = node.integrity
|
|
301
|
+
}
|
|
289
302
|
|
|
290
303
|
return n
|
|
291
304
|
}
|
|
@@ -309,7 +322,7 @@ class YarnLockEntry {
|
|
|
309
322
|
toString () {
|
|
310
323
|
// sort objects to the bottom, then alphabetical
|
|
311
324
|
return ([...this[_specs]]
|
|
312
|
-
.sort(
|
|
325
|
+
.sort(localeCompare)
|
|
313
326
|
.map(JSON.stringify).join(', ') +
|
|
314
327
|
':\n' +
|
|
315
328
|
Object.getOwnPropertyNames(this)
|
|
@@ -318,7 +331,7 @@ class YarnLockEntry {
|
|
|
318
331
|
(a, b) =>
|
|
319
332
|
/* istanbul ignore next - sort call order is unpredictable */
|
|
320
333
|
(typeof this[a] === 'object') === (typeof this[b] === 'object')
|
|
321
|
-
?
|
|
334
|
+
? localeCompare(a, b)
|
|
322
335
|
: typeof this[a] === 'object' ? 1 : -1)
|
|
323
336
|
.map(prop =>
|
|
324
337
|
typeof this[prop] !== 'object'
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
|
+
"@isaacs/string-locale-compare": "^1.0.1",
|
|
6
7
|
"@npmcli/installed-package-contents": "^1.0.7",
|
|
7
8
|
"@npmcli/map-workspaces": "^1.0.2",
|
|
8
9
|
"@npmcli/metavuln-calculator": "^1.1.0",
|
|
@@ -36,13 +37,9 @@
|
|
|
36
37
|
"walk-up-path": "^1.0.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
40
|
+
"@npmcli/lint": "^1.0.2",
|
|
39
41
|
"benchmark": "^2.1.4",
|
|
40
42
|
"chalk": "^4.1.0",
|
|
41
|
-
"eslint": "^7.9.0",
|
|
42
|
-
"eslint-plugin-import": "^2.22.0",
|
|
43
|
-
"eslint-plugin-node": "^11.1.0",
|
|
44
|
-
"eslint-plugin-promise": "^4.2.1",
|
|
45
|
-
"eslint-plugin-standard": "^4.0.1",
|
|
46
43
|
"minify-registry-metadata": "^2.1.0",
|
|
47
44
|
"tap": "^15.0.9",
|
|
48
45
|
"tcompare": "^5.0.6"
|
|
@@ -50,18 +47,19 @@
|
|
|
50
47
|
"scripts": {
|
|
51
48
|
"test": "npm run test-only --",
|
|
52
49
|
"test-only": "tap",
|
|
53
|
-
"posttest": "npm run lint",
|
|
50
|
+
"posttest": "npm run lint --",
|
|
54
51
|
"snap": "tap",
|
|
55
|
-
"postsnap": "npm run lintfix",
|
|
52
|
+
"postsnap": "npm run lintfix --",
|
|
56
53
|
"test-proxy": "ARBORIST_TEST_PROXY=1 tap --snapshot",
|
|
57
54
|
"preversion": "npm test",
|
|
58
55
|
"postversion": "npm publish",
|
|
59
56
|
"prepublishOnly": "git push origin --follow-tags",
|
|
60
57
|
"eslint": "eslint",
|
|
61
|
-
"lint": "npm run
|
|
58
|
+
"lint": "npm run npmclilint -- \"lib/**/*.*js\" \"bin/**/*.*js\" \"test/*.*js\" \"test/arborist/*.*js\"",
|
|
62
59
|
"lintfix": "npm run lint -- --fix",
|
|
63
60
|
"benchmark": "node scripts/benchmark.js",
|
|
64
|
-
"benchclean": "rm -rf scripts/benchmark/*/"
|
|
61
|
+
"benchclean": "rm -rf scripts/benchmark/*/",
|
|
62
|
+
"npmclilint": "npmcli-lint"
|
|
65
63
|
},
|
|
66
64
|
"repository": {
|
|
67
65
|
"type": "git",
|