From 5139035c0bf9d8ea028647973651727851e33a61 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 2 Apr 2026 19:43:05 +0530 Subject: [PATCH 1/2] fix(@angular/cli): gracefully skip unfetchable packages during ng update When package.json contains packages from non-npmjs registries (JSR, AWS CodeArtifact, private registries, local workspace packages), the registry fetch inside Promise.all would reject and hard-fail the entire update command with a 404 or network error. Wrap each per-package getNpmPackageJson call in a .catch() so that fetch failures emit a warning and return a partial sentinel object. The existing reduce step already handles partial objects correctly: auto-discovered packages are silently skipped while explicitly-requested packages still produce a clear SchematicsException, preserving intentional error visibility. Closes #28834 --- .../src/commands/update/schematic/index.ts | 10 +++++ .../commands/update/schematic/index_spec.ts | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/packages/angular/cli/src/commands/update/schematic/index.ts b/packages/angular/cli/src/commands/update/schematic/index.ts index e110480d09a5..3dbf7f9cbaea 100644 --- a/packages/angular/cli/src/commands/update/schematic/index.ts +++ b/packages/angular/cli/src/commands/update/schematic/index.ts @@ -854,6 +854,16 @@ export default function (options: UpdateSchema): Rule { registry: options.registry, usingYarn, verbose: options.verbose, + }).catch((error: unknown) => { + // If the package cannot be fetched (e.g. private registry, JSR, AWS CodeArtifact, + // or local workspace packages), return a partial object so the reduce below can + // decide whether to warn or hard-fail based on whether it was explicitly requested. + const message = error instanceof Error ? error.message : String(error); + logger.warn( + `Package '${depName}' could not be fetched from the registry: ${message}`, + ); + + return { requestedName: depName } as Partial; }), ), ); diff --git a/packages/angular/cli/src/commands/update/schematic/index_spec.ts b/packages/angular/cli/src/commands/update/schematic/index_spec.ts index 11b2a0b5855e..7fe6c5a56274 100644 --- a/packages/angular/cli/src/commands/update/schematic/index_spec.ts +++ b/packages/angular/cli/src/commands/update/schematic/index_spec.ts @@ -336,6 +336,48 @@ describe('@schematics/update', () => { expect(resultTreeContent.endsWith('}')).toBeTrue(); }); + it('skips packages that cannot be fetched from the registry and continues updating others', async () => { + // Regression test for https://github.com/angular/angular-cli/issues/28834 + // Packages from private registries, JSR, AWS CodeArtifact, or local workspaces + // may resolve as npm-registry packages (pass isPkgFromRegistry) but fail to fetch + // with a 404. The schematic should warn and skip them rather than hard-failing. + const inputTree = new UnitTestTree( + new HostTree( + new virtualFs.test.TestHost({ + '/package.json': JSON.stringify({ + name: 'blah', + dependencies: { + // A real package that can be updated: + '@angular-devkit-tests/update-base': '1.0.0', + // A scoped package that does not exist on the npm registry (simulates a + // private / JSR / CodeArtifact package that passes the registry specifier + // check but returns a 404 when fetched): + '@private-nonexistent/package-ng-update-issue-28834': '1.0.0', + }, + }), + }), + ), + ); + + const messages: string[] = []; + schematicRunner.logger.subscribe((x) => messages.push(x.message)); + + // Should NOT throw even though one package cannot be fetched. + const resultTree = await schematicRunner.runSchematic('update', undefined, inputTree); + + // The unfetchable package should be warned about. + expect( + messages.some((m) => m.includes('@private-nonexistent/package-ng-update-issue-28834')), + ).toBeTrue(); + + // The package.json should be unchanged (nothing to update in no-packages mode). + const { dependencies } = resultTree.readJson('/package.json') as { + dependencies: Record; + }; + expect(dependencies['@angular-devkit-tests/update-base']).toBe('1.0.0'); + expect(dependencies['@private-nonexistent/package-ng-update-issue-28834']).toBe('1.0.0'); + }, 45000); + it('updates group members to the same version as the targeted package', async () => { const packageJsonContent = `{ "name": "test", From d349179796e1e8d47e63d7cbef2fe53233fe20a5 Mon Sep 17 00:00:00 2001 From: Maruthan G Date: Thu, 2 Apr 2026 21:03:30 +0530 Subject: [PATCH 2/2] fix(@angular/cli): improve error handling and test for skipped registry packages --- .../src/commands/update/schematic/index.ts | 2 +- .../commands/update/schematic/index_spec.ts | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/angular/cli/src/commands/update/schematic/index.ts b/packages/angular/cli/src/commands/update/schematic/index.ts index 3dbf7f9cbaea..4741ff6e8661 100644 --- a/packages/angular/cli/src/commands/update/schematic/index.ts +++ b/packages/angular/cli/src/commands/update/schematic/index.ts @@ -858,7 +858,7 @@ export default function (options: UpdateSchema): Rule { // If the package cannot be fetched (e.g. private registry, JSR, AWS CodeArtifact, // or local workspace packages), return a partial object so the reduce below can // decide whether to warn or hard-fail based on whether it was explicitly requested. - const message = error instanceof Error ? error.message : String(error); + const message = (error as { message?: string }).message ?? String(error); logger.warn( `Package '${depName}' could not be fetched from the registry: ${message}`, ); diff --git a/packages/angular/cli/src/commands/update/schematic/index_spec.ts b/packages/angular/cli/src/commands/update/schematic/index_spec.ts index 7fe6c5a56274..8de48f0d4fc9 100644 --- a/packages/angular/cli/src/commands/update/schematic/index_spec.ts +++ b/packages/angular/cli/src/commands/update/schematic/index_spec.ts @@ -336,18 +336,19 @@ describe('@schematics/update', () => { expect(resultTreeContent.endsWith('}')).toBeTrue(); }); - it('skips packages that cannot be fetched from the registry and continues updating others', async () => { + it('continues updating others when one package fetch fails', async () => { // Regression test for https://github.com/angular/angular-cli/issues/28834 // Packages from private registries, JSR, AWS CodeArtifact, or local workspaces // may resolve as npm-registry packages (pass isPkgFromRegistry) but fail to fetch - // with a 404. The schematic should warn and skip them rather than hard-failing. + // with a 404. The schematic should warn and skip them rather than hard-failing, + // and other explicitly-requested packages should still be updated. const inputTree = new UnitTestTree( new HostTree( new virtualFs.test.TestHost({ '/package.json': JSON.stringify({ name: 'blah', dependencies: { - // A real package that can be updated: + // A real package that should be updated: '@angular-devkit-tests/update-base': '1.0.0', // A scoped package that does not exist on the npm registry (simulates a // private / JSR / CodeArtifact package that passes the registry specifier @@ -363,18 +364,22 @@ describe('@schematics/update', () => { schematicRunner.logger.subscribe((x) => messages.push(x.message)); // Should NOT throw even though one package cannot be fetched. - const resultTree = await schematicRunner.runSchematic('update', undefined, inputTree); + const resultTree = await schematicRunner.runSchematic( + 'update', + { packages: ['@angular-devkit-tests/update-base'] }, + inputTree, + ); - // The unfetchable package should be warned about. + // The unfetchable package should produce a warning. expect( messages.some((m) => m.includes('@private-nonexistent/package-ng-update-issue-28834')), ).toBeTrue(); - // The package.json should be unchanged (nothing to update in no-packages mode). + // The valid package should have been updated despite the other package failing. const { dependencies } = resultTree.readJson('/package.json') as { dependencies: Record; }; - expect(dependencies['@angular-devkit-tests/update-base']).toBe('1.0.0'); + expect(dependencies['@angular-devkit-tests/update-base']).toBe('1.1.0'); expect(dependencies['@private-nonexistent/package-ng-update-issue-28834']).toBe('1.0.0'); }, 45000);