Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/angular/cli/src/commands/update/schematic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message extraction could be more robust. If error is a plain object with a message property (which can happen with some HTTP clients or custom errors), instanceof Error will be false and String(error) will result in [object Object]. Consider using a fallback to the .message property if it exists to provide a more helpful warning.

Suggested change
const message = error instanceof Error ? error.message : String(error);
const message = error instanceof Error ? error.message : (error as any)?.message ?? String(error);

logger.warn(
`Package '${depName}' could not be fetched from the registry: ${message}`,
);

return { requestedName: depName } as Partial<NpmRepositoryPackageJson>;
}),
),
);
Expand Down
42 changes: 42 additions & 0 deletions packages/angular/cli/src/commands/update/schematic/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test case name indicates that it verifies the schematic 'continues updating others', but the current implementation only verifies that the analysis phase completes without throwing when no packages are requested for update. To fully validate the intended behavior, consider requesting an update for a valid package (e.g., @angular-devkit-tests/update-base) and asserting that it is successfully updated despite the fetch failure of the other package.

// 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<string, string>;
};
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",
Expand Down
Loading