fix: resolve peer of peer from the deps of direct dependent package (#7606)

close #7444

Peer dependencies of peer dependencies are now resolved correctly. When peer dependencies have peer dependencies of their own, the peer dependencies are grouped with their own peer dependencies before being linked to their dependents.

For instance, if `card` has `react` in peer dependencies and `react` has `typescript` in its peer dependencies, then the same version of `react` may be linked from different places if there are multiple versions of `typescript`. For instance:

```
project1/package.json
{
  "dependencies": {
    "card": "1.0.0",
    "react": "16.8.0",
    "typescript": "7.0.0"
  }
}
project2/package.json
{
  "dependencies": {
    "card": "1.0.0",
    "react": "16.8.0",
    "typescript": "8.0.0"
  }
}
node_modules
  .pnpm
    card@1.0.0(react@16.8.0(typescript@7.0.0))
      node_modules
        card
        react --> ../../react@16.8.0(typescript@7.0.0)/node_modules/react
    react@16.8.0(typescript@7.0.0)
      node_modules
        react
        typescript --> ../../typescript@7.0.0/node_modules/typescript
    typescript@7.0.0
      node_modules
        typescript
    card@1.0.0(react@16.8.0(typescript@8.0.0))
      node_modules
        card
        react --> ../../react@16.8.0(typescript@8.0.0)/node_modules/react
    react@16.8.0(typescript@8.0.0)
      node_modules
        react
        typescript --> ../../typescript@8.0.0/node_modules/typescript
    typescript@8.0.0
      node_modules
        typescript
```

In the above example, both projects have `card` in dependencies but the projects use different versions of `typescript`. Hence, even though the same version of `card` is used, `card` in `project1` will reference `react` from a directory where it is placed with `typescript@7.0.0` (because it resolves `typescript` from the dependencies of `project1`), while `card` in `project2` will reference `react` with `typescript@8.0.0`.
This commit is contained in:
Zoltan Kochan
2024-02-09 00:50:08 +01:00
committed by GitHub
parent e5fbac331e
commit 98a126699c
31 changed files with 921 additions and 439 deletions

View File

@@ -0,0 +1,56 @@
---
"@pnpm/resolve-dependencies": major
"pnpm": major
---
Peer dependencies of peer dependencies are now resolved correctly. When peer dependencies have peer dependencies of their own, the peer dependencies are grouped with their own peer dependencies before being linked to their dependents.
For instance, if `card` has `react` in peer dependencies and `react` has `typescript` in its peer dependencies, then the same version of `react` may be linked from different places if there are multiple versions of `typescript`. For instance:
```
project1/package.json
{
"dependencies": {
"card": "1.0.0",
"react": "16.8.0",
"typescript": "7.0.0"
}
}
project2/package.json
{
"dependencies": {
"card": "1.0.0",
"react": "16.8.0",
"typescript": "8.0.0"
}
}
node_modules
.pnpm
card@1.0.0(react@16.8.0(typescript@7.0.0))
node_modules
card
react --> ../../react@16.8.0(typescript@7.0.0)/node_modules/react
react@16.8.0(typescript@7.0.0)
node_modules
react
typescript --> ../../typescript@7.0.0/node_modules/typescript
typescript@7.0.0
node_modules
typescript
card@1.0.0(react@16.8.0(typescript@8.0.0))
node_modules
card
react --> ../../react@16.8.0(typescript@8.0.0)/node_modules/react
react@16.8.0(typescript@8.0.0)
node_modules
react
typescript --> ../../typescript@8.0.0/node_modules/typescript
typescript@8.0.0
node_modules
typescript
```
In the above example, both projects have `card` in dependencies but the projects use different versions of `typescript`. Hence, even though the same version of `card` is used, `card` in `project1` will reference `react` from a directory where it is placed with `typescript@7.0.0` (because it resolves `typescript` from the dependencies of `project1`), while `card` in `project2` will reference `react` with `typescript@8.0.0`.
Related issue: [#7444](https://github.com/pnpm/pnpm/issues/7444).
Related PR: [#7606](https://github.com/pnpm/pnpm/pull/7606).

View File

@@ -0,0 +1,5 @@
---
"@pnpm/dependency-path": minor
---
createPeersDirSuffix may accept dep path.

View File

@@ -44,7 +44,7 @@
"@pnpm/constants": "workspace:*",
"@pnpm/lockfile-types": "workspace:*",
"@pnpm/modules-yaml": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/types": "workspace:*",
"is-windows": "^1.0.2",
"isexe": "2.0.0",

View File

@@ -40,7 +40,7 @@
"test": "pnpm pretest && pnpm run compile && jest"
},
"dependencies": {
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/store.cafs": "workspace:*",
"path-exists": "^4.0.0"
},

View File

@@ -349,3 +349,18 @@ test('graph with many nodes. Sequencing a subgraph', () => {
}
)
})
// TODO: fix this test
test.skip('graph with big cycle', () => {
expect(graphSequencer(new Map([
['a', ['b']],
['b', ['a', 'c']],
['c', ['a', 'b']],
]))).toStrictEqual(
{
safe: false,
chunks: [['a', 'b', 'c']],
cycles: [['a', 'b', 'c']],
}
)
})

View File

@@ -35,7 +35,7 @@
"@pnpm/filter-workspace-packages": "workspace:*",
"@pnpm/plugin-commands-rebuild": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-fixtures": "workspace:*",
"@pnpm/test-ipc-server": "workspace:*",
"@types/ramda": "0.28.20",

View File

@@ -34,7 +34,7 @@
"@pnpm/filter-workspace-packages": "workspace:*",
"@pnpm/plugin-commands-script-runners": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-ipc-server": "workspace:*",
"@types/is-windows": "^1.0.2",
"@types/ramda": "0.28.20",

View File

@@ -39,7 +39,7 @@
"@commitlint/prompt-cli": "^17.8.1",
"@pnpm/eslint-config": "workspace:*",
"@pnpm/meta-updater": "1.0.0",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/tsconfig": "workspace:*",
"@pnpm/worker": "workspace:*",
"@types/jest": "^29.5.8",

View File

@@ -118,7 +118,19 @@ function depPathToFilenameUnescaped (depPath: string) {
return depPath.replace(':', '+')
}
export function createPeersDirSuffix (peers: Array<{ name: string, version: string }>): string {
const folderName = peers.map(({ name, version }) => `${name}@${version}`).sort().join(')(')
return `(${folderName})`
export type PeerId = { name: string, version: string } | string
export function createPeersDirSuffix (peerIds: PeerId[]): string {
const dirName = peerIds.map(
(peerId) => {
if (typeof peerId !== 'string') {
return `${peerId.name}@${peerId.version}`
}
if (peerId.startsWith('/')) {
return peerId.substring(1)
}
return peerId
}
).sort().join(')(')
return `(${dirName})`
}

View File

@@ -34,7 +34,7 @@
"@pnpm/filter-workspace-packages": "workspace:*",
"@pnpm/plugin-commands-patching": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-fixtures": "workspace:*",
"@types/normalize-path": "^3.0.2",
"@types/ramda": "0.28.20",

View File

@@ -82,7 +82,7 @@
"@pnpm/lockfile-types": "workspace:*",
"@pnpm/package-store": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/store-path": "workspace:*",
"@pnpm/store.cafs": "workspace:*",
"@pnpm/test-fixtures": "workspace:*",

View File

@@ -143,7 +143,7 @@ test('don\'t install the same missing peer dependency twice', async () => {
const lockfile = await project.readLockfile()
expect(Object.keys(lockfile.packages).sort()).toStrictEqual([
'/@pnpm/y@1.0.0',
`/@pnpm.e2e/has-has-y-peer-peer@1.0.0${createPeersDirSuffix([{ name: '@pnpm/y', version: '1.0.0' }, { name: '@pnpm.e2e/has-y-peer', version: '1.0.0' }])}`,
'/@pnpm.e2e/has-has-y-peer-peer@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0))(@pnpm/y@1.0.0)',
'/@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0)',
].sort())
})

View File

@@ -1,6 +1,7 @@
import path from 'path'
import fs from 'fs'
import { type RootLog } from '@pnpm/core-loggers'
import { depPathToFilename } from '@pnpm/dependency-path'
import { prepareEmpty } from '@pnpm/prepare'
import {
addDependenciesToPackage,
@@ -133,14 +134,14 @@ test('a subdependency is from a github repo with different name', async () => {
const lockfile = await project.readLockfile()
expect(lockfile.packages['/@pnpm.e2e/has-aliased-git-dependency@1.0.0'].dependencies).toStrictEqual({
'@pnpm.e2e/has-say-hi-peer': '1.0.0(hi@1.0.0)',
'@pnpm.e2e/has-say-hi-peer': '1.0.0(github.com/zkochan/hi/4cdebec76b7b9d1f6e219e06c42d92a6b8ea60cd)',
'say-hi': 'github.com/zkochan/hi/4cdebec76b7b9d1f6e219e06c42d92a6b8ea60cd',
})
await project.isExecutable('@pnpm.e2e/has-aliased-git-dependency/node_modules/.bin/hi')
await project.isExecutable('@pnpm.e2e/has-aliased-git-dependency/node_modules/.bin/szia')
expect(await exists(path.resolve('node_modules/.pnpm/@pnpm.e2e+has-say-hi-peer@1.0.0_hi@1.0.0/node_modules/@pnpm.e2e/has-say-hi-peer'))).toBeTruthy()
expect(await exists(path.resolve(`node_modules/.pnpm/${depPathToFilename('@pnpm.e2e/has-say-hi-peer@1.0.0(github.com/zkochan/hi/4cdebec76b7b9d1f6e219e06c42d92a6b8ea60cd)')}/node_modules/@pnpm.e2e/has-say-hi-peer`))).toBeTruthy()
})
test('from a git repo', async () => {

View File

@@ -986,10 +986,9 @@ test('peer dependency is resolved from parent package via its alias', async () =
}, await testDefaults())
const lockfile = await readYamlFile<Lockfile>(WANTED_LOCKFILE)
const suffix = createPeersDirSuffix([{ name: '@pnpm.e2e/tango-tango', version: '1.0.0' }])
expect(Object.keys(lockfile.packages ?? {})).toStrictEqual([
`/@pnpm.e2e/has-tango-as-peer-dep@1.0.0${suffix}`,
`/@pnpm.e2e/tango-tango@1.0.0${suffix}`,
'/@pnpm.e2e/has-tango-as-peer-dep@1.0.0(@pnpm.e2e/tango-tango@1.0.0(@pnpm.e2e/tango-tango@1.0.0))',
'/@pnpm.e2e/tango-tango@1.0.0(@pnpm.e2e/tango-tango@1.0.0)',
])
})
@@ -1466,8 +1465,7 @@ test('in a subdependency, when there are several aliased dependencies of the sam
expect(lockfile.packages['/@pnpm.e2e/abc@1.0.0(@pnpm.e2e/peer-c@2.0.0)']).toBeTruthy()
})
// TODO: fix this test
test.skip('peer having peer is resolved correctly', async () => {
test('peer having peer is resolved correctly', async () => {
const manifest1 = {
name: 'project-1',
@@ -1530,5 +1528,249 @@ test.skip('peer having peer is resolved correctly', async () => {
const lockfile = await readYamlFile<any>(path.resolve(WANTED_LOCKFILE)) // eslint-disable-line
expect(lockfile.importers['project-1'].dependencies?.['@pnpm.e2e/has-has-y-peer-only-as-peer']['version']).not.toEqual(lockfile.importers['project-2'].dependencies?.['@pnpm.e2e/has-has-y-peer-only-as-peer']['version'])
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer@1.0.0(@pnpm.e2e/has-y-peer@1.0.0)(@pnpm/y@1.0.0)'].transitivePeerDependencies).toEqual(['@pnpm/y'])
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0))'].dependencies['@pnpm.e2e/has-y-peer']).toEqual('1.0.0(@pnpm/y@1.0.0)')
})
test('peer having peer is resolved correctly. The peer is also in the dependencies of the dependent package', async () => {
const manifest1 = {
name: 'project-1',
dependencies: {
'@pnpm.e2e/has-has-y-peer-only-as-peer-and-y': '1.0.0',
'@pnpm.e2e/has-y-peer': '1.0.0',
'@pnpm/y': '2.0.0',
},
}
const manifest2 = {
name: 'project-2',
dependencies: {
'@pnpm.e2e/has-has-y-peer-only-as-peer-and-y': '1.0.0',
'@pnpm.e2e/has-y-peer': '1.0.0',
'@pnpm/y': '1.0.0',
},
}
preparePackages([
{
location: 'project-1',
package: manifest1,
},
{
location: 'project-2',
package: manifest2,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1'),
},
{
mutation: 'install',
rootDir: path.resolve('project-2'),
},
]
const allProjects = [
{
buildIndex: 0,
manifest: manifest1,
rootDir: path.resolve('project-1'),
},
{
buildIndex: 0,
manifest: manifest2,
rootDir: path.resolve('project-2'),
},
]
await mutateModules(importers, await testDefaults({
allProjects,
autoInstallPeers: false,
dedupePeerDependents: false,
lockfileOnly: true,
strictPeerDependencies: false,
}))
const lockfile = await readYamlFile<any>(path.resolve(WANTED_LOCKFILE)) // eslint-disable-line
expect(lockfile.importers['project-1'].dependencies?.['@pnpm.e2e/has-has-y-peer-only-as-peer-and-y']['version']).toEqual('1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))')
expect(lockfile.importers['project-2'].dependencies?.['@pnpm.e2e/has-has-y-peer-only-as-peer-and-y']['version']).toEqual('1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0))')
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0))'].dependencies['@pnpm/y']).toEqual('1.0.0')
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))'].dependencies['@pnpm/y']).toEqual('1.0.0')
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0))'].dependencies['@pnpm.e2e/has-y-peer']).toEqual('1.0.0(@pnpm/y@1.0.0)')
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))'].dependencies['@pnpm.e2e/has-y-peer']).toEqual('1.0.0(@pnpm/y@2.0.0)')
})
test('peer having peer is resolved correctly. The peer is also in the dependencies of the dependent package. Test #2', async () => {
const manifest1 = {
name: 'project-1',
dependencies: {
'@pnpm.e2e/has-has-y-peer-only-as-peer-and-y': '2.0.0',
'@pnpm.e2e/has-y-peer': '1.0.0',
'@pnpm/y': '1.0.0',
},
}
const manifest2 = {
name: 'project-2',
dependencies: {
'@pnpm.e2e/has-has-y-peer-only-as-peer-and-y': '1.0.0',
'@pnpm.e2e/has-y-peer': '1.0.0',
'@pnpm/y': '2.0.0',
},
}
preparePackages([
{
location: 'project-1',
package: manifest1,
},
{
location: 'project-2',
package: manifest2,
},
])
const importers: MutatedProject[] = [
{
mutation: 'install',
rootDir: path.resolve('project-1'),
},
{
mutation: 'install',
rootDir: path.resolve('project-2'),
},
]
const allProjects = [
{
buildIndex: 0,
manifest: manifest1,
rootDir: path.resolve('project-1'),
},
{
buildIndex: 0,
manifest: manifest2,
rootDir: path.resolve('project-2'),
},
]
await mutateModules(importers, await testDefaults({
allProjects,
autoInstallPeers: false,
dedupePeerDependents: false,
lockfileOnly: true,
strictPeerDependencies: false,
}))
const lockfile = await readYamlFile<any>(path.resolve(WANTED_LOCKFILE)) // eslint-disable-line
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))'].dependencies['@pnpm.e2e/has-y-peer']).toEqual('1.0.0(@pnpm/y@2.0.0)')
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@2.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@1.0.0))'].dependencies['@pnpm.e2e/has-y-peer']).toEqual('1.0.0(@pnpm/y@1.0.0)')
})
test('resolve peer of peer from the dependencies of the direct dependent package', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0', '@pnpm/y@2.0.0'], await testDefaults())
const lockfile = await project.readLockfile()
expect(lockfile.dependencies['@pnpm.e2e/has-has-y-peer-only-as-peer-and-y'].version).toBe('1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))')
// Even though @pnpm/y@1.0.0 is in the dependencies of the direct dependent package, we resolve y from above.
// It might make sense to print a warning in this case and suggest to make y a peer dependency in the dependent package too.
expect(lockfile.packages['/@pnpm.e2e/has-has-y-peer-only-as-peer-and-y@1.0.0(@pnpm.e2e/has-y-peer@1.0.0(@pnpm/y@2.0.0))'].dependencies?.['@pnpm.e2e/has-y-peer']).toBe('1.0.0(@pnpm/y@2.0.0)')
})
test('2 circular peers', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/circular-peer-a@1.0.0', '@pnpm.e2e/circular-peer-b@1.0.0'], await testDefaults())
const lockfile = await project.readLockfile()
expect(lockfile.dependencies['@pnpm.e2e/circular-peer-a'].version).toBe('1.0.0(@pnpm.e2e/circular-peer-b@1.0.0)')
expect(lockfile.dependencies['@pnpm.e2e/circular-peer-b'].version).toBe('1.0.0(@pnpm.e2e/circular-peer-a@1.0.0)')
})
test('3 circular peers', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, [
'@pnpm.e2e/circular-peers-1-of-3@1.0.0',
'@pnpm.e2e/circular-peers-2-of-3@1.0.0',
'@pnpm.e2e/circular-peers-3-of-3@1.0.0',
'@pnpm.e2e/peer-a@1.0.0',
], await testDefaults())
const lockfile = await project.readLockfile()
expect(lockfile.dependencies['@pnpm.e2e/circular-peers-1-of-3'].version).toBe('1.0.0(@pnpm.e2e/circular-peers-2-of-3@1.0.0)(@pnpm.e2e/peer-a@1.0.0)')
expect(lockfile.dependencies['@pnpm.e2e/circular-peers-2-of-3'].version).toBe('1.0.0(@pnpm.e2e/circular-peers-3-of-3@1.0.0)(@pnpm.e2e/peer-a@1.0.0)(@pnpm.e2e/peer-b@1.0.0)')
expect(lockfile.dependencies['@pnpm.e2e/circular-peers-3-of-3'].version).toBe('1.0.0(@pnpm.e2e/circular-peers-1-of-3@1.0.0)')
})
test('3 circular peers in workspace root', async () => {
const projects = preparePackages([
{
location: '.',
package: { name: 'root' },
},
{
location: 'pkg',
package: {},
},
])
const allProjects: ProjectOptions[] = [
{
buildIndex: 0,
manifest: {
name: 'root',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/circular-peers-1-of-3': '1.0.0',
'@pnpm.e2e/circular-peers-2-of-3': '1.0.0',
'@pnpm.e2e/circular-peers-3-of-3': '1.0.0',
'@pnpm.e2e/peer-a': '1.0.0',
},
},
rootDir: process.cwd(),
},
{
buildIndex: 0,
manifest: {
name: 'pkg',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/circular-peers-1-of-3': '1.0.0',
},
},
rootDir: path.resolve('pkg'),
},
]
const reporter = jest.fn()
await mutateModules([
{
mutation: 'install',
rootDir: path.resolve('pkg'),
},
{
mutation: 'install',
rootDir: process.cwd(),
},
], await testDefaults({ allProjects, reporter, autoInstallPeers: false, resolvePeersFromWorkspaceRoot: true, strictPeerDependencies: false }))
const lockfile = await projects.root.readLockfile()
expect(lockfile.importers.pkg?.dependencies?.['@pnpm.e2e/circular-peers-1-of-3'].version).toBe('1.0.0(@pnpm.e2e/circular-peers-2-of-3@1.0.0(@pnpm.e2e/circular-peers-3-of-3@1.0.0)(@pnpm.e2e/peer-a@1.0.0))(@pnpm.e2e/peer-a@1.0.0)')
})
test('resolves complex circular deps', async () => {
prepareEmpty()
await addDependenciesToPackage({}, [
'@pnpm.e2e/complex-circular-peers-a@1.0.0',
'@pnpm.e2e/complex-circular-peers-b@1.0.0',
'@pnpm.e2e/complex-circular-peers-c@1.0.0',
], await testDefaults({
autoInstallPeers: false,
}))
// it doesn't hang
})

View File

@@ -23,7 +23,7 @@
"@pnpm/package-store": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/read-projects-context": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/store-path": "workspace:*",
"@pnpm/store.cafs": "workspace:*",
"@pnpm/test-fixtures": "workspace:*",

View File

@@ -61,7 +61,7 @@
"@pnpm/client": "workspace:*",
"@pnpm/create-cafs-store": "workspace:*",
"@pnpm/package-requester": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-fixtures": "workspace:*",
"@types/normalize-path": "^3.0.2",
"@types/ramda": "0.28.20",

View File

@@ -34,7 +34,7 @@
"@pnpm/modules-yaml": "workspace:*",
"@pnpm/plugin-commands-installation": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-fixtures": "workspace:*",
"@pnpm/test-ipc-server": "workspace:*",
"@types/proxyquire": "^1.3.31",

View File

@@ -49,6 +49,7 @@
"@yarnpkg/core": "4.0.2",
"filenamify": "^4.3.0",
"get-npm-tarball-url": "^2.1.0",
"graph-cycles": "1.2.1",
"is-inner-link": "^4.0.0",
"is-subdir": "^1.2.0",
"normalize-path": "^3.0.0",

View File

@@ -12,7 +12,7 @@ export function depPathToRef (
if (depPath[0] === '/' && opts.alias === opts.realName) {
const ref = depPath.replace(`/${opts.realName}@`, '')
if (!ref.includes('/') || !ref.replace(/(\([^)]+\))+$/, '').includes('/')) return ref
return ref
}
return depPath
}

View File

@@ -178,7 +178,7 @@ export async function resolveDependencies (
dependenciesGraph,
dependenciesByProjectId,
peerDependencyIssuesByProjects,
} = resolvePeers({
} = await resolvePeers({
dependenciesTree,
dedupePeerDependents: opts.dedupePeerDependents,
dedupeInjectedDeps: opts.dedupeInjectedDeps,

View File

@@ -1,12 +1,14 @@
import filenamify from 'filenamify'
import { analyzeGraph, type Graph } from 'graph-cycles'
import path from 'path'
import pDefer from 'p-defer'
import semver from 'semver'
import { semverUtils } from '@yarnpkg/core'
import type {
PeerDependencyIssues,
PeerDependencyIssuesByProjects,
} from '@pnpm/types'
import { depPathToFilename, createPeersDirSuffix } from '@pnpm/dependency-path'
import { depPathToFilename, createPeersDirSuffix, type PeerId } from '@pnpm/dependency-path'
import mapValues from 'ramda/src/map'
import partition from 'ramda/src/partition'
import pick from 'ramda/src/pick'
@@ -59,7 +61,7 @@ export interface ProjectToResolve {
export type DependenciesByProjectId = Record<string, Record<string, string>>
export function resolvePeers<T extends PartialResolvedPackage> (
export async function resolvePeers<T extends PartialResolvedPackage> (
opts: {
projects: ProjectToResolve[]
dependenciesTree: DependenciesTree<T>
@@ -70,30 +72,39 @@ export function resolvePeers<T extends PartialResolvedPackage> (
dedupeInjectedDeps?: boolean
resolvedImporters: ResolvedImporters
}
): {
): Promise<{
dependenciesGraph: GenericDependenciesGraph<T>
dependenciesByProjectId: DependenciesByProjectId
peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects
} {
}> {
const depGraph: GenericDependenciesGraph<T> = {}
const pathsByNodeId = new Map<string, string>()
const pathsByNodeIdPromises = new Map<string, pDefer.DeferredPromise<string>>()
const depPathsByPkgId = new Map<string, Set<string>>()
const _createPkgsByName = createPkgsByName.bind(null, opts.dependenciesTree)
const rootPkgsByName = opts.resolvePeersFromWorkspaceRoot ? getRootPkgsByName(opts.dependenciesTree, opts.projects) : {}
const peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects = {}
const finishingList: FinishingResolutionPromise[] = []
for (const { directNodeIdsByAlias, topParents, rootDir, id } of opts.projects) {
const peerDependencyIssues: Pick<PeerDependencyIssues, 'bad' | 'missing'> = { bad: {}, missing: {} }
const pkgsByName = {
...rootPkgsByName,
..._createPkgsByName({ directNodeIdsByAlias, topParents }),
}
for (const { nodeId } of Object.values(pkgsByName)) {
if (nodeId && !pathsByNodeIdPromises.has(nodeId)) {
pathsByNodeIdPromises.set(nodeId, pDefer())
}
}
resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
// eslint-disable-next-line no-await-in-loop
const { finishing } = await resolvePeersOfChildren(directNodeIdsByAlias, pkgsByName, {
dependenciesTree: opts.dependenciesTree,
depGraph,
lockfileDir: opts.lockfileDir,
pathsByNodeId,
pathsByNodeIdPromises,
depPathsByPkgId,
peersCache: new Map(),
peerDependencyIssues,
@@ -101,6 +112,9 @@ export function resolvePeers<T extends PartialResolvedPackage> (
rootDir,
virtualStoreDir: opts.virtualStoreDir,
})
if (finishing) {
finishingList.push(finishing)
}
if (Object.keys(peerDependencyIssues.bad).length > 0 || Object.keys(peerDependencyIssues.missing).length > 0) {
peerDependencyIssuesByProjects[id] = {
...peerDependencyIssues,
@@ -108,6 +122,7 @@ export function resolvePeers<T extends PartialResolvedPackage> (
}
}
}
await Promise.all(finishingList)
Object.values(depGraph).forEach((node) => {
node.children = mapValues((childNodeId) => pathsByNodeId.get(childNodeId) ?? childNodeId, node.children)
@@ -261,7 +276,7 @@ function createPkgsByName<T extends PartialResolvedPackage> (
}
interface PeersCacheItem {
depPath: string
depPath: pDefer.DeferredPromise<string>
resolvedPeers: Map<string, string>
missingPeers: Set<string>
}
@@ -275,10 +290,14 @@ interface PeersResolution {
interface ResolvePeersContext {
pathsByNodeId: Map<string, string>
pathsByNodeIdPromises: Map<string, pDefer.DeferredPromise<string>>
depPathsByPkgId?: Map<string, Set<string>>
}
function resolvePeersOfNode<T extends PartialResolvedPackage> (
type CalculateDepPath = (cycles: string[][]) => Promise<void>
type FinishingResolutionPromise = Promise<void>
async function resolvePeersOfNode<T extends PartialResolvedPackage> (
nodeId: string,
parentParentPkgs: ParentRefs,
ctx: ResolvePeersContext & {
@@ -291,7 +310,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
rootDir: string
lockfileDir: string
}
): PeersResolution {
): Promise<PeersResolution & { finishing?: FinishingResolutionPromise, calculateDepPath?: CalculateDepPath }> {
const node = ctx.dependenciesTree.get(nodeId)!
if (node.depth === -1) return { resolvedPeers: new Map<string, string>(), missingPeers: new Set<string>() }
const resolvedPackage = node.resolvedPackage as T
@@ -301,6 +320,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
Object.keys(resolvedPackage.peerDependencies).length === 0
) {
ctx.pathsByNodeId.set(nodeId, resolvedPackage.depPath)
ctx.pathsByNodeIdPromises.get(nodeId)!.resolve(resolvedPackage.depPath)
return { resolvedPeers: new Map<string, string>(), missingPeers: new Set<string>() }
}
if (typeof node.children === 'function') {
@@ -326,7 +346,7 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
if (parentPkgNodeId === cachedNodeId) continue
if (
ctx.pathsByNodeId.has(cachedNodeId) &&
ctx.pathsByNodeId.get(cachedNodeId) === ctx.pathsByNodeId.get(parentPkgNodeId)
ctx.pathsByNodeId.get(cachedNodeId) === ctx.pathsByNodeId.get(parentPkgNodeId)
) continue
if (!ctx.dependenciesTree.has(parentPkgNodeId) && parentPkgNodeId.startsWith('link:')) {
return false
@@ -342,10 +362,14 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
return true
})
if (hit != null) {
ctx.pathsByNodeId.set(nodeId, hit.depPath)
ctx.depGraph[hit.depPath].depth = Math.min(ctx.depGraph[hit.depPath].depth, node.depth)
return {
missingPeers: hit.missingPeers,
finishing: (async () => {
const depPath = await hit.depPath.promise
ctx.pathsByNodeId.set(nodeId, depPath)
ctx.depGraph[depPath].depth = Math.min(ctx.depGraph[depPath].depth, node.depth)
ctx.pathsByNodeIdPromises.get(nodeId)!.resolve(depPath)
})(),
resolvedPeers: hit.resolvedPeers,
}
}
@@ -353,7 +377,8 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
const {
resolvedPeers: unknownResolvedPeersOfChildren,
missingPeers: missingPeersOfChildren,
} = resolvePeersOfChildren(children, parentPkgs, ctx)
finishing,
} = await resolvePeersOfChildren(children, parentPkgs, ctx)
const { resolvedPeers, missingPeers } = Object.keys(resolvedPackage.peerDependencies).length === 0
? { resolvedPeers: new Map<string, string>(), missingPeers: new Set<string>() }
@@ -382,35 +407,14 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
allMissingPeers.add(peer)
}
let depPath: string
if (allResolvedPeers.size === 0) {
depPath = resolvedPackage.depPath
} else {
const peersFolderSuffix = createPeersDirSuffix(
[...allResolvedPeers.entries()]
.map(([alias, nodeId]) => {
if (nodeId.startsWith('link:')) {
const linkedDir = nodeId.slice(5)
return {
name: alias,
version: filenamify(linkedDir, { replacement: '+' }),
}
}
const { name, version } = ctx.dependenciesTree.get(nodeId)!.resolvedPackage
return { name, version }
})
)
depPath = `${resolvedPackage.depPath}${peersFolderSuffix}`
}
const localLocation = path.join(ctx.virtualStoreDir, depPathToFilename(depPath))
const modules = path.join(localLocation, 'node_modules')
let cache: PeersCacheItem
const isPure = allResolvedPeers.size === 0 && allMissingPeers.size === 0
if (isPure) {
ctx.purePkgs.add(resolvedPackage.depPath)
} else {
const cache = {
cache = {
missingPeers: allMissingPeers,
depPath,
depPath: pDefer(),
resolvedPeers: allResolvedPeers,
}
if (ctx.peersCache.has(resolvedPackage.depPath)) {
@@ -420,49 +424,117 @@ function resolvePeersOfNode<T extends PartialResolvedPackage> (
}
}
ctx.pathsByNodeId.set(nodeId, depPath)
if (ctx.depPathsByPkgId != null) {
if (!ctx.depPathsByPkgId.has(resolvedPackage.depPath)) {
ctx.depPathsByPkgId.set(resolvedPackage.depPath, new Set())
let calculateDepPathIfNeeded: CalculateDepPath | undefined
if (allResolvedPeers.size === 0) {
addDepPathToGraph(resolvedPackage.depPath)
} else {
const peerIds: PeerId[] = []
const pendingPeerNodeIds: string[] = []
for (const [alias, peerNodeId] of allResolvedPeers.entries()) {
if (peerNodeId.startsWith('link:')) {
const linkedDir = peerNodeId.slice(5)
peerIds.push({
name: alias,
version: filenamify(linkedDir, { replacement: '+' }),
})
continue
}
const peerDepPath = ctx.pathsByNodeId.get(peerNodeId)
if (peerDepPath) {
peerIds.push(peerDepPath)
continue
}
pendingPeerNodeIds.push(peerNodeId)
}
if (!ctx.depPathsByPkgId.get(resolvedPackage.depPath)!.has(depPath)) {
ctx.depPathsByPkgId.get(resolvedPackage.depPath)!.add(depPath)
if (pendingPeerNodeIds.length === 0) {
const peersDirSuffix = createPeersDirSuffix(peerIds)
addDepPathToGraph(`${resolvedPackage.depPath}${peersDirSuffix}`)
} else {
calculateDepPathIfNeeded = calculateDepPath.bind(null, peerIds, pendingPeerNodeIds)
}
}
const peerDependencies = { ...resolvedPackage.peerDependencies }
if (!ctx.depGraph[depPath] || ctx.depGraph[depPath].depth > node.depth) {
const dir = path.join(modules, resolvedPackage.name)
const transitivePeerDependencies = new Set<string>()
for (const unknownPeer of allResolvedPeers.keys()) {
if (!peerDependencies[unknownPeer]) {
transitivePeerDependencies.add(unknownPeer)
return {
resolvedPeers: allResolvedPeers,
missingPeers: allMissingPeers,
calculateDepPath: calculateDepPathIfNeeded,
finishing,
}
async function calculateDepPath (
peerIds: PeerId[],
pendingPeerNodeIds: string[],
cycles: string[][]
) {
const cyclicPeerNodeIds = new Set()
for (const cycle of cycles) {
if (cycle.includes(nodeId)) {
for (const peerNodeId of cycle) {
cyclicPeerNodeIds.add(peerNodeId)
}
}
}
for (const unknownPeer of missingPeersOfChildren) {
if (!peerDependencies[unknownPeer]) {
transitivePeerDependencies.add(unknownPeer)
}
}
ctx.depGraph[depPath] = {
...(node.resolvedPackage as T),
children: Object.assign(
getPreviouslyResolvedChildren(nodeId, ctx.dependenciesTree),
children,
Object.fromEntries(resolvedPeers.entries())
const peersDirSuffix = createPeersDirSuffix([
...peerIds,
...await Promise.all(pendingPeerNodeIds
.map(async (peerNodeId) => {
if (cyclicPeerNodeIds.has(peerNodeId)) {
const { name, version } = (ctx.dependenciesTree.get(peerNodeId)!.resolvedPackage as T)
return `${name}@${version}`
}
return ctx.pathsByNodeIdPromises.get(peerNodeId)!.promise
})
),
depPath,
depth: node.depth,
dir,
installable: node.installable,
isPure,
modules,
peerDependencies,
transitivePeerDependencies,
resolvedPeerNames: new Set(allResolvedPeers.keys()),
])
addDepPathToGraph(`${resolvedPackage.depPath}${peersDirSuffix}`)
}
function addDepPathToGraph (depPath: string) {
cache?.depPath.resolve(depPath)
ctx.pathsByNodeId.set(nodeId, depPath)
ctx.pathsByNodeIdPromises.get(nodeId)!.resolve(depPath)
if (ctx.depPathsByPkgId != null) {
if (!ctx.depPathsByPkgId.has(resolvedPackage.depPath)) {
ctx.depPathsByPkgId.set(resolvedPackage.depPath, new Set([depPath]))
} else {
ctx.depPathsByPkgId.get(resolvedPackage.depPath)!.add(depPath)
}
}
const peerDependencies = { ...resolvedPackage.peerDependencies }
if (!ctx.depGraph[depPath] || ctx.depGraph[depPath].depth > node.depth) {
const modules = path.join(ctx.virtualStoreDir, depPathToFilename(depPath), 'node_modules')
const dir = path.join(modules, resolvedPackage.name)
const transitivePeerDependencies = new Set<string>()
for (const unknownPeer of allResolvedPeers.keys()) {
if (!peerDependencies[unknownPeer]) {
transitivePeerDependencies.add(unknownPeer)
}
}
for (const unknownPeer of missingPeersOfChildren) {
if (!peerDependencies[unknownPeer]) {
transitivePeerDependencies.add(unknownPeer)
}
}
ctx.depGraph[depPath] = {
...(node.resolvedPackage as T),
children: Object.assign(
getPreviouslyResolvedChildren(nodeId, ctx.dependenciesTree),
children,
Object.fromEntries(resolvedPeers.entries())
),
depPath,
depth: node.depth,
dir,
installable: node.installable,
isPure,
modules,
peerDependencies,
transitivePeerDependencies,
resolvedPeerNames: new Set(allResolvedPeers.keys()),
}
}
}
return { resolvedPeers: allResolvedPeers, missingPeers: allMissingPeers }
}
// When a package has itself in the subdependencies, so there's a cycle,
@@ -495,7 +567,7 @@ function getPreviouslyResolvedChildren<T extends PartialResolvedPackage> (nodeId
return allChildren
}
function resolvePeersOfChildren<T extends PartialResolvedPackage> (
async function resolvePeersOfChildren<T extends PartialResolvedPackage> (
children: {
[alias: string]: string
},
@@ -510,24 +582,53 @@ function resolvePeersOfChildren<T extends PartialResolvedPackage> (
rootDir: string
lockfileDir: string
}
): PeersResolution {
): Promise<PeersResolution & { finishing: Promise<void> }> {
const allResolvedPeers = new Map<string, string>()
const allMissingPeers = new Set<string>()
// Partition children based on whether they're repeated in parentPkgs.
// This impacts the efficiency of graph traversal and prevents potential out-of-memory errors.mes can even lead to out-of-memory exceptions.
const [repeated, notRepeated] = partition(([alias]) => parentPkgs[alias] != null, Object.entries(children))
const nodeIds = [...notRepeated, ...repeated]
for (const [,nodeId] of nodeIds) {
if (!ctx.pathsByNodeIdPromises.has(nodeId)) {
ctx.pathsByNodeIdPromises.set(nodeId, pDefer())
}
}
// Resolving non-repeated nodes before repeated nodes proved to be slightly faster.
for (const [, childNodeId] of [...notRepeated, ...repeated]) {
const { resolvedPeers, missingPeers } = resolvePeersOfNode(childNodeId, parentPkgs, ctx)
for (const [k, v] of resolvedPeers) {
allResolvedPeers.set(k, v)
const calculateDepPaths: CalculateDepPath[] = []
const graph = []
const finishingList: FinishingResolutionPromise[] = []
for (const [, childNodeId] of nodeIds) {
const {
resolvedPeers,
missingPeers,
calculateDepPath,
finishing,
} = await resolvePeersOfNode(childNodeId, parentPkgs, ctx) // eslint-disable-line no-await-in-loop
if (finishing) {
finishingList.push(finishing)
}
if (calculateDepPath) {
calculateDepPaths.push(calculateDepPath)
}
const edges = []
for (const [peerName, peerNodeId] of resolvedPeers) {
allResolvedPeers.set(peerName, peerNodeId)
edges.push(peerNodeId)
}
graph.push([childNodeId, edges])
for (const missingPeer of missingPeers) {
allMissingPeers.add(missingPeer)
}
}
if (calculateDepPaths.length) {
const { cycles } = analyzeGraph(graph as unknown as Graph)
finishingList.push(...calculateDepPaths.map((calculateDepPath) => calculateDepPath(cycles)))
}
const finishing = Promise.all(finishingList).then(() => {})
const unknownResolvedPeersOfChildren = new Map<string, string>()
for (const [alias, v] of allResolvedPeers) {
@@ -536,7 +637,7 @@ function resolvePeersOfChildren<T extends PartialResolvedPackage> (
}
}
return { resolvedPeers: unknownResolvedPeersOfChildren, missingPeers: allMissingPeers }
return { resolvedPeers: unknownResolvedPeersOfChildren, missingPeers: allMissingPeers, finishing }
}
function _resolvePeers<T extends PartialResolvedPackage> (

View File

@@ -1,7 +1,7 @@
import { type PartialResolvedPackage, resolvePeers } from '../lib/resolvePeers'
import { type DependenciesTreeNode } from '../lib/resolveDependencies'
test('packages are not deduplicated when versions do not match', () => {
test('packages are not deduplicated when versions do not match', async () => {
const fooPkg: PartialResolvedPackage = {
name: 'foo',
version: '1.0.0',
@@ -31,7 +31,7 @@ test('packages are not deduplicated when versions do not match', () => {
])
)
const { dependenciesByProjectId } = resolvePeers({
const { dependenciesByProjectId } = await resolvePeers({
projects: [
{
directNodeIdsByAlias: {

View File

@@ -1,8 +1,9 @@
/// <reference path="../../../__typings__/index.d.ts" />
import { type PeerDependencyIssuesByProjects } from '@pnpm/types'
import { type PartialResolvedPackage, resolvePeers } from '../lib/resolvePeers'
import { type DependenciesTreeNode, type PeerDependencies } from '../lib/resolveDependencies'
test('resolve peer dependencies of cyclic dependencies', () => {
test('resolve peer dependencies of cyclic dependencies', async () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
@@ -23,7 +24,7 @@ test('resolve peer dependencies of cyclic dependencies', () => {
},
id: '',
}
const { dependenciesGraph } = resolvePeers({
const { dependenciesGraph } = await resolvePeers({
projects: [
{
directNodeIdsByAlias: {
@@ -103,16 +104,16 @@ test('resolve peer dependencies of cyclic dependencies', () => {
lockfileDir: '',
})
expect(Object.keys(dependenciesGraph)).toStrictEqual([
'foo/1.0.0(qar@1.0.0)(zoo@1.0.0)',
'bar/1.0.0(foo@1.0.0)(zoo@1.0.0)',
'zoo/1.0.0(qar@1.0.0)',
'qar/1.0.0(bar@1.0.0)(foo@1.0.0)',
'bar/1.0.0(foo@1.0.0)',
'foo/1.0.0',
'bar/1.0.0(foo/1.0.0)',
'qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0)',
'zoo/1.0.0(qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0))',
'foo/1.0.0(qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0))(zoo/1.0.0(qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0)))',
'bar/1.0.0(foo/1.0.0(qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0))(zoo/1.0.0(qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0))))(zoo/1.0.0(qar/1.0.0(bar/1.0.0(foo/1.0.0))(foo/1.0.0)))',
])
})
test('when a package is referenced twice in the dependencies graph and one of the times it cannot resolve its peers, still try to resolve it in the other occurrence', () => {
test('when a package is referenced twice in the dependencies graph and one of the times it cannot resolve its peers, still try to resolve it in the other occurrence', async () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
@@ -136,7 +137,7 @@ test('when a package is referenced twice in the dependencies graph and one of th
peerDependencies: {} as PeerDependencies,
id: '',
}
const { dependenciesGraph } = resolvePeers({
const { dependenciesGraph } = await resolvePeers({
projects: [
{
directNodeIdsByAlias: {
@@ -203,182 +204,185 @@ test('when a package is referenced twice in the dependencies graph and one of th
virtualStoreDir: '',
lockfileDir: '',
})
expect(Object.keys(dependenciesGraph)).toStrictEqual([
'foo/1.0.0',
'zoo/1.0.0',
'foo/1.0.0(qar@1.0.0)',
'zoo/1.0.0(qar@1.0.0)',
'qar/1.0.0',
expect(Object.keys(dependenciesGraph).sort()).toStrictEqual([
'bar/1.0.0',
'foo/1.0.0',
'foo/1.0.0(qar/1.0.0)',
'qar/1.0.0',
'zoo/1.0.0',
'zoo/1.0.0(qar/1.0.0)',
])
})
describe('peer dependency issues', () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: { version: '1' },
},
id: '',
}
const fooWithOptionalPeer = {
name: 'foo',
depPath: 'foo/2.0.0',
version: '2.0.0',
peerDependencies: {
peer: { version: '1', optional: true },
},
id: '',
}
const barPkg = {
name: 'bar',
depPath: 'bar/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: { version: '2' },
},
id: '',
}
const barWithOptionalPeer = {
name: 'bar',
depPath: 'bar/2.0.0',
version: '2.0.0',
peerDependencies: {
peer: { version: '2', optional: true },
},
id: '',
}
const qarPkg = {
name: 'qar',
depPath: 'qar/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: { version: '^2.2.0' },
},
id: '',
}
const { peerDependencyIssuesByProjects } = resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project1',
let peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects
beforeAll(async () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: { version: '1' },
},
{
directNodeIdsByAlias: {
bar: '>project2>bar/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project2',
id: '',
}
const fooWithOptionalPeer = {
name: 'foo',
depPath: 'foo/2.0.0',
version: '2.0.0',
peerDependencies: {
peer: { version: '1', optional: true },
},
{
directNodeIdsByAlias: {
foo: '>project3>foo/1.0.0>',
bar: '>project3>bar/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project3',
id: '',
}
const barPkg = {
name: 'bar',
depPath: 'bar/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: { version: '2' },
},
{
directNodeIdsByAlias: {
bar: '>project4>bar/1.0.0>',
qar: '>project4>qar/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project4',
id: '',
}
const barWithOptionalPeer = {
name: 'bar',
depPath: 'bar/2.0.0',
version: '2.0.0',
peerDependencies: {
peer: { version: '2', optional: true },
},
{
directNodeIdsByAlias: {
foo: '>project5>foo/1.0.0>',
bar: '>project5>bar/2.0.0>',
},
topParents: [],
rootDir: '',
id: 'project5',
id: '',
}
const qarPkg = {
name: 'qar',
depPath: 'qar/1.0.0',
version: '1.0.0',
peerDependencies: {
peer: { version: '^2.2.0' },
},
{
directNodeIdsByAlias: {
foo: '>project6>foo/2.0.0>',
bar: '>project6>bar/2.0.0>',
id: '',
}
peerDependencyIssuesByProjects = (await resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project1',
},
topParents: [],
rootDir: '',
id: 'project6',
},
],
resolvedImporters: {},
dependenciesTree: new Map<string, DependenciesTreeNode<PartialResolvedPackage>>([
['>project1>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
}],
['>project2>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
}],
['>project3>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
}],
['>project3>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
}],
['>project4>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
}],
['>project4>qar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: qarPkg,
depth: 0,
}],
['>project5>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
}],
['>project5>bar/2.0.0>', {
children: {},
installable: true,
resolvedPackage: barWithOptionalPeer,
depth: 0,
}],
['>project6>foo/2.0.0>', {
children: {},
installable: true,
resolvedPackage: fooWithOptionalPeer,
depth: 0,
}],
['>project6>bar/2.0.0>', {
children: {},
installable: true,
resolvedPackage: barWithOptionalPeer,
depth: 0,
}],
]),
virtualStoreDir: '',
lockfileDir: '',
{
directNodeIdsByAlias: {
bar: '>project2>bar/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project2',
},
{
directNodeIdsByAlias: {
foo: '>project3>foo/1.0.0>',
bar: '>project3>bar/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project3',
},
{
directNodeIdsByAlias: {
bar: '>project4>bar/1.0.0>',
qar: '>project4>qar/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project4',
},
{
directNodeIdsByAlias: {
foo: '>project5>foo/1.0.0>',
bar: '>project5>bar/2.0.0>',
},
topParents: [],
rootDir: '',
id: 'project5',
},
{
directNodeIdsByAlias: {
foo: '>project6>foo/2.0.0>',
bar: '>project6>bar/2.0.0>',
},
topParents: [],
rootDir: '',
id: 'project6',
},
],
resolvedImporters: {},
dependenciesTree: new Map<string, DependenciesTreeNode<PartialResolvedPackage>>([
['>project1>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
}],
['>project2>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
}],
['>project3>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
}],
['>project3>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
}],
['>project4>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: barPkg,
depth: 0,
}],
['>project4>qar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: qarPkg,
depth: 0,
}],
['>project5>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: fooPkg,
depth: 0,
}],
['>project5>bar/2.0.0>', {
children: {},
installable: true,
resolvedPackage: barWithOptionalPeer,
depth: 0,
}],
['>project6>foo/2.0.0>', {
children: {},
installable: true,
resolvedPackage: fooWithOptionalPeer,
depth: 0,
}],
['>project6>bar/2.0.0>', {
children: {},
installable: true,
resolvedPackage: barWithOptionalPeer,
depth: 0,
}],
]),
virtualStoreDir: '',
lockfileDir: '',
})).peerDependencyIssuesByProjects
})
it('should find peer dependency conflicts', () => {
expect(peerDependencyIssuesByProjects['project3'].conflicts).toStrictEqual(['peer'])
@@ -402,63 +406,66 @@ describe('peer dependency issues', () => {
})
describe('unmet peer dependency issues', () => {
const { peerDependencyIssuesByProjects } = resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0>',
peer1: '>project1>peer1/1.0.0-rc.0>',
peer2: '>project1>peer2/1.1.0-rc.0>',
},
topParents: [],
rootDir: '',
id: 'project1',
},
],
resolvedImporters: {},
dependenciesTree: new Map<string, DependenciesTreeNode<PartialResolvedPackage>>([
['>project1>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'foo',
version: '1.0.0',
depPath: 'foo/1.0.0',
peerDependencies: {
peer1: { version: '*' },
peer2: { version: '>=1' },
let peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects
beforeAll(async () => {
peerDependencyIssuesByProjects = (await resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project1>foo/1.0.0>',
peer1: '>project1>peer1/1.0.0-rc.0>',
peer2: '>project1>peer2/1.1.0-rc.0>',
},
id: '',
topParents: [],
rootDir: '',
id: 'project1',
},
depth: 0,
}],
['>project1>peer1/1.0.0-rc.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'peer1',
version: '1.0.0-rc.0',
depPath: 'peer/1.0.0-rc.0',
peerDependencies: {},
id: '',
},
depth: 0,
}],
['>project1>peer2/1.1.0-rc.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'peer2',
version: '1.1.0-rc.0',
depPath: 'peer/1.1.0-rc.0',
peerDependencies: {},
id: '',
},
depth: 0,
}],
]),
virtualStoreDir: '',
lockfileDir: '',
],
resolvedImporters: {},
dependenciesTree: new Map<string, DependenciesTreeNode<PartialResolvedPackage>>([
['>project1>foo/1.0.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'foo',
version: '1.0.0',
depPath: 'foo/1.0.0',
peerDependencies: {
peer1: { version: '*' },
peer2: { version: '>=1' },
},
id: '',
},
depth: 0,
}],
['>project1>peer1/1.0.0-rc.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'peer1',
version: '1.0.0-rc.0',
depPath: 'peer/1.0.0-rc.0',
peerDependencies: {},
id: '',
},
depth: 0,
}],
['>project1>peer2/1.1.0-rc.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'peer2',
version: '1.1.0-rc.0',
depPath: 'peer/1.1.0-rc.0',
peerDependencies: {},
id: '',
},
depth: 0,
}],
]),
virtualStoreDir: '',
lockfileDir: '',
})).peerDependencyIssuesByProjects
})
it('should not warn when the found package has prerelease version and the wanted range is *', () => {
expect(peerDependencyIssuesByProjects).not.toHaveProperty(['project1', 'bad', 'peer1'])
@@ -469,70 +476,73 @@ describe('unmet peer dependency issues', () => {
})
describe('unmet peer dependency issue resolved from subdependency', () => {
const { peerDependencyIssuesByProjects } = resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project>foo/1.0.0>',
},
topParents: [],
rootDir: '',
id: 'project',
},
],
resolvedImporters: {},
dependenciesTree: new Map<string, DependenciesTreeNode<PartialResolvedPackage>>([
['>project>foo/1.0.0>', {
children: {
dep: '>project>foo/1.0.0>dep/1.0.0>',
bar: '>project>foo/1.0.0>bar/1.0.0>',
},
installable: true,
resolvedPackage: {
name: 'foo',
depPath: 'foo/1.0.0',
version: '1.0.0',
peerDependencies: {},
id: '',
},
depth: 0,
}],
['>project>foo/1.0.0>dep/1.0.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'dep',
depPath: 'dep/1.0.0',
version: '1.0.0',
peerDependencies: {},
id: '',
},
depth: 1,
}],
['>project>foo/1.0.0>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'bar',
depPath: 'bar/1.0.0',
version: '1.0.0',
peerDependencies: {
dep: { version: '10' },
let peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects
beforeAll(async () => {
peerDependencyIssuesByProjects = (await resolvePeers({
projects: [
{
directNodeIdsByAlias: {
foo: '>project>foo/1.0.0>',
},
id: '',
topParents: [],
rootDir: '',
id: 'project',
},
depth: 1,
}],
]),
virtualStoreDir: '',
lockfileDir: '',
],
resolvedImporters: {},
dependenciesTree: new Map<string, DependenciesTreeNode<PartialResolvedPackage>>([
['>project>foo/1.0.0>', {
children: {
dep: '>project>foo/1.0.0>dep/1.0.0>',
bar: '>project>foo/1.0.0>bar/1.0.0>',
},
installable: true,
resolvedPackage: {
name: 'foo',
depPath: 'foo/1.0.0',
version: '1.0.0',
peerDependencies: {},
id: '',
},
depth: 0,
}],
['>project>foo/1.0.0>dep/1.0.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'dep',
depPath: 'dep/1.0.0',
version: '1.0.0',
peerDependencies: {},
id: '',
},
depth: 1,
}],
['>project>foo/1.0.0>bar/1.0.0>', {
children: {},
installable: true,
resolvedPackage: {
name: 'bar',
depPath: 'bar/1.0.0',
version: '1.0.0',
peerDependencies: {
dep: { version: '10' },
},
id: '',
},
depth: 1,
}],
]),
virtualStoreDir: '',
lockfileDir: '',
})).peerDependencyIssuesByProjects
})
it('should return from where the bad peer dependency is resolved', () => {
expect(peerDependencyIssuesByProjects.project.bad.dep[0].resolvedFrom).toStrictEqual([{ name: 'foo', version: '1.0.0' }])
})
})
test('resolve peer dependencies with npm aliases', () => {
test('resolve peer dependencies with npm aliases', async () => {
const fooPkg = {
name: 'foo',
depPath: 'foo/1.0.0',
@@ -565,7 +575,7 @@ test('resolve peer dependencies with npm aliases', () => {
peerDependencies: {},
id: '',
}
const { dependenciesGraph } = resolvePeers({
const { dependenciesGraph } = await resolvePeers({
projects: [
{
directNodeIdsByAlias: {
@@ -625,10 +635,10 @@ test('resolve peer dependencies with npm aliases', () => {
virtualStoreDir: '',
lockfileDir: '',
})
expect(Object.keys(dependenciesGraph)).toStrictEqual([
expect(Object.keys(dependenciesGraph).sort()).toStrictEqual([
'bar/1.0.0',
'foo/1.0.0(bar@1.0.0)',
'bar/2.0.0',
'foo/2.0.0(bar@2.0.0)',
'foo/1.0.0(bar/1.0.0)',
'foo/2.0.0(bar/2.0.0)',
])
})

119
pnpm-lock.yaml generated
View File

@@ -71,8 +71,8 @@ importers:
specifier: 1.0.0
version: 1.0.0
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/tsconfig':
specifier: workspace:*
version: link:__utils__/tsconfig
@@ -189,8 +189,8 @@ importers:
specifier: workspace:*
version: link:../../pkg-manager/modules-yaml
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/types':
specifier: workspace:*
version: link:../../packages/types
@@ -226,8 +226,8 @@ importers:
__utils__/assert-store:
dependencies:
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/store.cafs':
specifier: workspace:*
version: link:../../store/cafs
@@ -1335,8 +1335,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -1456,8 +1456,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-ipc-server':
specifier: workspace:*
version: link:../../__utils__/test-ipc-server
@@ -2900,8 +2900,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -3160,8 +3160,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/store-path':
specifier: workspace:*
version: link:../../store/store-path
@@ -3442,8 +3442,8 @@ importers:
specifier: workspace:*
version: link:../read-projects-context
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/store-path':
specifier: workspace:*
version: link:../../store/store-path
@@ -3799,8 +3799,8 @@ importers:
specifier: workspace:*
version: 'link:'
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -3992,8 +3992,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -4198,6 +4198,9 @@ importers:
get-npm-tarball-url:
specifier: ^2.1.0
version: 2.1.0
graph-cycles:
specifier: 1.2.1
version: 1.2.1
is-inner-link:
specifier: ^4.0.0
version: 4.0.0
@@ -4549,8 +4552,8 @@ importers:
specifier: workspace:*
version: link:../pkg-manifest/read-project-manifest
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/run-npm':
specifier: workspace:*
version: link:../exec/run-npm
@@ -4809,8 +4812,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
releasing/plugin-commands-publishing:
dependencies:
@@ -4909,8 +4912,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-ipc-server':
specifier: workspace:*
version: link:../../__utils__/test-ipc-server
@@ -5472,8 +5475,8 @@ importers:
specifier: workspace:*
version: link:../../pkg-manifest/read-package-json
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -5539,8 +5542,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@types/ramda':
specifier: 0.28.20
version: 0.28.20
@@ -5633,8 +5636,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@pnpm/test-fixtures':
specifier: workspace:*
version: link:../../__utils__/test-fixtures
@@ -5965,8 +5968,8 @@ importers:
specifier: workspace:*
version: link:../../__utils__/prepare
'@pnpm/registry-mock':
specifier: 3.24.2
version: 3.24.2(typanion@3.14.0)
specifier: 3.25.0
version: 3.25.0(typanion@3.14.0)
'@types/archy':
specifier: 0.0.33
version: 0.0.33
@@ -6538,7 +6541,7 @@ packages:
'@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3)
'@babel/helper-skip-transparent-expression-wrappers': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
semver: 7.5.4
semver: 7.6.0
dev: true
/@babel/helper-environment-visitor@7.22.20:
@@ -8733,8 +8736,8 @@ packages:
strip-bom: 4.0.0
dev: true
/@pnpm/registry-mock@3.24.2(typanion@3.14.0):
resolution: {integrity: sha512-j756X+oEW71pYCHALwQ44MHxDXpL+/1uEVaghnI2phoCeiex9/E39mLP0sYp3rcO1a9Jqf+XaorKmAgtLT7Xbw==}
/@pnpm/registry-mock@3.25.0(typanion@3.14.0):
resolution: {integrity: sha512-rSKgbINc7T9s0hn8K/YHTJtMaUG2LWmXybzmiVNwDwrNx1SMHbLsZ+QafHz0wyktQhPQB2274BAd72jYLgFHFg==}
engines: {node: '>=10.13'}
hasBin: true
dependencies:
@@ -9004,6 +9007,10 @@ packages:
'@babel/types': 7.23.3
dev: true
/@types/bintrees@1.0.6:
resolution: {integrity: sha512-pZWT4Bz+tWwxlDspSjdoIza4PE5lbGI4Xvs3FZV/2v5m5SDA8LwNpU8AXxlndmARO7OaQ1Vf3zFenOsNMzaRkQ==}
dev: false
/@types/braces@3.0.4:
resolution: {integrity: sha512-0WR3b8eaISjEW7RpZnclONaLFDf7buaowRHdqLp4vLj54AsSAYWfh3DRbfiYJY9XDxMgx1B4sE1Afw2PGpuHOA==}
dev: true
@@ -10274,6 +10281,10 @@ packages:
write-file-atomic: 5.0.1
dev: false
/bintrees@1.0.2:
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
dev: false
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
@@ -12124,6 +12135,11 @@ packages:
/fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
/fast-string-compare@1.0.0:
resolution: {integrity: sha512-R0f7E2MvKy3LltU/eK7P9IYMc7kovnoaQIvWVvKx1LDDgHArVCrqgb4/6hlYJn6I9VYFn7mQoTKrsO3ELMVE8Q==}
engines: {node: '>=10'}
dev: false
/fastest-levenshtein@1.0.16:
resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
engines: {node: '>= 4.9.1'}
@@ -12785,6 +12801,15 @@ packages:
resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==}
dev: true
/graph-cycles@1.2.1:
resolution: {integrity: sha512-IJly1QJTKrACgy+HVQYELr9igxhCC6zGn78PbvEX9tj4AZBvv7ZNU45m9rOrhDgprrkLtJZYmOPyhY8exZLD/Q==}
engines: {node: '>=12'}
dependencies:
fast-string-compare: 1.0.0
rotated-array-set: 1.0.0
short-tree: 1.0.0
dev: false
/grapheme-splitter@1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
@@ -13859,7 +13884,7 @@ packages:
jest-util: 29.7.0
natural-compare: 1.4.0
pretty-format: 29.7.0
semver: 7.5.4
semver: 7.6.0
transitivePeerDependencies:
- supports-color
dev: true
@@ -14337,7 +14362,7 @@ packages:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
dependencies:
semver: 7.5.4
semver: 7.6.0
dev: true
/make-empty-dir@2.1.0:
@@ -16272,6 +16297,11 @@ packages:
path-temp: 2.0.0
dev: false
/rotated-array-set@1.0.0:
resolution: {integrity: sha512-MmMz9ERrZw7DVn8e6V7U/wkAWzEaULiPySEBXLWh4nA/F5gwSFx96bu+As4wyDpZ9bdhc+flB1CyTYmStwZo8A==}
engines: {node: '>=12'}
dev: false
/run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -16479,6 +16509,14 @@ packages:
rechoir: 0.6.2
dev: true
/short-tree@1.0.0:
resolution: {integrity: sha512-SPhGxbdypMMjYlmdVL/dzBUCT/5FboztmleoS4WPgvCI7DqZXv8xrLSTuJqzmmuAtCTkgxkIKzfZ+jfJR6ODZg==}
engines: {node: '>=12'}
dependencies:
'@types/bintrees': 1.0.6
bintrees: 1.0.2
dev: false
/shx@0.3.4:
resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==}
engines: {node: '>=6'}
@@ -18202,6 +18240,7 @@ packages:
dev: false
time:
/@pnpm/registry-mock@3.24.2: '2024-02-06T23:50:07.901Z'
/@pnpm/registry-mock@3.25.0: '2024-02-08T22:03:39.172Z'
/fuse-native@2.2.6: '2020-06-03T19:26:36.838Z'
/graph-cycles@1.2.1: '2021-04-11T09:47:00.295Z'
/node-gyp@10.0.1: '2023-11-02T18:13:42.360Z'

View File

@@ -64,7 +64,7 @@
"@pnpm/prepare": "workspace:*",
"@pnpm/read-package-json": "workspace:*",
"@pnpm/read-project-manifest": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/run-npm": "workspace:*",
"@pnpm/tabtab": "^0.5.2",
"@pnpm/test-fixtures": "workspace:*",

View File

@@ -39,7 +39,7 @@
"@pnpm/lockfile-types": "workspace:*",
"@pnpm/plugin-commands-deploy": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2"
"@pnpm/registry-mock": "3.25.0"
},
"dependencies": {
"@pnpm/cli-utils": "workspace:*",

View File

@@ -35,7 +35,7 @@
"@pnpm/filter-workspace-packages": "workspace:*",
"@pnpm/plugin-commands-publishing": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-ipc-server": "workspace:*",
"@types/cross-spawn": "^6.0.5",
"@types/is-windows": "^1.0.2",

View File

@@ -36,7 +36,7 @@
"@pnpm/plugin-commands-licenses": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/read-package-json": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-fixtures": "workspace:*",
"@types/ramda": "0.28.20",
"@types/semver": "7.5.3",

View File

@@ -34,7 +34,7 @@
"@pnpm/plugin-commands-installation": "workspace:*",
"@pnpm/plugin-commands-listing": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@types/ramda": "0.28.20",
"execa": "npm:safe-execa@0.1.2",
"strip-ansi": "^6.0.1",

View File

@@ -35,7 +35,7 @@
"@pnpm/plugin-commands-installation": "workspace:*",
"@pnpm/plugin-commands-outdated": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@pnpm/test-fixtures": "workspace:*",
"@types/ramda": "0.28.20",
"@types/wrap-ansi": "8.0.2",

View File

@@ -34,7 +34,7 @@
"@pnpm/lockfile-file": "workspace:*",
"@pnpm/plugin-commands-store": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@pnpm/registry-mock": "3.24.2",
"@pnpm/registry-mock": "3.25.0",
"@types/archy": "0.0.33",
"@types/ramda": "0.28.20",
"@types/ssri": "^7.1.4",