From 9435a678e9369d63a87303264d7244c86b742800 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:10:40 +0000 Subject: [PATCH] fix(@angular/cli): correctly report available package updates during bare ng update In v22.0.1, a performance optimization (4510dae) was introduced to fetch registry metadata lazily during `ng update`, only querying the registry for packages actively being updated or uninstalled. However, during a bare `ng update` to list available workspace updates (where `packages.size === 0`), `isUpdating` is false for every dependency. For installed dependencies with locked versions in package.json (no carets/tildes), localPkgJson exists, causing the resolver to fall back to local mock package info without registry versions or dist-tags. This commit updates the registry metadata check in `resolveUserUpdatePlan` to include listing mode (`packages.size === 0`), ensuring cached registry metadata is used to list available package updates. Closes #33712 --- .../src/commands/update/update-resolver.ts | 9 +++----- .../commands/update/update-resolver_spec.ts | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/angular/cli/src/commands/update/update-resolver.ts b/packages/angular/cli/src/commands/update/update-resolver.ts index 7b75ab7a9e20..995762d92bc9 100644 --- a/packages/angular/cli/src/commands/update/update-resolver.ts +++ b/packages/angular/cli/src/commands/update/update-resolver.ts @@ -942,11 +942,7 @@ export async function resolveUserUpdatePlan( }; if (packages.size === 0) { - await Promise.all( - Array.from(npmDeps.keys()).map(async (depName) => { - await getOrFetchPackageMetadata(depName); - }), - ); + await Promise.all(Array.from(npmDeps.keys(), (depName) => getOrFetchPackageMetadata(depName))); } else { let lastPackagesSize; do { @@ -999,12 +995,13 @@ export async function resolveUserUpdatePlan( } while (packages.size > lastPackagesSize); } + const isListingUpdates = packages.size === 0; const packageInfoEntries = await Promise.all( Array.from(npmDeps.keys(), async (depName) => { const isUpdating = packages.has(depName); const localPkgJson = getInstalledPackageJson(depName, workspaceRoot); - if (isUpdating || !localPkgJson) { + if (isListingUpdates || isUpdating || !localPkgJson) { const metadata = await getOrFetchPackageMetadata(depName); if (metadata) { const info = await _buildPackageInfo( diff --git a/packages/angular/cli/src/commands/update/update-resolver_spec.ts b/packages/angular/cli/src/commands/update/update-resolver_spec.ts index e18cba641016..9ced5829587e 100644 --- a/packages/angular/cli/src/commands/update/update-resolver_spec.ts +++ b/packages/angular/cli/src/commands/update/update-resolver_spec.ts @@ -289,6 +289,29 @@ describe('UpdateResolver', () => { expect(plan.packagesToUpdate.size).toBe(0); }); + it('includes registry metadata when listing updates with locked dependency versions in package.json', async () => { + createMockWorkspace( + { + name: 'blah', + dependencies: { + '@angular-devkit-tests/update-base': '1.0.0', + }, + }, + { + '@angular-devkit-tests/update-base': { version: '1.0.0' }, + }, + ); + + const plan = await resolvePlan({ + packages: [], + workspaceRoot: tempRoot, + }); + + const info = plan.packageInfoMap.get('@angular-devkit-tests/update-base'); + expect(info?.npmPackageJson['dist-tags']?.['latest']).toBe('1.1.0'); + expect(info?.npmPackageJson.versions).toContain('1.1.0'); + }); + it('should not error with yarn 2.0 protocols', async () => { createMockWorkspace( {