@npmcli/arborist 9.1.3 → 9.1.5

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.
@@ -1,6 +1,6 @@
1
1
  // mixin implementing the buildIdealTree method
2
2
  const localeCompare = require('@isaacs/string-locale-compare')('en')
3
- const rpj = require('read-package-json-fast')
3
+ const PackageJson = require('@npmcli/package-json')
4
4
  const npa = require('npm-package-arg')
5
5
  const pacote = require('pacote')
6
6
  const cacache = require('cacache')
@@ -268,7 +268,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
268
268
  root = await this.#globalRootNode()
269
269
  } else {
270
270
  try {
271
- const pkg = await rpj(this.path + '/package.json')
271
+ const { content: pkg } = await PackageJson.normalize(this.path)
272
272
  root = await this.#rootNodeFromPackage(pkg)
273
273
  } catch (err) {
274
274
  if (err.code === 'EJSONPARSE') {
@@ -448,7 +448,6 @@ module.exports = cls => class IdealTreeBuilder extends cls {
448
448
  const paths = await readdirScoped(nm).catch(() => [])
449
449
  for (const p of paths) {
450
450
  const name = p.replace(/\\/g, '/')
451
- tree.package.dependencies = tree.package.dependencies || {}
452
451
  const updateName = this[_updateNames].includes(name)
453
452
  if (this[_updateAll] || updateName) {
454
453
  if (updateName) {
@@ -1238,15 +1237,19 @@ This is a one-time fix-up, please be patient...
1238
1237
  // Check if the target is within the project root
1239
1238
  isProjectInternalFileSpec = targetPath.startsWith(resolvedProjectRoot + sep) || targetPath === resolvedProjectRoot
1240
1239
  }
1240
+
1241
+ // When using --install-links, we need to handle transitive file dependencies specially
1242
+ // If the parent was installed (not linked) due to --install-links, and this is a file: dep, we should also install it rather than link it
1243
+ const parentWasInstalled = parent && !parent.isLink && parent.resolved?.startsWith('file:')
1244
+ const isTransitiveFileDep = spec.type === 'directory' && parentWasInstalled && installLinks
1245
+
1241
1246
  // Decide whether to link or copy the dependency
1242
- const shouldLink = isWorkspace || isProjectInternalFileSpec || !installLinks
1247
+ const shouldLink = (isWorkspace || isProjectInternalFileSpec || !installLinks) && !isTransitiveFileDep
1243
1248
  if (spec.type === 'directory' && shouldLink) {
1244
1249
  return this.#linkFromSpec(name, spec, parent, edge)
1245
1250
  }
1246
1251
 
1247
- // if the spec matches a workspace name, then see if the workspace node will
1248
- // satisfy the edge. if it does, we return the workspace node to make sure it
1249
- // takes priority.
1252
+ // if the spec matches a workspace name, then see if the workspace node will satisfy the edge. if it does, we return the workspace node to make sure it takes priority.
1250
1253
  if (isWorkspace) {
1251
1254
  const existingNode = this.idealTree.edgesOut.get(spec.name).to
1252
1255
  if (existingNode && existingNode.isWorkspace && existingNode.satisfies(edge)) {
@@ -1254,6 +1257,15 @@ This is a one-time fix-up, please be patient...
1254
1257
  }
1255
1258
  }
1256
1259
 
1260
+ // For file: dependencies that we're installing (not linking), ensure proper resolution
1261
+ if (isTransitiveFileDep && edge) {
1262
+ // For transitive file deps, resolve relative to the parent's original source location
1263
+ const parentOriginalPath = parent.resolved.slice(5) // Remove 'file:' prefix
1264
+ const relativePath = edge.rawSpec.slice(5) // Remove 'file:' prefix
1265
+ const absolutePath = resolve(parentOriginalPath, relativePath)
1266
+ spec = npa.resolve(name, `file:${absolutePath}`)
1267
+ }
1268
+
1257
1269
  // spec isn't a directory, and either isn't a workspace or the workspace we have
1258
1270
  // doesn't satisfy the edge. try to fetch a manifest and build a node from that.
1259
1271
  return this.#fetchManifest(spec)
@@ -1275,14 +1287,15 @@ This is a one-time fix-up, please be patient...
1275
1287
  })
1276
1288
  }
1277
1289
 
1278
- #linkFromSpec (name, spec, parent) {
1290
+ async #linkFromSpec (name, spec, parent) {
1279
1291
  const realpath = spec.fetchSpec
1280
1292
  const { installLinks, legacyPeerDeps } = this
1281
- return rpj(realpath + '/package.json').catch(() => ({})).then(pkg => {
1282
- const link = new Link({ name, parent, realpath, pkg, installLinks, legacyPeerDeps })
1283
- this.#linkNodes.add(link)
1284
- return link
1293
+ const { content: pkg } = await PackageJson.normalize(realpath).catch(() => {
1294
+ return { content: {} }
1285
1295
  })
1296
+ const link = new Link({ name, parent, realpath, pkg, installLinks, legacyPeerDeps })
1297
+ this.#linkNodes.add(link)
1298
+ return link
1286
1299
  }
1287
1300
 
1288
1301
  // load all peer deps and meta-peer deps into the node's parent
@@ -1306,6 +1319,12 @@ This is a one-time fix-up, please be patient...
1306
1319
  .sort(({ name: a }, { name: b }) => localeCompare(a, b))
1307
1320
 
1308
1321
  for (const edge of peerEdges) {
1322
+ // node.parent gets mutated during loop execution due to recursive #nodeFromEdge calls.
1323
+ // When a compatible peer is found (e.g. a@1.1.0 replaces a@1.2.0), the original node loses its parent.
1324
+ // if node is detached/removed from the tree, or has no parent, so no need to check remaining edgesOut for that node.
1325
+ if (!node.parent) {
1326
+ break
1327
+ }
1309
1328
  // already placed this one, and we're happy with it.
1310
1329
  if (edge.valid && edge.to) {
1311
1330
  continue
@@ -1,8 +1,8 @@
1
1
  // mix-in implementing the loadActual method
2
2
 
3
- const { relative, dirname, resolve, join, normalize } = require('node:path')
3
+ const { dirname, join, normalize, relative, resolve } = require('node:path')
4
4
 
5
- const rpj = require('read-package-json-fast')
5
+ const PackageJson = require('@npmcli/package-json')
6
6
  const { readdirScoped } = require('@npmcli/fs')
7
7
  const { walkUp } = require('walk-up-path')
8
8
  const ancestorPath = require('common-ancestor-path')
@@ -279,12 +279,16 @@ module.exports = cls => class ActualLoader extends cls {
279
279
  }
280
280
 
281
281
  try {
282
- const pkg = await rpj(join(real, 'package.json'))
282
+ const { content: pkg } = await PackageJson.normalize(real)
283
283
  params.pkg = pkg
284
284
  if (useRootOverrides && root.overrides) {
285
285
  params.overrides = root.overrides.getNodeRule({ name: pkg.name, version: pkg.version })
286
286
  }
287
287
  } catch (err) {
288
+ if (err.code === 'EJSONPARSE') {
289
+ // TODO @npmcli/package-json should be doing this
290
+ err.path = join(real, 'package.json')
291
+ }
288
292
  params.error = err
289
293
  }
290
294
 
@@ -1,16 +1,15 @@
1
+ const { resolve } = require('node:path')
1
2
  // mixin providing the loadVirtual method
2
3
  const mapWorkspaces = require('@npmcli/map-workspaces')
3
-
4
- const { resolve } = require('node:path')
5
-
4
+ const PackageJson = require('@npmcli/package-json')
6
5
  const nameFromFolder = require('@npmcli/name-from-folder')
6
+
7
7
  const consistentResolve = require('../consistent-resolve.js')
8
8
  const Shrinkwrap = require('../shrinkwrap.js')
9
9
  const Node = require('../node.js')
10
10
  const Link = require('../link.js')
11
11
  const relpath = require('../relpath.js')
12
12
  const calcDepFlags = require('../calc-dep-flags.js')
13
- const rpj = require('read-package-json-fast')
14
13
  const treeCheck = require('../tree-check.js')
15
14
 
16
15
  const flagsSuspect = Symbol.for('flagsSuspect')
@@ -54,10 +53,11 @@ module.exports = cls => class VirtualLoader extends cls {
54
53
 
55
54
  // when building the ideal tree, we pass in a root node to this function
56
55
  // otherwise, load it from the root package json or the lockfile
56
+ const pkg = await PackageJson.normalize(this.path).then(p => p.content).catch(() => s.data.packages[''] || {})
57
+ // TODO clean this up
57
58
  const {
58
- root = await this.#loadRoot(s),
59
+ root = await this[setWorkspaces](this.#loadNode('', pkg, true)),
59
60
  } = options
60
-
61
61
  this.#rootOptionProvided = options.root
62
62
 
63
63
  await this.#loadFromShrinkwrap(s, root)
@@ -65,12 +65,6 @@ module.exports = cls => class VirtualLoader extends cls {
65
65
  return treeCheck(this.virtualTree)
66
66
  }
67
67
 
68
- async #loadRoot (s) {
69
- const pj = this.path + '/package.json'
70
- const pkg = await rpj(pj).catch(() => s.data.packages['']) || {}
71
- return this[setWorkspaces](this.#loadNode('', pkg, true))
72
- }
73
-
74
68
  async #loadFromShrinkwrap (s, root) {
75
69
  if (!this.#rootOptionProvided) {
76
70
  // root is never any of these things, but might be a brand new
@@ -219,11 +213,7 @@ To fix:
219
213
  // we always need to read the package.json for link targets
220
214
  // outside node_modules because they can be changed by the local user
221
215
  if (!link.target.parent) {
222
- const pj = link.realpath + '/package.json'
223
- const pkg = await rpj(pj).catch(() => null)
224
- if (pkg) {
225
- link.target.package = pkg
226
- }
216
+ await PackageJson.normalize(link.realpath).then(p => link.target.package = p.content).catch(() => null)
227
217
  }
228
218
  }
229
219
  }
@@ -1,20 +1,19 @@
1
1
  // Arborist.rebuild({path = this.path}) will do all the binlinks and
2
2
  // bundle building needed. Called by reify, and by `npm rebuild`.
3
3
 
4
+ const PackageJson = require('@npmcli/package-json')
5
+ const binLinks = require('bin-links')
4
6
  const localeCompare = require('@isaacs/string-locale-compare')('en')
5
- const { depth: dfwalk } = require('treeverse')
6
7
  const promiseAllRejectLate = require('promise-all-reject-late')
7
- const rpj = require('read-package-json-fast')
8
- const binLinks = require('bin-links')
9
8
  const runScript = require('@npmcli/run-script')
10
9
  const { callLimit: promiseCallLimit } = require('promise-call-limit')
11
- const { resolve } = require('node:path')
10
+ const { depth: dfwalk } = require('treeverse')
12
11
  const { isNodeGypPackage, defaultGypInstallScript } = require('@npmcli/node-gyp')
13
12
  const { log, time } = require('proc-log')
13
+ const { resolve } = require('node:path')
14
14
 
15
15
  const boolEnv = b => b ? '1' : ''
16
- const sortNodes = (a, b) =>
17
- (a.depth - b.depth) || localeCompare(a.path, b.path)
16
+ const sortNodes = (a, b) => (a.depth - b.depth) || localeCompare(a.path, b.path)
18
17
 
19
18
  const _checkBins = Symbol.for('checkBins')
20
19
 
@@ -250,7 +249,9 @@ module.exports = cls => class Builder extends cls {
250
249
  // add to the set then remove while we're reading the pj, so we
251
250
  // don't accidentally hit it multiple times.
252
251
  set.add(node)
253
- const pkg = await rpj(node.path + '/package.json').catch(() => ({}))
252
+ const { content: pkg } = await PackageJson.normalize(node.path).catch(() => {
253
+ return { content: {} }
254
+ })
254
255
  set.delete(node)
255
256
 
256
257
  const { scripts = {} } = pkg
@@ -1,48 +1,37 @@
1
1
  // mixin implementing the reify method
2
- const onExit = require('../signal-handling.js')
3
- const pacote = require('pacote')
4
- const AuditReport = require('../audit-report.js')
5
- const { subset, intersects } = require('semver')
6
- const npa = require('npm-package-arg')
7
- const semver = require('semver')
8
- const debug = require('../debug.js')
9
- const { walkUp } = require('walk-up-path')
10
- const { log, time } = require('proc-log')
11
- const rpj = require('read-package-json-fast')
12
- const hgi = require('hosted-git-info')
13
-
14
- const { dirname, resolve, relative, join } = require('node:path')
15
- const { depth: dfwalk } = require('treeverse')
16
- const {
17
- lstat,
18
- mkdir,
19
- rm,
20
- symlink,
21
- } = require('node:fs/promises')
22
- const { moveFile } = require('@npmcli/fs')
23
2
  const PackageJson = require('@npmcli/package-json')
3
+ const hgi = require('hosted-git-info')
4
+ const npa = require('npm-package-arg')
24
5
  const packageContents = require('@npmcli/installed-package-contents')
6
+ const pacote = require('pacote')
7
+ const promiseAllRejectLate = require('promise-all-reject-late')
25
8
  const runScript = require('@npmcli/run-script')
9
+ const { callLimit: promiseCallLimit } = require('promise-call-limit')
26
10
  const { checkEngine, checkPlatform } = require('npm-install-checks')
11
+ const { depth: dfwalk } = require('treeverse')
12
+ const { dirname, resolve, relative, join } = require('node:path')
13
+ const { log, time } = require('proc-log')
14
+ const { lstat, mkdir, rm, symlink } = require('node:fs/promises')
15
+ const { moveFile } = require('@npmcli/fs')
16
+ const { subset, intersects } = require('semver')
17
+ const { walkUp } = require('walk-up-path')
27
18
 
28
- const treeCheck = require('../tree-check.js')
29
- const relpath = require('../relpath.js')
19
+ const AuditReport = require('../audit-report.js')
30
20
  const Diff = require('../diff.js')
31
- const retirePath = require('../retire-path.js')
32
- const promiseAllRejectLate = require('promise-all-reject-late')
33
- const { callLimit: promiseCallLimit } = require('promise-call-limit')
34
- const optionalSet = require('../optional-set.js')
35
21
  const calcDepFlags = require('../calc-dep-flags.js')
22
+ const debug = require('../debug.js')
23
+ const onExit = require('../signal-handling.js')
24
+ const optionalSet = require('../optional-set.js')
25
+ const relpath = require('../relpath.js')
26
+ const retirePath = require('../retire-path.js')
27
+ const treeCheck = require('../tree-check.js')
28
+ const { defaultLockfileVersion } = require('../shrinkwrap.js')
36
29
  const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.js')
37
30
 
38
- const Shrinkwrap = require('../shrinkwrap.js')
39
- const { defaultLockfileVersion } = Shrinkwrap
40
-
41
31
  // Part of steps (steps need refactoring before we can do anything about these)
42
32
  const _retireShallowNodes = Symbol.for('retireShallowNodes')
43
33
  const _loadBundlesAndUpdateTrees = Symbol.for('loadBundlesAndUpdateTrees')
44
34
  const _submitQuickAudit = Symbol('submitQuickAudit')
45
- const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
46
35
  const _unpackNewModules = Symbol.for('unpackNewModules')
47
36
  const _build = Symbol.for('build')
48
37
 
@@ -85,6 +74,7 @@ module.exports = cls => class Reifier extends cls {
85
74
  #dryRun
86
75
  #nmValidated = new Set()
87
76
  #omit
77
+ #omitted
88
78
  #retiredPaths = {}
89
79
  #retiredUnchanged = {}
90
80
  #savePrefix
@@ -109,6 +99,7 @@ module.exports = cls => class Reifier extends cls {
109
99
  }
110
100
 
111
101
  this.#omit = new Set(options.omit)
102
+ this.#omitted = new Set()
112
103
 
113
104
  // start tracker block
114
105
  this.addTracker('reify')
@@ -141,6 +132,10 @@ module.exports = cls => class Reifier extends cls {
141
132
  this.idealTree = oldTree
142
133
  }
143
134
  await this[_saveIdealTree](options)
135
+ // clean omitted
136
+ for (const node of this.#omitted) {
137
+ node.parent = null
138
+ }
144
139
  // clean up any trash that is still in the tree
145
140
  for (const path of this[_trashList]) {
146
141
  const loc = relpath(this.idealTree.realpath, path)
@@ -315,7 +310,6 @@ module.exports = cls => class Reifier extends cls {
315
310
  ]],
316
311
  [_rollbackCreateSparseTree, [
317
312
  _createSparseTree,
318
- _addOmitsToTrashList,
319
313
  _loadShrinkwrapsAndUpdateTrees,
320
314
  _loadBundlesAndUpdateTrees,
321
315
  _submitQuickAudit,
@@ -470,6 +464,8 @@ module.exports = cls => class Reifier extends cls {
470
464
  // find all the nodes that need to change between the actual
471
465
  // and ideal trees.
472
466
  this.diff = Diff.calculate({
467
+ omit: this.#omit,
468
+ omitted: this.#omitted,
473
469
  shrinkwrapInflated: this.#shrinkwrapInflated,
474
470
  filterNodes,
475
471
  actual: this.actualTree,
@@ -554,37 +550,6 @@ module.exports = cls => class Reifier extends cls {
554
550
  })
555
551
  }
556
552
 
557
- // adding to the trash list will skip reifying, and delete them
558
- // if they are currently in the tree and otherwise untouched.
559
- [_addOmitsToTrashList] () {
560
- if (!this.#omit.size) {
561
- return
562
- }
563
-
564
- const timeEnd = time.start('reify:trashOmits')
565
- for (const node of this.idealTree.inventory.values()) {
566
- const { top } = node
567
-
568
- // if the top is not the root or workspace then we do not want to omit it
569
- if (!top.isProjectRoot && !top.isWorkspace) {
570
- continue
571
- }
572
-
573
- // if a diff filter has been created, then we do not omit the node if the
574
- // top node is not in that set
575
- if (this.diff?.filterSet?.size && !this.diff.filterSet.has(top)) {
576
- continue
577
- }
578
-
579
- // omit node if the dep type matches any omit flags that were set
580
- if (node.shouldOmit(this.#omit)) {
581
- this[_addNodeToTrashList](node)
582
- }
583
- }
584
-
585
- timeEnd()
586
- }
587
-
588
553
  [_createSparseTree] () {
589
554
  const timeEnd = time.start('reify:createSparse')
590
555
  // if we call this fn again, we look for the previous list
@@ -683,7 +648,6 @@ module.exports = cls => class Reifier extends cls {
683
648
  // reload the diff and sparse tree because the ideal tree changed
684
649
  .then(() => this[_diffTrees]())
685
650
  .then(() => this[_createSparseTree]())
686
- .then(() => this[_addOmitsToTrashList]())
687
651
  .then(() => this[_loadShrinkwrapsAndUpdateTrees]())
688
652
  .then(timeEnd)
689
653
  }
@@ -691,15 +655,10 @@ module.exports = cls => class Reifier extends cls {
691
655
  // create a symlink for Links, extract for Nodes
692
656
  // return the node object, since we usually want that
693
657
  // handle optional dep failures here
694
- // If node is in trash list, skip it
695
658
  // If reifying fails, and the node is optional, add it and its optionalSet
696
659
  // to the trash list
697
660
  // Always return the node.
698
661
  [_reifyNode] (node) {
699
- if (this[_trashList].has(node.path)) {
700
- return node
701
- }
702
-
703
662
  const timeEnd = time.start(`reifyNode:${node.location}`)
704
663
  this.addTracker('reify', node.name, node.location)
705
664
 
@@ -803,7 +762,7 @@ module.exports = cls => class Reifier extends cls {
803
762
  })
804
763
  // store nodes don't use Node class so node.package doesn't get updated
805
764
  if (node.isInStore) {
806
- const pkg = await rpj(join(node.path, 'package.json'))
765
+ const { content: pkg } = await PackageJson.normalize(node.path)
807
766
  node.package.scripts = pkg.scripts
808
767
  }
809
768
  return
@@ -885,6 +844,7 @@ module.exports = cls => class Reifier extends cls {
885
844
  // Replace the host with the registry host while keeping the path intact
886
845
  resolvedURL.hostname = registryURL.hostname
887
846
  resolvedURL.port = registryURL.port
847
+ resolvedURL.protocol = registryURL.protocol
888
848
 
889
849
  // Make sure we don't double-include the path if it's already there
890
850
  const registryPath = registryURL.pathname.replace(/\/$/, '')
@@ -1431,8 +1391,7 @@ module.exports = cls => class Reifier extends cls {
1431
1391
  if (options.saveType) {
1432
1392
  const depType = saveTypeMap.get(options.saveType)
1433
1393
  pkg[depType][name] = newSpec
1434
- // rpj will have moved it here if it was in both
1435
- // if it is empty it will be deleted later
1394
+ // PackageJson.normalize will have moved it here if it was in both, if it is empty it will be deleted later
1436
1395
  if (options.saveType === 'prod' && pkg.optionalDependencies) {
1437
1396
  delete pkg.optionalDependencies[name]
1438
1397
  }
@@ -1473,7 +1432,7 @@ module.exports = cls => class Reifier extends cls {
1473
1432
  const exactVersion = node => {
1474
1433
  for (const edge of node.edgesIn) {
1475
1434
  try {
1476
- if (semver.subset(edge.spec, node.version)) {
1435
+ if (subset(edge.spec, node.version)) {
1477
1436
  return false
1478
1437
  }
1479
1438
  } catch {
@@ -22,6 +22,7 @@ const calcDepFlagsStep = (node) => {
22
22
  // or normal dependency graphs overlap deep in the dep graph.
23
23
  // Since we're only walking through deps that are not already flagged
24
24
  // as non-dev/non-optional, it's typically a very shallow traversal
25
+
25
26
  node.extraneous = false
26
27
  resetParents(node, 'extraneous')
27
28
  resetParents(node, 'dev')
@@ -47,10 +48,16 @@ const calcDepFlagsStep = (node) => {
47
48
  if (!to) {
48
49
  return
49
50
  }
50
-
51
51
  // everything with any kind of edge into it is not extraneous
52
52
  to.extraneous = false
53
53
 
54
+ // If this is a peer edge, mark the target as peer
55
+ if (peer) {
56
+ to.peer = true
57
+ } else if (to.peer && !hasIncomingPeerEdge(to)) {
58
+ unsetFlag(to, 'peer')
59
+ }
60
+
54
61
  // devOptional is the *overlap* of the dev and optional tree.
55
62
  // however, for convenience and to save an extra rewalk, we leave
56
63
  // it set when we are in *either* tree, and then omit it from the
@@ -61,11 +68,6 @@ const calcDepFlagsStep = (node) => {
61
68
  // either the dev or opt trees
62
69
  const unsetDev = unsetDevOpt || !node.dev && !dev
63
70
  const unsetOpt = unsetDevOpt || !node.optional && !optional
64
- const unsetPeer = !node.peer && !peer
65
-
66
- if (unsetPeer) {
67
- unsetFlag(to, 'peer')
68
- }
69
71
 
70
72
  if (unsetDevOpt) {
71
73
  unsetFlag(to, 'devOptional')
@@ -83,6 +85,16 @@ const calcDepFlagsStep = (node) => {
83
85
  return node
84
86
  }
85
87
 
88
+ const hasIncomingPeerEdge = (node) => {
89
+ const target = node.isLink && node.target ? node.target : node
90
+ for (const edge of target.edgesIn) {
91
+ if (edge.type === 'peer') {
92
+ return true
93
+ }
94
+ }
95
+ return false
96
+ }
97
+
86
98
  const resetParents = (node, flag) => {
87
99
  if (node[flag]) {
88
100
  return
@@ -109,12 +121,19 @@ const unsetFlag = (node, flag) => {
109
121
  const children = []
110
122
  const targetNode = node.isLink && node.target ? node.target : node
111
123
  for (const edge of targetNode.edgesOut.values()) {
112
- if (
113
- edge.to &&
114
- edge.to[flag] &&
115
- ((flag !== 'peer' && edge.type === 'peer') || edge.type === 'prod')
116
- ) {
117
- children.push(edge.to)
124
+ if (edge.to?.[flag]) {
125
+ // For the peer flag, only follow peer edges to unset the flag
126
+ // Don't propagate peer flag through prod/dev/optional edges
127
+ if (flag === 'peer') {
128
+ if (edge.type === 'peer') {
129
+ children.push(edge.to)
130
+ }
131
+ } else {
132
+ // For other flags, follow prod edges (and peer edges for non-peer flags)
133
+ if (edge.type === 'prod' || edge.type === 'peer') {
134
+ children.push(edge.to)
135
+ }
136
+ }
118
137
  }
119
138
  }
120
139
  return children
package/lib/diff.js CHANGED
@@ -11,7 +11,9 @@ const { existsSync } = require('node:fs')
11
11
  const ssri = require('ssri')
12
12
 
13
13
  class Diff {
14
- constructor ({ actual, ideal, filterSet, shrinkwrapInflated }) {
14
+ constructor ({ actual, ideal, filterSet, shrinkwrapInflated, omit, omitted }) {
15
+ this.omit = omit
16
+ this.omitted = omitted
15
17
  this.filterSet = filterSet
16
18
  this.shrinkwrapInflated = shrinkwrapInflated
17
19
  this.children = []
@@ -36,6 +38,8 @@ class Diff {
36
38
  ideal,
37
39
  filterNodes = [],
38
40
  shrinkwrapInflated = new Set(),
41
+ omit = new Set(),
42
+ omitted = new Set(),
39
43
  }) {
40
44
  // if there's a filterNode, then:
41
45
  // - get the path from the root to the filterNode. The root or
@@ -94,18 +98,28 @@ class Diff {
94
98
  }
95
99
 
96
100
  return depth({
97
- tree: new Diff({ actual, ideal, filterSet, shrinkwrapInflated }),
101
+ tree: new Diff({ actual, ideal, filterSet, shrinkwrapInflated, omit, omitted }),
98
102
  getChildren,
99
103
  leave,
100
104
  })
101
105
  }
102
106
  }
103
107
 
104
- const getAction = ({ actual, ideal }) => {
108
+ const getAction = ({ actual, ideal, omit, omitted }) => {
105
109
  if (!ideal) {
106
110
  return 'REMOVE'
107
111
  }
108
112
 
113
+ if (ideal.shouldOmit?.(omit)) {
114
+ omitted.add(ideal)
115
+
116
+ if (actual) {
117
+ return 'REMOVE'
118
+ }
119
+
120
+ return null
121
+ }
122
+
109
123
  // bundled meta-deps are copied over to the ideal tree when we visit it,
110
124
  // so they'll appear to be missing here. There's no need to handle them
111
125
  // in the diff, though, because they'll be replaced at reify time anyway
@@ -184,6 +198,8 @@ const getChildren = diff => {
184
198
  removed,
185
199
  filterSet,
186
200
  shrinkwrapInflated,
201
+ omit,
202
+ omitted,
187
203
  } = diff
188
204
 
189
205
  // Note: we DON'T diff fsChildren themselves, because they are either
@@ -214,6 +230,8 @@ const getChildren = diff => {
214
230
  removed,
215
231
  filterSet,
216
232
  shrinkwrapInflated,
233
+ omit,
234
+ omitted,
217
235
  })
218
236
  }
219
237
 
@@ -232,12 +250,14 @@ const diffNode = ({
232
250
  removed,
233
251
  filterSet,
234
252
  shrinkwrapInflated,
253
+ omit,
254
+ omitted,
235
255
  }) => {
236
256
  if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual))) {
237
257
  return
238
258
  }
239
259
 
240
- const action = getAction({ actual, ideal })
260
+ const action = getAction({ actual, ideal, omit, omitted })
241
261
 
242
262
  // if it's a match, then get its children
243
263
  // otherwise, this is the child diff node
@@ -245,7 +265,7 @@ const diffNode = ({
245
265
  if (action === 'REMOVE') {
246
266
  removed.push(actual)
247
267
  }
248
- children.push(new Diff({ actual, ideal, filterSet, shrinkwrapInflated }))
268
+ children.push(new Diff({ actual, ideal, filterSet, shrinkwrapInflated, omit, omitted }))
249
269
  } else {
250
270
  unchanged.push(ideal)
251
271
  // !*! Weird dirty hack warning !*!
@@ -285,6 +305,8 @@ const diffNode = ({
285
305
  removed,
286
306
  filterSet,
287
307
  shrinkwrapInflated,
308
+ omit,
309
+ omitted,
288
310
  }))
289
311
  }
290
312
  }
package/lib/node.js CHANGED
@@ -28,22 +28,28 @@
28
28
  // where we need to quickly find all instances of a given package name within a
29
29
  // tree.
30
30
 
31
- const semver = require('semver')
31
+ const PackageJson = require('@npmcli/package-json')
32
32
  const nameFromFolder = require('@npmcli/name-from-folder')
33
+ const npa = require('npm-package-arg')
34
+ const semver = require('semver')
35
+ const util = require('node:util')
36
+ const { getPaths: getBinPaths } = require('bin-links')
37
+ const { log } = require('proc-log')
38
+ const { resolve, relative, dirname, basename } = require('node:path')
39
+ const { walkUp } = require('walk-up-path')
40
+
41
+ const CaseInsensitiveMap = require('./case-insensitive-map.js')
33
42
  const Edge = require('./edge.js')
34
43
  const Inventory = require('./inventory.js')
35
44
  const OverrideSet = require('./override-set.js')
36
- const { normalize } = require('read-package-json-fast')
37
- const { getPaths: getBinPaths } = require('bin-links')
38
- const npa = require('npm-package-arg')
45
+ const consistentResolve = require('./consistent-resolve.js')
39
46
  const debug = require('./debug.js')
40
47
  const gatherDepSet = require('./gather-dep-set.js')
48
+ const printableTree = require('./printable.js')
49
+ const querySelectorAll = require('./query-selector-all.js')
50
+ const relpath = require('./relpath.js')
41
51
  const treeCheck = require('./tree-check.js')
42
- const { walkUp } = require('walk-up-path')
43
- const { log } = require('proc-log')
44
52
 
45
- const { resolve, relative, dirname, basename } = require('node:path')
46
- const util = require('node:util')
47
53
  const _package = Symbol('_package')
48
54
  const _parent = Symbol('_parent')
49
55
  const _target = Symbol.for('_target')
@@ -58,14 +64,6 @@ const _delistFromMeta = Symbol.for('_delistFromMeta')
58
64
  const _explain = Symbol('_explain')
59
65
  const _explanation = Symbol('_explanation')
60
66
 
61
- const relpath = require('./relpath.js')
62
- const consistentResolve = require('./consistent-resolve.js')
63
-
64
- const printableTree = require('./printable.js')
65
- const CaseInsensitiveMap = require('./case-insensitive-map.js')
66
-
67
- const querySelectorAll = require('./query-selector-all.js')
68
-
69
67
  class Node {
70
68
  #global
71
69
  #meta
@@ -121,14 +119,25 @@ class Node {
121
119
  // package's dependencies in a virtual root.
122
120
  this.sourceReference = sourceReference
123
121
 
124
- // TODO if this came from pacote.manifest we don't have to do this,
125
- // we can be told to skip this step
126
- const pkg = sourceReference ? sourceReference.package
127
- : normalize(options.pkg || {})
122
+ // have to set the internal package ref before assigning the parent, because this.package is read when adding to inventory
123
+ if (sourceReference) {
124
+ this[_package] = sourceReference.package
125
+ } else {
126
+ // TODO if this came from pacote.manifest we don't have to do this, we can be told to skip this step
127
+ const pkg = new PackageJson()
128
+ let content = {}
129
+ // TODO this is overly guarded. If pkg is not an object we should not allow it at all.
130
+ if (options.pkg && typeof options.pkg === 'object') {
131
+ content = options.pkg
132
+ }
133
+ pkg.fromContent(content)
134
+ pkg.syncNormalize()
135
+ this[_package] = pkg.content
136
+ }
128
137
 
129
138
  this.name = name ||
130
- nameFromFolder(path || pkg.name || realpath) ||
131
- pkg.name ||
139
+ nameFromFolder(path || this.package.name || realpath) ||
140
+ this.package.name ||
132
141
  null
133
142
 
134
143
  // should be equal if not a link
@@ -156,13 +165,13 @@ class Node {
156
165
  // probably what we're getting from pacote, which IS trustworthy.
157
166
  //
158
167
  // Otherwise, hopefully a shrinkwrap will help us out.
159
- const resolved = consistentResolve(pkg._resolved)
160
- if (resolved && !(/^file:/.test(resolved) && pkg._where)) {
168
+ const resolved = consistentResolve(this.package._resolved)
169
+ if (resolved && !(/^file:/.test(resolved) && this.package._where)) {
161
170
  this.resolved = resolved
162
171
  }
163
172
  }
164
- this.integrity = integrity || pkg._integrity || null
165
- this.hasShrinkwrap = hasShrinkwrap || pkg._hasShrinkwrap || false
173
+ this.integrity = integrity || this.package._integrity || null
174
+ this.hasShrinkwrap = hasShrinkwrap || this.package._hasShrinkwrap || false
166
175
  this.installLinks = installLinks
167
176
  this.legacyPeerDeps = legacyPeerDeps
168
177
 
@@ -203,17 +212,13 @@ class Node {
203
212
  this.edgesIn = new Set()
204
213
  this.edgesOut = new CaseInsensitiveMap()
205
214
 
206
- // have to set the internal package ref before assigning the parent,
207
- // because this.package is read when adding to inventory
208
- this[_package] = pkg && typeof pkg === 'object' ? pkg : {}
209
-
210
215
  if (overrides) {
211
216
  this.overrides = overrides
212
217
  } else if (loadOverrides) {
213
- const overrides = this[_package].overrides || {}
218
+ const overrides = this.package.overrides || {}
214
219
  if (Object.keys(overrides).length > 0) {
215
220
  this.overrides = new OverrideSet({
216
- overrides: this[_package].overrides,
221
+ overrides: this.package.overrides,
217
222
  })
218
223
  }
219
224
  }
@@ -314,7 +319,7 @@ class Node {
314
319
  }
315
320
 
316
321
  return getBinPaths({
317
- pkg: this[_package],
322
+ pkg: this.package,
318
323
  path: this.path,
319
324
  global: this.global,
320
325
  top: this.globalTop,
@@ -328,11 +333,11 @@ class Node {
328
333
  }
329
334
 
330
335
  get version () {
331
- return this[_package].version || ''
336
+ return this.package.version || ''
332
337
  }
333
338
 
334
339
  get packageName () {
335
- return this[_package].name || null
340
+ return this.package.name || null
336
341
  }
337
342
 
338
343
  get pkgid () {
@@ -490,6 +495,18 @@ class Node {
490
495
  }
491
496
 
492
497
  shouldOmit (omitSet) {
498
+ if (!omitSet.size) {
499
+ return false
500
+ }
501
+
502
+ const { top } = this
503
+
504
+ // if the top is not the root or workspace then we do not want to omit it
505
+ if (!top.isProjectRoot && !top.isWorkspace) {
506
+ return false
507
+ }
508
+
509
+ // omit node if the dep type matches any omit flags that were set
493
510
  return (
494
511
  this.peer && omitSet.has('peer') ||
495
512
  this.dev && omitSet.has('dev') ||
package/package.json CHANGED
@@ -1,38 +1,37 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "9.1.3",
3
+ "version": "9.1.5",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@isaacs/string-locale-compare": "^1.1.0",
7
7
  "@npmcli/fs": "^4.0.0",
8
8
  "@npmcli/installed-package-contents": "^3.0.0",
9
- "@npmcli/map-workspaces": "^4.0.1",
10
- "@npmcli/metavuln-calculator": "^9.0.0",
9
+ "@npmcli/map-workspaces": "^5.0.0",
10
+ "@npmcli/metavuln-calculator": "^9.0.2",
11
11
  "@npmcli/name-from-folder": "^3.0.0",
12
12
  "@npmcli/node-gyp": "^4.0.0",
13
- "@npmcli/package-json": "^6.0.1",
13
+ "@npmcli/package-json": "^7.0.0",
14
14
  "@npmcli/query": "^4.0.0",
15
15
  "@npmcli/redact": "^3.0.0",
16
- "@npmcli/run-script": "^9.0.1",
16
+ "@npmcli/run-script": "^10.0.0",
17
17
  "bin-links": "^5.0.0",
18
- "cacache": "^19.0.1",
18
+ "cacache": "^20.0.1",
19
19
  "common-ancestor-path": "^1.0.1",
20
- "hosted-git-info": "^8.0.0",
20
+ "hosted-git-info": "^9.0.0",
21
21
  "json-stringify-nice": "^1.1.4",
22
- "lru-cache": "^10.2.2",
23
- "minimatch": "^9.0.4",
22
+ "lru-cache": "^11.2.1",
23
+ "minimatch": "^10.0.3",
24
24
  "nopt": "^8.0.0",
25
25
  "npm-install-checks": "^7.1.0",
26
- "npm-package-arg": "^12.0.0",
27
- "npm-pick-manifest": "^10.0.0",
28
- "npm-registry-fetch": "^18.0.1",
29
- "pacote": "^21.0.0",
26
+ "npm-package-arg": "^13.0.0",
27
+ "npm-pick-manifest": "^11.0.1",
28
+ "npm-registry-fetch": "^19.0.0",
29
+ "pacote": "^21.0.2",
30
30
  "parse-conflict-json": "^4.0.0",
31
31
  "proc-log": "^5.0.0",
32
32
  "proggy": "^3.0.0",
33
33
  "promise-all-reject-late": "^1.0.0",
34
34
  "promise-call-limit": "^3.0.1",
35
- "read-package-json-fast": "^4.0.0",
36
35
  "semver": "^7.3.7",
37
36
  "ssri": "^12.0.0",
38
37
  "treeverse": "^3.0.0",
@@ -41,7 +40,7 @@
41
40
  "devDependencies": {
42
41
  "@npmcli/eslint-config": "^5.0.1",
43
42
  "@npmcli/mock-registry": "^1.0.0",
44
- "@npmcli/template-oss": "4.24.4",
43
+ "@npmcli/template-oss": "4.25.1",
45
44
  "benchmark": "^2.1.4",
46
45
  "minify-registry-metadata": "^4.0.0",
47
46
  "nock": "^13.3.3",
@@ -93,7 +92,7 @@
93
92
  },
94
93
  "templateOSS": {
95
94
  "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
96
- "version": "4.24.4",
95
+ "version": "4.25.1",
97
96
  "content": "../../scripts/template-oss/index.js"
98
97
  }
99
98
  }