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 });