/
autoInstallPeers.ts
268 lines (246 loc) · 9.71 KB
/
autoInstallPeers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
import path from 'path'
import assertProject from '@pnpm/assert-project'
import { addDependenciesToPackage, install, mutateModules } from '@pnpm/core'
import { prepareEmpty, preparePackages } from '@pnpm/prepare'
import { addDistTag, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import rimraf from '@zkochan/rimraf'
import { createPeersFolderSuffix } from 'dependency-path'
import { testDefaults } from '../utils'
test('auto install non-optional peer dependencies', async () => {
await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' })
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/abc-optional-peers@1.0.0'], await testDefaults({ autoInstallPeers: true }))
const lockfile = await project.readLockfile()
expect(Object.keys(lockfile.packages)).toStrictEqual([
'/@pnpm.e2e/abc-optional-peers/1.0.0_@pnpm.e2e+peer-a@1.0.0',
'/@pnpm.e2e/peer-a/1.0.0',
])
await project.hasNot('@pnpm.e2e/peer-a')
})
test('auto install the common peer dependency', async () => {
await addDistTag({ package: '@pnpm.e2e/peer-c', version: '1.0.1', distTag: 'latest' })
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/wants-peer-c-1', '@pnpm.e2e/wants-peer-c-1.0.0'], await testDefaults({ autoInstallPeers: true }))
const lockfile = await project.readLockfile()
expect(Object.keys(lockfile.packages)).toStrictEqual([
'/@pnpm.e2e/peer-c/1.0.0',
'/@pnpm.e2e/wants-peer-c-1.0.0/1.0.0_@pnpm.e2e+peer-c@1.0.0',
'/@pnpm.e2e/wants-peer-c-1/1.0.0_@pnpm.e2e+peer-c@1.0.0',
])
})
test('do not auto install when there is no common peer dependency range intersection', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm.e2e/wants-peer-c-1', '@pnpm.e2e/wants-peer-c-2'], await testDefaults({ autoInstallPeers: true }))
const lockfile = await project.readLockfile()
expect(Object.keys(lockfile.packages)).toStrictEqual([
'/@pnpm.e2e/wants-peer-c-1/1.0.0',
'/@pnpm.e2e/wants-peer-c-2/1.0.0',
])
})
test('don\'t fail on linked package, when peers are auto installed', async () => {
const pkgManifest = {
dependencies: {
linked: 'link:../linked',
},
}
preparePackages([
{
location: 'linked',
package: {
name: 'linked',
peerDependencies: {
'peer-c': '1.0.0',
},
},
},
{
location: 'pkg',
package: pkgManifest,
},
])
process.chdir('pkg')
const updatedManifest = await addDependenciesToPackage(pkgManifest, ['@pnpm.e2e/peer-b'], await testDefaults({ autoInstallPeers: true }))
expect(Object.keys(updatedManifest.dependencies ?? {})).toStrictEqual(['linked', '@pnpm.e2e/peer-b'])
})
test('hoist a peer dependency in order to reuse it by other dependencies, when it satisfies them', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, ['@pnpm/xyz-parent-parent-parent-parent', '@pnpm/xyz-parent-parent-with-xyz'], await testDefaults({ autoInstallPeers: true }))
const lockfile = await project.readLockfile()
const suffix = createPeersFolderSuffix([{ name: '@pnpm/x', version: '1.0.0' }, { name: '@pnpm/y', version: '1.0.0' }, { name: '@pnpm/z', version: '1.0.0' }])
expect(Object.keys(lockfile.packages)).toStrictEqual([
'/@pnpm/x/1.0.0',
`/@pnpm/xyz-parent-parent-parent-parent/1.0.0${suffix}`,
`/@pnpm/xyz-parent-parent-parent/1.0.0${suffix}`,
'/@pnpm/xyz-parent-parent-with-xyz/1.0.0',
`/@pnpm/xyz-parent-parent/1.0.0${suffix}`,
`/@pnpm/xyz-parent/1.0.0${suffix}`,
`/@pnpm/xyz/1.0.0${suffix}`,
'/@pnpm/y/1.0.0',
'/@pnpm/z/1.0.0',
])
})
test('don\'t hoist a peer dependency when there is a root dependency by that name', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, [
'@pnpm/xyz-parent-parent-parent-parent',
'@pnpm/xyz-parent-parent-with-xyz',
'@pnpm/x@npm:@pnpm.e2e/peer-a@1.0.0',
`http://localhost:${REGISTRY_MOCK_PORT}/@pnpm/y/-/y-2.0.0.tgz`,
], await testDefaults({ autoInstallPeers: true }))
const lockfile = await project.readLockfile()
const suffix1 = createPeersFolderSuffix([{ name: '@pnpm/y', version: '2.0.0' }, { name: '@pnpm/z', version: '1.0.0' }, { name: '@pnpm.e2e/peer-a', version: '1.0.0' }])
const suffix2 = createPeersFolderSuffix([{ name: '@pnpm/x', version: '1.0.0' }, { name: '@pnpm/y', version: '1.0.0' }, { name: '@pnpm/z', version: '1.0.0' }])
expect(Object.keys(lockfile.packages).sort()).toStrictEqual([
'/@pnpm.e2e/peer-a/1.0.0',
'/@pnpm/x/1.0.0',
`/@pnpm/xyz-parent-parent-parent-parent/1.0.0${suffix1}`,
`/@pnpm/xyz-parent-parent-parent/1.0.0${suffix1}`,
'/@pnpm/xyz-parent-parent-with-xyz/1.0.0',
`/@pnpm/xyz-parent-parent/1.0.0${suffix1}`,
`/@pnpm/xyz-parent/1.0.0${suffix1}`,
`/@pnpm/xyz-parent/1.0.0${suffix2}`,
`/@pnpm/xyz/1.0.0${suffix1}`,
`/@pnpm/xyz/1.0.0${suffix2}`,
'/@pnpm/y/1.0.0',
'/@pnpm/y/2.0.0',
'/@pnpm/z/1.0.0',
].sort())
})
test('don\'t auto-install a peer dependency, when that dependency is in the root', async () => {
const project = prepareEmpty()
await addDependenciesToPackage({}, [
'@pnpm/xyz-parent-parent-parent-parent',
'@pnpm/x@npm:@pnpm.e2e/peer-a@1.0.0',
`http://localhost:${REGISTRY_MOCK_PORT}/@pnpm/y/-/y-2.0.0.tgz`,
], await testDefaults({ autoInstallPeers: true }))
const lockfile = await project.readLockfile()
const suffix = createPeersFolderSuffix([{ name: '@pnpm/y', version: '2.0.0' }, { name: '@pnpm/z', version: '1.0.0' }, { name: '@pnpm.e2e/peer-a', version: '1.0.0' }])
expect(Object.keys(lockfile.packages).sort()).toStrictEqual([
`/@pnpm/xyz-parent-parent-parent-parent/1.0.0${suffix}`,
`/@pnpm/xyz-parent-parent-parent/1.0.0${suffix}`,
`/@pnpm/xyz-parent-parent/1.0.0${suffix}`,
`/@pnpm/xyz-parent/1.0.0${suffix}`,
`/@pnpm/xyz/1.0.0${suffix}`,
'/@pnpm/y/2.0.0',
'/@pnpm/z/1.0.0',
'/@pnpm.e2e/peer-a/1.0.0',
].sort())
})
test('don\'t install the same missing peer dependency twice', async () => {
await addDistTag({ package: '@pnpm/y', version: '2.0.0', distTag: 'latest' })
const project = prepareEmpty()
await addDependenciesToPackage({}, [
'@pnpm.e2e/has-has-y-peer-peer',
], await testDefaults({ autoInstallPeers: true }))
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${createPeersFolderSuffix([{ name: '@pnpm/y', version: '1.0.0' }, { name: '@pnpm.e2e/has-y-peer', version: '1.0.0' }])}`,
'/@pnpm.e2e/has-y-peer/1.0.0_@pnpm+y@1.0.0',
].sort())
})
test('automatically install root peer dependencies', async () => {
const project = prepareEmpty()
let manifest = await install({
dependencies: {
'is-negative': '^1.0.0',
},
peerDependencies: {
'is-positive': '^1.0.0',
},
}, await testDefaults({ autoInstallPeers: true }))
await project.has('is-positive')
await project.has('is-negative')
{
const lockfile = await project.readLockfile()
expect(lockfile.specifiers).toStrictEqual({
'is-positive': '^1.0.0',
'is-negative': '^1.0.0',
})
expect(lockfile.dependencies).toStrictEqual({
'is-positive': '1.0.0',
'is-negative': '1.0.1',
})
}
// Automatically install the peer dependency when the lockfile is up to date
await rimraf('node_modules')
await install(manifest, await testDefaults({ autoInstallPeers: true, frozenLockfile: true }))
await project.has('is-positive')
await project.has('is-negative')
// The auto installed peer is not removed when a new dependency is added
manifest = await addDependenciesToPackage(manifest, ['is-odd@1.0.0'], await testDefaults({ autoInstallPeers: true }))
await project.has('is-odd')
await project.has('is-positive')
await project.has('is-negative')
{
const lockfile = await project.readLockfile()
expect(lockfile.specifiers).toStrictEqual({
'is-odd': '1.0.0',
'is-positive': '^1.0.0',
'is-negative': '^1.0.0',
})
expect(lockfile.dependencies).toStrictEqual({
'is-odd': '1.0.0',
'is-positive': '1.0.0',
'is-negative': '1.0.1',
})
}
// The auto installed peer is not removed when a dependency is removed
await mutateModules([
{
dependencyNames: ['is-odd'],
manifest,
mutation: 'uninstallSome',
rootDir: process.cwd(),
},
], await testDefaults({ autoInstallPeers: true }))
await project.hasNot('is-odd')
await project.has('is-positive')
await project.has('is-negative')
{
const lockfile = await project.readLockfile()
expect(lockfile.specifiers).toStrictEqual({
'is-positive': '^1.0.0',
'is-negative': '^1.0.0',
})
expect(lockfile.dependencies).toStrictEqual({
'is-positive': '1.0.0',
'is-negative': '1.0.1',
})
}
})
test('automatically install peer dependency when it is a dev dependency in another workspace project', async () => {
prepareEmpty()
await mutateModules([
{
buildIndex: 0,
manifest: {
name: 'project-1',
devDependencies: {
'is-positive': '1.0.0',
},
},
mutation: 'install',
rootDir: path.resolve('project-1'),
},
{
buildIndex: 0,
manifest: {
name: 'project-2',
peerDependencies: {
'is-positive': '1.0.0',
},
},
mutation: 'install',
rootDir: path.resolve('project-2'),
},
], await testDefaults({ autoInstallPeers: true }))
const project = assertProject(process.cwd())
const lockfile = await project.readLockfile()
expect(lockfile.importers['project-1'].devDependencies).toStrictEqual({
'is-positive': '1.0.0',
})
expect(lockfile.importers['project-2'].dependencies).toStrictEqual({
'is-positive': '1.0.0',
})
})