@npmcli/arborist 2.2.2 → 2.2.6

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.
@@ -31,7 +31,9 @@ for (const arg of process.argv.slice(2)) {
31
31
  } else if (/^--omit=/.test(arg)) {
32
32
  options.omit = options.omit || []
33
33
  options.omit.push(arg.substr('--omit='.length))
34
- } else if (/^--[^=]+=/.test(arg)) {
34
+ } else if (/^--before=/.test(arg))
35
+ options.before = new Date(arg.substr('--before='.length))
36
+ else if (/^--[^=]+=/.test(arg)) {
35
37
  const [key, ...v] = arg.replace(/^--/, '').split('=')
36
38
  const val = v.join('=')
37
39
  options[key] = val === 'false' ? false : val === 'true' ? true : val
@@ -398,6 +398,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
398
398
  if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
399
399
  const nm = resolve(this.path, 'node_modules')
400
400
  for (const name of await readdir(nm).catch(() => [])) {
401
+ if (this[_updateNames].includes(name))
402
+ this[_explicitRequests].add(name)
401
403
  tree.package.dependencies = tree.package.dependencies || {}
402
404
  if (this[_updateAll] || this[_updateNames].includes(name))
403
405
  tree.package.dependencies[name] = '*'
@@ -1121,8 +1123,11 @@ This is a one-time fix-up, please be patient...
1121
1123
  // we don't like it. always fail strictly, always allow forcibly or
1122
1124
  // in non-strict mode if it's not our fault. don't warn here, because
1123
1125
  // we are going to warn again when we place the deps, if we end up
1124
- // overriding for something else.
1125
- if (conflictOK)
1126
+ // overriding for something else. If the thing that has this dep
1127
+ // isn't also required, then there's a good chance we won't need it,
1128
+ // so allow it for now and let it conflict if it turns out to actually
1129
+ // be necessary for the installation.
1130
+ if (conflictOK || !required.has(edge.from))
1126
1131
  continue
1127
1132
 
1128
1133
  // ok, it's the root, or we're in unforced strict mode, so this is bad
@@ -28,6 +28,7 @@
28
28
 
29
29
  const {resolve} = require('path')
30
30
  const {homedir} = require('os')
31
+ const procLog = require('../proc-log.js')
31
32
 
32
33
  const mixins = [
33
34
  require('../tracker.js'),
@@ -54,6 +55,7 @@ class Arborist extends Base {
54
55
  path: options.path || '.',
55
56
  cache: options.cache || `${homedir()}/.npm/_cacache`,
56
57
  packumentCache: new Map(),
58
+ log: options.log || procLog,
57
59
  }
58
60
  this.cache = resolve(this.options.cache)
59
61
  this.path = resolve(this.options.path)
@@ -16,6 +16,7 @@ const mkdirp = require('mkdirp-infer-owner')
16
16
  const moveFile = require('@npmcli/move-file')
17
17
  const rimraf = promisify(require('rimraf'))
18
18
  const packageContents = require('@npmcli/installed-package-contents')
19
+ const { checkEngine, checkPlatform } = require('npm-install-checks')
19
20
 
20
21
  const treeCheck = require('../tree-check.js')
21
22
  const relpath = require('../relpath.js')
@@ -43,6 +44,7 @@ const _loadTrees = Symbol.for('loadTrees')
43
44
  const _diffTrees = Symbol.for('diffTrees')
44
45
  const _createSparseTree = Symbol.for('createSparseTree')
45
46
  const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
47
+ const _shrinkwrapUnpacked = Symbol('shrinkwrapUnpacked')
46
48
  const _reifyNode = Symbol.for('reifyNode')
47
49
  const _extractOrLink = Symbol('extractOrLink')
48
50
  // defined by rebuild mixin
@@ -102,6 +104,7 @@ module.exports = cls => class Reifier extends cls {
102
104
 
103
105
  this.diff = null
104
106
  this[_retiredPaths] = {}
107
+ this[_shrinkwrapUnpacked] = new Set()
105
108
  this[_retiredUnchanged] = {}
106
109
  this[_sparseTreeDirs] = new Set()
107
110
  this[_sparseTreeRoots] = new Set()
@@ -233,9 +236,9 @@ module.exports = cls => class Reifier extends cls {
233
236
  const actualOpt = this[_global] ? {
234
237
  ignoreMissing: true,
235
238
  global: true,
236
- filter: (node, kid) => this[_explicitRequests].size === 0 || !node.isProjectRoot
237
- ? true
238
- : (node.edgesOut.has(kid) || this[_explicitRequests].has(kid)),
239
+ filter: (node, kid) =>
240
+ this[_explicitRequests].size === 0 || !node.isProjectRoot ? true
241
+ : (this.idealTree.edgesOut.has(kid) || this[_explicitRequests].has(kid)),
239
242
  } : { ignoreMissing: true }
240
243
 
241
244
  if (!this[_global]) {
@@ -404,7 +407,8 @@ module.exports = cls => class Reifier extends cls {
404
407
  // shrinkwrap nodes define their dependency branches with a file, so
405
408
  // we need to unpack them, read that shrinkwrap file, and then update
406
409
  // the tree by calling loadVirtual with the node as the root.
407
- [_loadShrinkwrapsAndUpdateTrees] (seen = new Set()) {
410
+ [_loadShrinkwrapsAndUpdateTrees] () {
411
+ const seen = this[_shrinkwrapUnpacked]
408
412
  const shrinkwraps = this.diff.leaves
409
413
  .filter(d => (d.action === 'CHANGE' || d.action === 'ADD') &&
410
414
  d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
@@ -428,6 +432,8 @@ module.exports = cls => class Reifier extends cls {
428
432
  // reload the diff and sparse tree because the ideal tree changed
429
433
  .then(() => this[_diffTrees]())
430
434
  .then(() => this[_createSparseTree]())
435
+ .then(() => this[_addOmitsToTrashList]())
436
+ .then(() => this[_loadShrinkwrapsAndUpdateTrees]())
431
437
  .then(() => process.emit('timeEnd', 'reify:loadShrinkwraps'))
432
438
  }
433
439
 
@@ -446,7 +452,19 @@ module.exports = cls => class Reifier extends cls {
446
452
  process.emit('time', timer)
447
453
  this.addTracker('reify', node.name, node.location)
448
454
 
455
+ const { npmVersion, nodeVersion } = this.options
449
456
  const p = Promise.resolve()
457
+ .then(() => {
458
+ // when we reify an optional node, check the engine and platform
459
+ // first. be sure to ignore the --force and --engine-strict flags,
460
+ // since we always want to skip any optional packages we can't install.
461
+ // these checks throwing will result in a rollback and removal
462
+ // of the mismatches
463
+ if (node.optional) {
464
+ checkEngine(node.package, npmVersion, nodeVersion, false)
465
+ checkPlatform(node.package, false)
466
+ }
467
+ })
450
468
  .then(() => this[_checkBins](node))
451
469
  .then(() => this[_extractOrLink](node))
452
470
  .then(() => this[_warnDeprecated](node))
@@ -718,7 +736,7 @@ module.exports = cls => class Reifier extends cls {
718
736
 
719
737
  const node = diff.ideal
720
738
  const bd = node.package.bundleDependencies
721
- const sw = node.hasShrinkwrap
739
+ const sw = this[_shrinkwrapUnpacked].has(node)
722
740
 
723
741
  // check whether we still need to unpack this one.
724
742
  // test the inDepBundle last, since that's potentially a tree walk.
@@ -268,8 +268,8 @@ class AuditReport extends Map {
268
268
  id,
269
269
  url,
270
270
  title,
271
- severity,
272
- vulnerable_versions,
271
+ severity = 'high',
272
+ vulnerable_versions = '*',
273
273
  module_name: name,
274
274
  } = advisory
275
275
  bulk[name] = bulk[name] || []
package/lib/edge.js CHANGED
@@ -87,16 +87,24 @@ class Edge {
87
87
 
88
88
  // return the edge data, and an explanation of how that edge came to be here
89
89
  [_explain] (seen) {
90
- const { error, from } = this
90
+ const { error, from, bundled } = this
91
91
  return {
92
92
  type: this.type,
93
93
  name: this.name,
94
94
  spec: this.spec,
95
+ ...(bundled ? { bundled } : {}),
95
96
  ...(error ? { error } : {}),
96
97
  ...(from ? { from: from.explain(null, seen) } : {}),
97
98
  }
98
99
  }
99
100
 
101
+ get bundled () {
102
+ if (!this.from)
103
+ return false
104
+ const { package: { bundleDependencies = [] } } = this.from
105
+ return bundleDependencies.includes(this.name)
106
+ }
107
+
100
108
  get workspace () {
101
109
  return this[_type] === 'workspace'
102
110
  }
package/lib/node.js CHANGED
@@ -731,7 +731,6 @@ class Node {
731
731
  // Note the subtle breaking change from v6: it is no longer possible
732
732
  // to have a different spec for a devDep than production dep.
733
733
  this[_loadDepType](this.package.optionalDependencies, 'optional')
734
- this[_loadDepType](this.package.dependencies, 'prod')
735
734
 
736
735
  // Linked targets that are disconnected from the tree are tops,
737
736
  // but don't have a 'path' field, only a 'realpath', because we
@@ -755,6 +754,8 @@ class Node {
755
754
  this[_loadDepType](peerDependencies, 'peer')
756
755
  this[_loadDepType](peerOptional, 'peerOptional')
757
756
  }
757
+
758
+ this[_loadDepType](this.package.dependencies, 'prod')
758
759
  }
759
760
 
760
761
  [_loadDepType] (obj, type) {
@@ -763,8 +764,10 @@ class Node {
763
764
  for (const [name, spec] of Object.entries(obj || {})) {
764
765
  const accept = ad[name]
765
766
  // if it's already set, then we keep the existing edge
767
+ // Prod deps should not be marked as dev, however.
766
768
  // NB: the Edge ctor adds itself to from.edgesOut
767
- if (!this.edgesOut.get(name))
769
+ const current = this.edgesOut.get(name)
770
+ if (!current || current.dev && type === 'prod')
768
771
  new Edge({ from, name, spec, accept, type })
769
772
  }
770
773
  }
package/lib/shrinkwrap.js CHANGED
@@ -32,6 +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
36
  const YarnLock = require('./yarn-lock.js')
36
37
  const {promisify} = require('util')
37
38
  const rimraf = promisify(require('rimraf'))
@@ -39,7 +40,23 @@ const fs = require('fs')
39
40
  const readFile = promisify(fs.readFile)
40
41
  const writeFile = promisify(fs.writeFile)
41
42
  const stat = promisify(fs.stat)
42
- const readdir = promisify(fs.readdir)
43
+ const readdir_ = promisify(fs.readdir)
44
+
45
+ // XXX remove when drop support for node v10
46
+ const lstat = promisify(fs.lstat)
47
+ /* istanbul ignore next - version specific polyfill */
48
+ const readdir = async (path, opt) => {
49
+ if (!opt || !opt.withFileTypes)
50
+ return readdir_(path, opt)
51
+ const ents = await readdir_(path, opt)
52
+ if (typeof ents[0] === 'string') {
53
+ return Promise.all(ents.map(async ent => {
54
+ return Object.assign(await lstat(path + '/' + ent), { name: ent })
55
+ }))
56
+ }
57
+ return ents
58
+ }
59
+
43
60
  const { resolve, basename } = require('path')
44
61
  const specFromLock = require('./spec-from-lock.js')
45
62
  const versionFromTgz = require('./version-from-tgz.js')
@@ -59,6 +76,10 @@ const swKeyOrder = [
59
76
  'dependencies',
60
77
  ]
61
78
 
79
+ // used to rewrite from yarn registry to npm registry
80
+ const yarnRegRe = /^https?:\/\/registry.yarnpkg.com\//
81
+ const npmRegRe = /^https?:\/\/registry.npmjs.org\//
82
+
62
83
  // sometimes resolved: is weird or broken, or something npa can't handle
63
84
  const specFromResolved = resolved => {
64
85
  try {
@@ -261,7 +282,10 @@ class Shrinkwrap {
261
282
  newline = '\n',
262
283
  shrinkwrapOnly = false,
263
284
  hiddenLockfile = false,
285
+ log = procLog,
264
286
  } = options
287
+
288
+ this.log = log
265
289
  this[_awaitingUpdate] = new Map()
266
290
  this.tree = null
267
291
  this.path = resolve(path || '.')
@@ -291,8 +315,6 @@ class Shrinkwrap {
291
315
  if (fromYarn && fromYarn.version) {
292
316
  // if it's the yarn or npm default registry, use the version as
293
317
  // our effective spec. if it's any other kind of thing, use that.
294
- const yarnRegRe = /^https?:\/\/registry.yarnpkg.com\//
295
- const npmRegRe = /^https?:\/\/registry.npmjs.org\//
296
318
  const {resolved, version, integrity} = fromYarn
297
319
  const isYarnReg = spec.registry && yarnRegRe.test(resolved)
298
320
  const isnpmReg = spec.registry && !isYarnReg && npmRegRe.test(resolved)
@@ -396,6 +418,8 @@ class Shrinkwrap {
396
418
  // all good! hidden lockfile is the newest thing in here.
397
419
  return data
398
420
  }).catch(er => {
421
+ const rel = relpath(this.path, this.filename)
422
+ this.log.verbose('shrinkwrap', `failed to load ${rel}`, er)
399
423
  this.loadingError = er
400
424
  this.loadedFromDisk = false
401
425
  this.ancientLockfile = false
@@ -733,6 +757,7 @@ class Shrinkwrap {
733
757
  : !/file:/.test(node.resolved) ? node.resolved
734
758
  : consistentResolve(node.resolved, node.path, this.path, true)
735
759
 
760
+ const spec = npa(`${node.name}@${edge.spec}`)
736
761
  const entry = this.yarnLock.entries.get(`${node.name}@${edge.spec}`)
737
762
 
738
763
  if (!entry ||
@@ -741,6 +766,9 @@ class Shrinkwrap {
741
766
  mismatch(pathFixed, entry.resolved))
742
767
  return
743
768
 
769
+ if (entry.resolved && yarnRegRe.test(entry.resolved) && spec.registry)
770
+ entry.resolved = entry.resolved.replace(yarnRegRe, 'https://registry.npmjs.org/')
771
+
744
772
  node.integrity = node.integrity || entry.integrity || null
745
773
  node.resolved = node.resolved ||
746
774
  consistentResolve(entry.resolved, this.path, node.path) || null
package/lib/tracker.js CHANGED
@@ -1,13 +1,12 @@
1
- const procLog = require('./proc-log.js')
2
-
3
1
  const _progress = Symbol('_progress')
4
2
  const _onError = Symbol('_onError')
3
+ const procLog = require('./proc-log.js')
5
4
 
6
5
  module.exports = cls => class Tracker extends cls {
7
6
  constructor (options = {}) {
8
7
  super(options)
9
- this[_progress] = new Map()
10
8
  this.log = options.log || procLog
9
+ this[_progress] = new Map()
11
10
  }
12
11
 
13
12
  addTracker (section, subsection = null, key = null) {
@@ -15,11 +15,18 @@ const depTypes = new Set([
15
15
  'peerDependencies',
16
16
  ])
17
17
 
18
+ const parseJsonSafe = json => {
19
+ try {
20
+ return parseJSON(json)
21
+ } catch (er) {
22
+ return null
23
+ }
24
+ }
25
+
18
26
  const updateRootPackageJson = async tree => {
19
27
  const filename = resolve(tree.path, 'package.json')
20
- const originalContent = await readFile(filename, 'utf8')
21
- .then(data => parseJSON(data))
22
- .catch(() => null)
28
+ const originalJson = await readFile(filename, 'utf8').catch(() => null)
29
+ const originalContent = parseJsonSafe(originalJson)
23
30
 
24
31
  const depsData = orderDeps({
25
32
  ...tree.package,
@@ -36,12 +43,29 @@ const updateRootPackageJson = async tree => {
36
43
  }
37
44
 
38
45
  // if there's no package.json, just use internal pkg info as source of truth
39
- const packageJsonContent = originalContent || depsData
46
+ // clone the object though, so we can still refer to what it originally was
47
+ const packageJsonContent = !originalContent ? depsData
48
+ : Object.assign({}, originalContent)
40
49
 
41
50
  // loop through all types of dependencies and update package json content
42
51
  for (const type of depTypes)
43
52
  packageJsonContent[type] = depsData[type]
44
53
 
54
+ // if original package.json had dep in peerDeps AND deps, preserve that.
55
+ const { dependencies: origProd, peerDependencies: origPeer } =
56
+ originalContent || {}
57
+ const { peerDependencies: newPeer } = packageJsonContent
58
+ if (origProd && origPeer && newPeer) {
59
+ // we have original prod/peer deps, and new peer deps
60
+ // copy over any that were in both in the original
61
+ for (const name of Object.keys(origPeer)) {
62
+ if (origProd[name] !== undefined && newPeer[name] !== undefined) {
63
+ packageJsonContent.dependencies = packageJsonContent.dependencies || {}
64
+ packageJsonContent.dependencies[name] = newPeer[name]
65
+ }
66
+ }
67
+ }
68
+
45
69
  // format content
46
70
  const {
47
71
  [Symbol.for('indent')]: indent,
@@ -52,7 +76,8 @@ const updateRootPackageJson = async tree => {
52
76
  const content = (JSON.stringify(packageJsonContent, null, format) + '\n')
53
77
  .replace(/\n/g, eol)
54
78
 
55
- return writeFile(filename, content)
79
+ if (content !== originalJson)
80
+ return writeFile(filename, content)
56
81
  }
57
82
 
58
83
  module.exports = updateRootPackageJson
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "2.2.2",
3
+ "version": "2.2.6",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
- "@npmcli/installed-package-contents": "^1.0.6",
6
+ "@npmcli/installed-package-contents": "^1.0.7",
7
7
  "@npmcli/map-workspaces": "^1.0.2",
8
- "@npmcli/metavuln-calculator": "^1.0.1",
8
+ "@npmcli/metavuln-calculator": "^1.1.0",
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",
@@ -24,7 +24,7 @@
24
24
  "parse-conflict-json": "^1.1.1",
25
25
  "promise-all-reject-late": "^1.0.0",
26
26
  "promise-call-limit": "^1.0.1",
27
- "read-package-json-fast": "^2.0.1",
27
+ "read-package-json-fast": "^2.0.2",
28
28
  "readdir-scoped-modules": "^1.1.0",
29
29
  "semver": "^7.3.4",
30
30
  "tar": "^6.1.0",
@@ -46,7 +46,8 @@
46
46
  "tcompare": "^3.0.4"
47
47
  },
48
48
  "scripts": {
49
- "test": "tap",
49
+ "test": "npm run test-only --",
50
+ "test-only": "tap",
50
51
  "posttest": "npm run lint",
51
52
  "snap": "tap",
52
53
  "postsnap": "npm run lint",
@@ -76,12 +77,16 @@
76
77
  },
77
78
  "tap": {
78
79
  "100": true,
79
- "node-arg": [
80
- "--unhandled-rejections=strict"
81
- ],
82
80
  "after": "test/fixtures/cleanup.js",
83
81
  "coverage-map": "map.js",
84
82
  "esm": false,
83
+ "test-env": [
84
+ "NODE_OPTIONS=--no-warnings"
85
+ ],
86
+ "node-arg": [
87
+ "--no-warnings",
88
+ "--no-deprecation"
89
+ ],
85
90
  "timeout": "120"
86
91
  }
87
92
  }