@npmcli/arborist 5.1.1 → 5.2.2

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/README.md CHANGED
@@ -9,7 +9,7 @@ Inspect and manage `node_modules` trees.
9
9
  ![a tree with the word ARBORIST superimposed on it](https://raw.githubusercontent.com/npm/arborist/main/docs/logo.svg?sanitize=true)
10
10
 
11
11
  There's more documentation [in the docs
12
- folder](https://github.com/npm/arborist/tree/main/docs).
12
+ folder](https://github.com/npm/cli/tree/latest/workspaces/arborist/docs).
13
13
 
14
14
  ## USAGE
15
15
 
@@ -329,6 +329,7 @@ Try using the package name instead, e.g:
329
329
  ? Shrinkwrap.reset({
330
330
  path: this.path,
331
331
  lockfileVersion: this.options.lockfileVersion,
332
+ resolveOptions: this.options,
332
333
  }).then(meta => Object.assign(root, { meta }))
333
334
  : this.loadVirtual({ root }))
334
335
 
@@ -388,6 +389,7 @@ Try using the package name instead, e.g:
388
389
  const meta = new Shrinkwrap({
389
390
  path: this.path,
390
391
  lockfileVersion: this.options.lockfileVersion,
392
+ resolveOptions: this.options,
391
393
  })
392
394
  meta.reset()
393
395
  root.meta = meta
@@ -671,7 +673,7 @@ Try using the package name instead, e.g:
671
673
  const breakingMessage = isSemVerMajor
672
674
  ? 'a SemVer major change'
673
675
  : 'outside your stated dependency range'
674
- log.warn('audit', `Updating ${name} to ${version},` +
676
+ log.warn('audit', `Updating ${name} to ${version}, ` +
675
677
  `which is ${breakingMessage}.`)
676
678
 
677
679
  await this[_add](node, { add: [`${name}@${version}`] })
@@ -147,6 +147,7 @@ module.exports = cls => class ActualLoader extends cls {
147
147
  const meta = await Shrinkwrap.load({
148
148
  path: this[_actualTree].path,
149
149
  hiddenLockfile: true,
150
+ resolveOptions: this.options,
150
151
  })
151
152
  if (meta.loadedFromDisk) {
152
153
  this[_actualTree].meta = meta
@@ -155,6 +156,7 @@ module.exports = cls => class ActualLoader extends cls {
155
156
  const meta = await Shrinkwrap.load({
156
157
  path: this[_actualTree].path,
157
158
  lockfileVersion: this.options.lockfileVersion,
159
+ resolveOptions: this.options,
158
160
  })
159
161
  this[_actualTree].meta = meta
160
162
  return this[_loadActualActually]({ root, ignoreMissing })
@@ -57,6 +57,7 @@ module.exports = cls => class VirtualLoader extends cls {
57
57
  const s = await Shrinkwrap.load({
58
58
  path: this.path,
59
59
  lockfileVersion: this.options.lockfileVersion,
60
+ resolveOptions: this.options,
60
61
  })
61
62
  if (!s.loadedFromDisk && !options.root) {
62
63
  const er = new Error('loadVirtual requires existing shrinkwrap file')
@@ -21,6 +21,8 @@ const sortNodes = (a, b) =>
21
21
 
22
22
  const _workspaces = Symbol.for('workspaces')
23
23
  const _build = Symbol('build')
24
+ const _loadDefaultNodes = Symbol('loadDefaultNodes')
25
+ const _retrieveNodesByType = Symbol('retrieveNodesByType')
24
26
  const _resetQueues = Symbol('resetQueues')
25
27
  const _rebuildBundle = Symbol('rebuildBundle')
26
28
  const _ignoreScripts = Symbol('ignoreScripts')
@@ -39,6 +41,7 @@ const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot')
39
41
  const _workspacesEnabled = Symbol.for('workspacesEnabled')
40
42
 
41
43
  const _force = Symbol.for('force')
44
+ const _global = Symbol.for('global')
42
45
 
43
46
  // defined by reify mixin
44
47
  const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
@@ -75,36 +78,60 @@ module.exports = cls => class Builder extends cls {
75
78
  // running JUST a rebuild, we treat optional failures as real fails
76
79
  this[_doHandleOptionalFailure] = handleOptionalFailure
77
80
 
78
- // if we don't have a set of nodes, then just rebuild
79
- // the actual tree on disk.
80
81
  if (!nodes) {
81
- const tree = await this.loadActual()
82
- let filterSet
83
- if (!this[_workspacesEnabled]) {
84
- filterSet = this.excludeWorkspacesDependencySet(tree)
85
- nodes = tree.inventory.filter(node =>
86
- filterSet.has(node) || node.isProjectRoot
87
- )
88
- } else if (this[_workspaces] && this[_workspaces].length) {
89
- filterSet = this.workspaceDependencySet(
90
- tree,
91
- this[_workspaces],
92
- this[_includeWorkspaceRoot]
93
- )
94
- nodes = tree.inventory.filter(node => filterSet.has(node))
95
- } else {
96
- nodes = tree.inventory.values()
97
- }
82
+ nodes = await this[_loadDefaultNodes]()
98
83
  }
99
84
 
100
85
  // separates links nodes so that it can run
101
86
  // prepare scripts and link bins in the expected order
102
87
  process.emit('time', 'build')
88
+
89
+ const {
90
+ depNodes,
91
+ linkNodes,
92
+ } = this[_retrieveNodesByType](nodes)
93
+
94
+ // build regular deps
95
+ await this[_build](depNodes, {})
96
+
97
+ // build link deps
98
+ if (linkNodes.size) {
99
+ this[_resetQueues]()
100
+ await this[_build](linkNodes, { type: 'links' })
101
+ }
102
+
103
+ process.emit('timeEnd', 'build')
104
+ }
105
+
106
+ // if we don't have a set of nodes, then just rebuild
107
+ // the actual tree on disk.
108
+ async [_loadDefaultNodes] () {
109
+ let nodes
110
+ const tree = await this.loadActual()
111
+ let filterSet
112
+ if (!this[_workspacesEnabled]) {
113
+ filterSet = this.excludeWorkspacesDependencySet(tree)
114
+ nodes = tree.inventory.filter(node =>
115
+ filterSet.has(node) || node.isProjectRoot
116
+ )
117
+ } else if (this[_workspaces] && this[_workspaces].length) {
118
+ filterSet = this.workspaceDependencySet(
119
+ tree,
120
+ this[_workspaces],
121
+ this[_includeWorkspaceRoot]
122
+ )
123
+ nodes = tree.inventory.filter(node => filterSet.has(node))
124
+ } else {
125
+ nodes = tree.inventory.values()
126
+ }
127
+ return nodes
128
+ }
129
+
130
+ [_retrieveNodesByType] (nodes) {
103
131
  const depNodes = new Set()
104
132
  const linkNodes = new Set()
133
+
105
134
  for (const node of nodes) {
106
- // we skip the target nodes to that workspace in order to make sure
107
- // we only run lifecycle scripts / place bin links once per workspace
108
135
  if (node.isLink) {
109
136
  linkNodes.add(node)
110
137
  } else {
@@ -112,14 +139,22 @@ module.exports = cls => class Builder extends cls {
112
139
  }
113
140
  }
114
141
 
115
- await this[_build](depNodes, {})
116
-
117
- if (linkNodes.size) {
118
- this[_resetQueues]()
119
- await this[_build](linkNodes, { type: 'links' })
142
+ // deduplicates link nodes and their targets, avoids
143
+ // calling lifecycle scripts twice when running `npm rebuild`
144
+ // ref: https://github.com/npm/cli/issues/2905
145
+ //
146
+ // we avoid doing so if global=true since `bin-links` relies
147
+ // on having the target nodes available in global mode.
148
+ if (!this[_global]) {
149
+ for (const node of linkNodes) {
150
+ depNodes.delete(node.target)
151
+ }
120
152
  }
121
153
 
122
- process.emit('timeEnd', 'build')
154
+ return {
155
+ depNodes,
156
+ linkNodes,
157
+ }
123
158
  }
124
159
 
125
160
  [_resetQueues] () {
@@ -136,24 +171,22 @@ module.exports = cls => class Builder extends cls {
136
171
  process.emit('time', `build:${type}`)
137
172
 
138
173
  await this[_buildQueues](nodes)
174
+
175
+ if (!this[_ignoreScripts]) {
176
+ await this[_runScripts]('preinstall')
177
+ }
178
+
139
179
  // links should run prepare scripts and only link bins after that
140
- if (type !== 'links') {
141
- if (!this[_ignoreScripts]) {
142
- await this[_runScripts]('preinstall')
143
- }
144
- if (this[_binLinks]) {
145
- await this[_linkAllBins]()
146
- }
147
- if (!this[_ignoreScripts]) {
148
- await this[_runScripts]('install')
149
- await this[_runScripts]('postinstall')
150
- }
151
- } else {
180
+ if (type === 'links') {
152
181
  await this[_runScripts]('prepare')
182
+ }
183
+ if (this[_binLinks]) {
184
+ await this[_linkAllBins]()
185
+ }
153
186
 
154
- if (this[_binLinks]) {
155
- await this[_linkAllBins]()
156
- }
187
+ if (!this[_ignoreScripts]) {
188
+ await this[_runScripts]('install')
189
+ await this[_runScripts]('postinstall')
157
190
  }
158
191
 
159
192
  process.emit('timeEnd', `build:${type}`)
@@ -1105,7 +1105,8 @@ module.exports = cls => class Reifier extends cls {
1105
1105
  // skip links that only live within node_modules as they are most
1106
1106
  // likely managed by packages we installed, we only want to rebuild
1107
1107
  // unchanged links we directly manage
1108
- if (node.isLink && node.target.fsTop === tree) {
1108
+ const linkedFromRoot = node.parent === tree || node.target.fsTop === tree
1109
+ if (node.isLink && linkedFromRoot) {
1109
1110
  nodes.push(node)
1110
1111
  }
1111
1112
  }
package/lib/edge.js CHANGED
@@ -92,7 +92,12 @@ class Edge {
92
92
  return false
93
93
  }
94
94
 
95
- return depValid(node, this.spec, this.accept, this.from)
95
+ // NOTE: this condition means we explicitly do not support overriding
96
+ // bundled or shrinkwrapped dependencies
97
+ const spec = (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle)
98
+ ? this.rawSpec
99
+ : this.spec
100
+ return depValid(node, spec, this.accept, this.from)
96
101
  }
97
102
 
98
103
  explain (seen = []) {
package/lib/node.js CHANGED
@@ -524,6 +524,18 @@ class Node {
524
524
  return this === this.root || this === this.root.target
525
525
  }
526
526
 
527
+ get isRegistryDependency () {
528
+ if (this.edgesIn.size === 0) {
529
+ return false
530
+ }
531
+ for (const edge of this.edgesIn) {
532
+ if (!npa(edge.spec).registry) {
533
+ return false
534
+ }
535
+ }
536
+ return true
537
+ }
538
+
527
539
  * ancestry () {
528
540
  for (let anc = this; anc; anc = anc.resolveParent) {
529
541
  yield anc
@@ -0,0 +1,11 @@
1
+ function overrideResolves (resolved, opts = {}) {
2
+ const { omitLockfileRegistryResolved = false } = opts
3
+
4
+ if (omitLockfileRegistryResolved) {
5
+ return undefined
6
+ }
7
+
8
+ return resolved
9
+ }
10
+
11
+ module.exports = { overrideResolves }
package/lib/shrinkwrap.js CHANGED
@@ -95,6 +95,7 @@ const specFromResolved = resolved => {
95
95
  const relpath = require('./relpath.js')
96
96
 
97
97
  const consistentResolve = require('./consistent-resolve.js')
98
+ const { overrideResolves } = require('./override-resolves.js')
98
99
 
99
100
  const maybeReadFile = file => {
100
101
  return readFile(file, 'utf8').then(d => d, er => {
@@ -265,7 +266,7 @@ class Shrinkwrap {
265
266
  return s
266
267
  }
267
268
 
268
- static metaFromNode (node, path) {
269
+ static metaFromNode (node, path, options = {}) {
269
270
  if (node.isLink) {
270
271
  return {
271
272
  resolved: relpath(path, node.realpath),
@@ -299,7 +300,12 @@ class Shrinkwrap {
299
300
  })
300
301
 
301
302
  const resolved = consistentResolve(node.resolved, node.path, path, true)
302
- if (resolved) {
303
+ // hide resolved from registry dependencies.
304
+ if (!resolved) {
305
+ // no-op
306
+ } else if (node.isRegistryDependency) {
307
+ meta.resolved = overrideResolves(resolved, options)
308
+ } else {
303
309
  meta.resolved = resolved
304
310
  }
305
311
 
@@ -330,6 +336,7 @@ class Shrinkwrap {
330
336
  shrinkwrapOnly = false,
331
337
  hiddenLockfile = false,
332
338
  lockfileVersion,
339
+ resolveOptions = {},
333
340
  } = options
334
341
 
335
342
  this.lockfileVersion = hiddenLockfile ? 3
@@ -347,6 +354,7 @@ class Shrinkwrap {
347
354
  this.yarnLock = null
348
355
  this.hiddenLockfile = hiddenLockfile
349
356
  this.loadingError = null
357
+ this.resolveOptions = resolveOptions
350
358
  // only load npm-shrinkwrap.json in dep trees, not package-lock
351
359
  this.shrinkwrapOnly = shrinkwrapOnly
352
360
  }
@@ -830,7 +838,7 @@ class Shrinkwrap {
830
838
  resolved,
831
839
  integrity,
832
840
  hasShrinkwrap,
833
- } = Shrinkwrap.metaFromNode(node, this.path)
841
+ } = Shrinkwrap.metaFromNode(node, this.path, this.resolveOptions)
834
842
  node.resolved = node.resolved || resolved || null
835
843
  node.integrity = node.integrity || integrity || null
836
844
  node.hasShrinkwrap = node.hasShrinkwrap || hasShrinkwrap || false
@@ -886,7 +894,10 @@ class Shrinkwrap {
886
894
  [_updateWaitingNode] (loc) {
887
895
  const node = this[_awaitingUpdate].get(loc)
888
896
  this[_awaitingUpdate].delete(loc)
889
- this.data.packages[loc] = Shrinkwrap.metaFromNode(node, this.path)
897
+ this.data.packages[loc] = Shrinkwrap.metaFromNode(
898
+ node,
899
+ this.path,
900
+ this.resolveOptions)
890
901
  }
891
902
 
892
903
  commit () {
@@ -894,7 +905,10 @@ class Shrinkwrap {
894
905
  if (this.yarnLock) {
895
906
  this.yarnLock.fromTree(this.tree)
896
907
  }
897
- const root = Shrinkwrap.metaFromNode(this.tree.target, this.path)
908
+ const root = Shrinkwrap.metaFromNode(
909
+ this.tree.target,
910
+ this.path,
911
+ this.resolveOptions)
898
912
  this.data.packages = {}
899
913
  if (Object.keys(root).length) {
900
914
  this.data.packages[''] = root
@@ -905,7 +919,10 @@ class Shrinkwrap {
905
919
  continue
906
920
  }
907
921
  const loc = relpath(this.path, node.path)
908
- this.data.packages[loc] = Shrinkwrap.metaFromNode(node, this.path)
922
+ this.data.packages[loc] = Shrinkwrap.metaFromNode(
923
+ node,
924
+ this.path,
925
+ this.resolveOptions)
909
926
  }
910
927
  } else if (this[_awaitingUpdate].size > 0) {
911
928
  for (const loc of this[_awaitingUpdate].keys()) {
@@ -1013,7 +1030,7 @@ class Shrinkwrap {
1013
1030
  spec.type !== 'git' &&
1014
1031
  spec.type !== 'file' &&
1015
1032
  spec.type !== 'remote') {
1016
- lock.resolved = node.resolved
1033
+ lock.resolved = overrideResolves(node.resolved, this.resolveOptions)
1017
1034
  }
1018
1035
 
1019
1036
  if (node.integrity) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "5.1.1",
3
+ "version": "5.2.2",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@isaacs/string-locale-compare": "^1.1.0",
@@ -11,7 +11,7 @@
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/run-script": "^3.0.0",
14
+ "@npmcli/run-script": "^4.1.0",
15
15
  "bin-links": "^3.0.0",
16
16
  "cacache": "^16.0.6",
17
17
  "common-ancestor-path": "^1.0.1",
@@ -25,7 +25,7 @@
25
25
  "npm-pick-manifest": "^7.0.0",
26
26
  "npm-registry-fetch": "^13.0.0",
27
27
  "npmlog": "^6.0.2",
28
- "pacote": "^13.0.5",
28
+ "pacote": "^13.6.1",
29
29
  "parse-conflict-json": "^2.0.1",
30
30
  "proc-log": "^2.0.0",
31
31
  "promise-all-reject-late": "^1.0.0",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@npmcli/eslint-config": "^3.0.1",
43
- "@npmcli/template-oss": "3.4.2",
43
+ "@npmcli/template-oss": "3.5.0",
44
44
  "benchmark": "^2.1.4",
45
45
  "chalk": "^4.1.0",
46
46
  "minify-registry-metadata": "^2.1.0",
@@ -101,6 +101,6 @@
101
101
  },
102
102
  "templateOSS": {
103
103
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
104
- "version": "3.4.2"
104
+ "version": "3.5.0"
105
105
  }
106
106
  }