Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0d53e82

Browse files
committed
feat(@angular/cli): provide detailed peer dependency conflict errors in ng add
This commit enhances the `ng add` command's version resolution to provide more informative feedback to the user, especially when peer dependency conflicts occur. Key improvements include: - When a compatible version cannot be found, the command now lists the specific peer dependency conflicts that caused recent versions to be rejected. By default, up to 5 conflicts are shown. - The peer dependency check now collects and reports all conflicts for a given package version, not just the first one it finds. - In verbose mode (`--verbose`), all detected peer dependency conflicts will be displayed.
1 parent a8b049a commit 0d53e82

File tree

1 file changed

+77
-57
lines changed
  • packages/angular/cli/src/commands/add

1 file changed

+77
-57
lines changed

‎packages/angular/cli/src/commands/add/cli.ts‎

Lines changed: 77 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5555
type 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+
7375
export 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

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /