pnpm will flag a peer dependency as missing if:
- The package with the peer dependency is the one being installed
- in this recreation, that is the b packagem, declaring a peer dependency on
eslint - Eslint is not "special" in this recreation. Virutally any package could be used for demonstration, but it is a common package to be referenced in peer dependencies.
- in this recreation, that is the b packagem, declaring a peer dependency on
- The package installing the peer dependency, installs it under an alias
- in this recreation, that is c, which installs both
bandeslintas an aliasedcustom-eslint.
- in this recreation, that is c, which installs both
- This package is yet again installed by another package.
- in this recreation, that is d
pnpm --filter "./*" exec pnpm pack- For sake of reproduction, simple pack of trivial packages is easiest. But in real world, these packages would probably already be published to npm.
pnpm i- Note the warning messages about "missing peer" (there is also an "unmet peer" warning. This is not the topic of the issue, and does not recreate when using npm instead of tgz)
d
└─┬ c 0.0.1
└─┬ b 0.0.1
└── ✕ missing peer a@file:../a/a-0.0.1.tgz
Peer dependencies that should be installed:
a@file:../a/a-0.0.1.tgz
pnpm --filter "./d" exec node ./d.js- logs:
{ c: { eslint: [Object] } } } - The command is successful! So the peer dependency must actually exist, and therefore be a false positive.
- logs:
There are 3 packages in this reproduction.
- b
- This package imports
eslintand re-exports with a wrappedb = { eslint }. eslintis a peer dependency. It is not installed directly, and as a result thebpackage has nonode_modulesfile.
- This package imports
- c
- This package imports both
eslintandb, althougheslintis aliased as a different namecustom-eslint. The resolution is the same though. - It re-exports b with a wrapped
c = { b }. This should only be possible ifbis successfully loaded, which in turn means the peer dependency was satisfied.
- This package imports both
- d
- This package imports
cnormally. Nothing is suspicious about this package directly, but it is the source of our error!
- This package imports
The b package would be the actual plugin implementation. It needs to depend on eslint, but shouldn't bring it's own implemention, since it needs to be an exact match for the user's version.
When discovering this issue, that was my haywire-launcher package
The c package would be the user that is actually consuming the plugin. In most cases users installed dependencies based on the name (e.g. "eslint": "9.17.0" as opposed to "eslint": "npm:[email protected]), but that isn't strictly required.
A possible reason to use an alias is to indicate a package that must come from npm, as opposed to a locally named package.
When discovering this issue, that was my nx-update-ts-references package, which loads those packages (all of them, so dependencies are all fulfilled) from npm to avoid circular dependencies (those packages in turn depend on the local version, e.g. haywire-launcher)
The d package is simply a package that imports c. Maybe its yet another plugin wrapper.
When discovering this issue, there were multiple packages that fulfilled this, for example common-proxy
This issue does not occur when performing local links (either via the workspace:^ keyword, or link:../a). This is a false negative though, since the script does not work.
The workaround I've used locally is to also install the peer dependency as a dev dependency. I have had no issues so far.
An even more simple reproduction is add a dependency on nx-update-ts-references and see the error.
The root package.json has done that already.
.
└─┬ nx-update-ts-references 0.1.1
└─┬ haywire-launcher 0.1.9
├── ✕ missing peer entry-script@^3.0.7
└── ✕ missing peer haywire@^0.1.6
Peer dependencies that should be installed:
entry-script@^3.0.7 haywire@^0.1.6