Edit:
With pnpm v7.29.0, you no longer have to perform the hack described below, but just leaving it here for educational purposes.
Now the solution is just to set dedupe-peer-dependents=true
(e.g. in your .npmrc
).
From pnpm docs
- foo-parent-1
- bar@1.0.0
- baz@1.0.0
- foo@1.0.0
- foo-parent-2
- bar@1.0.0
- baz@1.1.0
- foo@1.0.0
In the example above, foo@1.0.0 is installed for foo-parent-1 and foo-parent-2. Both packages have bar and baz as well, but they depend on different versions of baz. As a result, foo@1.0.0 has two different sets of dependencies: one with baz@1.0.0 and the other one with baz@1.1.0. To support these use cases, pnpm has to hard link foo@1.0.0 as many times as there are different dependency sets.
For your specific case, foo === @nestjs/core, baz === @nestjs/microservices. Although the example used here is for "different versions", the same applies for optional peer dependencies. So to re-illustrate the example, in your context:
- my-nestjs-app
- @nestjs/microservices@9.1.4
- @nestjs/core@9.1.4
- my-other-nestjs-app
- @nestjs/core@9.1.4
Normally, if a package does not have peer dependencies, it is hard linked to a node_modules folder next to symlinks of its dependencies, like so:
However, if foo [@nestjs/core] has peer dependencies, there may be multiple sets of dependencies for it, so we create different sets for different peer dependency resolutions
^ This is usually ok for most packages out there. However @nestjs/core is special. It's stateful so that it can take care of all the runtime dependency injections. pnpm creating multiple copies of @nestjs/core in a monorepo will result in the confusing behaviour you're seeing, as your app could depend on 1 copy, while other NestJS libs depend on another. This seems like a common problem felt by devs using pnpm + nest, according to the NestJS discord.
Solution
Use pnpm hooks to modify nestjs packages' peerDependenciesMeta
at resolution time:
// .pnpmfile.cjs in your monorepo's root
function readPackage(pkg, context) {
if (pkg.name && pkg.name.startsWith('@nestjs/')) {
context.log(`${pkg.name}: make all peer dependencies required`);
pkg.peerDependenciesMeta = {};
}
return pkg;
}
module.exports = {
hooks: {
readPackage,
}
};
This is a hack IMO, and it's really annoying to deal with because Renovate
/ Dependabot
will ignore the .pnpmfile.cjs when it performs dependency updates. I'd suggest going with Nx or some other package manager that Nest / stateful packages work better with.