mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-29 20:41:47 -04:00
fix: when automatically installing peer deps, prefer the version found in the root of the workspace (#9835)
This commit is contained in:
6
.changeset/common-crabs-push.md
Normal file
6
.changeset/common-crabs-push.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/resolve-dependencies": patch
|
||||
"pnpm": patch
|
||||
---
|
||||
|
||||
When automatically installing missing peer dependencies, prefer versions that are already present in the direct dependencies of the root workspace package [#9835](https://github.com/pnpm/pnpm/pull/9835).
|
||||
@@ -331,6 +331,85 @@ test('peer dependency is resolved from the dependencies of the workspace root pr
|
||||
}
|
||||
})
|
||||
|
||||
test('peer dependency is resolved from the dependencies of the workspace root project even if there are other versions of the peer dependency present in the dependency graph', async () => {
|
||||
const projects = preparePackages([
|
||||
{
|
||||
location: '.',
|
||||
package: { name: 'root' },
|
||||
},
|
||||
{
|
||||
location: 'pkg',
|
||||
package: {},
|
||||
},
|
||||
{
|
||||
location: 'pkg2',
|
||||
package: {},
|
||||
},
|
||||
])
|
||||
const allProjects: ProjectOptions[] = [
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: {
|
||||
name: 'root',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
ajv: '4.10.0',
|
||||
},
|
||||
},
|
||||
rootDir: process.cwd() as ProjectRootDir,
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: {
|
||||
name: 'pkg',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
'ajv-keywords': '1.5.0',
|
||||
},
|
||||
},
|
||||
rootDir: path.resolve('pkg') as ProjectRootDir,
|
||||
},
|
||||
{
|
||||
buildIndex: 0,
|
||||
manifest: {
|
||||
name: 'pkg2',
|
||||
version: '1.0.0',
|
||||
|
||||
dependencies: {
|
||||
ajv: '5.0.0',
|
||||
},
|
||||
},
|
||||
rootDir: path.resolve('pkg2') as ProjectRootDir,
|
||||
},
|
||||
]
|
||||
const reporter = jest.fn()
|
||||
await mutateModules([
|
||||
{
|
||||
mutation: 'install',
|
||||
rootDir: process.cwd() as ProjectRootDir,
|
||||
},
|
||||
{
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('pkg') as ProjectRootDir,
|
||||
},
|
||||
{
|
||||
mutation: 'install',
|
||||
rootDir: path.resolve('pkg2') as ProjectRootDir,
|
||||
},
|
||||
], testDefaults({ allProjects, reporter, resolvePeersFromWorkspaceRoot: true }))
|
||||
|
||||
expect(reporter).not.toHaveBeenCalledWith(expect.objectContaining({
|
||||
name: 'pnpm:peer-dependency-issues',
|
||||
}))
|
||||
|
||||
{
|
||||
const lockfile = projects.root.readLockfile()
|
||||
expect(lockfile.importers.pkg?.dependencies?.['ajv-keywords'].version).toBe('1.5.0(ajv@4.10.0)')
|
||||
}
|
||||
})
|
||||
|
||||
test('warning is reported when cannot resolve peer dependency for non-top-level dependency', async () => {
|
||||
prepareEmpty()
|
||||
await addDistTag({ package: '@pnpm.e2e/abc-parent-with-ab', version: '1.0.0', distTag: 'latest' })
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
import { type PreferredVersions } from '@pnpm/resolver-base'
|
||||
import semver from 'semver'
|
||||
import { type PkgAddressOrLink } from './resolveDependencies'
|
||||
|
||||
export function hoistPeers (
|
||||
missingRequiredPeers: Array<[string, { range: string }]>,
|
||||
opts: {
|
||||
autoInstallPeers: boolean
|
||||
allPreferredVersions?: PreferredVersions
|
||||
}
|
||||
workspaceRootDeps: PkgAddressOrLink[]
|
||||
},
|
||||
missingRequiredPeers: Array<[string, { range: string }]>
|
||||
): Record<string, string> {
|
||||
const dependencies: Record<string, string> = {}
|
||||
for (const [peerName, { range }] of missingRequiredPeers) {
|
||||
const rootDep = opts.workspaceRootDeps.find((rootDep) => rootDep.alias === peerName)
|
||||
if (rootDep?.version) {
|
||||
dependencies[peerName] = rootDep.version
|
||||
continue
|
||||
}
|
||||
if (opts.allPreferredVersions![peerName]) {
|
||||
const versions: string[] = []
|
||||
const nonVersions: string[] = []
|
||||
|
||||
@@ -145,6 +145,7 @@ export interface ResolutionContext {
|
||||
forceFullResolution: boolean
|
||||
ignoreScripts?: boolean
|
||||
resolvedPkgsById: ResolvedPkgsById
|
||||
resolvePeersFromWorkspaceRoot?: boolean
|
||||
outdatedDependencies: Record<PkgResolutionId, string>
|
||||
childrenByParentId: ChildrenByParentId
|
||||
patchedDependencies?: PatchGroupRecord
|
||||
@@ -320,6 +321,18 @@ export async function resolveRootDependencies (
|
||||
time,
|
||||
}
|
||||
}
|
||||
let workspaceRootDeps!: PkgAddressOrLink[]
|
||||
if (ctx.resolvePeersFromWorkspaceRoot) {
|
||||
const rootImporterIndex = importers.findIndex(({ options }) => options.parentIds[0] === '.')
|
||||
workspaceRootDeps = pkgAddressesByImportersWithoutPeers[rootImporterIndex]?.pkgAddresses ?? []
|
||||
} else {
|
||||
workspaceRootDeps = []
|
||||
}
|
||||
const _hoistPeers = hoistPeers.bind(null, {
|
||||
autoInstallPeers: ctx.autoInstallPeers,
|
||||
allPreferredVersions: ctx.allPreferredVersions,
|
||||
workspaceRootDeps,
|
||||
})
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (true) {
|
||||
const allMissingOptionalPeersByImporters = await Promise.all(pkgAddressesByImportersWithoutPeers.map(async (importerResolutionResult, index) => {
|
||||
@@ -357,7 +370,7 @@ export async function resolveRootDependencies (
|
||||
}
|
||||
}
|
||||
if (!missingRequiredPeers.length) break
|
||||
const dependencies = hoistPeers(missingRequiredPeers, ctx)
|
||||
const dependencies = _hoistPeers(missingRequiredPeers)
|
||||
if (!Object.keys(dependencies).length) break
|
||||
const wantedDependencies = getNonDevWantedDependencies({ dependencies })
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ export async function resolveDependencyTree<T> (
|
||||
readPackageHook: opts.hooks.readPackage,
|
||||
registries: opts.registries,
|
||||
resolvedPkgsById: {} as ResolvedPkgsById,
|
||||
resolvePeersFromWorkspaceRoot: opts.resolvePeersFromWorkspaceRoot,
|
||||
resolutionMode: opts.resolutionMode,
|
||||
skipped: wantedToBeSkippedPackageIds,
|
||||
storeController: opts.storeController,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { hoistPeers, getHoistableOptionalPeers } from '../lib/hoistPeers'
|
||||
|
||||
test('hoistPeers picks an already available prerelease version', () => {
|
||||
expect(hoistPeers([['foo', { range: '*' }]], {
|
||||
expect(hoistPeers({
|
||||
autoInstallPeers: false,
|
||||
allPreferredVersions: {
|
||||
foo: {
|
||||
'1.0.0-beta.0': 'version',
|
||||
},
|
||||
},
|
||||
})).toStrictEqual({
|
||||
workspaceRootDeps: [],
|
||||
}, [['foo', { range: '*' }]])).toStrictEqual({
|
||||
foo: '1.0.0-beta.0',
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user