@npmcli/arborist 5.2.3 → 5.4.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/lib/arborist/build-ideal-tree.js +3 -4
- package/lib/arborist/index.js +4 -0
- package/lib/arborist/load-actual.js +1 -1
- package/lib/arborist/load-virtual.js +1 -1
- package/lib/arborist/reify.js +34 -3
- package/lib/consistent-resolve.js +2 -2
- package/lib/dep-valid.js +1 -1
- package/lib/edge.js +5 -1
- package/lib/from-path.js +15 -4
- package/lib/link.js +1 -1
- package/lib/node.js +9 -1
- package/lib/query-selector-all.js +561 -0
- package/lib/shrinkwrap.js +3 -3
- package/package.json +3 -1
|
@@ -484,7 +484,7 @@ Try using the package name instead, e.g:
|
|
|
484
484
|
.catch(/* istanbul ignore next */ er => null)
|
|
485
485
|
if (st && st.isSymbolicLink()) {
|
|
486
486
|
const target = await readlink(dir)
|
|
487
|
-
const real = resolve(dirname(dir), target)
|
|
487
|
+
const real = resolve(dirname(dir), target).replace(/#/g, '%23')
|
|
488
488
|
tree.package.dependencies[name] = `file:${real}`
|
|
489
489
|
} else {
|
|
490
490
|
tree.package.dependencies[name] = '*'
|
|
@@ -603,7 +603,7 @@ Try using the package name instead, e.g:
|
|
|
603
603
|
if (filepath) {
|
|
604
604
|
const { name } = spec
|
|
605
605
|
const tree = this.idealTree.target
|
|
606
|
-
spec = npa(`file:${relpath(tree.path, filepath)}`, tree.path)
|
|
606
|
+
spec = npa(`file:${relpath(tree.path, filepath).replace(/#/g, '%23')}`, tree.path)
|
|
607
607
|
spec.name = name
|
|
608
608
|
}
|
|
609
609
|
return spec
|
|
@@ -1076,9 +1076,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1076
1076
|
// if it fails at this point, though, dont' worry because it
|
|
1077
1077
|
// may well be an optional dep that has gone missing. it'll
|
|
1078
1078
|
// fail later anyway.
|
|
1079
|
-
const from = fromPath(placed)
|
|
1080
1079
|
promises.push(...this[_problemEdges](placed).map(e =>
|
|
1081
|
-
this[_fetchManifest](npa.resolve(e.name, e.spec,
|
|
1080
|
+
this[_fetchManifest](npa.resolve(e.name, e.spec, fromPath(placed, e)))
|
|
1082
1081
|
.catch(er => null)))
|
|
1083
1082
|
},
|
|
1084
1083
|
})
|
package/lib/arborist/index.js
CHANGED
|
@@ -74,8 +74,12 @@ class Arborist extends Base {
|
|
|
74
74
|
cache: options.cache || `${homedir()}/.npm/_cacache`,
|
|
75
75
|
packumentCache: options.packumentCache || new Map(),
|
|
76
76
|
workspacesEnabled: options.workspacesEnabled !== false,
|
|
77
|
+
replaceRegistryHost: options.replaceRegistryHost,
|
|
77
78
|
lockfileVersion: lockfileVersion(options.lockfileVersion),
|
|
78
79
|
}
|
|
80
|
+
this.replaceRegistryHost = this.options.replaceRegistryHost =
|
|
81
|
+
(!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ?
|
|
82
|
+
'registry.npmjs.org' : this.options.replaceRegistryHost
|
|
79
83
|
|
|
80
84
|
this[_workspacesEnabled] = this.options.workspacesEnabled
|
|
81
85
|
|
|
@@ -196,7 +196,7 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
196
196
|
const actualRoot = tree.isLink ? tree.target : tree
|
|
197
197
|
const { dependencies = {} } = actualRoot.package
|
|
198
198
|
for (const [name, kid] of actualRoot.children.entries()) {
|
|
199
|
-
const def = kid.isLink ? `file:${kid.realpath}` : '*'
|
|
199
|
+
const def = kid.isLink ? `file:${kid.realpath.replace(/#/g, '%23')}` : '*'
|
|
200
200
|
dependencies[name] = dependencies[name] || def
|
|
201
201
|
}
|
|
202
202
|
actualRoot.package = { ...actualRoot.package, dependencies }
|
|
@@ -162,7 +162,7 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
162
162
|
lockfile: s.data,
|
|
163
163
|
})
|
|
164
164
|
for (const [name, path] of workspaces.entries()) {
|
|
165
|
-
lockWS.push(['workspace', name, `file:${path}`])
|
|
165
|
+
lockWS.push(['workspace', name, `file:${path.replace(/#/g, '%23')}`])
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
const lockEdges = [
|
package/lib/arborist/reify.js
CHANGED
|
@@ -22,6 +22,7 @@ const moveFile = require('@npmcli/move-file')
|
|
|
22
22
|
const rimraf = promisify(require('rimraf'))
|
|
23
23
|
const PackageJson = require('@npmcli/package-json')
|
|
24
24
|
const packageContents = require('@npmcli/installed-package-contents')
|
|
25
|
+
const runScript = require('@npmcli/run-script')
|
|
25
26
|
const { checkEngine, checkPlatform } = require('npm-install-checks')
|
|
26
27
|
const _force = Symbol.for('force')
|
|
27
28
|
|
|
@@ -711,13 +712,19 @@ module.exports = cls => class Reifier extends cls {
|
|
|
711
712
|
[_registryResolved] (resolved) {
|
|
712
713
|
// the default registry url is a magic value meaning "the currently
|
|
713
714
|
// configured registry".
|
|
715
|
+
// `resolved` must never be falsey.
|
|
714
716
|
//
|
|
715
717
|
// XXX: use a magic string that isn't also a valid value, like
|
|
716
718
|
// ${REGISTRY} or something. This has to be threaded through the
|
|
717
719
|
// Shrinkwrap and Node classes carefully, so for now, just treat
|
|
718
720
|
// the default reg as the magical animal that it has been.
|
|
719
|
-
|
|
720
|
-
|
|
721
|
+
const resolvedURL = new URL(resolved)
|
|
722
|
+
if ((this.options.replaceRegistryHost === resolvedURL.hostname)
|
|
723
|
+
|| this.options.replaceRegistryHost === 'always') {
|
|
724
|
+
// this.registry always has a trailing slash
|
|
725
|
+
resolved = `${this.registry.slice(0, -1)}${resolvedURL.pathname}${resolvedURL.searchParams}`
|
|
726
|
+
}
|
|
727
|
+
return resolved
|
|
721
728
|
}
|
|
722
729
|
|
|
723
730
|
// bundles are *sort of* like shrinkwraps, in that the branch is defined
|
|
@@ -1240,7 +1247,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1240
1247
|
// path initially, in which case we can end up with the wrong
|
|
1241
1248
|
// thing, so just get the ultimate fetchSpec and relativize it.
|
|
1242
1249
|
const p = req.fetchSpec.replace(/^file:/, '')
|
|
1243
|
-
const rel = relpath(addTree.realpath, p)
|
|
1250
|
+
const rel = relpath(addTree.realpath, p).replace(/#/g, '%23')
|
|
1244
1251
|
newSpec = `file:${rel}`
|
|
1245
1252
|
}
|
|
1246
1253
|
} else {
|
|
@@ -1516,6 +1523,30 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1516
1523
|
|
|
1517
1524
|
if (!this[_global]) {
|
|
1518
1525
|
await this.actualTree.meta.save()
|
|
1526
|
+
const ignoreScripts = !!this.options.ignoreScripts
|
|
1527
|
+
// if we aren't doing a dry run or ignoring scripts and we actually made changes to the dep
|
|
1528
|
+
// tree, then run the dependencies scripts
|
|
1529
|
+
if (!this[_dryRun] && !ignoreScripts && this.diff && this.diff.children.length) {
|
|
1530
|
+
const { path, package: pkg } = this.actualTree.target
|
|
1531
|
+
const stdio = this.options.foregroundScripts ? 'inherit' : 'pipe'
|
|
1532
|
+
const { scripts = {} } = pkg
|
|
1533
|
+
for (const event of ['predependencies', 'dependencies', 'postdependencies']) {
|
|
1534
|
+
if (Object.prototype.hasOwnProperty.call(scripts, event)) {
|
|
1535
|
+
const timer = `reify:run:${event}`
|
|
1536
|
+
process.emit('time', timer)
|
|
1537
|
+
log.info('run', pkg._id, event, scripts[event])
|
|
1538
|
+
await runScript({
|
|
1539
|
+
event,
|
|
1540
|
+
path,
|
|
1541
|
+
pkg,
|
|
1542
|
+
stdioString: true,
|
|
1543
|
+
stdio,
|
|
1544
|
+
scriptShell: this.options.scriptShell,
|
|
1545
|
+
})
|
|
1546
|
+
process.emit('timeEnd', timer)
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1519
1550
|
}
|
|
1520
1551
|
}
|
|
1521
1552
|
}
|
|
@@ -20,8 +20,8 @@ const consistentResolve = (resolved, fromPath, toPath, relPaths = false) => {
|
|
|
20
20
|
raw,
|
|
21
21
|
} = npa(resolved, fromPath)
|
|
22
22
|
const isPath = type === 'file' || type === 'directory'
|
|
23
|
-
return isPath && !relPaths ? `file:${fetchSpec}`
|
|
24
|
-
: isPath ? 'file:' + (toPath ? relpath(toPath, fetchSpec) : fetchSpec)
|
|
23
|
+
return isPath && !relPaths ? `file:${fetchSpec.replace(/#/g, '%23')}`
|
|
24
|
+
: isPath ? 'file:' + (toPath ? relpath(toPath, fetchSpec.replace(/#/g, '%23')) : fetchSpec.replace(/#/g, '%23'))
|
|
25
25
|
: hosted ? `git+${
|
|
26
26
|
hosted.auth ? hosted.https(hostedOpt) : hosted.sshurl(hostedOpt)
|
|
27
27
|
}`
|
package/lib/dep-valid.js
CHANGED
|
@@ -20,7 +20,7 @@ const depValid = (child, requested, requestor) => {
|
|
|
20
20
|
// file: deps that depend on other files/dirs, we must resolve the
|
|
21
21
|
// location based on the *requestor* file/dir, not where it ends up.
|
|
22
22
|
// '' is equivalent to '*'
|
|
23
|
-
requested = npa.resolve(child.name, requested || '*', fromPath(requestor))
|
|
23
|
+
requested = npa.resolve(child.name, requested || '*', fromPath(requestor, requestor.edgesOut.get(child.name)))
|
|
24
24
|
} catch (er) {
|
|
25
25
|
// Not invalid because the child doesn't match, but because
|
|
26
26
|
// the spec itself is not supported. Nothing would match,
|
package/lib/edge.js
CHANGED
|
@@ -169,7 +169,11 @@ class Edge {
|
|
|
169
169
|
if (this.overrides && this.overrides.value && this.overrides.name === this.name) {
|
|
170
170
|
if (this.overrides.value.startsWith('$')) {
|
|
171
171
|
const ref = this.overrides.value.slice(1)
|
|
172
|
-
|
|
172
|
+
// we may be a virtual root, if we are we want to resolve reference overrides
|
|
173
|
+
// from the real root, not the virtual one
|
|
174
|
+
const pkg = this.from.sourceReference
|
|
175
|
+
? this.from.sourceReference.root.package
|
|
176
|
+
: this.from.root.package
|
|
173
177
|
const overrideSpec = (pkg.devDependencies && pkg.devDependencies[ref]) ||
|
|
174
178
|
(pkg.optionalDependencies && pkg.optionalDependencies[ref]) ||
|
|
175
179
|
(pkg.dependencies && pkg.dependencies[ref]) ||
|
package/lib/from-path.js
CHANGED
|
@@ -6,8 +6,19 @@
|
|
|
6
6
|
const { dirname } = require('path')
|
|
7
7
|
const npa = require('npm-package-arg')
|
|
8
8
|
|
|
9
|
-
const fromPath = (node, spec) =>
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const fromPath = (node, spec, edge) => {
|
|
10
|
+
if (edge && edge.overrides && edge.overrides.name === edge.name && edge.overrides.value) {
|
|
11
|
+
// fromPath could be called with a node that has a virtual root, if that happens
|
|
12
|
+
// we want to make sure we get the real root node when overrides are in use. this
|
|
13
|
+
// is to allow things like overriding a dependency with a tarball file that's a
|
|
14
|
+
// relative path from the project root
|
|
15
|
+
return node.sourceReference
|
|
16
|
+
? node.sourceReference.root.realpath
|
|
17
|
+
: node.root.realpath
|
|
18
|
+
}
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
return spec && spec.type === 'file' ? dirname(spec.fetchSpec)
|
|
21
|
+
: node.realpath
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = (node, edge) => fromPath(node, node.resolved && npa(node.resolved), edge)
|
package/lib/link.js
CHANGED
|
@@ -118,7 +118,7 @@ class Link extends Node {
|
|
|
118
118
|
// the path/realpath guard is there for the benefit of setting
|
|
119
119
|
// these things in the "wrong" order
|
|
120
120
|
return this.path && this.realpath
|
|
121
|
-
? `file:${relpath(dirname(this.path), this.realpath)}`
|
|
121
|
+
? `file:${relpath(dirname(this.path), this.realpath).replace(/#/g, '%23')}`
|
|
122
122
|
: null
|
|
123
123
|
}
|
|
124
124
|
|
package/lib/node.js
CHANGED
|
@@ -69,6 +69,8 @@ const consistentResolve = require('./consistent-resolve.js')
|
|
|
69
69
|
const printableTree = require('./printable.js')
|
|
70
70
|
const CaseInsensitiveMap = require('./case-insensitive-map.js')
|
|
71
71
|
|
|
72
|
+
const querySelectorAll = require('./query-selector-all.js')
|
|
73
|
+
|
|
72
74
|
class Node {
|
|
73
75
|
constructor (options) {
|
|
74
76
|
// NB: path can be null if it's a link target
|
|
@@ -824,7 +826,7 @@ class Node {
|
|
|
824
826
|
}
|
|
825
827
|
|
|
826
828
|
for (const [name, path] of this[_workspaces].entries()) {
|
|
827
|
-
new Edge({ from: this, name, spec: `file:${path}`, type: 'workspace' })
|
|
829
|
+
new Edge({ from: this, name, spec: `file:${path.replace(/#/g, '%23')}`, type: 'workspace' })
|
|
828
830
|
}
|
|
829
831
|
}
|
|
830
832
|
|
|
@@ -1446,6 +1448,12 @@ class Node {
|
|
|
1446
1448
|
return base === name && basename(nm) === 'node_modules' ? dir : false
|
|
1447
1449
|
}
|
|
1448
1450
|
|
|
1451
|
+
// maybe accept both string value or array of strings
|
|
1452
|
+
// seems to be what dom API does
|
|
1453
|
+
querySelectorAll (query) {
|
|
1454
|
+
return querySelectorAll(this, query)
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1449
1457
|
toJSON () {
|
|
1450
1458
|
return printableTree(this)
|
|
1451
1459
|
}
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { resolve } = require('path')
|
|
4
|
+
const { parser, arrayDelimiter } = require('@npmcli/query')
|
|
5
|
+
const localeCompare = require('@isaacs/string-locale-compare')('en')
|
|
6
|
+
const npa = require('npm-package-arg')
|
|
7
|
+
const minimatch = require('minimatch')
|
|
8
|
+
const semver = require('semver')
|
|
9
|
+
|
|
10
|
+
// handle results for parsed query asts, results are stored in a map that has a
|
|
11
|
+
// key that points to each ast selector node and stores the resulting array of
|
|
12
|
+
// arborist nodes as its value, that is essential to how we handle multiple
|
|
13
|
+
// query selectors, e.g: `#a, #b, #c` <- 3 diff ast selector nodes
|
|
14
|
+
class Results {
|
|
15
|
+
#currentAstSelector
|
|
16
|
+
#initialItems
|
|
17
|
+
#inventory
|
|
18
|
+
#pendingCombinator
|
|
19
|
+
#results = new Map()
|
|
20
|
+
#targetNode
|
|
21
|
+
|
|
22
|
+
constructor (opts) {
|
|
23
|
+
this.#currentAstSelector = opts.rootAstNode.nodes[0]
|
|
24
|
+
this.#inventory = opts.inventory
|
|
25
|
+
this.#initialItems = opts.initialItems
|
|
26
|
+
this.#targetNode = opts.targetNode
|
|
27
|
+
|
|
28
|
+
this.currentResults = this.#initialItems
|
|
29
|
+
|
|
30
|
+
// reset by rootAstNode walker
|
|
31
|
+
this.currentAstNode = opts.rootAstNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get currentResults () {
|
|
35
|
+
return this.#results.get(this.#currentAstSelector)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set currentResults (value) {
|
|
39
|
+
this.#results.set(this.#currentAstSelector, value)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// retrieves the initial items to which start the filtering / matching
|
|
43
|
+
// for most of the different types of recognized ast nodes, e.g: class (aka
|
|
44
|
+
// depType), id, *, etc in different contexts we need to start with the
|
|
45
|
+
// current list of filtered results, for example a query for `.workspace`
|
|
46
|
+
// actually means the same as `*.workspace` so we want to start with the full
|
|
47
|
+
// inventory if that's the first ast node we're reading but if it appears in
|
|
48
|
+
// the middle of a query it should respect the previous filtered results,
|
|
49
|
+
// combinators are a special case in which we always want to have the
|
|
50
|
+
// complete inventory list in order to use the left-hand side ast node as a
|
|
51
|
+
// filter combined with the element on its right-hand side
|
|
52
|
+
get initialItems () {
|
|
53
|
+
const firstParsed =
|
|
54
|
+
(this.currentAstNode.parent.nodes[0] === this.currentAstNode) &&
|
|
55
|
+
(this.currentAstNode.parent.parent.type === 'root')
|
|
56
|
+
|
|
57
|
+
if (firstParsed) {
|
|
58
|
+
return this.#initialItems
|
|
59
|
+
}
|
|
60
|
+
if (this.currentAstNode.prev().type === 'combinator') {
|
|
61
|
+
return this.#inventory
|
|
62
|
+
}
|
|
63
|
+
return this.currentResults
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// combinators need information about previously filtered items along
|
|
67
|
+
// with info of the items parsed / retrieved from the selector right
|
|
68
|
+
// past the combinator, for this reason combinators are stored and
|
|
69
|
+
// only ran as the last part of each selector logic
|
|
70
|
+
processPendingCombinator (nextResults) {
|
|
71
|
+
if (this.#pendingCombinator) {
|
|
72
|
+
const res = this.#pendingCombinator(this.currentResults, nextResults)
|
|
73
|
+
this.#pendingCombinator = null
|
|
74
|
+
this.currentResults = res
|
|
75
|
+
} else {
|
|
76
|
+
this.currentResults = nextResults
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// when collecting results to a root astNode, we traverse the list of child
|
|
81
|
+
// selector nodes and collect all of their resulting arborist nodes into a
|
|
82
|
+
// single/flat Set of items, this ensures we also deduplicate items
|
|
83
|
+
collect (rootAstNode) {
|
|
84
|
+
return new Set(rootAstNode.nodes.flatMap(n => this.#results.get(n)))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// selector types map to the '.type' property of the ast nodes via `${astNode.type}Type`
|
|
88
|
+
//
|
|
89
|
+
// attribute selector [name=value], etc
|
|
90
|
+
attributeType () {
|
|
91
|
+
const nextResults = this.initialItems.filter(node =>
|
|
92
|
+
attributeMatch(this.currentAstNode, node.package)
|
|
93
|
+
)
|
|
94
|
+
this.processPendingCombinator(nextResults)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// dependency type selector (i.e. .prod, .dev, etc)
|
|
98
|
+
// css calls this class, we interpret is as dependency type
|
|
99
|
+
classType () {
|
|
100
|
+
const depTypeFn = depTypes[String(this.currentAstNode)]
|
|
101
|
+
if (!depTypeFn) {
|
|
102
|
+
throw Object.assign(
|
|
103
|
+
new Error(`\`${String(this.currentAstNode)}\` is not a supported dependency type.`),
|
|
104
|
+
{ code: 'EQUERYNODEPTYPE' }
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
const nextResults = depTypeFn(this.initialItems)
|
|
108
|
+
this.processPendingCombinator(nextResults)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// combinators (i.e. '>', ' ', '~')
|
|
112
|
+
combinatorType () {
|
|
113
|
+
this.#pendingCombinator = combinators[String(this.currentAstNode)]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// name selectors (i.e. #foo, #foo@1.0.0)
|
|
117
|
+
// css calls this id, we interpret it as name
|
|
118
|
+
idType () {
|
|
119
|
+
const spec = npa(this.currentAstNode.value)
|
|
120
|
+
const nextResults = this.initialItems.filter(node =>
|
|
121
|
+
(node.name === spec.name || node.package.name === spec.name) &&
|
|
122
|
+
(semver.satisfies(node.version, spec.fetchSpec) || !spec.rawSpec))
|
|
123
|
+
this.processPendingCombinator(nextResults)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// pseudo selectors (prefixed with :)
|
|
127
|
+
pseudoType () {
|
|
128
|
+
const pseudoFn = `${this.currentAstNode.value.slice(1)}Pseudo`
|
|
129
|
+
if (!this[pseudoFn]) {
|
|
130
|
+
throw Object.assign(
|
|
131
|
+
new Error(`\`${this.currentAstNode.value
|
|
132
|
+
}\` is not a supported pseudo selector.`),
|
|
133
|
+
{ code: 'EQUERYNOPSEUDO' }
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
const nextResults = this[pseudoFn]()
|
|
137
|
+
this.processPendingCombinator(nextResults)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
selectorType () {
|
|
141
|
+
this.#currentAstSelector = this.currentAstNode
|
|
142
|
+
// starts a new array in which resulting items
|
|
143
|
+
// can be stored for each given ast selector
|
|
144
|
+
if (!this.currentResults) {
|
|
145
|
+
this.currentResults = []
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
universalType () {
|
|
150
|
+
this.processPendingCombinator(this.initialItems)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// pseudo selectors map to the 'value' property of the pseudo selectors in the ast nodes
|
|
154
|
+
// via selectors via `${value.slice(1)}Pseudo`
|
|
155
|
+
attrPseudo () {
|
|
156
|
+
const { lookupProperties, attributeMatcher } = this.currentAstNode
|
|
157
|
+
|
|
158
|
+
return this.initialItems.filter(node => {
|
|
159
|
+
let objs = [node.package]
|
|
160
|
+
for (const prop of lookupProperties) {
|
|
161
|
+
// if an isArray symbol is found that means we'll need to iterate
|
|
162
|
+
// over the previous found array to basically make sure we traverse
|
|
163
|
+
// all its indexes testing for possible objects that may eventually
|
|
164
|
+
// hold more keys specified in a selector
|
|
165
|
+
if (prop === arrayDelimiter) {
|
|
166
|
+
objs = objs.flat()
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// otherwise just maps all currently found objs
|
|
171
|
+
// to the next prop from the lookup properties list,
|
|
172
|
+
// filters out any empty key lookup
|
|
173
|
+
objs = objs.flatMap(obj => obj[prop] || [])
|
|
174
|
+
|
|
175
|
+
// in case there's no property found in the lookup
|
|
176
|
+
// just filters that item out
|
|
177
|
+
const noAttr = objs.every(obj => !obj)
|
|
178
|
+
if (noAttr) {
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// if any of the potential object matches
|
|
184
|
+
// that item should be in the final result
|
|
185
|
+
return objs.some(obj => attributeMatch(attributeMatcher, obj))
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
emptyPseudo () {
|
|
190
|
+
return this.initialItems.filter(node => node.edgesOut.size === 0)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
extraneousPseudo () {
|
|
194
|
+
return this.initialItems.filter(node => node.extraneous)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
hasPseudo () {
|
|
198
|
+
const found = []
|
|
199
|
+
for (const item of this.initialItems) {
|
|
200
|
+
const res = retrieveNodesFromParsedAst({
|
|
201
|
+
// This is the one time initialItems differs from inventory
|
|
202
|
+
initialItems: [item],
|
|
203
|
+
inventory: this.#inventory,
|
|
204
|
+
rootAstNode: this.currentAstNode.nestedNode,
|
|
205
|
+
targetNode: item,
|
|
206
|
+
})
|
|
207
|
+
if (res.size > 0) {
|
|
208
|
+
found.push(item)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return found
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
invalidPseudo () {
|
|
215
|
+
const found = []
|
|
216
|
+
for (const node of this.initialItems) {
|
|
217
|
+
for (const edge of node.edgesIn) {
|
|
218
|
+
if (edge.invalid) {
|
|
219
|
+
found.push(node)
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return found
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
isPseudo () {
|
|
228
|
+
const res = retrieveNodesFromParsedAst({
|
|
229
|
+
initialItems: this.initialItems,
|
|
230
|
+
inventory: this.#inventory,
|
|
231
|
+
rootAstNode: this.currentAstNode.nestedNode,
|
|
232
|
+
targetNode: this.currentAstNode,
|
|
233
|
+
})
|
|
234
|
+
return [...res]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
linkPseudo () {
|
|
238
|
+
return this.initialItems.filter(node => node.isLink || (node.isTop && !node.isRoot))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
missingPseudo () {
|
|
242
|
+
return this.#inventory.reduce((res, node) => {
|
|
243
|
+
for (const edge of node.edgesOut.values()) {
|
|
244
|
+
if (edge.missing) {
|
|
245
|
+
const pkg = { name: edge.name, version: edge.spec }
|
|
246
|
+
res.push(new this.#targetNode.constructor({ pkg }))
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return res
|
|
250
|
+
}, [])
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
notPseudo () {
|
|
254
|
+
const res = retrieveNodesFromParsedAst({
|
|
255
|
+
initialItems: this.initialItems,
|
|
256
|
+
inventory: this.#inventory,
|
|
257
|
+
rootAstNode: this.currentAstNode.nestedNode,
|
|
258
|
+
targetNode: this.currentAstNode,
|
|
259
|
+
})
|
|
260
|
+
const internalSelector = new Set(res)
|
|
261
|
+
return this.initialItems.filter(node =>
|
|
262
|
+
!internalSelector.has(node))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
pathPseudo () {
|
|
266
|
+
return this.initialItems.filter(node => {
|
|
267
|
+
if (!this.currentAstNode.pathValue) {
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
return minimatch(
|
|
271
|
+
node.realpath.replace(/\\+/g, '/'),
|
|
272
|
+
resolve(node.root.realpath, this.currentAstNode.pathValue).replace(/\\+/g, '/')
|
|
273
|
+
)
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
privatePseudo () {
|
|
278
|
+
return this.initialItems.filter(node => node.package.private)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
rootPseudo () {
|
|
282
|
+
return this.initialItems.filter(node => node === this.#targetNode.root)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
scopePseudo () {
|
|
286
|
+
return this.initialItems.filter(node => node === this.#targetNode)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
semverPseudo () {
|
|
290
|
+
if (!this.currentAstNode.semverValue) {
|
|
291
|
+
return this.initialItems
|
|
292
|
+
}
|
|
293
|
+
return this.initialItems.filter(node =>
|
|
294
|
+
semver.satisfies(node.version, this.currentAstNode.semverValue))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
typePseudo () {
|
|
298
|
+
if (!this.currentAstNode.typeValue) {
|
|
299
|
+
return this.initialItems
|
|
300
|
+
}
|
|
301
|
+
return this.initialItems
|
|
302
|
+
.flatMap(node => {
|
|
303
|
+
const found = []
|
|
304
|
+
for (const edge of node.edgesIn) {
|
|
305
|
+
if (npa(`${edge.name}@${edge.spec}`).type === this.currentAstNode.typeValue) {
|
|
306
|
+
found.push(edge.to)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return found
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
dedupedPseudo () {
|
|
314
|
+
return this.initialItems.filter(node => node.target.edgesIn.size > 1)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// operators for attribute selectors
|
|
319
|
+
const attributeOperators = {
|
|
320
|
+
// attribute value is equivalent
|
|
321
|
+
'=' ({ attr, value, insensitive }) {
|
|
322
|
+
return attr === value
|
|
323
|
+
},
|
|
324
|
+
// attribute value contains word
|
|
325
|
+
'~=' ({ attr, value, insensitive }) {
|
|
326
|
+
return (attr.match(/\w+/g) || []).includes(value)
|
|
327
|
+
},
|
|
328
|
+
// attribute value contains string
|
|
329
|
+
'*=' ({ attr, value, insensitive }) {
|
|
330
|
+
return attr.includes(value)
|
|
331
|
+
},
|
|
332
|
+
// attribute value is equal or starts with
|
|
333
|
+
'|=' ({ attr, value, insensitive }) {
|
|
334
|
+
return attr.startsWith(`${value}-`)
|
|
335
|
+
},
|
|
336
|
+
// attribute value starts with
|
|
337
|
+
'^=' ({ attr, value, insensitive }) {
|
|
338
|
+
return attr.startsWith(value)
|
|
339
|
+
},
|
|
340
|
+
// attribute value ends with
|
|
341
|
+
'$=' ({ attr, value, insensitive }) {
|
|
342
|
+
return attr.endsWith(value)
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const attributeOperator = ({ attr, value, insensitive, operator }) => {
|
|
347
|
+
if (typeof attr === 'number') {
|
|
348
|
+
attr = String(attr)
|
|
349
|
+
}
|
|
350
|
+
if (typeof attr !== 'string') {
|
|
351
|
+
// It's an object or an array, bail
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
if (insensitive) {
|
|
355
|
+
attr = attr.toLowerCase()
|
|
356
|
+
}
|
|
357
|
+
return attributeOperators[operator]({
|
|
358
|
+
attr,
|
|
359
|
+
insensitive,
|
|
360
|
+
value,
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const attributeMatch = (matcher, obj) => {
|
|
365
|
+
const insensitive = !!matcher.insensitive
|
|
366
|
+
const operator = matcher.operator || ''
|
|
367
|
+
const attribute = matcher.qualifiedAttribute
|
|
368
|
+
let value = matcher.value || ''
|
|
369
|
+
// return early if checking existence
|
|
370
|
+
if (operator === '') {
|
|
371
|
+
return Boolean(obj[attribute])
|
|
372
|
+
}
|
|
373
|
+
if (insensitive) {
|
|
374
|
+
value = value.toLowerCase()
|
|
375
|
+
}
|
|
376
|
+
// in case the current object is an array
|
|
377
|
+
// then we try to match every item in the array
|
|
378
|
+
if (Array.isArray(obj[attribute])) {
|
|
379
|
+
return obj[attribute].find((i, index) => {
|
|
380
|
+
const attr = obj[attribute][index] || ''
|
|
381
|
+
return attributeOperator({ attr, value, insensitive, operator })
|
|
382
|
+
})
|
|
383
|
+
} else {
|
|
384
|
+
const attr = obj[attribute] || ''
|
|
385
|
+
return attributeOperator({ attr, value, insensitive, operator })
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const edgeIsType = (node, type, seen = new Set()) => {
|
|
390
|
+
for (const edgeIn of node.edgesIn) {
|
|
391
|
+
// TODO Need a test with an infinite loop
|
|
392
|
+
if (seen.has(edgeIn)) {
|
|
393
|
+
continue
|
|
394
|
+
}
|
|
395
|
+
seen.add(edgeIn)
|
|
396
|
+
if (edgeIn.type === type || edgeIn.from[type] || edgeIsType(edgeIn.from, type, seen)) {
|
|
397
|
+
return true
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return false
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const filterByType = (nodes, type) => {
|
|
404
|
+
const found = []
|
|
405
|
+
for (const node of nodes) {
|
|
406
|
+
if (node[type] || edgeIsType(node, type)) {
|
|
407
|
+
found.push(node)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return found
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const depTypes = {
|
|
414
|
+
// dependency
|
|
415
|
+
'.prod' (prevResults) {
|
|
416
|
+
const found = []
|
|
417
|
+
for (const node of prevResults) {
|
|
418
|
+
if (!node.dev) {
|
|
419
|
+
found.push(node)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return found
|
|
423
|
+
},
|
|
424
|
+
// devDependency
|
|
425
|
+
'.dev' (prevResults) {
|
|
426
|
+
return filterByType(prevResults, 'dev')
|
|
427
|
+
},
|
|
428
|
+
// optionalDependency
|
|
429
|
+
'.optional' (prevResults) {
|
|
430
|
+
return filterByType(prevResults, 'optional')
|
|
431
|
+
},
|
|
432
|
+
// peerDependency
|
|
433
|
+
'.peer' (prevResults) {
|
|
434
|
+
return filterByType(prevResults, 'peer')
|
|
435
|
+
},
|
|
436
|
+
// workspace
|
|
437
|
+
'.workspace' (prevResults) {
|
|
438
|
+
return prevResults.filter(node => node.isWorkspace)
|
|
439
|
+
},
|
|
440
|
+
// bundledDependency
|
|
441
|
+
'.bundled' (prevResults) {
|
|
442
|
+
return prevResults.filter(node => node.inBundle)
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// checks if a given node has a direct parent in any of the nodes provided in
|
|
447
|
+
// the compare nodes array
|
|
448
|
+
const hasParent = (node, compareNodes) => {
|
|
449
|
+
// All it takes is one so we loop and return on the first hit
|
|
450
|
+
for (const compareNode of compareNodes) {
|
|
451
|
+
// follows logical parent for link anscestors
|
|
452
|
+
if (node.isTop && (node.resolveParent === compareNode)) {
|
|
453
|
+
return true
|
|
454
|
+
}
|
|
455
|
+
// follows edges-in to check if they match a possible parent
|
|
456
|
+
for (const edge of node.edgesIn) {
|
|
457
|
+
if (edge && edge.from === compareNode) {
|
|
458
|
+
return true
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// checks if a given node is a descendant of any of the nodes provided in the
|
|
466
|
+
// compareNodes array
|
|
467
|
+
const hasAscendant = (node, compareNodes, seen = new Set()) => {
|
|
468
|
+
// TODO (future) loop over ancestry property
|
|
469
|
+
if (hasParent(node, compareNodes)) {
|
|
470
|
+
return true
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (node.isTop && node.resolveParent) {
|
|
474
|
+
return hasAscendant(node.resolveParent, compareNodes)
|
|
475
|
+
}
|
|
476
|
+
for (const edge of node.edgesIn) {
|
|
477
|
+
// TODO Need a test with an infinite loop
|
|
478
|
+
if (seen.has(edge)) {
|
|
479
|
+
continue
|
|
480
|
+
}
|
|
481
|
+
seen.add(edge)
|
|
482
|
+
if (edge && edge.from && hasAscendant(edge.from, compareNodes, seen)) {
|
|
483
|
+
return true
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return false
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const combinators = {
|
|
490
|
+
// direct descendant
|
|
491
|
+
'>' (prevResults, nextResults) {
|
|
492
|
+
return nextResults.filter(node => hasParent(node, prevResults))
|
|
493
|
+
},
|
|
494
|
+
// any descendant
|
|
495
|
+
' ' (prevResults, nextResults) {
|
|
496
|
+
return nextResults.filter(node => hasAscendant(node, prevResults))
|
|
497
|
+
},
|
|
498
|
+
// sibling
|
|
499
|
+
'~' (prevResults, nextResults) {
|
|
500
|
+
// Return any node in nextResults that is a sibling of (aka shares a
|
|
501
|
+
// parent with) a node in prevResults
|
|
502
|
+
const parentNodes = new Set() // Parents of everything in prevResults
|
|
503
|
+
for (const node of prevResults) {
|
|
504
|
+
for (const edge of node.edgesIn) {
|
|
505
|
+
// edge.from always exists cause it's from another node's edgesIn
|
|
506
|
+
parentNodes.add(edge.from)
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return nextResults.filter(node =>
|
|
510
|
+
!prevResults.includes(node) && hasParent(node, [...parentNodes])
|
|
511
|
+
)
|
|
512
|
+
},
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const retrieveNodesFromParsedAst = (opts) => {
|
|
516
|
+
// when we first call this it's the parsed query. all other times it's
|
|
517
|
+
// results.currentNode.nestedNode
|
|
518
|
+
const rootAstNode = opts.rootAstNode
|
|
519
|
+
|
|
520
|
+
if (!rootAstNode.nodes) {
|
|
521
|
+
return new Set()
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const results = new Results(opts)
|
|
525
|
+
|
|
526
|
+
rootAstNode.walk((nextAstNode) => {
|
|
527
|
+
// This is the only place we reset currentAstNode
|
|
528
|
+
results.currentAstNode = nextAstNode
|
|
529
|
+
const updateFn = `${results.currentAstNode.type}Type`
|
|
530
|
+
if (typeof results[updateFn] !== 'function') {
|
|
531
|
+
throw Object.assign(
|
|
532
|
+
new Error(`\`${results.currentAstNode.type}\` is not a supported selector.`),
|
|
533
|
+
{ code: 'EQUERYNOSELECTOR' }
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
results[updateFn]()
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
return results.collect(rootAstNode)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// We are keeping this async in the event that we do add async operators, we
|
|
543
|
+
// won't have to have a breaking change on this function signature.
|
|
544
|
+
const querySelectorAll = async (targetNode, query) => {
|
|
545
|
+
// This never changes ever we just pass it around. But we can't scope it to
|
|
546
|
+
// this whole file if we ever want to support concurrent calls to this
|
|
547
|
+
// function.
|
|
548
|
+
const inventory = [...targetNode.root.inventory.values()]
|
|
549
|
+
// res is a Set of items returned for each parsed css ast selector
|
|
550
|
+
const res = retrieveNodesFromParsedAst({
|
|
551
|
+
initialItems: inventory,
|
|
552
|
+
inventory,
|
|
553
|
+
rootAstNode: parser(query),
|
|
554
|
+
targetNode,
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
// returns nodes ordered by realpath
|
|
558
|
+
return [...res].sort((a, b) => localeCompare(a.location, b.location))
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
module.exports = querySelectorAll
|
package/lib/shrinkwrap.js
CHANGED
|
@@ -815,7 +815,7 @@ class Shrinkwrap {
|
|
|
815
815
|
const pathFixed = !resolved ? null
|
|
816
816
|
: !/^file:/.test(resolved) ? resolved
|
|
817
817
|
// resolve onto the metadata path
|
|
818
|
-
: `file:${resolve(this.path, resolved.slice(5))}`
|
|
818
|
+
: `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}`
|
|
819
819
|
|
|
820
820
|
// if we have one, only set the other if it matches
|
|
821
821
|
// otherwise it could be for a completely different thing.
|
|
@@ -996,7 +996,7 @@ class Shrinkwrap {
|
|
|
996
996
|
: npa.resolve(node.name, edge.spec, edge.from.realpath)
|
|
997
997
|
|
|
998
998
|
if (node.isLink) {
|
|
999
|
-
lock.version = `file:${relpath(this.path, node.realpath)}`
|
|
999
|
+
lock.version = `file:${relpath(this.path, node.realpath).replace(/#/g, '%23')}`
|
|
1000
1000
|
} else if (spec && (spec.type === 'file' || spec.type === 'remote')) {
|
|
1001
1001
|
lock.version = spec.saveSpec
|
|
1002
1002
|
} else if (spec && spec.type === 'git' || rSpec.type === 'git') {
|
|
@@ -1074,7 +1074,7 @@ class Shrinkwrap {
|
|
|
1074
1074
|
// this especially shows up with workspace edges when the root
|
|
1075
1075
|
// node is also a workspace in the set.
|
|
1076
1076
|
const p = resolve(node.realpath, spec.slice('file:'.length))
|
|
1077
|
-
set[k] = `file:${relpath(node.realpath, p)}`
|
|
1077
|
+
set[k] = `file:${relpath(node.realpath, p).replace(/#/g, '%23')}`
|
|
1078
1078
|
} else {
|
|
1079
1079
|
set[k] = spec
|
|
1080
1080
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@isaacs/string-locale-compare": "^1.1.0",
|
|
@@ -11,12 +11,14 @@
|
|
|
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": "^1.1.1",
|
|
14
15
|
"@npmcli/run-script": "^4.1.3",
|
|
15
16
|
"bin-links": "^3.0.0",
|
|
16
17
|
"cacache": "^16.0.6",
|
|
17
18
|
"common-ancestor-path": "^1.0.1",
|
|
18
19
|
"json-parse-even-better-errors": "^2.3.1",
|
|
19
20
|
"json-stringify-nice": "^1.1.4",
|
|
21
|
+
"minimatch": "^5.1.0",
|
|
20
22
|
"mkdirp": "^1.0.4",
|
|
21
23
|
"mkdirp-infer-owner": "^2.0.0",
|
|
22
24
|
"nopt": "^5.0.0",
|