From 3f67f73de9b0971325ac5c3091543e70200c156c Mon Sep 17 00:00:00 2001 From: MeAkib Date: Sat, 31 Oct 2026 01:16:19 +0600 Subject: [PATCH] fix(@angular/cli): ignore registry manifests that do not match the requested version Modern yarn returns the manifest of the latest version instead of an error when the requested version or range does not exist (for example, `yarn npm info @angular/core@99.99.99` prints the latest version with exit code 0). As a result, `ng add` and `ng update` could continue with an unrelated version instead of reporting that the requested version does not exist. Return `null` from `getRegistryManifest` when the fetched version does not satisfy the requested version or range. --- .../cli/src/package-managers/package-manager.ts | 11 ++++++++++- .../src/package-managers/package-manager_spec.ts | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/angular/cli/src/package-managers/package-manager.ts b/packages/angular/cli/src/package-managers/package-manager.ts index 8e68279a5c20..c7f2aa279c85 100644 --- a/packages/angular/cli/src/package-managers/package-manager.ts +++ b/packages/angular/cli/src/package-managers/package-manager.ts @@ -14,7 +14,7 @@ import { join, relative, resolve } from 'node:path'; import npa from 'npm-package-arg'; -import { maxSatisfying, valid } from 'semver'; +import { maxSatisfying, satisfies, valid, validRange } from 'semver'; import { PackageManagerError } from './error'; import { Host } from './host'; import { Logger } from './logger'; @@ -564,6 +564,15 @@ export class PackageManager { { ...options, cache: this.#manifestCache, cacheKey }, ); + // Modern yarn returns the latest version instead of an error when the requested one does not exist. + if ( + manifest && + validRange(version) && + !satisfies(manifest.version, version, { includePrerelease: true }) + ) { + return null; + } + // If the provided version was not a specific version, also cache the specific fetched version if (manifest && manifest.version !== version) { const manifestSpecifier = `${manifest.name}@${manifest.version}`; diff --git a/packages/angular/cli/src/package-managers/package-manager_spec.ts b/packages/angular/cli/src/package-managers/package-manager_spec.ts index e6b8f95c0c4c..f215661032a0 100644 --- a/packages/angular/cli/src/package-managers/package-manager_spec.ts +++ b/packages/angular/cli/src/package-managers/package-manager_spec.ts @@ -22,6 +22,21 @@ describe('PackageManager', () => { }); describe('getRegistryManifest', () => { + it('should return null when the fetched manifest does not match the requested version', async () => { + // Modern yarn falls back to the latest version when the requested version does not exist. + const pm = new PackageManager(host, '/tmp', SUPPORTED_PACKAGE_MANAGERS['yarn']); + runCommandSpy.and.resolveTo({ + stdout: JSON.stringify({ name: 'foo', version: '2.0.0' }), + stderr: '', + }); + + expect(await pm.getRegistryManifest('foo', '99.99.99')).toBeNull(); + expect(await pm.getRegistryManifest('foo', '^99.0.0')).toBeNull(); + expect(await pm.getRegistryManifest('foo', '^2.0.0')).toEqual( + jasmine.objectContaining({ version: '2.0.0' }), + ); + }); + it('should quote complex range specifiers when required by the host', async () => { // Simulate a quoting host Object.assign(host, { requiresQuoting: true });