@@ -49,7 +49,7 @@ interface AddCommandTaskContext {
49
49
savePackage ?: NgAddSaveDependency ;
50
50
collectionName ?: string ;
51
51
executeSchematic : AddCommandModule [ 'executeSchematic' ] ;
52
- hasMismatchedPeer : AddCommandModule [ 'hasMismatchedPeer ' ] ;
52
+ getPeerDependencyConflicts : AddCommandModule [ 'getPeerDependencyConflicts ' ] ;
53
53
}
54
54
55
55
type AddCommandTaskWrapper = ListrTaskWrapper <
@@ -70,6 +70,8 @@ const packageVersionExclusions: Record<string, string | Range> = {
70
70
'@angular/material' : '7.x' ,
71
71
} ;
72
72
73
+ const DEFAULT_CONFLICT_DISPLAY_LIMIT = 5 ;
74
+
73
75
export default class AddCommandModule
74
76
extends SchematicsCommandModule
75
77
implements CommandModuleImplementation < AddCommandArgs >
@@ -158,7 +160,7 @@ export default class AddCommandModule
158
160
const taskContext : AddCommandTaskContext = {
159
161
packageIdentifier,
160
162
executeSchematic : this . executeSchematic . bind ( this ) ,
161
- hasMismatchedPeer : this . hasMismatchedPeer . bind ( this ) ,
163
+ getPeerDependencyConflicts : this . getPeerDependencyConflicts . bind ( this ) ,
162
164
} ;
163
165
164
166
const tasks = new Listr < AddCommandTaskContext > (
@@ -248,69 +250,83 @@ export default class AddCommandModule
248
250
throw new CommandError ( `Unable to load package information from registry: ${ e . message } ` ) ;
249
251
}
250
252
253
+ const rejectionReasons : string [ ] = [ ] ;
254
+
251
255
// Start with the version tagged as `latest` if it exists
252
256
const latestManifest = packageMetadata . tags [ 'latest' ] ;
253
257
if ( latestManifest ) {
254
- context . packageIdentifier = npa . resolve ( latestManifest . name , latestManifest . version ) ;
258
+ const latestConflicts = await this . getPeerDependencyConflicts ( latestManifest ) ;
259
+ if ( latestConflicts ) {
260
+ // 'latest' is invalid so search for most recent matching package
261
+ rejectionReasons . push ( ...latestConflicts ) ;
262
+ } else {
263
+ context . packageIdentifier = npa . resolve ( latestManifest . name , latestManifest . version ) ;
264
+ task . output = `Found compatible package version: ${ color . blue ( latestManifest . version ) } .` ;
265
+
266
+ return ;
267
+ }
255
268
}
256
269
257
- // Adjust the version based on name and peer dependencies
258
- if (
259
- latestManifest ?. peerDependencies &&
260
- Object . keys ( latestManifest . peerDependencies ) . length === 0
261
- ) {
262
- task . output = `Found compatible package version: ${ color . blue ( latestManifest . version ) } .` ;
263
- } else if ( ! latestManifest || ( await context . hasMismatchedPeer ( latestManifest ) ) ) {
264
- // 'latest' is invalid so search for most recent matching package
265
-
266
- // Allow prelease versions if the CLI itself is a prerelease
267
- const allowPrereleases = prerelease ( VERSION . full ) ;
268
-
269
- const versionExclusions = packageVersionExclusions [ packageMetadata . name ] ;
270
- const versionManifests = Object . values ( packageMetadata . versions ) . filter (
271
- ( value : PackageManifest ) => {
272
- // Prerelease versions are not stable and should not be considered by default
273
- if ( ! allowPrereleases && prerelease ( value . version ) ) {
274
- return false ;
275
- }
276
- // Deprecated versions should not be used or considered
277
- if ( value . deprecated ) {
278
- return false ;
279
- }
280
- // Excluded package versions should not be considered
281
- if (
282
- versionExclusions &&
283
- satisfies ( value . version , versionExclusions , { includePrerelease : true } )
284
- ) {
285
- return false ;
286
- }
270
+ // Allow prelease versions if the CLI itself is a prerelease
271
+ const allowPrereleases = prerelease ( VERSION . full ) ;
287
272
288
- return true ;
289
- } ,
290
- ) ;
273
+ const versionExclusions = packageVersionExclusions [ packageMetadata . name ] ;
274
+ const versionManifests = Object . values ( packageMetadata . versions ) . filter (
275
+ ( value : PackageManifest ) => {
276
+ // Already checked the 'latest' version
277
+ if ( latestManifest . version === value . version ) {
278
+ return false ;
279
+ }
280
+ // Prerelease versions are not stable and should not be considered by default
281
+ if ( ! allowPrereleases && prerelease ( value . version ) ) {
282
+ return false ;
283
+ }
284
+ // Deprecated versions should not be used or considered
285
+ if ( value . deprecated ) {
286
+ return false ;
287
+ }
288
+ // Excluded package versions should not be considered
289
+ if (
290
+ versionExclusions &&
291
+ satisfies ( value . version , versionExclusions , { includePrerelease : true } )
292
+ ) {
293
+ return false ;
294
+ }
291
295
292
- // Sort in reverse SemVer order so that the newest compatible version is chosen
293
- versionManifests . sort ( ( a , b ) => compare ( b . version , a . version , true ) ) ;
296
+ return true ;
297
+ } ,
298
+ ) ;
294
299
295
- let found = false ;
296
- for ( const versionManifest of versionManifests ) {
297
- const mismatch = await context . hasMismatchedPeer ( versionManifest ) ;
298
- if ( mismatch ) {
299
- continue ;
300
- }
300
+ // Sort in reverse SemVer order so that the newest compatible version is chosen
301
+ versionManifests . sort ( ( a , b ) => compare ( b . version , a . version , true ) ) ;
301
302
302
- context . packageIdentifier = npa . resolve ( versionManifest . name , versionManifest . version ) ;
303
- found = true ;
304
- break ;
303
+ let found = false ;
304
+ for ( const versionManifest of versionManifests ) {
305
+ const conflicts = await this . getPeerDependencyConflicts ( versionManifest ) ;
306
+ if ( conflicts ) {
307
+ if ( options . verbose || rejectionReasons . length < DEFAULT_CONFLICT_DISPLAY_LIMIT ) {
308
+ rejectionReasons . push ( ...conflicts ) ;
309
+ }
310
+ continue ;
305
311
}
306
312
307
- if ( ! found ) {
308
- task . output = "Unable to find compatible package. Using 'latest' tag." ;
309
- } else {
310
- task . output = `Found compatible package version: ${ color . blue (
311
- context . packageIdentifier . toString ( ) ,
312
- ) } .`;
313
+ context . packageIdentifier = npa . resolve ( versionManifest . name , versionManifest . version ) ;
314
+ found = true ;
315
+ break ;
316
+ }
317
+
318
+ if ( ! found ) {
319
+ let message = `Unable to find compatible package. Using 'latest' tag.` ;
320
+ if ( rejectionReasons . length > 0 ) {
321
+ message +=
322
+ '\nThis is often because of incompatible peer dependencies.\n' +
323
+ 'These versions were rejected due to the following conflicts:\n' +
324
+ rejectionReasons
325
+ . slice ( 0 , options . verbose ? undefined : DEFAULT_CONFLICT_DISPLAY_LIMIT )
326
+ . map ( ( r ) => ` - ${ r } ` )
327
+ . join ( '\n' ) ;
313
328
}
329
+ task . output = message ;
314
330
} else {
315
331
task . output = `Found compatible package version: ${ color . blue (
316
332
context . packageIdentifier . toString ( ) ,
@@ -343,7 +359,7 @@ export default class AddCommandModule
343
359
context . savePackage = manifest [ 'ng-add' ] ?. save ;
344
360
context . collectionName = manifest . name ;
345
361
346
- if ( await context . hasMismatchedPeer ( manifest ) ) {
362
+ if ( await this . getPeerDependencyConflicts ( manifest ) ) {
347
363
task . output = color . yellow (
348
364
figures . warning +
349
365
' Package has unmet peer dependencies. Adding the package may not succeed.' ,
@@ -563,7 +579,8 @@ export default class AddCommandModule
563
579
return null ;
564
580
}
565
581
566
- private async hasMismatchedPeer ( manifest : PackageManifest ) : Promise < boolean > {
582
+ private async getPeerDependencyConflicts ( manifest : PackageManifest ) : Promise < string [ ] | false > {
583
+ const conflicts : string [ ] = [ ] ;
567
584
for ( const peer in manifest . peerDependencies ) {
568
585
let peerIdentifier ;
569
586
try {
@@ -586,7 +603,10 @@ export default class AddCommandModule
586
603
! intersects ( version , peerIdentifier . rawSpec , options ) &&
587
604
! satisfies ( version , peerIdentifier . rawSpec , options )
588
605
) {
589
- return true ;
606
+ conflicts . push (
607
+ `Package "${ manifest . name } @${ manifest . version } " has an incompatible peer dependency to "` +
608
+ `${ peer } @${ peerIdentifier . rawSpec } " (requires "${ version } " in project).` ,
609
+ ) ;
590
610
}
591
611
} catch {
592
612
// Not found or invalid so ignore
@@ -598,6 +618,6 @@ export default class AddCommandModule
598
618
}
599
619
}
600
620
601
- return false ;
621
+ return conflicts . length > 0 && conflicts ;
602
622
}
603
623
}
0 commit comments