@npmcli/arborist 2.6.2 → 2.7.1

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/lib/timers.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const timers = Object.create(null)
2
2
  const { format } = require('util')
3
+ const options = require('./options.js')
3
4
 
4
5
  process.on('time', name => {
5
6
  if (timers[name])
@@ -15,7 +16,8 @@ process.on('timeEnd', name => {
15
16
  const res = process.hrtime(timers[name])
16
17
  delete timers[name]
17
18
  const msg = format(`${process.pid} ${name}`, res[0] * 1e3 + res[1] / 1e6)
18
- console.error(dim(msg))
19
+ if (options.timers !== false)
20
+ console.error(dim(msg))
19
21
  })
20
22
 
21
23
  process.on('exit', () => {
@@ -7,7 +7,7 @@ const semver = require('semver')
7
7
  const promiseCallLimit = require('promise-call-limit')
8
8
  const getPeerSet = require('../peer-set.js')
9
9
  const realpath = require('../../lib/realpath.js')
10
- const { resolve } = require('path')
10
+ const { resolve, dirname } = require('path')
11
11
  const { promisify } = require('util')
12
12
  const treeCheck = require('../tree-check.js')
13
13
  const readdir = promisify(require('readdir-scoped-modules'))
@@ -324,7 +324,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
324
324
  .then(async root => {
325
325
  if (!this[_updateAll] && !this[_global] && !root.meta.loadedFromDisk) {
326
326
  await new this.constructor(this.options).loadActual({ root })
327
- const tree = root.target || root
327
+ const tree = root.target
328
328
  // even though we didn't load it from a package-lock.json FILE,
329
329
  // we still loaded it "from disk", meaning we have to reset
330
330
  // dep flags before assuming that any mutations were reflected.
@@ -396,7 +396,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
396
396
  // update.names request by queueing nodes dependent on those named.
397
397
  async [_applyUserRequests] (options) {
398
398
  process.emit('time', 'idealTree:userRequests')
399
- const tree = this.idealTree.target || this.idealTree
399
+ const tree = this.idealTree.target
400
400
 
401
401
  if (!this[_workspaces].length)
402
402
  await this[_applyUserRequestsToNode](tree, options)
@@ -532,7 +532,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
532
532
  /* istanbul ignore else - should also be covered by realpath failure */
533
533
  if (filepath) {
534
534
  const { name } = spec
535
- const tree = this.idealTree.target || this.idealTree
535
+ const tree = this.idealTree.target
536
536
  spec = npa(`file:${relpath(tree.path, filepath)}`, tree.path)
537
537
  spec.name = name
538
538
  }
@@ -661,7 +661,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
661
661
  const ancient = meta.ancientLockfile
662
662
  const old = meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)
663
663
 
664
- if (inventory.size === 0 || !ancient && !(old && this[_complete]))
664
+ if (inventory.size === 0 || !ancient && !old)
665
665
  return
666
666
 
667
667
  // if the lockfile is from node v5 or earlier, then we'll have to reload
@@ -688,10 +688,12 @@ This is a one-time fix-up, please be patient...
688
688
  this.log.silly('inflate', node.location)
689
689
  const { resolved, version, path, name, location, integrity } = node
690
690
  // don't try to hit the registry for linked deps
691
- const useResolved = !version ||
692
- resolved && resolved.startsWith('file:')
693
- const id = useResolved ? resolved : version
694
- const spec = npa.resolve(name, id, path)
691
+ const useResolved = resolved && (
692
+ !version || resolved.startsWith('file:')
693
+ )
694
+ const id = useResolved ? resolved
695
+ : version || `file:${node.path}`
696
+ const spec = npa.resolve(name, id, dirname(path))
695
697
  const sloc = location.substr('node_modules/'.length)
696
698
  const t = `idealTree:inflate:${sloc}`
697
699
  this.addTracker(t)
@@ -728,7 +730,7 @@ This is a one-time fix-up, please be patient...
728
730
  // or extraneous.
729
731
  [_buildDeps] () {
730
732
  process.emit('time', 'idealTree:buildDeps')
731
- const tree = this.idealTree.target || this.idealTree
733
+ const tree = this.idealTree.target
732
734
  this[_depsQueue].push(tree)
733
735
  this.log.silly('idealTree', 'buildDeps')
734
736
  this.addTracker('idealTree', tree.name, '')
@@ -786,7 +788,11 @@ This is a one-time fix-up, please be patient...
786
788
  const Arborist = this.constructor
787
789
  const opt = { ...this.options }
788
790
  await cacache.tmp.withTmp(this.cache, opt, async path => {
789
- await pacote.extract(node.resolved, path, opt)
791
+ await pacote.extract(node.resolved, path, {
792
+ ...opt,
793
+ resolved: node.resolved,
794
+ integrity: node.integrity,
795
+ })
790
796
 
791
797
  if (hasShrinkwrap) {
792
798
  await new Arborist({ ...this.options, path })
@@ -912,7 +918,7 @@ This is a one-time fix-up, please be patient...
912
918
  await Promise.all(promises)
913
919
 
914
920
  for (const { to } of node.edgesOut.values()) {
915
- if (to && to.isLink)
921
+ if (to && to.isLink && to.target)
916
922
  this[_linkNodes].add(to)
917
923
  }
918
924
 
@@ -1291,7 +1297,7 @@ This is a one-time fix-up, please be patient...
1291
1297
 
1292
1298
  // when installing globally, or just in global style, we never place
1293
1299
  // deps above the first level.
1294
- const tree = this.idealTree && this.idealTree.target || this.idealTree
1300
+ const tree = this.idealTree && this.idealTree.target
1295
1301
  if (this[_globalStyle] && check.resolveParent === tree)
1296
1302
  break
1297
1303
  }
@@ -1360,7 +1366,7 @@ This is a one-time fix-up, please be patient...
1360
1366
  integrity: dep.integrity,
1361
1367
  legacyPeerDeps: this.legacyPeerDeps,
1362
1368
  error: dep.errors[0],
1363
- ...(dep.target ? { target: dep.target, realpath: dep.target.path } : {}),
1369
+ ...(dep.isLink ? { target: dep.target, realpath: dep.target.path } : {}),
1364
1370
  })
1365
1371
  if (this[_loadFailures].has(dep))
1366
1372
  this[_loadFailures].add(newDep)
@@ -1419,7 +1425,7 @@ This is a one-time fix-up, please be patient...
1419
1425
  // prune anything deeper in the tree that can be replaced by this
1420
1426
  if (this.idealTree) {
1421
1427
  for (const node of this.idealTree.inventory.query('name', newDep.name)) {
1422
- if (node.isDescendantOf(target))
1428
+ if (!node.isTop && node.isDescendantOf(target))
1423
1429
  this[_pruneDedupable](node, false)
1424
1430
  }
1425
1431
  }
@@ -1817,7 +1823,7 @@ This is a one-time fix-up, please be patient...
1817
1823
  const current = target !== entryEdge.from && target.resolve(dep.name)
1818
1824
  if (current) {
1819
1825
  for (const edge of current.edgesIn.values()) {
1820
- if (edge.from.isDescendantOf(target) && edge.valid) {
1826
+ if (!edge.from.isTop && edge.from.isDescendantOf(target) && edge.valid) {
1821
1827
  if (!edge.satisfiedBy(dep))
1822
1828
  return CONFLICT
1823
1829
  }
@@ -1874,7 +1880,8 @@ This is a one-time fix-up, please be patient...
1874
1880
  if (link.root !== this.idealTree)
1875
1881
  continue
1876
1882
 
1877
- const external = /^\.\.(\/|$)/.test(relpath(this.path, link.realpath))
1883
+ const tree = this.idealTree.target
1884
+ const external = !link.target.isDescendantOf(tree)
1878
1885
 
1879
1886
  // outside the root, somebody else's problem, ignore it
1880
1887
  if (external && !this[_follow])
@@ -28,7 +28,7 @@
28
28
 
29
29
  const {resolve} = require('path')
30
30
  const {homedir} = require('os')
31
- const procLog = require('../proc-log.js')
31
+ const procLog = require('proc-log')
32
32
  const { saveTypeMap } = require('../add-rm-pkg-deps.js')
33
33
 
34
34
  const mixins = [
@@ -81,7 +81,7 @@ class Arborist extends Base {
81
81
  const dep = edge.to
82
82
  if (dep) {
83
83
  set.add(dep)
84
- if (dep.target)
84
+ if (dep.isLink)
85
85
  set.add(dep.target)
86
86
  }
87
87
  }
@@ -22,6 +22,7 @@ const _loadFSTree = Symbol('loadFSTree')
22
22
  const _loadFSChildren = Symbol('loadFSChildren')
23
23
  const _findMissingEdges = Symbol('findMissingEdges')
24
24
  const _findFSParents = Symbol('findFSParents')
25
+ const _resetDepFlags = Symbol('resetDepFlags')
25
26
 
26
27
  const _actualTreeLoaded = Symbol('actualTreeLoaded')
27
28
  const _rpcache = Symbol.for('realpathCache')
@@ -74,6 +75,19 @@ module.exports = cls => class ActualLoader extends cls {
74
75
  this[_topNodes] = new Set()
75
76
  }
76
77
 
78
+ [_resetDepFlags] (tree, root) {
79
+ // reset all deps to extraneous prior to recalc
80
+ if (!root) {
81
+ for (const node of tree.inventory.values())
82
+ node.extraneous = true
83
+ }
84
+
85
+ // only reset root flags if we're not re-rooting,
86
+ // otherwise leave as-is
87
+ calcDepFlags(tree, !root)
88
+ return tree
89
+ }
90
+
77
91
  // public method
78
92
  async loadActual (options = {}) {
79
93
  // allow the user to set options on the ctor as well.
@@ -88,6 +102,7 @@ module.exports = cls => class ActualLoader extends cls {
88
102
  return this.actualTree ? this.actualTree
89
103
  : this[_actualTreePromise] ? this[_actualTreePromise]
90
104
  : this[_actualTreePromise] = this[_loadActual](options)
105
+ .then(tree => this[_resetDepFlags](tree, options.root))
91
106
  .then(tree => this.actualTree = treeCheck(tree))
92
107
  }
93
108
 
@@ -152,8 +167,7 @@ module.exports = cls => class ActualLoader extends cls {
152
167
  root: this[_actualTree],
153
168
  })
154
169
  await this[_loadWorkspaces](this[_actualTree])
155
- if (this[_actualTree].workspaces && this[_actualTree].workspaces.size)
156
- calcDepFlags(this[_actualTree], !root)
170
+
157
171
  this[_transplant](root)
158
172
  return this[_actualTree]
159
173
  }
@@ -178,8 +192,6 @@ module.exports = cls => class ActualLoader extends cls {
178
192
  dependencies[name] = dependencies[name] || '*'
179
193
  actualRoot.package = { ...actualRoot.package, dependencies }
180
194
  }
181
- // only reset root flags if we're not re-rooting, otherwise leave as-is
182
- calcDepFlags(this[_actualTree], !root)
183
195
  return this[_actualTree]
184
196
  }
185
197
 
@@ -303,7 +315,7 @@ module.exports = cls => class ActualLoader extends cls {
303
315
 
304
316
  [_loadFSTree] (node) {
305
317
  const did = this[_actualTreeLoaded]
306
- node = node.target || node
318
+ node = node.target
307
319
 
308
320
  // if a Link target has started, but not completed, then
309
321
  // a Promise will be in the cache to indicate this.
@@ -221,7 +221,7 @@ module.exports = cls => class VirtualLoader extends cls {
221
221
  [assignBundles] (nodes) {
222
222
  for (const [location, node] of nodes) {
223
223
  // Skip assignment of parentage for the root package
224
- if (!location || node.target && !node.target.location)
224
+ if (!location || node.isLink && !node.target.location)
225
225
  continue
226
226
  const { name, parent, package: { inBundle }} = node
227
227
 
@@ -169,7 +169,7 @@ module.exports = cls => class Builder extends cls {
169
169
  const queue = [...set].sort(sortNodes)
170
170
 
171
171
  for (const node of queue) {
172
- const { package: { bin, scripts = {} } } = node
172
+ const { package: { bin, scripts = {} } } = node.target
173
173
  const { preinstall, install, postinstall, prepare } = scripts
174
174
  const tests = { bin, preinstall, install, postinstall, prepare }
175
175
  for (const [key, has] of Object.entries(tests)) {
@@ -202,7 +202,7 @@ module.exports = cls => class Builder extends cls {
202
202
  !(meta.originalLockfileVersion >= 2)
203
203
  }
204
204
 
205
- const { package: pkg, hasInstallScript } = node
205
+ const { package: pkg, hasInstallScript } = node.target
206
206
  const { gypfile, bin, scripts = {} } = pkg
207
207
 
208
208
  const { preinstall, install, postinstall, prepare } = scripts
@@ -263,7 +263,7 @@ module.exports = cls => class Builder extends cls {
263
263
  devOptional,
264
264
  package: pkg,
265
265
  location,
266
- } = node.target || node
266
+ } = node.target
267
267
 
268
268
  // skip any that we know we'll be deleting
269
269
  if (this[_trashList].has(path))
@@ -2,7 +2,6 @@
2
2
 
3
3
  const onExit = require('../signal-handling.js')
4
4
  const pacote = require('pacote')
5
- const rpj = require('read-package-json-fast')
6
5
  const AuditReport = require('../audit-report.js')
7
6
  const {subset, intersects} = require('semver')
8
7
  const npa = require('npm-package-arg')
@@ -16,6 +15,7 @@ const mkdirp = require('mkdirp-infer-owner')
16
15
  const justMkdirp = require('mkdirp')
17
16
  const moveFile = require('@npmcli/move-file')
18
17
  const rimraf = promisify(require('rimraf'))
18
+ const PackageJson = require('@npmcli/package-json')
19
19
  const packageContents = require('@npmcli/installed-package-contents')
20
20
  const { checkEngine, checkPlatform } = require('npm-install-checks')
21
21
 
@@ -25,7 +25,6 @@ const Diff = require('../diff.js')
25
25
  const retirePath = require('../retire-path.js')
26
26
  const promiseAllRejectLate = require('promise-all-reject-late')
27
27
  const optionalSet = require('../optional-set.js')
28
- const updateRootPackageJson = require('../update-root-package-json.js')
29
28
  const calcDepFlags = require('../calc-dep-flags.js')
30
29
  const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
31
30
 
@@ -57,7 +56,6 @@ const _extractOrLink = Symbol('extractOrLink')
57
56
  const _checkBins = Symbol.for('checkBins')
58
57
  const _symlink = Symbol('symlink')
59
58
  const _warnDeprecated = Symbol('warnDeprecated')
60
- const _loadAncientPackageDetails = Symbol('loadAncientPackageDetails')
61
59
  const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
62
60
  const _submitQuickAudit = Symbol('submitQuickAudit')
63
61
  const _awaitQuickAudit = Symbol('awaitQuickAudit')
@@ -291,8 +289,8 @@ module.exports = cls => class Reifier extends cls {
291
289
 
292
290
  const filterNodes = []
293
291
  if (this[_global] && this.explicitRequests.size) {
294
- const idealTree = this.idealTree.target || this.idealTree
295
- const actualTree = this.actualTree.target || this.actualTree
292
+ const idealTree = this.idealTree.target
293
+ const actualTree = this.actualTree.target
296
294
  // we ONLY are allowed to make changes in the global top-level
297
295
  // children where there's an explicit request.
298
296
  for (const { name } of this.explicitRequests) {
@@ -406,10 +404,9 @@ module.exports = cls => class Reifier extends cls {
406
404
  return
407
405
 
408
406
  process.emit('time', 'reify:trashOmits')
409
- // node.parent is checked to make sure this is a node that's in the tree, and
410
- // not the parent-less top level nodes
407
+
411
408
  const filter = node =>
412
- node.isDescendantOf(this.idealTree) &&
409
+ node.top.isProjectRoot &&
413
410
  (node.peer && this[_omitPeer] ||
414
411
  node.dev && this[_omitDev] ||
415
412
  node.optional && this[_omitOptional] ||
@@ -522,7 +519,6 @@ module.exports = cls => class Reifier extends cls {
522
519
  await this[_checkBins](node)
523
520
  await this[_extractOrLink](node)
524
521
  await this[_warnDeprecated](node)
525
- await this[_loadAncientPackageDetails](node)
526
522
  })
527
523
 
528
524
  return this[_handleOptionalFailure](node, p)
@@ -583,32 +579,6 @@ module.exports = cls => class Reifier extends cls {
583
579
  this.log.warn('deprecated', `${_id}: ${deprecated}`)
584
580
  }
585
581
 
586
- async [_loadAncientPackageDetails] (node, forceReload = false) {
587
- // If we're loading from a v1 lockfile, load details from the package.json
588
- // that weren't recorded in the old format.
589
- const {meta} = this.idealTree
590
- const ancient = meta.ancientLockfile
591
- const old = meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)
592
-
593
- // already replaced with the manifest if it's truly ancient
594
- if (node.path && (forceReload || (old && !ancient))) {
595
- // XXX should have a shared location where package.json is read,
596
- // so we don't ever read the same pj more than necessary.
597
- let pkg
598
- try {
599
- pkg = await rpj(node.path + '/package.json')
600
- } catch (err) {}
601
-
602
- if (pkg) {
603
- node.package.bin = pkg.bin
604
- node.package.os = pkg.os
605
- node.package.cpu = pkg.cpu
606
- node.package.engines = pkg.engines
607
- meta.add(node)
608
- }
609
- }
610
- }
611
-
612
582
  // if the node is optional, then the failure of the promise is nonfatal
613
583
  // just add it and its optional set to the trash list.
614
584
  [_handleOptionalFailure] (node, p) {
@@ -693,7 +663,7 @@ module.exports = cls => class Reifier extends cls {
693
663
  const node = diff.ideal
694
664
  if (!node)
695
665
  return
696
- if (node.isProjectRoot || (node.target && node.target.isProjectRoot))
666
+ if (node.isProjectRoot)
697
667
  return
698
668
 
699
669
  const { bundleDependencies } = node.package
@@ -916,6 +886,18 @@ module.exports = cls => class Reifier extends cls {
916
886
  filter: diff => diff.action === 'ADD' || diff.action === 'CHANGE',
917
887
  })
918
888
 
889
+ // pick up link nodes from the unchanged list as we want to run their
890
+ // scripts in every install despite of having a diff status change
891
+ for (const node of this.diff.unchanged) {
892
+ const tree = node.root.target
893
+
894
+ // skip links that only live within node_modules as they are most
895
+ // likely managed by packages we installed, we only want to rebuild
896
+ // unchanged links we directly manage
897
+ if (node.isLink && node.target.fsTop === tree)
898
+ nodes.push(node)
899
+ }
900
+
919
901
  return this.rebuild({ nodes, handleOptionalFailure: true })
920
902
  .then(() => process.emit('timeEnd', 'reify:build'))
921
903
  }
@@ -1058,6 +1040,25 @@ module.exports = cls => class Reifier extends cls {
1058
1040
 
1059
1041
  const promises = [this[_saveLockFile](saveOpt)]
1060
1042
 
1043
+ const updatePackageJson = async (tree) => {
1044
+ const pkgJson = await PackageJson.load(tree.path)
1045
+ .catch(() => new PackageJson(tree.path))
1046
+ const {
1047
+ dependencies = {},
1048
+ devDependencies = {},
1049
+ optionalDependencies = {},
1050
+ peerDependencies = {},
1051
+ } = tree.package
1052
+
1053
+ pkgJson.update({
1054
+ dependencies,
1055
+ devDependencies,
1056
+ optionalDependencies,
1057
+ peerDependencies,
1058
+ })
1059
+ await pkgJson.save()
1060
+ }
1061
+
1061
1062
  // grab any from explicitRequests that had deps removed
1062
1063
  for (const { from: tree } of this.explicitRequests)
1063
1064
  updatedTrees.add(tree)
@@ -1065,7 +1066,7 @@ module.exports = cls => class Reifier extends cls {
1065
1066
  for (const tree of updatedTrees) {
1066
1067
  // refresh the edges so they have the correct specs
1067
1068
  tree.package = tree.package
1068
- promises.push(updateRootPackageJson(tree))
1069
+ promises.push(updatePackageJson(tree))
1069
1070
  }
1070
1071
 
1071
1072
  await Promise.all(promises)
@@ -1079,12 +1080,6 @@ module.exports = cls => class Reifier extends cls {
1079
1080
 
1080
1081
  const { meta } = this.idealTree
1081
1082
 
1082
- // might have to update metadata for bins and stuff that gets lost
1083
- if (meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)) {
1084
- for (const node of this.idealTree.inventory.values())
1085
- await this[_loadAncientPackageDetails](node, true)
1086
- }
1087
-
1088
1083
  return meta.save(saveOpt)
1089
1084
  }
1090
1085
 
@@ -12,7 +12,7 @@ const _fixAvailable = Symbol('fixAvailable')
12
12
  const _checkTopNode = Symbol('checkTopNode')
13
13
  const _init = Symbol('init')
14
14
  const _omit = Symbol('omit')
15
- const procLog = require('./proc-log.js')
15
+ const procLog = require('proc-log')
16
16
 
17
17
  const fetch = require('npm-registry-fetch')
18
18
 
@@ -22,15 +22,19 @@ const calcDepFlagsStep = (node) => {
22
22
  // Since we're only walking through deps that are not already flagged
23
23
  // as non-dev/non-optional, it's typically a very shallow traversal
24
24
  node.extraneous = false
25
+ resetParents(node, 'extraneous')
26
+ resetParents(node, 'dev')
27
+ resetParents(node, 'peer')
28
+ resetParents(node, 'devOptional')
29
+ resetParents(node, 'optional')
25
30
 
26
31
  // for links, map their hierarchy appropriately
27
- if (node.target) {
32
+ if (node.isLink) {
28
33
  node.target.dev = node.dev
29
34
  node.target.optional = node.optional
30
35
  node.target.devOptional = node.devOptional
31
36
  node.target.peer = node.peer
32
- node.target.extraneous = false
33
- node = node.target
37
+ return calcDepFlagsStep(node.target)
34
38
  }
35
39
 
36
40
  node.edgesOut.forEach(({peer, optional, dev, to}) => {
@@ -71,6 +75,14 @@ const calcDepFlagsStep = (node) => {
71
75
  return node
72
76
  }
73
77
 
78
+ const resetParents = (node, flag) => {
79
+ if (node[flag])
80
+ return
81
+
82
+ for (let p = node; p && (p === node || p[flag]); p = p.resolveParent)
83
+ p[flag] = false
84
+ }
85
+
74
86
  // typically a short walk, since it only traverses deps that
75
87
  // have the flag set.
76
88
  const unsetFlag = (node, flag) => {
@@ -80,10 +92,10 @@ const unsetFlag = (node, flag) => {
80
92
  tree: node,
81
93
  visit: node => {
82
94
  node.extraneous = node[flag] = false
83
- if (node.target)
95
+ if (node.isLink)
84
96
  node.target.extraneous = node.target[flag] = false
85
97
  },
86
- getChildren: node => [...(node.target || node).edgesOut.values()]
98
+ getChildren: node => [...node.target.edgesOut.values()]
87
99
  .filter(edge => edge.to && edge.to[flag] &&
88
100
  (flag !== 'peer' && edge.type === 'peer' || edge.type === 'prod'))
89
101
  .map(edge => edge.to),
package/lib/diff.js CHANGED
@@ -45,8 +45,7 @@ class Diff {
45
45
  const { root } = filterNode
46
46
  if (root !== ideal && root !== actual)
47
47
  throw new Error('invalid filterNode: outside idealTree/actualTree')
48
- const { target } = root
49
- const rootTarget = target || root
48
+ const rootTarget = root.target
50
49
  const edge = [...rootTarget.edgesOut.values()].filter(e => {
51
50
  return e.to && (e.to === filterNode || e.to.target === filterNode)
52
51
  })[0]
@@ -56,8 +55,7 @@ class Diff {
56
55
  filterSet.add(actual)
57
56
  if (edge && edge.to) {
58
57
  filterSet.add(edge.to)
59
- if (edge.to.target)
60
- filterSet.add(edge.to.target)
58
+ filterSet.add(edge.to.target)
61
59
  }
62
60
  filterSet.add(filterNode)
63
61
 
@@ -65,7 +63,7 @@ class Diff {
65
63
  tree: filterNode,
66
64
  visit: node => filterSet.add(node),
67
65
  getChildren: node => {
68
- node = node.target || node
66
+ node = node.target
69
67
  const loc = node.location
70
68
  const idealNode = ideal.inventory.get(loc)
71
69
  const ideals = !idealNode ? []
@@ -145,9 +143,9 @@ const allChildren = node => {
145
143
  if (!node)
146
144
  return new Map()
147
145
 
148
- // if the node is a global root, and also a link, then what we really
146
+ // if the node is root, and also a link, then what we really
149
147
  // want is to traverse the target's children
150
- if (node.global && node.isRoot && node.isLink)
148
+ if (node.isRoot && node.isLink)
151
149
  return allChildren(node.target)
152
150
 
153
151
  const kids = new Map()
package/lib/inventory.js CHANGED
@@ -7,6 +7,20 @@ const _index = Symbol('_index')
7
7
  const defaultKeys = ['name', 'license', 'funding', 'realpath', 'packageName']
8
8
  const { hasOwnProperty } = Object.prototype
9
9
  const debug = require('./debug.js')
10
+
11
+ // handling for the outdated "licenses" array, just pick the first one
12
+ // also support the alternative spelling "licence"
13
+ const getLicense = pkg => {
14
+ if (pkg) {
15
+ const lic = pkg.license || pkg.licence
16
+ if (lic)
17
+ return lic
18
+ const lics = pkg.licenses || pkg.licences
19
+ if (Array.isArray(lics))
20
+ return lics[0]
21
+ }
22
+ }
23
+
10
24
  class Inventory extends Map {
11
25
  constructor (opt = {}) {
12
26
  const { primary, keys } = opt
@@ -56,7 +70,9 @@ class Inventory extends Map {
56
70
  for (const [key, map] of this[_index].entries()) {
57
71
  // if the node has the value, but it's false, then use that
58
72
  const val_ = hasOwnProperty.call(node, key) ? node[key]
59
- : node[key] || (node.package && node.package[key])
73
+ : key === 'license' ? getLicense(node.package)
74
+ : node[key] ? node[key]
75
+ : node.package && node.package[key]
60
76
  const val = typeof val_ === 'string' ? val_
61
77
  : !val_ || typeof val_ !== 'object' ? val_
62
78
  : key === 'license' ? val_.type
package/lib/node.js CHANGED
@@ -409,7 +409,7 @@ class Node {
409
409
  }
410
410
 
411
411
  isDescendantOf (node) {
412
- for (let p = this; p; p = p.parent) {
412
+ for (let p = this; p; p = p.resolveParent) {
413
413
  if (p === node)
414
414
  return true
415
415
  }
@@ -547,6 +547,8 @@ class Node {
547
547
 
548
548
  // try to find our parent/fsParent in the new root inventory
549
549
  for (const p of walkUp(dirname(this.path))) {
550
+ if (p === this.path)
551
+ continue
550
552
  const ploc = relpath(root.realpath, p)
551
553
  const parent = root.inventory.get(ploc)
552
554
  if (parent) {
@@ -647,7 +649,7 @@ class Node {
647
649
  })
648
650
 
649
651
  if (this.isLink) {
650
- const target = node.target || node
652
+ const target = node.target
651
653
  this[_target] = target
652
654
  this[_package] = target.package
653
655
  target.linksIn.add(this)
@@ -783,7 +785,13 @@ class Node {
783
785
  }
784
786
 
785
787
  get fsParent () {
786
- return this[_fsParent]
788
+ const parent = this[_fsParent]
789
+ /* istanbul ignore next - should be impossible */
790
+ debug(() => {
791
+ if (parent === this)
792
+ throw new Error('node set to its own fsParent')
793
+ })
794
+ return parent
787
795
  }
788
796
 
789
797
  set fsParent (fsParent) {
@@ -1009,7 +1017,13 @@ class Node {
1009
1017
  }
1010
1018
 
1011
1019
  get parent () {
1012
- return this[_parent]
1020
+ const parent = this[_parent]
1021
+ /* istanbul ignore next - should be impossible */
1022
+ debug(() => {
1023
+ if (parent === this)
1024
+ throw new Error('node set to its own parent')
1025
+ })
1026
+ return parent
1013
1027
  }
1014
1028
 
1015
1029
  // This setter keeps everything in order when we move a node from
@@ -1160,7 +1174,7 @@ class Node {
1160
1174
  }
1161
1175
 
1162
1176
  get target () {
1163
- return null
1177
+ return this
1164
1178
  }
1165
1179
 
1166
1180
  set target (n) {
@@ -1183,6 +1197,14 @@ class Node {
1183
1197
  return this.isTop ? this : this.parent.top
1184
1198
  }
1185
1199
 
1200
+ get isFsTop () {
1201
+ return !this.fsParent
1202
+ }
1203
+
1204
+ get fsTop () {
1205
+ return this.isFsTop ? this : this.fsParent.fsTop
1206
+ }
1207
+
1186
1208
  get resolveParent () {
1187
1209
  return this.parent || this.fsParent
1188
1210
  }
package/lib/shrinkwrap.js CHANGED
@@ -32,7 +32,7 @@ const mismatch = (a, b) => a && b && a !== b
32
32
  // After calling this.commit(), any nodes not present in the tree will have
33
33
  // been removed from the shrinkwrap data as well.
34
34
 
35
- const procLog = require('./proc-log.js')
35
+ const procLog = require('proc-log')
36
36
  const YarnLock = require('./yarn-lock.js')
37
37
  const {promisify} = require('util')
38
38
  const rimraf = promisify(require('rimraf'))
@@ -349,6 +349,7 @@ class Shrinkwrap {
349
349
  reset () {
350
350
  this.tree = null
351
351
  this[_awaitingUpdate] = new Map()
352
+ this.originalLockfileVersion = lockfileVersion
352
353
  this.data = {
353
354
  lockfileVersion,
354
355
  requires: true,
@@ -801,7 +802,7 @@ class Shrinkwrap {
801
802
  if (this.tree) {
802
803
  if (this.yarnLock)
803
804
  this.yarnLock.fromTree(this.tree)
804
- const root = Shrinkwrap.metaFromNode(this.tree.target || this.tree, this.path)
805
+ const root = Shrinkwrap.metaFromNode(this.tree.target, this.path)
805
806
  this.data.packages = {}
806
807
  if (Object.keys(root).length)
807
808
  this.data.packages[''] = root
@@ -863,7 +864,7 @@ class Shrinkwrap {
863
864
  const spec = !edge ? rSpec
864
865
  : npa.resolve(node.name, edge.spec, edge.from.realpath)
865
866
 
866
- if (node.target)
867
+ if (node.isLink)
867
868
  lock.version = `file:${relpath(this.path, node.realpath)}`
868
869
  else if (spec && (spec.type === 'file' || spec.type === 'remote'))
869
870
  lock.version = spec.saveSpec
@@ -887,7 +888,7 @@ class Shrinkwrap {
887
888
  // when we didn't resolve to git, file, or dir, and didn't request
888
889
  // git, file, dir, or remote, then the resolved value is necessary.
889
890
  if (node.resolved &&
890
- !node.target &&
891
+ !node.isLink &&
891
892
  rSpec.type !== 'git' &&
892
893
  rSpec.type !== 'file' &&
893
894
  rSpec.type !== 'directory' &&
@@ -916,7 +917,7 @@ class Shrinkwrap {
916
917
  lock.optional = true
917
918
  }
918
919
 
919
- const depender = node.target || node
920
+ const depender = node.target
920
921
  if (depender.edgesOut.size > 0) {
921
922
  if (node !== this.tree) {
922
923
  lock.requires = [...depender.edgesOut.entries()].reduce((set, [k, v]) => {
@@ -941,7 +942,7 @@ class Shrinkwrap {
941
942
  }
942
943
 
943
944
  // now we walk the children, putting them in the 'dependencies' object
944
- const {children} = node.target || node
945
+ const {children} = node.target
945
946
  if (!children.size)
946
947
  delete lock.dependencies
947
948
  else {
package/lib/tracker.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const _progress = Symbol('_progress')
2
2
  const _onError = Symbol('_onError')
3
- const procLog = require('./proc-log.js')
3
+ const procLog = require('proc-log')
4
4
 
5
5
  module.exports = cls => class Tracker extends cls {
6
6
  constructor (options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "2.6.2",
3
+ "version": "2.7.1",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@npmcli/installed-package-contents": "^1.0.7",
@@ -9,12 +9,14 @@
9
9
  "@npmcli/move-file": "^1.1.0",
10
10
  "@npmcli/name-from-folder": "^1.0.1",
11
11
  "@npmcli/node-gyp": "^1.0.1",
12
+ "@npmcli/package-json": "^1.0.1",
12
13
  "@npmcli/run-script": "^1.8.2",
13
14
  "bin-links": "^2.2.1",
14
15
  "cacache": "^15.0.3",
15
16
  "common-ancestor-path": "^1.0.1",
16
17
  "json-parse-even-better-errors": "^2.3.1",
17
18
  "json-stringify-nice": "^1.1.4",
19
+ "mkdirp": "^1.0.4",
18
20
  "mkdirp-infer-owner": "^2.0.0",
19
21
  "npm-install-checks": "^4.0.0",
20
22
  "npm-package-arg": "^8.1.0",
@@ -22,11 +24,14 @@
22
24
  "npm-registry-fetch": "^11.0.0",
23
25
  "pacote": "^11.2.6",
24
26
  "parse-conflict-json": "^1.1.1",
27
+ "proc-log": "^1.0.0",
25
28
  "promise-all-reject-late": "^1.0.0",
26
29
  "promise-call-limit": "^1.0.1",
27
30
  "read-package-json-fast": "^2.0.2",
28
31
  "readdir-scoped-modules": "^1.1.0",
32
+ "rimraf": "^3.0.2",
29
33
  "semver": "^7.3.5",
34
+ "ssri": "^8.0.1",
30
35
  "tar": "^6.1.0",
31
36
  "treeverse": "^1.0.4",
32
37
  "walk-up-path": "^1.0.0"
package/lib/proc-log.js DELETED
@@ -1,21 +0,0 @@
1
- // default logger.
2
- // emits 'log' events on the process
3
- const LEVELS = [
4
- 'notice',
5
- 'error',
6
- 'warn',
7
- 'info',
8
- 'verbose',
9
- 'http',
10
- 'silly',
11
- 'pause',
12
- 'resume',
13
- ]
14
-
15
- const log = level => (...args) => process.emit('log', level, ...args)
16
-
17
- const logger = {}
18
- for (const level of LEVELS)
19
- logger[level] = log(level)
20
-
21
- module.exports = logger
@@ -1,95 +0,0 @@
1
- const fs = require('fs')
2
- const promisify = require('util').promisify
3
- const readFile = promisify(fs.readFile)
4
- const writeFile = promisify(fs.writeFile)
5
- const {resolve} = require('path')
6
-
7
- const parseJSON = require('json-parse-even-better-errors')
8
-
9
- const depTypes = new Set([
10
- 'dependencies',
11
- 'optionalDependencies',
12
- 'devDependencies',
13
- 'peerDependencies',
14
- ])
15
-
16
- // sort alphabetically all types of deps for a given package
17
- const orderDeps = (pkg) => {
18
- for (const type of depTypes) {
19
- if (pkg && pkg[type]) {
20
- pkg[type] = Object.keys(pkg[type])
21
- .sort((a, b) => a.localeCompare(b, 'en'))
22
- .reduce((res, key) => {
23
- res[key] = pkg[type][key]
24
- return res
25
- }, {})
26
- }
27
- }
28
- return pkg
29
- }
30
- const parseJsonSafe = json => {
31
- try {
32
- return parseJSON(json)
33
- } catch (er) {
34
- return null
35
- }
36
- }
37
-
38
- const updateRootPackageJson = async tree => {
39
- const filename = resolve(tree.path, 'package.json')
40
- const originalJson = await readFile(filename, 'utf8').catch(() => null)
41
- const originalContent = parseJsonSafe(originalJson)
42
-
43
- const depsData = orderDeps({
44
- ...tree.package,
45
- })
46
-
47
- // optionalDependencies don't need to be repeated in two places
48
- if (depsData.dependencies) {
49
- if (depsData.optionalDependencies) {
50
- for (const name of Object.keys(depsData.optionalDependencies))
51
- delete depsData.dependencies[name]
52
- }
53
- if (Object.keys(depsData.dependencies).length === 0)
54
- delete depsData.dependencies
55
- }
56
-
57
- // if there's no package.json, just use internal pkg info as source of truth
58
- // clone the object though, so we can still refer to what it originally was
59
- const packageJsonContent = !originalContent ? depsData
60
- : Object.assign({}, originalContent)
61
-
62
- // loop through all types of dependencies and update package json content
63
- for (const type of depTypes)
64
- packageJsonContent[type] = depsData[type]
65
-
66
- // if original package.json had dep in peerDeps AND deps, preserve that.
67
- const { dependencies: origProd, peerDependencies: origPeer } =
68
- originalContent || {}
69
- const { peerDependencies: newPeer } = packageJsonContent
70
- if (origProd && origPeer && newPeer) {
71
- // we have original prod/peer deps, and new peer deps
72
- // copy over any that were in both in the original
73
- for (const name of Object.keys(origPeer)) {
74
- if (origProd[name] !== undefined && newPeer[name] !== undefined) {
75
- packageJsonContent.dependencies = packageJsonContent.dependencies || {}
76
- packageJsonContent.dependencies[name] = newPeer[name]
77
- }
78
- }
79
- }
80
-
81
- // format content
82
- const {
83
- [Symbol.for('indent')]: indent,
84
- [Symbol.for('newline')]: newline,
85
- } = tree.package
86
- const format = indent === undefined ? ' ' : indent
87
- const eol = newline === undefined ? '\n' : newline
88
- const content = (JSON.stringify(packageJsonContent, null, format) + '\n')
89
- .replace(/\n/g, eol)
90
-
91
- if (content !== originalJson)
92
- return writeFile(filename, content)
93
- }
94
-
95
- module.exports = updateRootPackageJson