@npmcli/arborist 9.3.0 → 9.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.
@@ -339,7 +339,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
339
339
  filter: node => node,
340
340
  visit: node => {
341
341
  for (const edge of node.edgesOut.values()) {
342
- if ((!edge.to && edge.type !== 'peerOptional') || !edge.valid) {
342
+ const skipPeerOptional = edge.type === 'peerOptional' && this.options.save === false
343
+ if (!skipPeerOptional && (!edge.to || !edge.valid)) {
343
344
  this.#depsQueue.push(node)
344
345
  break // no need to continue the loop after the first hit
345
346
  }
@@ -966,9 +967,17 @@ This is a one-time fix-up, please be patient...
966
967
  continue
967
968
  }
968
969
  const { from, valid, peerConflicted } = edgeIn
969
- if (!peerConflicted && !valid && !this.#depsSeen.has(from)) {
970
- this.addTracker('idealTree', from.name, from.location)
971
- this.#depsQueue.push(edgeIn.from)
970
+ if (!peerConflicted && !valid) {
971
+ if (this.#depsSeen.has(from) && this.options.save) {
972
+ // Re-queue already-processed nodes when a newly placed dep creates an invalid edge during npm install (save=true).
973
+ // This handles the case where a peerOptional dep was valid (missing) when the node was first processed, but becomes invalid when the dep is later placed by another path with a version that doesn't satisfy the peer spec.
974
+ // See npm/cli#8726.
975
+ this.#depsSeen.delete(from)
976
+ this.#depsQueue.push(from)
977
+ } else if (!this.#depsSeen.has(from)) {
978
+ this.addTracker('idealTree', from.name, from.location)
979
+ this.#depsQueue.push(from)
980
+ }
972
981
  }
973
982
  }
974
983
  } else {
@@ -1165,9 +1174,13 @@ This is a one-time fix-up, please be patient...
1165
1174
  continue
1166
1175
  }
1167
1176
 
1168
- // If the edge has an error, there's a problem.
1177
+ // If the edge has an error, there's a problem, unless it's peerOptional and we're not saving (e.g. npm ci), in which case we trust the lockfile and skip re-resolution.
1178
+ // When saving (npm install), peerOptional invalid edges ARE treated as problems so the lockfile gets fixed.
1179
+ // See npm/cli#8726.
1169
1180
  if (!edge.valid) {
1170
- problems.push(edge)
1181
+ if (edge.type !== 'peerOptional' || this.options.save !== false) {
1182
+ problems.push(edge)
1183
+ }
1171
1184
  continue
1172
1185
  }
1173
1186
 
@@ -105,7 +105,7 @@ module.exports = cls => class IsolatedReifier extends cls {
105
105
  node.root.path,
106
106
  'node_modules',
107
107
  '.store',
108
- `${node.name}@${node.version}`
108
+ `${node.packageName}@${node.version}`
109
109
  )
110
110
  mkdirSync(dir, { recursive: true })
111
111
  // TODO this approach feels wrong
@@ -145,6 +145,21 @@ module.exports = cls => class IsolatedReifier extends cls {
145
145
  const optionalDeps = edges.filter(e => e.optional).map(e => e.to.target)
146
146
  const nonOptionalDeps = edges.filter(e => !e.optional).map(e => e.to.target)
147
147
 
148
+ // When legacyPeerDeps is enabled, peer dep edges are not created on the
149
+ // node. Resolve them from the tree so they get symlinked in the store.
150
+ const peerDeps = node.package.peerDependencies
151
+ if (peerDeps && node.legacyPeerDeps) {
152
+ const edgeNames = new Set(edges.map(e => e.name))
153
+ for (const peerName of Object.keys(peerDeps)) {
154
+ if (!edgeNames.has(peerName)) {
155
+ const resolved = node.resolve(peerName)
156
+ if (resolved && resolved !== node && !resolved.inert) {
157
+ nonOptionalDeps.push(resolved)
158
+ }
159
+ }
160
+ }
161
+ }
162
+
148
163
  result.localDependencies = await Promise.all(nonOptionalDeps.filter(n => n.isWorkspace).map(this.workspaceProxyMemo))
149
164
  result.externalDependencies = await Promise.all(nonOptionalDeps.filter(n => !n.isWorkspace && !n.inert).map(this.externalProxyMemo))
150
165
  result.externalOptionalDependencies = await Promise.all(optionalDeps.filter(n => !n.inert).map(this.externalProxyMemo))
@@ -155,7 +170,9 @@ module.exports = cls => class IsolatedReifier extends cls {
155
170
  ]
156
171
  result.root = this.rootNode
157
172
  result.id = this.counter++
158
- result.name = node.name
173
+ /* istanbul ignore next - packageName is always set for real packages */
174
+ result.name = result.isWorkspace ? (node.packageName || node.name) : node.name
175
+ result.packageName = node.packageName || node.name
159
176
  result.package = { ...node.package }
160
177
  result.package.bundleDependencies = undefined
161
178
  result.hasInstallScript = node.hasInstallScript
@@ -228,7 +245,7 @@ module.exports = cls => class IsolatedReifier extends cls {
228
245
  getChildren: node => node.dependencies,
229
246
  filter: node => node,
230
247
  visit: node => {
231
- branch.push(`${node.name}@${node.version}`)
248
+ branch.push(`${node.packageName}@${node.version}`)
232
249
  deps.push(`${branch.join('->')}::${node.resolved}`)
233
250
  },
234
251
  leave: () => {
@@ -246,7 +263,7 @@ module.exports = cls => class IsolatedReifier extends cls {
246
263
  }
247
264
 
248
265
  const getKey = (idealTreeNode) => {
249
- return `${idealTreeNode.name}@${idealTreeNode.version}-${treeHash(idealTreeNode)}`
266
+ return `${idealTreeNode.packageName}@${idealTreeNode.version}-${treeHash(idealTreeNode)}`
250
267
  }
251
268
 
252
269
  const root = {
@@ -301,7 +318,7 @@ module.exports = cls => class IsolatedReifier extends cls {
301
318
  isProjectRoot: false,
302
319
  isTop: false,
303
320
  location,
304
- name: node.name,
321
+ name: node.packageName || node.name,
305
322
  optional: node.optional,
306
323
  top: { path: proxiedIdealTree.root.localPath },
307
324
  children: [],
@@ -335,7 +352,7 @@ module.exports = cls => class IsolatedReifier extends cls {
335
352
  return
336
353
  }
337
354
  processed.add(key)
338
- const location = join('node_modules', '.store', key, 'node_modules', c.name)
355
+ const location = join('node_modules', '.store', key, 'node_modules', c.packageName)
339
356
  generateChild(c, location, c.package, true)
340
357
  })
341
358
  bundledTree.nodes.forEach(node => {
@@ -361,13 +378,17 @@ module.exports = cls => class IsolatedReifier extends cls {
361
378
 
362
379
  let from, nmFolder
363
380
  if (externalEdge) {
364
- const fromLocation = join('node_modules', '.store', key, 'node_modules', node.name)
381
+ const fromLocation = join('node_modules', '.store', key, 'node_modules', node.packageName)
365
382
  from = root.children.find(c => c.location === fromLocation)
366
383
  nmFolder = join('node_modules', '.store', key, 'node_modules')
367
384
  } else {
368
385
  from = node.isProjectRoot ? root : root.fsChildren.find(c => c.location === node.localLocation)
369
386
  nmFolder = join(node.localLocation, 'node_modules')
370
387
  }
388
+ /* istanbul ignore next - strict-peer-deps can exclude nodes from the tree */
389
+ if (!from) {
390
+ return
391
+ }
371
392
 
372
393
  const processDeps = (dep, optional, external) => {
373
394
  optional = !!optional
@@ -379,12 +400,16 @@ module.exports = cls => class IsolatedReifier extends cls {
379
400
 
380
401
  let target
381
402
  if (external) {
382
- const toLocation = join('node_modules', '.store', toKey, 'node_modules', dep.name)
403
+ const toLocation = join('node_modules', '.store', toKey, 'node_modules', dep.packageName)
383
404
  target = root.children.find(c => c.location === toLocation)
384
405
  } else {
385
406
  target = root.fsChildren.find(c => c.location === dep.localLocation)
386
407
  }
387
408
  // TODO: we should no-op is an edge has already been created with the same fromKey and toKey
409
+ /* istanbul ignore next - strict-peer-deps can exclude nodes from the tree */
410
+ if (!target) {
411
+ return
412
+ }
388
413
 
389
414
  binNames.forEach(bn => {
390
415
  target.binPaths.push(join(from.realpath, 'node_modules', '.bin', bn))
@@ -295,12 +295,12 @@ module.exports = cls => class Builder extends cls {
295
295
  devOptional,
296
296
  package: pkg,
297
297
  location,
298
- isStoreLink,
299
298
  } = node.target
300
299
 
301
300
  // skip any that we know we'll be deleting
302
- // or storeLinks
303
- if (this[_trashList].has(path) || isStoreLink) {
301
+ // or links to store entries (their scripts run on the store
302
+ // entry itself, not through the link)
303
+ if (this[_trashList].has(path) || (node.isLink && node.target?.isInStore)) {
304
304
  return
305
305
  }
306
306
 
@@ -422,7 +422,7 @@ module.exports = cls => class Reifier extends cls {
422
422
  if (includeWorkspaces) {
423
423
  // add all ws nodes to filterNodes
424
424
  for (const ws of this.options.workspaces) {
425
- const ideal = this.idealTree.children.get(ws)
425
+ const ideal = this.idealTree.children.get && this.idealTree.children.get(ws)
426
426
  if (ideal) {
427
427
  filterNodes.push(ideal)
428
428
  }
@@ -427,11 +427,19 @@ class Results {
427
427
  if (!this.currentAstNode.typeValue) {
428
428
  return this.initialItems
429
429
  }
430
+ // TODO this differs subtly with `:type()` because it now iterates on edgesIn, which means extraneous deps won't show up
431
+ // note how "@npmcli/abbrev@2.0.0-beta.45" is in the `:type()` results in the test but not in any of the other results.
430
432
  return this.initialItems
431
433
  .flatMap(node => {
432
434
  const found = []
435
+ const { typeValue } = this.currentAstNode
433
436
  for (const edge of node.edgesIn) {
434
- if (npa(`${edge.name}@${edge.spec}`).type === this.currentAstNode.typeValue) {
437
+ const parsedArg = npa(`${edge.name}@${edge.spec}`)
438
+ if (typeValue === 'registry') {
439
+ if (parsedArg.registry) {
440
+ found.push(edge.to)
441
+ }
442
+ } else if (parsedArg.type === typeValue) {
435
443
  found.push(edge.to)
436
444
  }
437
445
  }
package/lib/shrinkwrap.js CHANGED
@@ -95,6 +95,7 @@ const pkgMetaKeys = [
95
95
  'engines',
96
96
  'os',
97
97
  'cpu',
98
+ 'libc',
98
99
  '_integrity',
99
100
  'license',
100
101
  '_hasShrinkwrap',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "9.3.0",
3
+ "version": "9.4.0",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@isaacs/string-locale-compare": "^1.1.0",
@@ -40,7 +40,7 @@
40
40
  "devDependencies": {
41
41
  "@npmcli/eslint-config": "^5.0.1",
42
42
  "@npmcli/mock-registry": "^1.0.0",
43
- "@npmcli/template-oss": "4.25.1",
43
+ "@npmcli/template-oss": "4.29.0",
44
44
  "benchmark": "^2.1.4",
45
45
  "minify-registry-metadata": "^4.0.0",
46
46
  "nock": "^13.3.3",
@@ -92,7 +92,7 @@
92
92
  },
93
93
  "templateOSS": {
94
94
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
95
- "version": "4.25.1",
95
+ "version": "4.29.0",
96
96
  "content": "../../scripts/template-oss/index.js"
97
97
  }
98
98
  }