Skip to content

Commit d368d1a

Browse files
committed
fix(@angular/cli): handle pnpm catalog: protocol in ng update
When using pnpm's `catalog:` protocol for dependency versions in `package.json`, `ng update` would fail with "Package X was not found on the registry" because the `catalog:` specifier is not a valid npm package argument and cannot be resolved by the npm registry. This change adds two fixes: - `isPkgFromRegistry()` now recognizes `catalog:` as referencing registry packages instead of rejecting them - `_getAllDependencies()` resolves `catalog:` specifiers to `^<version>` using the installed version from node_modules, providing a valid semver range for downstream processing Fixes angular#30443
1 parent 7fbc715 commit d368d1a

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -773,11 +773,31 @@ function _getAllDependencies(tree: Tree): Array<readonly [string, VersionRange]>
773773
'/package.json',
774774
) as PackageManifest;
775775

776-
return [
776+
const allDeps = [
777777
...(Object.entries(peerDependencies || {}) as Array<[string, VersionRange]>),
778778
...(Object.entries(devDependencies || {}) as Array<[string, VersionRange]>),
779779
...(Object.entries(dependencies || {}) as Array<[string, VersionRange]>),
780780
];
781+
782+
// Resolve pnpm catalog: protocol references to actual version ranges.
783+
// The catalog: protocol is not a valid semver range and must be resolved
784+
// before downstream processing.
785+
return allDeps.map(([name, specifier]) => {
786+
if (!specifier.startsWith('catalog:')) {
787+
return [name, specifier] as const;
788+
}
789+
790+
// Fall back to the installed version from node_modules
791+
const pkgJsonPath = `/node_modules/${name}/package.json`;
792+
if (tree.exists(pkgJsonPath)) {
793+
const { version } = tree.readJson(pkgJsonPath) as PackageManifest;
794+
if (version) {
795+
return [name, `^${version}` as VersionRange] as const;
796+
}
797+
}
798+
799+
return [name, specifier] as const;
800+
});
781801
}
782802

783803
function _formatVersion(version: string | undefined) {
@@ -804,6 +824,11 @@ function _formatVersion(version: string | undefined) {
804824
* @throws When the specifier cannot be parsed.
805825
*/
806826
function isPkgFromRegistry(name: string, specifier: string): boolean {
827+
// pnpm catalog: protocol always references registry packages
828+
if (specifier.startsWith('catalog:')) {
829+
return true;
830+
}
831+
807832
const result = npa.resolve(name, specifier);
808833

809834
return !!result.registry;

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,35 @@ describe('@schematics/update', () => {
9797
expect(dependencies['@angular-devkit-tests/update-base']).toBe('1.1.0');
9898
});
9999

100+
it('should not error with pnpm catalog: protocol', async () => {
101+
const newTree = new UnitTestTree(
102+
new HostTree(
103+
new virtualFs.test.TestHost({
104+
'/package.json': `{
105+
"name": "blah",
106+
"dependencies": {
107+
"@angular-devkit-tests/update-base": "catalog:"
108+
}
109+
}`,
110+
'/node_modules/@angular-devkit-tests/update-base/package.json': `{
111+
"name": "@angular-devkit-tests/update-base",
112+
"version": "1.0.0"
113+
}`,
114+
}),
115+
),
116+
);
117+
118+
const resultTree = await schematicRunner.runSchematic(
119+
'update',
120+
{
121+
packages: ['@angular-devkit-tests/update-base'],
122+
},
123+
newTree,
124+
);
125+
const { dependencies } = JSON.parse(resultTree.readContent('/package.json'));
126+
expect(dependencies['@angular-devkit-tests/update-base']).toBe('1.1.0');
127+
});
128+
100129
it('updates Angular as compatible with Angular N-1', async () => {
101130
// Add the basic migration package.
102131
const content = virtualFs.fileBufferToString(host.sync.read(normalize('/package.json')));

0 commit comments

Comments
 (0)