fix(@angular/cli): supplement workspace member dependencies in ng update#32931
fix(@angular/cli): supplement workspace member dependencies in ng update#32931maruthang wants to merge 2 commits intoangular:mainfrom
Conversation
In npm/pnpm/yarn workspace setups, the package manager's list command runs against the workspace root and may not include packages that are only declared in a workspace member's package.json. This caused ng update to report "Package X is not a dependency" for packages installed in a workspace member even though they were present and installed. The fix reads the Angular project root's package.json directly and resolves any declared dependencies that are resolvable from node_modules but were absent from the package manager's output. This restores the behaviour that was present before the package-manager abstraction was introduced. Closes angular#32787
There was a problem hiding this comment.
Code Review
This pull request adds the supplementWithLocalDependencies function to correctly resolve local dependencies in workspace environments where standard package manager commands might miss them. It also includes a new test suite to validate this functionality. The reviewer suggested a performance optimization to move the creation of the module resolution context outside of the dependency loop to reduce overhead.
| const localManifest = await readPackageManifest(path.join(projectRoot, 'package.json')); | ||
| if (!localManifest) { | ||
| return; | ||
| } | ||
|
|
||
| const localDeps: Record<string, string> = { | ||
| ...localManifest.dependencies, | ||
| ...localManifest.devDependencies, | ||
| ...localManifest.peerDependencies, | ||
| }; | ||
|
|
||
| for (const depName of Object.keys(localDeps)) { | ||
| if (dependencies.has(depName)) { | ||
| continue; | ||
| } | ||
| const pkgJsonPath = findPackageJson(projectRoot, depName); | ||
| if (!pkgJsonPath) { | ||
| continue; | ||
| } | ||
| const installed = await readPackageManifest(pkgJsonPath); | ||
| if (installed?.version) { | ||
| dependencies.set(depName, { | ||
| name: depName, | ||
| version: installed.version, | ||
| path: path.dirname(pkgJsonPath), | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Calling findPackageJson inside the loop is inefficient because it creates a new Require object via createRequire for every dependency. It is better to create the Require object once outside the loop and use it to resolve the package.json paths directly. This also allows for a cleaner implementation by reusing the manifest path.
const localManifestPath = path.join(projectRoot, 'package.json');
const localManifest = await readPackageManifest(localManifestPath);
if (!localManifest) {
return;
}
const localDeps: Record<string, string | undefined> = {
...localManifest.dependencies,
...localManifest.devDependencies,
...localManifest.peerDependencies,
};
const projectRequire = createRequire(localManifestPath);
for (const depName of Object.keys(localDeps)) {
if (dependencies.has(depName)) {
continue;
}
let pkgJsonPath: string | undefined;
try {
pkgJsonPath = projectRequire.resolve(depName + '/package.json');
} catch {
// Package not found or package.json not accessible
}
if (!pkgJsonPath) {
continue;
}
const installed = await readPackageManifest(pkgJsonPath);
if (installed?.version) {
dependencies.set(depName, {
name: depName,
version: installed.version,
path: path.dirname(pkgJsonPath),
});
}
}
Current behavior
When running
ng update @angular/core@21(or any Angular package) from inside an npm/pnpm/yarn workspace member directory, the command fails with:This happens even when
@angular/coreis declared in the workspace member'spackage.jsonand is properly installed innode_modules.Expected behavior
ng updatecorrectly recognizes packages declared in the Angular project'spackage.jsonas dependencies, regardless of whether the project is a workspace root or a workspace member.Root cause
In v21, commit
60a16dc0dreplaced the directpackage.jsonreading approach (getProjectDependencies(dir)) withpackageManager.getProjectDependencies(), which runsnpm list --depth=0 --jsonagainst the package manager's working directory.In npm/pnpm/yarn workspace setups, the package manager's
listcommand runs against the workspace root rather than the workspace member. The workspace root'spackage.jsonmay not include dependencies that are only declared in the member'spackage.json(e.g.@angular/core), so those packages are absent from the returned map. This causes the "Package X is not a dependency" error.Fix
After calling
packageManager.getProjectDependencies(), supplement the result with any packages that are:package.json(the workspace member)node_modules(i.e. actually installed)This restores the pre-v21 behaviour without regressing the improvements made by the package-manager abstraction.
Test plan
cli_spec.tsforsupplementWithLocalDependencies:devDependenciesandpeerDependenciespackage.jsongracefullyng update @angular/core@<version>from the member directoryCloses #32787