diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index c420ea2..899bc99 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -35,6 +35,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 (typeof pkg.version !== 'string' || pkg.version.length === 0) { + 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(); + }); });