fix: install peer of optional peer (#8330)

close #8323
This commit is contained in:
Zoltan Kochan
2024-07-25 18:00:24 +02:00
committed by GitHub
parent 4398d20f2c
commit 96821291a8
4 changed files with 172 additions and 75 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/resolve-dependencies": patch
"pnpm": patch
---
Peer dependencies of optional peer dependencies should be automatically installed [#8323](https://github.com/pnpm/pnpm/issues/8323).

View File

@@ -619,3 +619,48 @@ test('auto install hoisted peer dependency', async () => {
'@pnpm.e2e/peer-c@2.0.0',
])
})
test('auto install peer of optional peer', async () => {
const project = prepareEmpty()
await mutateModules([
{
mutation: 'install',
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
mutation: 'install',
rootDir: path.resolve('project-2') as ProjectRootDir,
},
], testDefaults({
allProjects: [
{
buildIndex: 0,
manifest: {
name: 'project-1',
dependencies: {
'@pnpm.e2e/has-optional-peer-with-peer': '1.0.0',
},
},
rootDir: path.resolve('project-1') as ProjectRootDir,
},
{
buildIndex: 0,
manifest: {
name: 'project-2',
dependencies: {
'@pnpm.e2e/has-y-peer': '1.0.0',
},
},
rootDir: path.resolve('project-2') as ProjectRootDir,
},
],
autoInstallPeers: true,
}))
const lockfile = project.readLockfile()
expect(Object.keys(lockfile.snapshots)).toStrictEqual([
'@pnpm.e2e/has-optional-peer-with-peer@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))',
'@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0)',
'@pnpm/y@2.0.0',
])
project.hasNot('@pnpm.e2e/peer-a')
})

View File

@@ -303,72 +303,86 @@ export async function resolveRootDependencies (
ctx.allPreferredVersions = {}
}
const { pkgAddressesByImportersWithoutPeers, publishedBy, time } = await resolveDependenciesOfImporters(ctx, importers)
const pkgAddressesByImporters = await Promise.all(zipWith(async (importerResolutionResult, { parentPkgAliases, preferredVersions, options }) => {
const pkgAddresses = importerResolutionResult.pkgAddresses
if (!ctx.hoistPeers) return pkgAddresses
const allMissingOptionalPeers: Record<string, string[]> = {}
while (true) {
for (const pkgAddress of importerResolutionResult.pkgAddresses) {
parentPkgAliases[pkgAddress.alias] = true
}
const [missingOptionalPeers, missingRequiredPeers] = partition(([, { optional }]) => optional, Object.entries(importerResolutionResult.missingPeers ?? {}))
for (const missingPeerName of Object.keys(missingRequiredPeers)) {
parentPkgAliases[missingPeerName] = true
}
if (ctx.autoInstallPeers) {
// All the missing peers should get installed in the root.
// Otherwise, pending nodes will not work.
// even those peers should be hoisted that are not autoinstalled
for (const [resolvedPeerName, resolvedPeerAddress] of Object.entries(importerResolutionResult.resolvedPeers ?? {})) {
if (!parentPkgAliases[resolvedPeerName]) {
pkgAddresses.push(resolvedPeerAddress)
if (!ctx.hoistPeers) {
return {
pkgAddressesByImporters: pkgAddressesByImportersWithoutPeers.map(({ pkgAddresses }) => pkgAddresses),
time,
}
}
/* eslint-disable no-await-in-loop */
while (true) {
const allMissingOptionalPeersByImporters = await Promise.all(pkgAddressesByImportersWithoutPeers.map(async (importerResolutionResult, index) => {
const { parentPkgAliases, preferredVersions, options } = importers[index]
const allMissingOptionalPeers: Record<string, string[]> = {}
while (true) {
for (const pkgAddress of importerResolutionResult.pkgAddresses) {
parentPkgAliases[pkgAddress.alias] = true
}
const [missingOptionalPeers, missingRequiredPeers] = partition(([, { optional }]) => optional, Object.entries(importerResolutionResult.missingPeers ?? {}))
for (const missingPeerName of Object.keys(missingRequiredPeers)) {
parentPkgAliases[missingPeerName] = true
}
if (ctx.autoInstallPeers) {
// All the missing peers should get installed in the root.
// Otherwise, pending nodes will not work.
// even those peers should be hoisted that are not autoinstalled
for (const [resolvedPeerName, resolvedPeerAddress] of Object.entries(importerResolutionResult.resolvedPeers ?? {})) {
if (!parentPkgAliases[resolvedPeerName]) {
importerResolutionResult.pkgAddresses.push(resolvedPeerAddress)
}
}
}
}
for (const [missingOptionalPeerName, { range: missingOptionalPeerRange }] of missingOptionalPeers) {
if (!allMissingOptionalPeers[missingOptionalPeerName]) {
allMissingOptionalPeers[missingOptionalPeerName] = [missingOptionalPeerRange]
} else if (!allMissingOptionalPeers[missingOptionalPeerName].includes(missingOptionalPeerRange)) {
allMissingOptionalPeers[missingOptionalPeerName].push(missingOptionalPeerRange)
for (const [missingOptionalPeerName, { range: missingOptionalPeerRange }] of missingOptionalPeers) {
if (!allMissingOptionalPeers[missingOptionalPeerName]) {
allMissingOptionalPeers[missingOptionalPeerName] = [missingOptionalPeerRange]
} else if (!allMissingOptionalPeers[missingOptionalPeerName].includes(missingOptionalPeerRange)) {
allMissingOptionalPeers[missingOptionalPeerName].push(missingOptionalPeerRange)
}
}
}
if (!missingRequiredPeers.length) break
const dependencies = hoistPeers(missingRequiredPeers, ctx)
if (!Object.keys(dependencies).length) break
const wantedDependencies = getNonDevWantedDependencies({ dependencies })
if (!missingRequiredPeers.length) break
const dependencies = hoistPeers(missingRequiredPeers, ctx)
if (!Object.keys(dependencies).length) break
const wantedDependencies = getNonDevWantedDependencies({ dependencies })
// eslint-disable-next-line no-await-in-loop
const resolveDependenciesResult = await resolveDependencies(ctx, preferredVersions, wantedDependencies, {
...options,
parentPkgAliases,
publishedBy,
})
importerResolutionResult = {
pkgAddresses: resolveDependenciesResult.pkgAddresses,
// eslint-disable-next-line no-await-in-loop
...filterMissingPeers(await resolveDependenciesResult.resolvingPeers, parentPkgAliases),
}
pkgAddresses.push(...importerResolutionResult.pkgAddresses)
}
if (Object.keys(allMissingOptionalPeers).length && ctx.allPreferredVersions) {
const optionalDependencies = getHoistableOptionalPeers(allMissingOptionalPeers, ctx.allPreferredVersions)
if (Object.keys(optionalDependencies).length) {
const wantedDependencies = getNonDevWantedDependencies({ optionalDependencies })
const resolveDependenciesResult = await resolveDependencies(ctx, preferredVersions, wantedDependencies, {
...options,
parentPkgAliases,
publishedBy,
})
importerResolutionResult = {
pkgAddresses: resolveDependenciesResult.pkgAddresses,
...filterMissingPeers(await resolveDependenciesResult.resolvingPeers, parentPkgAliases),
}
pkgAddresses.push(...importerResolutionResult.pkgAddresses)
importerResolutionResult.pkgAddresses.push(...resolveDependenciesResult.pkgAddresses)
Object.assign(importerResolutionResult,
filterMissingPeers(await resolveDependenciesResult.resolvingPeers, parentPkgAliases)
)
}
}
return pkgAddresses
}, pkgAddressesByImportersWithoutPeers, importers))
return { pkgAddressesByImporters, time }
return allMissingOptionalPeers
}))
let hasNewMissingPeers = false
await Promise.all(allMissingOptionalPeersByImporters.map(async (allMissingOptionalPeers, index) => {
const { preferredVersions, parentPkgAliases, options } = importers[index]
if (Object.keys(allMissingOptionalPeers).length && ctx.allPreferredVersions) {
const optionalDependencies = getHoistableOptionalPeers(allMissingOptionalPeers, ctx.allPreferredVersions)
if (Object.keys(optionalDependencies).length) {
hasNewMissingPeers = true
const wantedDependencies = getNonDevWantedDependencies({ optionalDependencies })
const resolveDependenciesResult = await resolveDependencies(ctx, preferredVersions, wantedDependencies, {
...options,
parentPkgAliases,
publishedBy,
})
pkgAddressesByImportersWithoutPeers[index].pkgAddresses.push(...resolveDependenciesResult.pkgAddresses)
Object.assign(pkgAddressesByImportersWithoutPeers[index],
filterMissingPeers(await resolveDependenciesResult.resolvingPeers, parentPkgAliases)
)
}
}
}))
if (!hasNewMissingPeers) break
}
/* eslint-enable no-await-in-loop */
return {
pkgAddressesByImporters: pkgAddressesByImportersWithoutPeers.map(({ pkgAddresses }) => pkgAddresses),
time,
}
}
interface ResolvedDependenciesResult {

70
pnpm-lock.yaml generated
View File

@@ -14689,7 +14689,7 @@ snapshots:
'@jest/console@29.7.0':
dependencies:
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
@@ -14702,14 +14702,14 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0(@babel/types@7.24.7)
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q)
jest-changed-files: 29.7.0
jest-config: 29.7.0(@babel/types@7.24.7)(@types/node@18.19.34)(ts-node@10.9.2(@types/node@18.19.34)(typescript@5.4.5))
jest-config: 29.7.0(@babel/types@7.24.7)(@types/node@20.14.9)(ts-node@10.9.2(@types/node@18.19.34)(typescript@5.4.5))
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@@ -14735,7 +14735,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
jest-mock: 29.7.0
'@jest/expect-utils@29.7.0':
@@ -14753,7 +14753,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 18.19.34
'@types/node': 20.14.9
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -14775,7 +14775,7 @@ snapshots:
'@jest/transform': 29.7.0(@babel/types@7.24.7)
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.25
'@types/node': 18.19.34
'@types/node': 20.14.9
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@@ -14847,7 +14847,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 18.19.34
'@types/node': 20.14.9
'@types/yargs': 17.0.32
chalk: 4.1.2
@@ -15484,7 +15484,7 @@ snapshots:
'@types/isexe@2.0.2':
dependencies:
'@types/node': 18.19.34
'@types/node': 20.14.9
'@types/istanbul-lib-coverage@2.0.6': {}
@@ -15607,7 +15607,7 @@ snapshots:
'@types/touch@3.1.5':
dependencies:
'@types/node': 18.19.34
'@types/node': 20.14.9
'@types/treeify@1.0.3': {}
@@ -15795,7 +15795,7 @@ snapshots:
'@yarnpkg/core@4.0.3(typanion@3.14.0)':
dependencies:
'@arcanis/slice-ansi': 1.1.1
'@types/semver': 7.5.3
'@types/semver': 7.5.8
'@types/treeify': 1.0.3
'@yarnpkg/fslib': 3.1.0
'@yarnpkg/libzip': 3.1.0(@yarnpkg/fslib@3.1.0)
@@ -18445,7 +18445,7 @@ snapshots:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
chalk: 4.1.2
co: 4.6.0
dedent: 1.5.3
@@ -18518,6 +18518,38 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-config@29.7.0(@babel/types@7.24.7)(@types/node@20.14.9)(ts-node@10.9.2(@types/node@18.19.34)(typescript@5.4.5)):
dependencies:
'@babel/core': 7.24.7
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.24.7)(@babel/types@7.24.7)
chalk: 4.1.2
ci-info: 3.9.0
deepmerge: 4.3.1
glob: 7.2.3
graceful-fs: 4.2.11(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q)
jest-circus: 29.7.0(@babel/types@7.24.7)
jest-environment-node: 29.7.0
jest-get-type: 29.6.3
jest-regex-util: 29.6.3
jest-resolve: 29.7.0
jest-runner: 29.7.0(@babel/types@7.24.7)
jest-util: 29.7.0
jest-validate: 29.7.0
micromatch: 4.0.7
parse-json: 5.2.0
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 20.14.9
ts-node: 10.9.2(@types/node@18.19.34)(typescript@5.4.5)
transitivePeerDependencies:
- '@babel/types'
- babel-plugin-macros
- supports-color
jest-diff@29.7.0:
dependencies:
chalk: 4.1.2
@@ -18542,7 +18574,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -18552,7 +18584,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 18.19.34
'@types/node': 20.14.9
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q)
@@ -18591,7 +18623,7 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
@@ -18626,7 +18658,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0(@babel/types@7.24.7)
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q)
@@ -18655,7 +18687,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0(@babel/types@7.24.7)
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
chalk: 4.1.2
cjs-module-lexer: 1.3.1
collect-v8-coverage: 1.0.2
@@ -18702,7 +18734,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q)
@@ -18721,7 +18753,7 @@ snapshots:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 18.19.34
'@types/node': 20.14.9
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@@ -18730,7 +18762,7 @@ snapshots:
jest-worker@29.7.0:
dependencies:
'@types/node': 18.19.34
'@types/node': 20.14.9
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1