From 5d43dc16629a349a9518d9852f4350c33b9970a1 Mon Sep 17 00:00:00 2001 From: Lonny Jepson Date: Tue, 28 Jul 2026 18:08:36 -0600 Subject: [PATCH] fix(arborist): handle links with cleared targets Reassigning a node's root clears `target` on every link pointing at it, leaving a link with `isLink` true but no target. Comparing such a link in `matches()` crashed with `TypeError: Cannot read properties of null (reading 'matches')` instead of reporting no match. --- workspaces/arborist/lib/node.js | 3 ++- workspaces/arborist/test/node.js | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/workspaces/arborist/lib/node.js b/workspaces/arborist/lib/node.js index 13370a50ab475..4d9822a39e963 100644 --- a/workspaces/arborist/lib/node.js +++ b/workspaces/arborist/lib/node.js @@ -1190,7 +1190,8 @@ class Node { // if they're links, they match if the targets match if (this.isLink) { - return node.isLink && this.target.matches(node.target) + return node.isLink && !!this.target && !!node.target && + this.target.matches(node.target) } // if they're two project root nodes, they're different if the paths differ diff --git a/workspaces/arborist/test/node.js b/workspaces/arborist/test/node.js index ec474e0b7d75e..ad995ceb9cfff 100644 --- a/workspaces/arborist/test/node.js +++ b/workspaces/arborist/test/node.js @@ -1449,6 +1449,19 @@ t.test('detect that two nodes are the same thing', async t => { check(a, b, true, 'links match if targets match') } + { + const root = new Node({ path: '/root', pkg: { name: 'root', version: '1.0.0' } }) + const target = new Node({ root, path: '/root/packages/x', pkg: { name: 'x', version: '1.2.3' } }) + const a = new Link({ root, parent: root, name: 'x', target }) + const b = new Node({ root, parent: root, name: 'b', pkg: { name: 'b', version: '1.0.0' } }) + // Nested link to the same target + const bx = new Link({ root, parent: b, name: 'x', target }) + target.root = new Node({ path: '/other-root' }) + t.equal(a.target, null, 'target was cleared') + t.equal(bx.target, null, 'nested link target was cleared') + check(a, bx, false, 'links with cleared targets do not match') + } + { const a = new Node({ path: '/foo', pkg: { name: 'x', version: '1.2.3' } }) const b = new Node({ path: '/foo', pkg: { name: 'x', version: '1.2.3' } })