@@ -49,7 +49,7 @@ interface AddCommandTaskContext {
4949 savePackage ?: NgAddSaveDependency ;
5050 collectionName ?: string ;
5151 executeSchematic : AddCommandModule [ 'executeSchematic' ] ;
52- hasMismatchedPeer : AddCommandModule [ 'hasMismatchedPeer ' ] ;
52+ getPeerDependencyConflicts : AddCommandModule [ 'getPeerDependencyConflicts ' ] ;
5353}
5454
5555type AddCommandTaskWrapper = ListrTaskWrapper <
@@ -70,6 +70,8 @@ const packageVersionExclusions: Record<string, string | Range> = {
7070 '@angular/material' : '7.x' ,
7171} ;
7272
73+ const DEFAULT_CONFLICT_DISPLAY_LIMIT = 5 ;
74+
7375export default class AddCommandModule
7476 extends SchematicsCommandModule
7577 implements CommandModuleImplementation < AddCommandArgs >
@@ -158,7 +160,7 @@ export default class AddCommandModule
158160 const taskContext : AddCommandTaskContext = {
159161 packageIdentifier,
160162 executeSchematic : this . executeSchematic . bind ( this ) ,
161- hasMismatchedPeer : this . hasMismatchedPeer . bind ( this ) ,
163+ getPeerDependencyConflicts : this . getPeerDependencyConflicts . bind ( this ) ,
162164 } ;
163165
164166 const tasks = new Listr < AddCommandTaskContext > (
@@ -248,69 +250,83 @@ export default class AddCommandModule
248250 throw new CommandError ( `Unable to load package information from registry: ${ e . message } ` ) ;
249251 }
250252
253+ const rejectionReasons : string [ ] = [ ] ;
254+
251255 // Start with the version tagged as `latest` if it exists
252256 const latestManifest = packageMetadata . tags [ 'latest' ] ;
253257 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+ }
255268 }
256269
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 ) ;
287272
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+ }
291295
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+ ) ;
294299
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 ) ) ;
301302
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 ;
305311 }
306312
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' ) ;
313328 }
329+ task . output = message ;
314330 } else {
315331 task . output = `Found compatible package version: ${ color . blue (
316332 context . packageIdentifier . toString ( ) ,
@@ -343,7 +359,7 @@ export default class AddCommandModule
343359 context . savePackage = manifest [ 'ng-add' ] ?. save ;
344360 context . collectionName = manifest . name ;
345361
346- if ( await context . hasMismatchedPeer ( manifest ) ) {
362+ if ( await this . getPeerDependencyConflicts ( manifest ) ) {
347363 task . output = color . yellow (
348364 figures . warning +
349365 ' Package has unmet peer dependencies. Adding the package may not succeed.' ,
@@ -563,7 +579,8 @@ export default class AddCommandModule
563579 return null ;
564580 }
565581
566- private async hasMismatchedPeer ( manifest : PackageManifest ) : Promise < boolean > {
582+ private async getPeerDependencyConflicts ( manifest : PackageManifest ) : Promise < string [ ] | false > {
583+ const conflicts : string [ ] = [ ] ;
567584 for ( const peer in manifest . peerDependencies ) {
568585 let peerIdentifier ;
569586 try {
@@ -586,7 +603,10 @@ export default class AddCommandModule
586603 ! intersects ( version , peerIdentifier . rawSpec , options ) &&
587604 ! satisfies ( version , peerIdentifier . rawSpec , options )
588605 ) {
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+ ) ;
590610 }
591611 } catch {
592612 // Not found or invalid so ignore
@@ -598,6 +618,6 @@ export default class AddCommandModule
598618 }
599619 }
600620
601- return false ;
621+ return conflicts . length > 0 && conflicts ;
602622 }
603623}
0 commit comments