Skip to content

fix(@angular/cli): supplement workspace member dependencies in ng update#32931

Open
maruthang wants to merge 2 commits intoangular:mainfrom
maruthang:fix-32787-ng-update-npm-workspaces
Open

fix(@angular/cli): supplement workspace member dependencies in ng update#32931
maruthang wants to merge 2 commits intoangular:mainfrom
maruthang:fix-32787-ng-update-npm-workspaces

Conversation

@maruthang
Copy link
Copy Markdown
Contributor

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:

Using package manager: npm
Collecting installed dependencies...
Found 1 dependencies.
Package '@angular/core' is not a dependency.

This happens even when @angular/core is declared in the workspace member's package.json and is properly installed in node_modules.

Expected behavior

ng update correctly recognizes packages declared in the Angular project's package.json as dependencies, regardless of whether the project is a workspace root or a workspace member.

Root cause

In v21, commit 60a16dc0d replaced the direct package.json reading approach (getProjectDependencies(dir)) with packageManager.getProjectDependencies(), which runs npm list --depth=0 --json against the package manager's working directory.

In npm/pnpm/yarn workspace setups, the package manager's list command runs against the workspace root rather than the workspace member. The workspace root's package.json may not include dependencies that are only declared in the member's package.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:

  1. Declared in the Angular project root's package.json (the workspace member)
  2. Resolvable from node_modules (i.e. actually installed)
  3. Not already returned by the package manager

This restores the pre-v21 behaviour without regressing the improvements made by the package-manager abstraction.

Test plan

  • New unit tests in cli_spec.ts for supplementWithLocalDependencies:
    • adds missing workspace member dependencies to the map
    • does not overwrite packages already returned by the package manager
    • skips packages not installed in node_modules
    • handles devDependencies and peerDependencies
    • handles missing package.json gracefully
  • Manual: create an npm workspace, add an Angular project as a member, run ng update @angular/core@<version> from the member directory

Closes #32787

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
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines +708 to +735
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),
});
}
}
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

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),
      });
    }
  }

@angular-robot angular-robot bot added the area: performance Issues related to performance label Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: @angular/cli area: performance Issues related to performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v21.2.2 seems to be unable to handle npm workspaces

1 participant