From e897be913676fe823aaa298fc899e730856eab65 Mon Sep 17 00:00:00 2001 From: Tristan Manchester Date: Tue, 31 Mar 2026 16:55:32 +0200 Subject: [PATCH 1/2] fix duplicate dependency analysis for workspace links --- src/analyze/duplicate-dependencies.ts | 15 ++++ .../duplicate-dependencies.test.ts.snap | 26 +++++++ src/test/duplicate-dependencies.test.ts | 77 +++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index c420ea2..6778e0b 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -7,6 +7,14 @@ interface Version { parents: string[]; } +function hasResolvedVersion( + pkg: Pick +): pkg is Pick & { + version: string; +} { + return typeof pkg.version === 'string' && pkg.version.length > 0; +} + /** * Outputs packages with duplicate versions and suggest possible fixes * @param context @@ -35,6 +43,13 @@ function resolveDuplicateDependencies( ): Map { const resolvedDependencies: Map = new Map(); for (const pkg of lockfile.packages) { + // npm workspace links appear in package-lock.json without a version. + // They are not real installed versions and should not participate in + // duplicate-version analysis. + if (!hasResolvedVersion(pkg)) { + continue; + } + const entry: Version = { version: pkg.version, parents: [] diff --git a/src/test/__snapshots__/duplicate-dependencies.test.ts.snap b/src/test/__snapshots__/duplicate-dependencies.test.ts.snap index 0298ac2..02a43e7 100644 --- a/src/test/__snapshots__/duplicate-dependencies.test.ts.snap +++ b/src/test/__snapshots__/duplicate-dependencies.test.ts.snap @@ -27,6 +27,32 @@ exports[`Duplicate Dependency Detection > should detect multiple versions 1`] = } `; +exports[`Duplicate Dependency Detection > should ignore workspace link entries without versions 1`] = ` +{ + "messages": [ + { + "message": "[duplicate dependency] shared-lib has 2 installed versions: +1.0.0 via the following 1 package(s) package-a@1.0.0 +2.0.0 via the following 1 package(s) package-b@1.0.0 +💡 Suggestions +- Consider upgrading consuming packages as this may resolve this duplicate version. +", + "score": 0, + "severity": "warning", + }, + ], + "stats": { + "extraStats": [ + { + "label": "Duplicate Dependency Count", + "name": "duplicateDependencyCount", + "value": 1, + }, + ], + }, +} +`; + exports[`Duplicate Dependency Detection > should not detect duplicates when there are none 1`] = ` { "messages": [], diff --git a/src/test/duplicate-dependencies.test.ts b/src/test/duplicate-dependencies.test.ts index 37c6a7d..8819717 100644 --- a/src/test/duplicate-dependencies.test.ts +++ b/src/test/duplicate-dependencies.test.ts @@ -182,4 +182,81 @@ describe('Duplicate Dependency Detection', () => { expect(stats).toMatchSnapshot(); }); + + it('should ignore workspace link entries without versions', async () => { + const sharedLibv1: ParsedDependency = { + name: 'shared-lib', + version: '1.0.0', + dependencies: [], + devDependencies: [], + optionalDependencies: [], + peerDependencies: [] + }; + const sharedLibv2: ParsedDependency = { + name: 'shared-lib', + version: '2.0.0', + dependencies: [], + devDependencies: [], + optionalDependencies: [], + peerDependencies: [] + }; + const workspaceLink = { + name: 'react-native-dotgrid', + dependencies: [], + devDependencies: [], + optionalDependencies: [], + peerDependencies: [] + } as unknown as ParsedDependency; + const packageA: ParsedDependency = { + name: 'package-a', + version: '1.0.0', + dependencies: [sharedLibv1], + devDependencies: [], + peerDependencies: [], + optionalDependencies: [] + }; + const packageB: ParsedDependency = { + name: 'package-b', + version: '1.0.0', + dependencies: [sharedLibv2], + devDependencies: [], + peerDependencies: [], + optionalDependencies: [] + }; + + context = { + fs: fileSystem, + root: '.', + messages: [], + stats: { + name: 'unknown', + version: 'unknown', + dependencyCount: { + production: 0, + development: 0 + }, + extraStats: [] + }, + lockfile: { + type: 'npm', + packages: [workspaceLink, packageA, packageB, sharedLibv1, sharedLibv2], + root: { + name: 'root-package', + version: '1.0.0', + dependencies: [workspaceLink, packageA, packageB], + devDependencies: [], + optionalDependencies: [], + peerDependencies: [] + } + }, + packageFile: { + name: 'test-package', + version: '1.0.0' + } + }; + + const stats = await runDuplicateDependencyAnalysis(context); + + expect(stats).toMatchSnapshot(); + }); }); From b0b25ca035a737969ef2078c0e4240888814dddd Mon Sep 17 00:00:00 2001 From: Tristan Manchester Date: Wed, 1 Apr 2026 05:50:30 +0200 Subject: [PATCH 2/2] Simplify duplicate dependency version guard --- src/analyze/duplicate-dependencies.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index 6778e0b..899bc99 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -7,14 +7,6 @@ interface Version { parents: string[]; } -function hasResolvedVersion( - pkg: Pick -): pkg is Pick & { - version: string; -} { - return typeof pkg.version === 'string' && pkg.version.length > 0; -} - /** * Outputs packages with duplicate versions and suggest possible fixes * @param context @@ -46,7 +38,7 @@ function resolveDuplicateDependencies( // npm workspace links appear in package-lock.json without a version. // They are not real installed versions and should not participate in // duplicate-version analysis. - if (!hasResolvedVersion(pkg)) { + if (typeof pkg.version !== 'string' || pkg.version.length === 0) { continue; }