@npmcli/arborist 6.0.0-pre.1 → 6.0.0-pre.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.
- package/lib/arborist/build-ideal-tree.js +1 -0
- package/lib/arborist/reify.js +1 -0
- package/lib/node.js +5 -2
- package/lib/query-selector-all.js +193 -14
- package/package.json +14 -9
package/lib/arborist/reify.js
CHANGED
package/lib/node.js
CHANGED
|
@@ -103,6 +103,9 @@ class Node {
|
|
|
103
103
|
dummy = false,
|
|
104
104
|
sourceReference = null,
|
|
105
105
|
} = options
|
|
106
|
+
// this object gives querySelectorAll somewhere to stash context about a node
|
|
107
|
+
// while processing a query
|
|
108
|
+
this.queryContext = {}
|
|
106
109
|
|
|
107
110
|
// true if part of a global install
|
|
108
111
|
this[_global] = global
|
|
@@ -1455,8 +1458,8 @@ class Node {
|
|
|
1455
1458
|
|
|
1456
1459
|
// maybe accept both string value or array of strings
|
|
1457
1460
|
// seems to be what dom API does
|
|
1458
|
-
querySelectorAll (query) {
|
|
1459
|
-
return querySelectorAll(this, query)
|
|
1461
|
+
querySelectorAll (query, opts) {
|
|
1462
|
+
return querySelectorAll(this, query, opts)
|
|
1460
1463
|
}
|
|
1461
1464
|
|
|
1462
1465
|
toJSON () {
|
|
@@ -6,6 +6,7 @@ const localeCompare = require('@isaacs/string-locale-compare')('en')
|
|
|
6
6
|
const log = require('proc-log')
|
|
7
7
|
const minimatch = require('minimatch')
|
|
8
8
|
const npa = require('npm-package-arg')
|
|
9
|
+
const pacote = require('pacote')
|
|
9
10
|
const semver = require('semver')
|
|
10
11
|
|
|
11
12
|
// handle results for parsed query asts, results are stored in a map that has a
|
|
@@ -16,6 +17,7 @@ class Results {
|
|
|
16
17
|
#currentAstSelector
|
|
17
18
|
#initialItems
|
|
18
19
|
#inventory
|
|
20
|
+
#outdatedCache = new Map()
|
|
19
21
|
#pendingCombinator
|
|
20
22
|
#results = new Map()
|
|
21
23
|
#targetNode
|
|
@@ -28,6 +30,9 @@ class Results {
|
|
|
28
30
|
|
|
29
31
|
this.currentResults = this.#initialItems
|
|
30
32
|
|
|
33
|
+
// We get this when first called and need to pass it to pacote
|
|
34
|
+
this.flatOptions = opts.flatOptions || {}
|
|
35
|
+
|
|
31
36
|
// reset by rootAstNode walker
|
|
32
37
|
this.currentAstNode = opts.rootAstNode
|
|
33
38
|
}
|
|
@@ -58,6 +63,7 @@ class Results {
|
|
|
58
63
|
if (firstParsed) {
|
|
59
64
|
return this.#initialItems
|
|
60
65
|
}
|
|
66
|
+
|
|
61
67
|
if (this.currentAstNode.prev().type === 'combinator') {
|
|
62
68
|
return this.#inventory
|
|
63
69
|
}
|
|
@@ -125,7 +131,7 @@ class Results {
|
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
// pseudo selectors (prefixed with :)
|
|
128
|
-
pseudoType () {
|
|
134
|
+
async pseudoType () {
|
|
129
135
|
const pseudoFn = `${this.currentAstNode.value.slice(1)}Pseudo`
|
|
130
136
|
if (!this[pseudoFn]) {
|
|
131
137
|
throw Object.assign(
|
|
@@ -134,7 +140,7 @@ class Results {
|
|
|
134
140
|
{ code: 'EQUERYNOPSEUDO' }
|
|
135
141
|
)
|
|
136
142
|
}
|
|
137
|
-
const nextResults = this[pseudoFn]()
|
|
143
|
+
const nextResults = await this[pseudoFn]()
|
|
138
144
|
this.processPendingCombinator(nextResults)
|
|
139
145
|
}
|
|
140
146
|
|
|
@@ -195,11 +201,12 @@ class Results {
|
|
|
195
201
|
return this.initialItems.filter(node => node.extraneous)
|
|
196
202
|
}
|
|
197
203
|
|
|
198
|
-
hasPseudo () {
|
|
204
|
+
async hasPseudo () {
|
|
199
205
|
const found = []
|
|
200
206
|
for (const item of this.initialItems) {
|
|
201
|
-
|
|
202
|
-
|
|
207
|
+
// This is the one time initialItems differs from inventory
|
|
208
|
+
const res = await retrieveNodesFromParsedAst({
|
|
209
|
+
flatOptions: this.flatOptions,
|
|
203
210
|
initialItems: [item],
|
|
204
211
|
inventory: this.#inventory,
|
|
205
212
|
rootAstNode: this.currentAstNode.nestedNode,
|
|
@@ -225,8 +232,9 @@ class Results {
|
|
|
225
232
|
return found
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
isPseudo () {
|
|
229
|
-
const res = retrieveNodesFromParsedAst({
|
|
235
|
+
async isPseudo () {
|
|
236
|
+
const res = await retrieveNodesFromParsedAst({
|
|
237
|
+
flatOptions: this.flatOptions,
|
|
230
238
|
initialItems: this.initialItems,
|
|
231
239
|
inventory: this.#inventory,
|
|
232
240
|
rootAstNode: this.currentAstNode.nestedNode,
|
|
@@ -251,8 +259,9 @@ class Results {
|
|
|
251
259
|
}, [])
|
|
252
260
|
}
|
|
253
261
|
|
|
254
|
-
notPseudo () {
|
|
255
|
-
const res = retrieveNodesFromParsedAst({
|
|
262
|
+
async notPseudo () {
|
|
263
|
+
const res = await retrieveNodesFromParsedAst({
|
|
264
|
+
flatOptions: this.flatOptions,
|
|
256
265
|
initialItems: this.initialItems,
|
|
257
266
|
inventory: this.#inventory,
|
|
258
267
|
rootAstNode: this.currentAstNode.nestedNode,
|
|
@@ -422,6 +431,135 @@ class Results {
|
|
|
422
431
|
dedupedPseudo () {
|
|
423
432
|
return this.initialItems.filter(node => node.target.edgesIn.size > 1)
|
|
424
433
|
}
|
|
434
|
+
|
|
435
|
+
async outdatedPseudo () {
|
|
436
|
+
const { outdatedKind = 'any' } = this.currentAstNode
|
|
437
|
+
|
|
438
|
+
// filter the initialItems
|
|
439
|
+
// NOTE: this uses a Promise.all around a map without in-line concurrency handling
|
|
440
|
+
// since the only async action taken is retrieving the packument, which is limited
|
|
441
|
+
// based on the max-sockets config in make-fetch-happen
|
|
442
|
+
const initialResults = await Promise.all(this.initialItems.map(async (node) => {
|
|
443
|
+
// the root can't be outdated, skip it
|
|
444
|
+
if (node.isProjectRoot) {
|
|
445
|
+
return false
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// we cache the promise representing the full versions list, this helps reduce the
|
|
449
|
+
// number of requests we send by keeping population of the cache in a single tick
|
|
450
|
+
// making it less likely that multiple requests for the same package will be inflight
|
|
451
|
+
if (!this.#outdatedCache.has(node.name)) {
|
|
452
|
+
this.#outdatedCache.set(node.name, getPackageVersions(node.name, this.flatOptions))
|
|
453
|
+
}
|
|
454
|
+
const availableVersions = await this.#outdatedCache.get(node.name)
|
|
455
|
+
|
|
456
|
+
// we attach _all_ versions to the queryContext to allow consumers to do their own
|
|
457
|
+
// filtering and comparisons
|
|
458
|
+
node.queryContext.versions = availableVersions
|
|
459
|
+
|
|
460
|
+
// next we further reduce the set to versions that are greater than the current one
|
|
461
|
+
const greaterVersions = availableVersions.filter((available) => {
|
|
462
|
+
return semver.gt(available, node.version)
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
// no newer versions than the current one, drop this node from the result set
|
|
466
|
+
if (!greaterVersions.length) {
|
|
467
|
+
return false
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// if we got here, we know that newer versions exist, if the kind is 'any' we're done
|
|
471
|
+
if (outdatedKind === 'any') {
|
|
472
|
+
return node
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// look for newer versions that differ from current by a specific part of the semver version
|
|
476
|
+
if (['major', 'minor', 'patch'].includes(outdatedKind)) {
|
|
477
|
+
// filter the versions greater than our current one based on semver.diff
|
|
478
|
+
const filteredVersions = greaterVersions.filter((version) => {
|
|
479
|
+
return semver.diff(node.version, version) === outdatedKind
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// no available versions are of the correct diff type
|
|
483
|
+
if (!filteredVersions.length) {
|
|
484
|
+
return false
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return node
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// look for newer versions that satisfy at least one edgeIn to this node
|
|
491
|
+
if (outdatedKind === 'in-range') {
|
|
492
|
+
const inRangeContext = []
|
|
493
|
+
for (const edge of node.edgesIn) {
|
|
494
|
+
const inRangeVersions = greaterVersions.filter((version) => {
|
|
495
|
+
return semver.satisfies(version, edge.spec)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
// this edge has no in-range candidates, just move on
|
|
499
|
+
if (!inRangeVersions.length) {
|
|
500
|
+
continue
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
inRangeContext.push({
|
|
504
|
+
from: edge.from.location,
|
|
505
|
+
versions: inRangeVersions,
|
|
506
|
+
})
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// if we didn't find at least one match, drop this node
|
|
510
|
+
if (!inRangeContext.length) {
|
|
511
|
+
return false
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// now add to the context each version that is in-range for each edgeIn
|
|
515
|
+
node.queryContext.outdated = {
|
|
516
|
+
...node.queryContext.outdated,
|
|
517
|
+
inRange: inRangeContext,
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return node
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// look for newer versions that _do not_ satisfy at least one edgeIn
|
|
524
|
+
if (outdatedKind === 'out-of-range') {
|
|
525
|
+
const outOfRangeContext = []
|
|
526
|
+
for (const edge of node.edgesIn) {
|
|
527
|
+
const outOfRangeVersions = greaterVersions.filter((version) => {
|
|
528
|
+
return !semver.satisfies(version, edge.spec)
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
// this edge has no out-of-range candidates, skip it
|
|
532
|
+
if (!outOfRangeVersions.length) {
|
|
533
|
+
continue
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
outOfRangeContext.push({
|
|
537
|
+
from: edge.from.location,
|
|
538
|
+
versions: outOfRangeVersions,
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// if we didn't add at least one thing to the context, this node is not a match
|
|
543
|
+
if (!outOfRangeContext.length) {
|
|
544
|
+
return false
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// attach the out-of-range context to the node
|
|
548
|
+
node.queryContext.outdated = {
|
|
549
|
+
...node.queryContext.outdated,
|
|
550
|
+
outOfRange: outOfRangeContext,
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return node
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// any other outdatedKind is unknown and will never match
|
|
557
|
+
return false
|
|
558
|
+
}))
|
|
559
|
+
|
|
560
|
+
// return an array with the holes for non-matching nodes removed
|
|
561
|
+
return initialResults.filter(Boolean)
|
|
562
|
+
}
|
|
425
563
|
}
|
|
426
564
|
|
|
427
565
|
// operators for attribute selectors
|
|
@@ -622,7 +760,41 @@ const combinators = {
|
|
|
622
760
|
},
|
|
623
761
|
}
|
|
624
762
|
|
|
625
|
-
|
|
763
|
+
// get a list of available versions of a package filtered to respect --before
|
|
764
|
+
// NOTE: this runs over each node and should not throw
|
|
765
|
+
const getPackageVersions = async (name, opts) => {
|
|
766
|
+
let packument
|
|
767
|
+
try {
|
|
768
|
+
packument = await pacote.packument(name, {
|
|
769
|
+
...opts,
|
|
770
|
+
fullMetadata: false, // we only need the corgi
|
|
771
|
+
})
|
|
772
|
+
} catch (err) {
|
|
773
|
+
// if the fetch fails, log a warning and pretend there are no versions
|
|
774
|
+
log.warn('query', `could not retrieve packument for ${name}: ${err.message}`)
|
|
775
|
+
return []
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// start with a sorted list of all versions (lowest first)
|
|
779
|
+
let candidates = Object.keys(packument.versions).sort(semver.compare)
|
|
780
|
+
|
|
781
|
+
// if the packument has a time property, and the user passed a before flag, then
|
|
782
|
+
// we filter this list down to only those versions that existed before the specified date
|
|
783
|
+
if (packument.time && opts.before) {
|
|
784
|
+
candidates = candidates.filter((version) => {
|
|
785
|
+
// this version isn't found in the times at all, drop it
|
|
786
|
+
if (!packument.time[version]) {
|
|
787
|
+
return false
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return Date.parse(packument.time[version]) <= opts.before
|
|
791
|
+
})
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
return candidates
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const retrieveNodesFromParsedAst = async (opts) => {
|
|
626
798
|
// when we first call this it's the parsed query. all other times it's
|
|
627
799
|
// results.currentNode.nestedNode
|
|
628
800
|
const rootAstNode = opts.rootAstNode
|
|
@@ -633,7 +805,13 @@ const retrieveNodesFromParsedAst = (opts) => {
|
|
|
633
805
|
|
|
634
806
|
const results = new Results(opts)
|
|
635
807
|
|
|
808
|
+
const astNodeQueue = new Set()
|
|
809
|
+
// walk is sync, so we have to build up our async functions and then await them later
|
|
636
810
|
rootAstNode.walk((nextAstNode) => {
|
|
811
|
+
astNodeQueue.add(nextAstNode)
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
for (const nextAstNode of astNodeQueue) {
|
|
637
815
|
// This is the only place we reset currentAstNode
|
|
638
816
|
results.currentAstNode = nextAstNode
|
|
639
817
|
const updateFn = `${results.currentAstNode.type}Type`
|
|
@@ -643,23 +821,24 @@ const retrieveNodesFromParsedAst = (opts) => {
|
|
|
643
821
|
{ code: 'EQUERYNOSELECTOR' }
|
|
644
822
|
)
|
|
645
823
|
}
|
|
646
|
-
results[updateFn]()
|
|
647
|
-
}
|
|
824
|
+
await results[updateFn]()
|
|
825
|
+
}
|
|
648
826
|
|
|
649
827
|
return results.collect(rootAstNode)
|
|
650
828
|
}
|
|
651
829
|
|
|
652
830
|
// We are keeping this async in the event that we do add async operators, we
|
|
653
831
|
// won't have to have a breaking change on this function signature.
|
|
654
|
-
const querySelectorAll = async (targetNode, query) => {
|
|
832
|
+
const querySelectorAll = async (targetNode, query, flatOptions) => {
|
|
655
833
|
// This never changes ever we just pass it around. But we can't scope it to
|
|
656
834
|
// this whole file if we ever want to support concurrent calls to this
|
|
657
835
|
// function.
|
|
658
836
|
const inventory = [...targetNode.root.inventory.values()]
|
|
659
837
|
// res is a Set of items returned for each parsed css ast selector
|
|
660
|
-
const res = retrieveNodesFromParsedAst({
|
|
838
|
+
const res = await retrieveNodesFromParsedAst({
|
|
661
839
|
initialItems: inventory,
|
|
662
840
|
inventory,
|
|
841
|
+
flatOptions,
|
|
663
842
|
rootAstNode: parser(query),
|
|
664
843
|
targetNode,
|
|
665
844
|
})
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "6.0.0-pre.
|
|
3
|
+
"version": "6.0.0-pre.3",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@isaacs/string-locale-compare": "^1.1.0",
|
|
7
7
|
"@npmcli/installed-package-contents": "^1.0.7",
|
|
8
8
|
"@npmcli/map-workspaces": "^2.0.3",
|
|
9
|
-
"@npmcli/metavuln-calculator": "^
|
|
9
|
+
"@npmcli/metavuln-calculator": "^4.0.0-pre.0",
|
|
10
10
|
"@npmcli/move-file": "^2.0.0",
|
|
11
11
|
"@npmcli/name-from-folder": "^1.0.1",
|
|
12
12
|
"@npmcli/node-gyp": "^2.0.0",
|
|
13
13
|
"@npmcli/package-json": "^2.0.0",
|
|
14
|
-
"@npmcli/query": "^
|
|
14
|
+
"@npmcli/query": "^2.0.0",
|
|
15
15
|
"@npmcli/run-script": "^4.1.3",
|
|
16
16
|
"bin-links": "^3.0.3",
|
|
17
17
|
"cacache": "^16.1.3",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"npm-pick-manifest": "^7.0.2",
|
|
28
28
|
"npm-registry-fetch": "^13.0.0",
|
|
29
29
|
"npmlog": "^6.0.2",
|
|
30
|
-
"pacote": "^
|
|
30
|
+
"pacote": "^14.0.0-pre.3",
|
|
31
31
|
"parse-conflict-json": "^2.0.1",
|
|
32
32
|
"proc-log": "^2.0.0",
|
|
33
33
|
"promise-all-reject-late": "^1.0.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@npmcli/eslint-config": "^3.1.0",
|
|
45
|
-
"@npmcli/template-oss": "4.1
|
|
45
|
+
"@npmcli/template-oss": "4.4.1",
|
|
46
46
|
"benchmark": "^2.1.4",
|
|
47
47
|
"chalk": "^4.1.0",
|
|
48
48
|
"minify-registry-metadata": "^2.1.0",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"test": "tap",
|
|
55
|
-
"posttest": "
|
|
55
|
+
"posttest": "node ../.. run lint",
|
|
56
56
|
"snap": "tap",
|
|
57
57
|
"postsnap": "npm run lintfix",
|
|
58
58
|
"test-proxy": "ARBORIST_TEST_PROXY=1 tap --snapshot",
|
|
59
59
|
"eslint": "eslint",
|
|
60
60
|
"lint": "eslint \"**/*.js\"",
|
|
61
|
-
"lintfix": "
|
|
61
|
+
"lintfix": "node ../.. run lint -- --fix",
|
|
62
62
|
"benchmark": "node scripts/benchmark.js",
|
|
63
63
|
"benchclean": "rm -rf scripts/benchmark/*/",
|
|
64
64
|
"npmclilint": "npmcli-lint",
|
|
@@ -93,13 +93,18 @@
|
|
|
93
93
|
"--no-warnings",
|
|
94
94
|
"--no-deprecation"
|
|
95
95
|
],
|
|
96
|
-
"timeout": "360"
|
|
96
|
+
"timeout": "360",
|
|
97
|
+
"nyc-arg": [
|
|
98
|
+
"--exclude",
|
|
99
|
+
"tap-snapshots/**"
|
|
100
|
+
]
|
|
97
101
|
},
|
|
98
102
|
"engines": {
|
|
99
103
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
|
100
104
|
},
|
|
101
105
|
"templateOSS": {
|
|
102
106
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
|
103
|
-
"version": "4.1
|
|
107
|
+
"version": "4.4.1",
|
|
108
|
+
"content": "../../scripts/template-oss/index.js"
|
|
104
109
|
}
|
|
105
110
|
}
|