@npmcli/arborist 2.3.0 → 2.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.
@@ -20,13 +20,21 @@ const levelMap = new Map(levels.reduce((set, level, index) => {
20
20
  }, []))
21
21
 
22
22
  const { inspect, format } = require('util')
23
+ const colors = process.stderr.isTTY
24
+ const magenta = colors ? msg => `\x1B[35m${msg}\x1B[39m` : m => m
23
25
  if (loglevel !== 'silent') {
24
26
  process.on('log', (level, ...args) => {
25
27
  if (levelMap.get(level) < levelMap.get(loglevel))
26
28
  return
27
- const pref = `${process.pid} ${level} `
29
+ const pref = `${process.pid} ${magenta(level)} `
28
30
  if (level === 'warn' && args[0] === 'ERESOLVE')
29
- args[2] = inspect(args[2], { depth: 10 })
31
+ args[2] = inspect(args[2], { depth: 10, colors })
32
+ else {
33
+ args = args.map(a => {
34
+ return typeof a === 'string' ? a
35
+ : inspect(a, { depth: 10, colors })
36
+ })
37
+ }
30
38
  const msg = pref + format(...args).trim().split('\n').join(`\n${pref}`)
31
39
  console.error(msg)
32
40
  })
package/bin/lib/timers.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const timers = Object.create(null)
2
+ const { format } = require('util')
2
3
 
3
4
  process.on('time', name => {
4
5
  if (timers[name])
@@ -6,17 +7,20 @@ process.on('time', name => {
6
7
  timers[name] = process.hrtime()
7
8
  })
8
9
 
10
+ const dim = process.stderr.isTTY ? msg => `\x1B[2m${msg}\x1B[22m` : m => m
11
+ const red = process.stderr.isTTY ? msg => `\x1B[31m${msg}\x1B[39m` : m => m
9
12
  process.on('timeEnd', name => {
10
13
  if (!timers[name])
11
14
  throw new Error('timer not started! ' + name)
12
15
  const res = process.hrtime(timers[name])
13
16
  delete timers[name]
14
- console.error(`${process.pid} ${name}`, res[0] * 1e3 + res[1] / 1e6)
17
+ const msg = format(`${process.pid} ${name}`, res[0] * 1e3 + res[1] / 1e6)
18
+ console.error(dim(msg))
15
19
  })
16
20
 
17
21
  process.on('exit', () => {
18
22
  for (const name of Object.keys(timers)) {
19
- console.error('Dangling timer: ', name)
23
+ console.error(red('Dangling timer:'), name)
20
24
  process.exitCode = 1
21
25
  }
22
26
  })
package/bin/virtual.js CHANGED
@@ -8,7 +8,8 @@ require('./lib/timers.js')
8
8
  const start = process.hrtime()
9
9
  new Arborist(options).loadVirtual().then(tree => {
10
10
  const end = process.hrtime(start)
11
- print(tree)
11
+ if (!options.quiet)
12
+ print(tree)
12
13
  if (options.save)
13
14
  tree.meta.save()
14
15
  console.error(`read ${tree.inventory.size} deps in ${end[0] * 1000 + end[1] / 1e6}ms`)
@@ -1188,6 +1188,16 @@ This is a one-time fix-up, please be patient...
1188
1188
  }
1189
1189
  }
1190
1190
 
1191
+ // There is something present already, and we're not happy about it
1192
+ // See if the thing we WOULD be happy with is also going to satisfy
1193
+ // the other dependents on the current node.
1194
+ const current = edge.to
1195
+ const dep = await this[_nodeFromEdge](edge, null, null, required)
1196
+ if (dep.canReplace(current)) {
1197
+ await this[_nodeFromEdge](edge, node.parent, null, required)
1198
+ continue
1199
+ }
1200
+
1191
1201
  // at this point we know that there is a dep there, and
1192
1202
  // we don't like it. always fail strictly, always allow forcibly or
1193
1203
  // in non-strict mode if it's not our fault. don't warn here, because
@@ -54,7 +54,7 @@ class Arborist extends Base {
54
54
  ...options,
55
55
  path: options.path || '.',
56
56
  cache: options.cache || `${homedir()}/.npm/_cacache`,
57
- packumentCache: new Map(),
57
+ packumentCache: options.packumentCache || new Map(),
58
58
  log: options.log || procLog,
59
59
  }
60
60
  this.cache = resolve(this.options.cache)
@@ -93,7 +93,8 @@ module.exports = cls => class VirtualLoader extends cls {
93
93
  this.virtualTree = root
94
94
  const {links, nodes} = this[resolveNodes](s, root)
95
95
  await this[resolveLinks](links, nodes)
96
- this[assignBundles](nodes)
96
+ if (!(s.originalLockfileVersion >= 2))
97
+ this[assignBundles](nodes)
97
98
  if (this[flagsSuspect])
98
99
  this[reCalcDepFlags](nodes.values())
99
100
  return root
@@ -220,22 +221,24 @@ module.exports = cls => class VirtualLoader extends cls {
220
221
  [assignBundles] (nodes) {
221
222
  for (const [location, node] of nodes) {
222
223
  // Skip assignment of parentage for the root package
223
- if (!location)
224
+ if (!location || node.target && !node.target.location)
224
225
  continue
225
226
  const { name, parent, package: { inBundle }} = node
227
+
226
228
  if (!parent)
227
229
  continue
228
230
 
229
231
  // read inBundle from package because 'package' here is
230
232
  // actually a v2 lockfile metadata entry.
231
- // If the *parent* is also bundled, though, then we assume
232
- // that it's being pulled in just by virtue of that.
233
+ // If the *parent* is also bundled, though, or if the parent has
234
+ // no dependency on it, then we assume that it's being pulled in
235
+ // just by virtue of its parent or a transitive dep being bundled.
233
236
  const { package: ppkg } = parent
234
237
  const { inBundle: parentBundled } = ppkg
235
- if (inBundle && !parentBundled) {
238
+ if (inBundle && !parentBundled && parent.edgesOut.has(node.name)) {
236
239
  if (!ppkg.bundleDependencies)
237
240
  ppkg.bundleDependencies = [name]
238
- else if (!ppkg.bundleDependencies.includes(name))
241
+ else
239
242
  ppkg.bundleDependencies.push(name)
240
243
  }
241
244
  }
@@ -115,10 +115,6 @@ module.exports = cls => class Builder extends cls {
115
115
  await this[_runScripts]('preinstall')
116
116
  if (this[_binLinks] && type !== 'links')
117
117
  await this[_linkAllBins]()
118
- if (!this[_ignoreScripts]) {
119
- await this[_runScripts]('install')
120
- await this[_runScripts]('postinstall')
121
- }
122
118
 
123
119
  // links should also run prepare scripts and only link bins after that
124
120
  if (type === 'links') {
@@ -128,6 +124,11 @@ module.exports = cls => class Builder extends cls {
128
124
  await this[_linkAllBins]()
129
125
  }
130
126
 
127
+ if (!this[_ignoreScripts]) {
128
+ await this[_runScripts]('install')
129
+ await this[_runScripts]('postinstall')
130
+ }
131
+
131
132
  process.emit('timeEnd', `build:${type}`)
132
133
  }
133
134
 
@@ -49,7 +49,8 @@ const _loadTrees = Symbol.for('loadTrees')
49
49
  const _diffTrees = Symbol.for('diffTrees')
50
50
  const _createSparseTree = Symbol.for('createSparseTree')
51
51
  const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
52
- const _shrinkwrapUnpacked = Symbol('shrinkwrapUnpacked')
52
+ const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
53
+ const _bundleUnpacked = Symbol('bundleUnpacked')
53
54
  const _reifyNode = Symbol.for('reifyNode')
54
55
  const _extractOrLink = Symbol('extractOrLink')
55
56
  // defined by rebuild mixin
@@ -108,7 +109,7 @@ module.exports = cls => class Reifier extends cls {
108
109
 
109
110
  this.diff = null
110
111
  this[_retiredPaths] = {}
111
- this[_shrinkwrapUnpacked] = new Set()
112
+ this[_shrinkwrapInflated] = new Set()
112
113
  this[_retiredUnchanged] = {}
113
114
  this[_sparseTreeDirs] = new Set()
114
115
  this[_sparseTreeRoots] = new Set()
@@ -316,6 +317,7 @@ module.exports = cls => class Reifier extends cls {
316
317
  // find all the nodes that need to change between the actual
317
318
  // and ideal trees.
318
319
  this.diff = Diff.calculate({
320
+ shrinkwrapInflated: this[_shrinkwrapInflated],
319
321
  filterNodes,
320
322
  actual: this.actualTree,
321
323
  ideal: this.idealTree,
@@ -423,7 +425,8 @@ module.exports = cls => class Reifier extends cls {
423
425
  const dirs = this.diff.leaves
424
426
  .filter(diff => {
425
427
  return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
426
- !this[_sparseTreeDirs].has(diff.ideal.path)
428
+ !this[_sparseTreeDirs].has(diff.ideal.path) &&
429
+ !diff.ideal.isLink
427
430
  })
428
431
  .map(diff => diff.ideal.path)
429
432
 
@@ -457,9 +460,9 @@ module.exports = cls => class Reifier extends cls {
457
460
  // we need to unpack them, read that shrinkwrap file, and then update
458
461
  // the tree by calling loadVirtual with the node as the root.
459
462
  [_loadShrinkwrapsAndUpdateTrees] () {
460
- const seen = this[_shrinkwrapUnpacked]
463
+ const seen = this[_shrinkwrapInflated]
461
464
  const shrinkwraps = this.diff.leaves
462
- .filter(d => (d.action === 'CHANGE' || d.action === 'ADD') &&
465
+ .filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
463
466
  d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
464
467
  !this[_trashList].has(d.ideal.path))
465
468
 
@@ -472,7 +475,7 @@ module.exports = cls => class Reifier extends cls {
472
475
  return promiseAllRejectLate(shrinkwraps.map(diff => {
473
476
  const node = diff.ideal
474
477
  seen.add(node)
475
- return this[_reifyNode](node)
478
+ return diff.action ? this[_reifyNode](node) : node
476
479
  }))
477
480
  .then(nodes => promiseAllRejectLate(nodes.map(node => new Arborist({
478
481
  ...this.options,
@@ -503,7 +506,7 @@ module.exports = cls => class Reifier extends cls {
503
506
 
504
507
  const { npmVersion, nodeVersion } = this.options
505
508
  const p = Promise.resolve()
506
- .then(() => {
509
+ .then(async () => {
507
510
  // when we reify an optional node, check the engine and platform
508
511
  // first. be sure to ignore the --force and --engine-strict flags,
509
512
  // since we always want to skip any optional packages we can't install.
@@ -513,11 +516,11 @@ module.exports = cls => class Reifier extends cls {
513
516
  checkEngine(node.package, npmVersion, nodeVersion, false)
514
517
  checkPlatform(node.package, false)
515
518
  }
519
+ await this[_checkBins](node)
520
+ await this[_extractOrLink](node)
521
+ await this[_warnDeprecated](node)
522
+ await this[_loadAncientPackageDetails](node)
516
523
  })
517
- .then(() => this[_checkBins](node))
518
- .then(() => this[_extractOrLink](node))
519
- .then(() => this[_warnDeprecated](node))
520
- .then(() => this[_loadAncientPackageDetails](node))
521
524
 
522
525
  return this[_handleOptionalFailure](node, p)
523
526
  .then(() => {
@@ -563,10 +566,11 @@ module.exports = cls => class Reifier extends cls {
563
566
  })
564
567
  }
565
568
 
566
- [_symlink] (node) {
569
+ async [_symlink] (node) {
567
570
  const dir = dirname(node.path)
568
571
  const target = node.realpath
569
572
  const rel = relative(dir, target)
573
+ await mkdirp(dir)
570
574
  return symlink(rel, node.path, 'junction')
571
575
  }
572
576
 
@@ -633,8 +637,10 @@ module.exports = cls => class Reifier extends cls {
633
637
  [_loadBundlesAndUpdateTrees] (
634
638
  depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
635
639
  ) {
636
- if (depth === 0)
640
+ if (depth === 0) {
641
+ this[_bundleUnpacked] = new Set()
637
642
  process.emit('time', 'reify:loadBundles')
643
+ }
638
644
  const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
639
645
  if (depth > maxBundleDepth) {
640
646
  // if we did something, then prune the tree and update the diffs
@@ -650,13 +656,17 @@ module.exports = cls => class Reifier extends cls {
650
656
  // shallower bundle overwriting them with a bundled meta-dep.
651
657
  const set = (bundlesByDepth.get(depth) || [])
652
658
  .filter(node => node.root === this.idealTree &&
659
+ node.target !== node.root &&
653
660
  !this[_trashList].has(node.path))
654
661
 
655
662
  if (!set.length)
656
663
  return this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth)
657
664
 
658
665
  // extract all the nodes with bundles
659
- return promiseAllRejectLate(set.map(node => this[_reifyNode](node)))
666
+ return promiseAllRejectLate(set.map(node => {
667
+ this[_bundleUnpacked].add(node)
668
+ return this[_reifyNode](node)
669
+ }))
660
670
  // then load their unpacked children and move into the ideal tree
661
671
  .then(nodes =>
662
672
  promiseAllRejectLate(nodes.map(node => new this.constructor({
@@ -678,8 +688,13 @@ module.exports = cls => class Reifier extends cls {
678
688
  tree: this.diff,
679
689
  visit: diff => {
680
690
  const node = diff.ideal
681
- if (node && !node.isProjectRoot && node.package.bundleDependencies &&
682
- node.package.bundleDependencies.length) {
691
+ if (!node)
692
+ return
693
+ if (node.isProjectRoot || (node.target && node.target.isProjectRoot))
694
+ return
695
+
696
+ const { bundleDependencies } = node.package
697
+ if (bundleDependencies && bundleDependencies.length) {
683
698
  maxBundleDepth = Math.max(maxBundleDepth, node.depth)
684
699
  if (!bundlesByDepth.has(node.depth))
685
700
  bundlesByDepth.set(node.depth, [node])
@@ -784,14 +799,14 @@ module.exports = cls => class Reifier extends cls {
784
799
  return
785
800
 
786
801
  const node = diff.ideal
787
- const bd = node.package.bundleDependencies
788
- const sw = this[_shrinkwrapUnpacked].has(node)
802
+ const bd = this[_bundleUnpacked].has(node)
803
+ const sw = this[_shrinkwrapInflated].has(node)
789
804
 
790
805
  // check whether we still need to unpack this one.
791
806
  // test the inDepBundle last, since that's potentially a tree walk.
792
807
  const doUnpack = node && // can't unpack if removed!
793
808
  !node.isRoot && // root node already exists
794
- !(bd && bd.length) && // already unpacked to read bundle
809
+ !bd && // already unpacked to read bundle
795
810
  !sw && // already unpacked to read sw
796
811
  !node.inDepBundle // already unpacked by another dep's bundle
797
812
 
package/lib/debug.js CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  // run in debug mode if explicitly requested, running arborist tests,
14
14
  // or working in the arborist project directory.
15
+
15
16
  const debug = process.env.ARBORIST_DEBUG !== '0' && (
16
17
  process.env.ARBORIST_DEBUG === '1' ||
17
18
  /\barborist\b/.test(process.env.NODE_DEBUG || '') ||
@@ -21,4 +22,10 @@ const debug = process.env.ARBORIST_DEBUG !== '0' && (
21
22
  )
22
23
 
23
24
  module.exports = debug ? fn => fn() : () => {}
24
- module.exports.log = (...msg) => module.exports(() => console.error(...msg))
25
+ const red = process.stderr.isTTY ? msg => `\x1B[31m${msg}\x1B[39m` : m => m
26
+ module.exports.log = (...msg) => module.exports(() => {
27
+ const { format } = require('util')
28
+ const prefix = `\n${process.pid} ${red(format(msg.shift()))} `
29
+ msg = (prefix + format(...msg).trim().split('\n').join(prefix)).trim()
30
+ console.error(msg)
31
+ })
package/lib/diff.js CHANGED
@@ -11,8 +11,9 @@ const {existsSync} = require('fs')
11
11
  const ssri = require('ssri')
12
12
 
13
13
  class Diff {
14
- constructor ({actual, ideal, filterSet}) {
14
+ constructor ({actual, ideal, filterSet, shrinkwrapInflated}) {
15
15
  this.filterSet = filterSet
16
+ this.shrinkwrapInflated = shrinkwrapInflated
16
17
  this.children = []
17
18
  this.actual = actual
18
19
  this.ideal = ideal
@@ -30,7 +31,7 @@ class Diff {
30
31
  this.removed = []
31
32
  }
32
33
 
33
- static calculate ({actual, ideal, filterNodes = []}) {
34
+ static calculate ({actual, ideal, filterNodes = [], shrinkwrapInflated = new Set()}) {
34
35
  // if there's a filterNode, then:
35
36
  // - get the path from the root to the filterNode. The root or
36
37
  // root.target should have an edge either to the filterNode or
@@ -77,7 +78,7 @@ class Diff {
77
78
  }
78
79
 
79
80
  return depth({
80
- tree: new Diff({actual, ideal, filterSet}),
81
+ tree: new Diff({actual, ideal, filterSet, shrinkwrapInflated}),
81
82
  getChildren,
82
83
  leave,
83
84
  })
@@ -135,7 +136,7 @@ const allChildren = node => {
135
136
  // to create the diff tree
136
137
  const getChildren = diff => {
137
138
  const children = []
138
- const {actual, ideal, unchanged, removed, filterSet} = diff
139
+ const {actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated} = diff
139
140
 
140
141
  // Note: we DON'T diff fsChildren themselves, because they are either
141
142
  // included in the package contents, or part of some other project, and
@@ -144,11 +145,20 @@ const getChildren = diff => {
144
145
  // responsible for installing.
145
146
  const actualKids = allChildren(actual)
146
147
  const idealKids = allChildren(ideal)
148
+
149
+ if (ideal && ideal.hasShrinkwrap && !shrinkwrapInflated.has(ideal)) {
150
+ // Guaranteed to get a diff.leaves here, because we always
151
+ // be called with a proper Diff object when ideal has a shrinkwrap
152
+ // that has not been inflated.
153
+ diff.leaves.push(diff)
154
+ return children
155
+ }
156
+
147
157
  const paths = new Set([...actualKids.keys(), ...idealKids.keys()])
148
158
  for (const path of paths) {
149
159
  const actual = actualKids.get(path)
150
160
  const ideal = idealKids.get(path)
151
- diffNode(actual, ideal, children, unchanged, removed, filterSet)
161
+ diffNode(actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated)
152
162
  }
153
163
 
154
164
  if (diff.leaves && !children.length)
@@ -157,7 +167,7 @@ const getChildren = diff => {
157
167
  return children
158
168
  }
159
169
 
160
- const diffNode = (actual, ideal, children, unchanged, removed, filterSet) => {
170
+ const diffNode = (actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated) => {
161
171
  if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual)))
162
172
  return
163
173
 
@@ -165,10 +175,10 @@ const diffNode = (actual, ideal, children, unchanged, removed, filterSet) => {
165
175
 
166
176
  // if it's a match, then get its children
167
177
  // otherwise, this is the child diff node
168
- if (action) {
178
+ if (action || (!shrinkwrapInflated.has(ideal) && ideal.hasShrinkwrap)) {
169
179
  if (action === 'REMOVE')
170
180
  removed.push(actual)
171
- children.push(new Diff({actual, ideal, filterSet}))
181
+ children.push(new Diff({actual, ideal, filterSet, shrinkwrapInflated}))
172
182
  } else {
173
183
  unchanged.push(ideal)
174
184
  // !*! Weird dirty hack warning !*!
@@ -199,7 +209,7 @@ const diffNode = (actual, ideal, children, unchanged, removed, filterSet) => {
199
209
  for (const node of bundledChildren)
200
210
  node.parent = ideal
201
211
  }
202
- children.push(...getChildren({actual, ideal, unchanged, removed, filterSet}))
212
+ children.push(...getChildren({actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated}))
203
213
  }
204
214
  }
205
215
 
package/lib/index.js CHANGED
@@ -3,5 +3,6 @@ module.exports.Arborist = module.exports
3
3
  module.exports.Node = require('./node.js')
4
4
  module.exports.Link = require('./link.js')
5
5
  module.exports.Edge = require('./edge.js')
6
+ module.exports.Shrinkwrap = require('./shrinkwrap.js')
6
7
  // XXX export the other classes, too. shrinkwrap, diff, etc.
7
8
  // they're handy!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@npmcli/installed-package-contents": "^1.0.7",
@@ -19,7 +19,7 @@
19
19
  "npm-install-checks": "^4.0.0",
20
20
  "npm-package-arg": "^8.1.0",
21
21
  "npm-pick-manifest": "^6.1.0",
22
- "npm-registry-fetch": "^9.0.0",
22
+ "npm-registry-fetch": "^10.0.0",
23
23
  "pacote": "^11.2.6",
24
24
  "parse-conflict-json": "^1.1.1",
25
25
  "promise-all-reject-late": "^1.0.0",
@@ -41,8 +41,7 @@
41
41
  "eslint-plugin-standard": "^4.0.1",
42
42
  "minify-registry-metadata": "^2.1.0",
43
43
  "mutate-fs": "^2.1.1",
44
- "require-inject": "^1.4.4",
45
- "tap": "^14.11.0",
44
+ "tap": "^15.0.4",
46
45
  "tcompare": "^3.0.4"
47
46
  },
48
47
  "scripts": {
@@ -76,10 +75,8 @@
76
75
  "arborist": "bin/index.js"
77
76
  },
78
77
  "tap": {
79
- "100": true,
80
78
  "after": "test/fixtures/cleanup.js",
81
79
  "coverage-map": "map.js",
82
- "esm": false,
83
80
  "test-env": [
84
81
  "NODE_OPTIONS=--no-warnings"
85
82
  ],
@@ -87,6 +84,6 @@
87
84
  "--no-warnings",
88
85
  "--no-deprecation"
89
86
  ],
90
- "timeout": "120"
87
+ "timeout": "240"
91
88
  }
92
89
  }