@npmcli/arborist
Advanced tools
@@ -16,3 +16,2 @@ // mixin implementing the buildIdealTree method | ||
| const { redact } = require('@npmcli/redact') | ||
| const semver = require('semver') | ||
@@ -298,6 +297,2 @@ const { | ||
| return this.loadVirtual({ root }) | ||
| .then(tree => { | ||
| this.#applyRootOverridesToWorkspaces(tree) | ||
| return tree | ||
| }) | ||
| } | ||
@@ -411,2 +406,3 @@ }) | ||
| legacyPeerDeps: this.legacyPeerDeps, | ||
| loadOverrides: true, | ||
| root, | ||
@@ -456,2 +452,7 @@ }) | ||
| const name = p.replace(/\\/g, '/') | ||
| // Match loadActual behavior: hidden entries and retired scoped package | ||
| // folders are not installed global packages. | ||
| if (/^(@[^/]+\/)?\./.test(name)) { | ||
| continue | ||
| } | ||
| const updateName = this[_updateNames].includes(name) | ||
@@ -920,5 +921,18 @@ if (this[_updateAll] || updateName) { | ||
| const parent = edge.peer ? virtualRoot : null | ||
| const dep = vrDep && vrDep.satisfies(edge) ? vrDep | ||
| : await this.#nodeFromEdge(edge, parent, null, required) | ||
| let dep = vrDep && vrDep.satisfies(edge) ? vrDep : null | ||
| // A peerOptional conflict can be resolved by finding an existing node in the tree that satisfies the edge, avoiding a registry fetch that may introduce an extraneous package. See npm/cli#9249. | ||
| // Skip the shortcut when the user has signaled an explicit re-fetch intent (npm update by name, explicit request, or audit fix), so we honor those signals rather than silently keeping the existing node. | ||
| const skipExistingShortcut = this[_updateNames].includes(edge.name) | ||
| || this.#explicitRequests.has(edge) | ||
| || (edge.to && this.auditReport?.isVulnerable(edge.to)) | ||
| if (!dep && edge.type === 'peerOptional' && !skipExistingShortcut) { | ||
| dep = this.#findHoistableNode( | ||
| /* istanbul ignore next - resolveParent is always set for non-root nodes */ | ||
| edge.from.resolveParent || edge.from, edge) | ||
| } | ||
| if (!dep) { | ||
| dep = await this.#nodeFromEdge(edge, parent, null, required) | ||
| } | ||
| /* istanbul ignore next */ | ||
@@ -1052,2 +1066,20 @@ debug(() => { | ||
| // BFS descendants of `root` for a node satisfying `edge`. | ||
| // Prefers nodes closer to root. Skips bundled nodes. | ||
| #findHoistableNode (root, edge) { | ||
| const queue = [...root.children.values()] | ||
| while (queue.length) { | ||
| const node = queue.shift() | ||
| if (node.name === edge.name | ||
| && !node.inDepBundle | ||
| && node.satisfies(edge)) { | ||
| return node | ||
| } | ||
| for (const child of node.children.values()) { | ||
| queue.push(child) | ||
| } | ||
| } | ||
| return null | ||
| } | ||
| // loads a node from an edge, and then loads its peer deps (and their peer deps, on down the line) into a virtual root parent. | ||
@@ -1516,28 +1548,2 @@ async #nodeFromEdge (edge, parent_, secondEdge, required) { | ||
| #applyRootOverridesToWorkspaces (tree) { | ||
| const rootOverrides = tree.root.package.overrides || {} | ||
| for (const node of tree.root.inventory.values()) { | ||
| if (!node.isWorkspace) { | ||
| continue | ||
| } | ||
| for (const depName of Object.keys(rootOverrides)) { | ||
| const edge = node.edgesOut.get(depName) | ||
| const rootNode = tree.root.children.get(depName) | ||
| // safely skip if either edge or rootNode doesn't exist yet | ||
| if (!edge || !rootNode) { | ||
| continue | ||
| } | ||
| const resolvedRootVersion = rootNode.package.version | ||
| if (!semver.satisfies(resolvedRootVersion, edge.spec)) { | ||
| edge.detach() | ||
| node.children.delete(depName) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #idealTreePrune () { | ||
@@ -1544,0 +1550,0 @@ for (const node of this.idealTree.inventory.values()) { |
@@ -291,2 +291,12 @@ // The arborist manages three trees: | ||
| // Build an ideal tree (or reuse an already-built one) and return the | ||
| // resulting lockfile contents as a string, without writing to disk. | ||
| // Useful for callers that want to inspect, diff, or store a lockfile | ||
| // somewhere other than the project's `package-lock.json`. | ||
| async lockfileString (options = {}) { | ||
| await this.buildIdealTree(options) | ||
| return this.idealTree.meta.toString(options) | ||
| } | ||
| async dedupe (options = {}) { | ||
@@ -293,0 +303,0 @@ // allow the user to set options on the ctor as well. |
+8
-0
@@ -112,2 +112,10 @@ const relpath = require('./relpath.js') | ||
| // When a Link receives overrides (via edgesIn), forward them to the target node which holds the actual edgesOut. | ||
| // Without this, overrides stop at the Link and never reach the target's dependency edges. | ||
| recalculateOutEdgesOverrides () { | ||
| if (this.target) { | ||
| this.target.updateOverridesEdgeInAdded(this.overrides) | ||
| } | ||
| } | ||
| // links can't have children, only their targets can | ||
@@ -114,0 +122,0 @@ // fix it to an empty list so that we can still call |
+1
-1
| { | ||
| "name": "@npmcli/arborist", | ||
| "version": "9.4.3", | ||
| "version": "9.5.0", | ||
| "description": "Manage node_modules trees", | ||
@@ -5,0 +5,0 @@ "dependencies": { |
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
495606
1.57%12994
1.25%