diff --git a/.changeset/short-beds-run.md b/.changeset/short-beds-run.md new file mode 100644 index 0000000000..3ee0199cc8 --- /dev/null +++ b/.changeset/short-beds-run.md @@ -0,0 +1,6 @@ +--- +"@pnpm/resolve-dependencies": patch +"pnpm": patch +--- + +Dedupe deps with the same alias in direct dependencies [6966](https://github.com/pnpm/pnpm/issues/6966) diff --git a/pkg-manager/core/test/install/fromRepo.ts b/pkg-manager/core/test/install/fromRepo.ts index 072cc9c501..b459c28e3e 100644 --- a/pkg-manager/core/test/install/fromRepo.ts +++ b/pkg-manager/core/test/install/fromRepo.ts @@ -233,3 +233,48 @@ test.skip('from a github repo that needs to be built. hoisted node linker is us await install(manifest, await testDefaults({ frozenLockfile: true, ignoreScripts: true, nodeLinker: 'hoisted' }, { ignoreScripts: true })) await project.hasNot('@pnpm.e2e/prepare-script-works/prepare.txt') }) + +test('re-adding a git repo with a different tag', async () => { + const project = prepareEmpty() + let manifest = await addDependenciesToPackage({}, ['kevva/is-negative#1.0.0'], await testDefaults()) + await project.has('is-negative') + expect(manifest.dependencies).toStrictEqual({ + 'is-negative': 'github:kevva/is-negative#1.0.0', + }) + expect(JSON.parse(fs.readFileSync('./node_modules/is-negative/package.json', 'utf8')).version).toBe('1.0.0') + let lockfile = await project.readLockfile() + expect(lockfile.dependencies['is-negative']).toEqual({ + specifier: 'github:kevva/is-negative#1.0.0', + version: 'github.com/kevva/is-negative/163360a8d3ae6bee9524541043197ff356f8ed99', + }) + expect(lockfile.packages).toEqual( + { + 'github.com/kevva/is-negative/163360a8d3ae6bee9524541043197ff356f8ed99': { + resolution: { tarball: 'https://codeload.github.com/kevva/is-negative/tar.gz/163360a8d3ae6bee9524541043197ff356f8ed99' }, + name: 'is-negative', + version: '1.0.0', + engines: { node: '>=0.10.0' }, + dev: false, + }, + } + ) + manifest = await addDependenciesToPackage(manifest, ['kevva/is-negative#1.0.1'], await testDefaults()) + await project.has('is-negative') + expect(JSON.parse(fs.readFileSync('./node_modules/is-negative/package.json', 'utf8')).version).toBe('1.0.1') + lockfile = await project.readLockfile() + expect(lockfile.dependencies['is-negative']).toEqual({ + specifier: 'github:kevva/is-negative#1.0.1', + version: 'github.com/kevva/is-negative/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72', + }) + expect(lockfile.packages).toEqual( + { + 'github.com/kevva/is-negative/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72': { + resolution: { tarball: 'https://codeload.github.com/kevva/is-negative/tar.gz/9a89df745b2ec20ae7445d3d9853ceaeef5b0b72' }, + name: 'is-negative', + version: '1.0.1', + engines: { node: '>=0.10.0' }, + dev: false, + }, + } + ) +}) diff --git a/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts b/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts index acaeffe548..6c57dda9f4 100644 --- a/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts +++ b/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts @@ -187,10 +187,9 @@ export async function resolveDependencyTree ( } } - for (const { id } of importers) { - const directDeps = directDepsByImporterId[id] + for (const { id, wantedDependencies } of importers) { + const directDeps = dedupeSameAliasDirectDeps(directDepsByImporterId[id], wantedDependencies) const [linkedDependencies, directNonLinkedDeps] = partition((dep) => dep.isLinkedDependency === true, directDeps) as [LinkedDependency[], PkgAddress[]] - resolvedImporters[id] = { directDependencies: directDeps .map((dep) => { @@ -269,3 +268,28 @@ function buildTree ( } return childrenNodeIds } + +/** + * There may be cases where multiple dependencies have the same alias in the directDeps array. + * E.g., when there is "is-negative: github:kevva/is-negative#1.0.0" in the package.json dependencies, + * and then re-execute `pnpm add github:kevva/is-negative#1.0.1`. + * In order to make sure that the latest 1.0.1 version is installed, we need to remove the duplicate dependency. + * fix https://github.com/pnpm/pnpm/issues/6966 + */ +function dedupeSameAliasDirectDeps (directDeps: Array, wantedDependencies: Array) { + const deps = new Map() + for (const directDep of directDeps) { + const { alias, normalizedPref } = directDep + if (!deps.has(alias)) { + deps.set(alias, directDep) + } else { + const wantedDep = wantedDependencies.find(dep => + dep.alias ? dep.alias === alias : dep.pref === normalizedPref + ) + if (wantedDep?.isNew) { + deps.set(alias, directDep) + } + } + } + return Array.from(deps.values()) +}