Skip to content

Commit 5139035

Browse files
committed
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
1 parent 7fbc715 commit 5139035

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

packages/angular/cli/src/commands/update/schematic/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,16 @@ export default function (options: UpdateSchema): Rule {
854854
registry: options.registry,
855855
usingYarn,
856856
verbose: options.verbose,
857+
}).catch((error: unknown) => {
858+
// If the package cannot be fetched (e.g. private registry, JSR, AWS CodeArtifact,
859+
// or local workspace packages), return a partial object so the reduce below can
860+
// decide whether to warn or hard-fail based on whether it was explicitly requested.
861+
const message = error instanceof Error ? error.message : String(error);
862+
logger.warn(
863+
`Package '${depName}' could not be fetched from the registry: ${message}`,
864+
);
865+
866+
return { requestedName: depName } as Partial<NpmRepositoryPackageJson>;
857867
}),
858868
),
859869
);

packages/angular/cli/src/commands/update/schematic/index_spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,48 @@ describe('@schematics/update', () => {
336336
expect(resultTreeContent.endsWith('}')).toBeTrue();
337337
});
338338

339+
it('skips packages that cannot be fetched from the registry and continues updating others', async () => {
340+
// Regression test for https://github.com/angular/angular-cli/issues/28834
341+
// Packages from private registries, JSR, AWS CodeArtifact, or local workspaces
342+
// may resolve as npm-registry packages (pass isPkgFromRegistry) but fail to fetch
343+
// with a 404. The schematic should warn and skip them rather than hard-failing.
344+
const inputTree = new UnitTestTree(
345+
new HostTree(
346+
new virtualFs.test.TestHost({
347+
'/package.json': JSON.stringify({
348+
name: 'blah',
349+
dependencies: {
350+
// A real package that can be updated:
351+
'@angular-devkit-tests/update-base': '1.0.0',
352+
// A scoped package that does not exist on the npm registry (simulates a
353+
// private / JSR / CodeArtifact package that passes the registry specifier
354+
// check but returns a 404 when fetched):
355+
'@private-nonexistent/package-ng-update-issue-28834': '1.0.0',
356+
},
357+
}),
358+
}),
359+
),
360+
);
361+
362+
const messages: string[] = [];
363+
schematicRunner.logger.subscribe((x) => messages.push(x.message));
364+
365+
// Should NOT throw even though one package cannot be fetched.
366+
const resultTree = await schematicRunner.runSchematic('update', undefined, inputTree);
367+
368+
// The unfetchable package should be warned about.
369+
expect(
370+
messages.some((m) => m.includes('@private-nonexistent/package-ng-update-issue-28834')),
371+
).toBeTrue();
372+
373+
// The package.json should be unchanged (nothing to update in no-packages mode).
374+
const { dependencies } = resultTree.readJson('/package.json') as {
375+
dependencies: Record<string, string>;
376+
};
377+
expect(dependencies['@angular-devkit-tests/update-base']).toBe('1.0.0');
378+
expect(dependencies['@private-nonexistent/package-ng-update-issue-28834']).toBe('1.0.0');
379+
}, 45000);
380+
339381
it('updates group members to the same version as the targeted package', async () => {
340382
const packageJsonContent = `{
341383
"name": "test",

0 commit comments

Comments
 (0)