Skip to content

infra: declare pnpm via devEngines.packageManager - #4001

Draft
Shinigami92 wants to merge 1 commit into
nextfrom
infra-devengines-packagemanager
Draft

infra: declare pnpm via devEngines.packageManager#4001
Shinigami92 wants to merge 1 commit into
nextfrom
infra-devengines-packagemanager

Conversation

@Shinigami92

@Shinigami92 Shinigami92 commented Aug 16, 2026

Copy link
Copy Markdown
Member

Adds devEngines.packageManager to the root package.json so that npm actually refuses to install this repo.

The problem

packageManager only does something when Corepack is enabled. npm itself never looks at it. So today this just works:

$ npm install
up to date in 211ms

npm resolves our whole tree, writes a package-lock.json (which is in .gitignore, so nobody ever sees it in a diff) and leaves behind a node_modules that has nothing to do with pnpm-lock.yaml. npm run build and the rest are equally happy. The first sign that something is off is usually a weird bug report.

devEngines is the field npm does read, and it checks it before install, ci and run.

The change

"devEngines": {
  "packageManager": {
    "name": "pnpm",
    "version": "11.21.0",
    "onFail": "download"
  }
}

Only packageManager for now. No runtime entry, Node is already covered by engines and .nvmrc, and I did not want two topics in one PR.

npm 11 and npm 12, before and after

Before, on both npm 11.19.0 and npm 12.0.2:

$ npm install
up to date in 211ms
[exit code: 0]

After:

$ npm install
npm error code EBADDEVENGINES
npm error EBADDEVENGINES The developer of this package has specified the following through devEngines
npm error EBADDEVENGINES Invalid devEngines.packageManager
npm error EBADDEVENGINES Invalid name "pnpm" does not match "npm" for "packageManager"
npm error EBADDEVENGINES {
npm error EBADDEVENGINES   current: { name: 'npm', version: '12.0.2' },
npm error EBADDEVENGINES   required: { name: 'pnpm', version: '11.21.0', onFail: 'download' }
npm error EBADDEVENGINES }
[exit code: 1]

Identical output for npm run <script>, not only for install.

This sits next to packageManager, it does not replace it

I did check whether we can drop the old field. We can't, at least not yet:

  • yarn 1 only knows packageManager. With devEngines alone it installs without a word and writes a yarn.lock.
  • pnpm 10 only knows packageManager. devEngines.packageManager landed in pnpm 11.0.0, so pnpm 10 ignores it and keeps running itself instead of switching to 11.21.0.
  • Corepack prefers packageManager, and it chokes on a range in devEngines: Invalid package manager specification in package.json (pnpm@^11.21.0); expected a semver version.
  • Renovate's npm manager tracks packageManager and knows nothing about devEngines.

The two fields do different jobs. packageManager keeps pinning the version for Corepack, pnpm and pnpm/action-setup, devEngines is the part that makes npm say no.

Why an exact version and why onFail: download

I got both of these wrong on the first attempt and only noticed because of the Docker runs below.

A range like ^11.21.0 next to our exact packageManager makes pnpm complain on every single command, and it then resolves the range to the newest match (11.22.0 right now), quietly overriding the version we pinned for CI.

onFail: "error" looks like the obvious choice, but it is a regression. Today someone on pnpm 11.22.0 gets silently switched down to 11.21.0 and never notices. With error they hit a wall instead. download keeps the behaviour we have today: pnpm 10.34.5, 11.0.0 and 11.22.0 all end with Done in ...ms using pnpm v11.21.0 and no warning.

npm never validates the onFail value here because the name mismatch short circuits first, so pnpm's non standard download (npm documents only warn, error and ignore) causes no trouble on the npm side.

What a range and what onFail: error look like

Range ^11.21.0 plus "packageManager": "[email protected]", printed before every command:

[WARN] "packageManager" and "devEngines.packageManager" specify different versions of pnpm in package.json. "packageManager" will be ignored

Exact version with onFail: "error", running pnpm 11.22.0:

[ERROR] This project is configured to use 11.21.0 of pnpm. Your current pnpm is v11.22.0
If you want to bypass this version check, you can set the "pmOnFail" configuration to "warn" or "ignore" (e.g. via --pm-on-fail=ignore). If using "devEngines.packageManager", you can set its "onFail" to "warn" or "ignore"

That error also fires for anyone on a pnpm 11 patch we did not pin, which is not what I want to hand a first time contributor.

Lockfile

pnpm-lock.yaml grows at the top by some lines. pnpm writes a new leading YAML document with packageManagerDependencies in it (pnpm, @pnpm/exe and the seven platform binaries with their integrity hashes). It is a single hunk at the top of the file, nothing else moves, no removals.

I made sure this cannot break CI. pnpm install --frozen-lockfile passes in all three cases I could think of: against a lockfile from before this PR, against the regenerated one, and against one where only packageManager was bumped.

How I checked all of this

The docs disagree with each other on a few points, so instead of guessing I built a throwaway Docker matrix. Seven images:

Image npm install today npm install / pnpm install / yarn install with this PR
npm 11.19.0 installs, exit 0 EBADDEVENGINES, exit 1
npm 12.0.2 installs, exit 0 EBADDEVENGINES, exit 1
yarn 1.22.22, no Corepack blocked, exit 1 blocked, exit 1
yarn 4.18.0 via Corepack blocked, exit 1 blocked, exit 1
pnpm 10.34.5 switches to 11.21.0 switches to 11.21.0
pnpm 11.0.0 switches to 11.21.0 switches to 11.21.0
pnpm 11.22.0 switches to 11.21.0 switches to 11.21.0

Each image runs against 8 stripped down package.json fixtures (with and without packageManager, with and without devEngines, exact version vs range, onFail: error vs download) and executes both <pm> install and <pm> run <script> in each of them, since npm validates before run too. Then I replayed the winning fixture against the real package.json and pnpm-lock.yaml in a container to get the actual lockfile diff.

The setup is not part of this PR, it lives in my local scratch folder. I can push it as a gist if anyone wants to re-run it.

yarn, for completeness (unchanged by this PR)

yarn 1 classic is already blocked by packageManager, though the message is a bit of a mess:

$ yarn install
error This project's package.json defines "packageManager": "yarn@[email protected]". However the current global version of Yarn is 1.22.22.
Presence of the "packageManager" field indicates that the project is meant to be used with Corepack, a tool included by default with all official Node.js distributions starting from 16.9 and 14.19.

Note the "yarn@[email protected]", yarn 1 blindly prefixes yarn@ to whatever it finds. Ugly, but it exits 1.

yarn 4 through Corepack is clean:

$ yarn install
This project is configured to use pnpm because /work/package.json has a "packageManager" field

Both of these come from packageManager, not from devEngines. That is the main reason the old field stays.

Follow up

Renovate will bump packageManager and leave devEngines.packageManager on the old version, and from that moment pnpm prints the "specify different versions" warning on every command until someone fixes it by hand. Either we add a customManagers regex to renovate.json5, or we just keep an eye on it during review. I left renovate.json5 untouched here, say the word if you want it in this PR.

@Shinigami92 Shinigami92 added this to the v11.0 milestone Aug 16, 2026
@Shinigami92 Shinigami92 self-assigned this Aug 16, 2026
@Shinigami92 Shinigami92 added the c: infra Changes to our infrastructure or project setup label Aug 16, 2026
@netlify

netlify Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploy Preview for fakerjs ready!

Name Link
🔨 Latest commit 4390d66
🔍 Latest deploy log https://app.netlify.com/projects/fakerjs/deploys/6a841fe621e7b00008b1c4fa
😎 Deploy Preview https://deploy-preview-4001.fakerjs.dev
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.92%. Comparing base (9cca2f2) to head (4390d66).
⚠️ Report is 1 commits behind head on next.

Additional details and impacted files
@@            Coverage Diff             @@
##             next    #4001      +/-   ##
==========================================
- Coverage   98.98%   98.92%   -0.07%     
==========================================
  Files         926      926              
  Lines        3246     3246              
  Branches      569      569              
==========================================
- Hits         3213     3211       -2     
- Misses         29       31       +2     
  Partials        4        4              

see 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ST-DDT ST-DDT added the p: 1-normal Nothing urgent label Aug 16, 2026
@Shinigami92
Shinigami92 marked this pull request as ready for review August 16, 2026 08:50
@Shinigami92
Shinigami92 requested a review from a team as a code owner August 16, 2026 08:50
@Shinigami92
Shinigami92 force-pushed the infra-devengines-packagemanager branch from bc44947 to 1b90106 Compare August 16, 2026 08:50

@ST-DDT ST-DDT left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note sure, whether we need exact pnpm version matches, but according to your comment pnpm requires it.

Renovate's npm manager tracks packageManager and knows nothing about devEngines.

So will every renovate pnpm PR explode due to it only ever updating only one of them?
If yes, that may be a reason to NACK this PR.

Can we test that in a test repo or something?

EDIT: It appears like this is the case

@ST-DDT

ST-DDT commented Aug 16, 2026

Copy link
Copy Markdown
Member

Does this PR provide any benefit other than blocking npm as package manager?
If that is the only reason, it might not be worth it considering the downsides.

@xDivisionByZerox

Copy link
Copy Markdown
Member

Does this PR provide any benefit other than blocking npm as package manager?

I would even add a question on top of that: Why is blocking npm as the package manager a benefit to begin with? If a contributor does not want to install a completely new package manager to contribute, why force them? Is there any downside to using npm, other than slower dependency resolution for the user that choose to use npm?

@Shinigami92

Copy link
Copy Markdown
Member Author

Ok, again many questions about a new thing I'm personally already used to.

So lets start with https://github.com/faker-js/faker/pull/4002/changes#diff-dfa415899a467bf9067cd5ebdd0b6d359d1b25500bc76de5855631f459b3188bR45 example: this isn't really needed tbh, because we can utilize guarded tooling to prevent invalid usage. Like we use linter to guard coding, we can configure keys to guide with hard instead of soft fails.

And this is not only helpful for humans, but especially for AI usage. e.g. when using claude, it tries very often in a fresh session to call npm or npx commands instead of pnpm or pnpx or pnpm exec. Be setting devEngines.packageManager claude will hard-fail by doing so and then automatically switch towards pnpm fully without the need of e.g. an AGENTS.md or CLAUDE.md (files btw I wont like to introduce).

So two questions from your side specifically:

Does this PR provide any benefit other than blocking npm as package manager?
If that is the only reason, it might not be worth it considering the downsides.

Why is blocking npm as the package manager a benefit to begin with? If a contributor does not want to install a completely new package manager to contribute, why force them? Is there any downside to using npm, other than slower dependency resolution for the user that choose to use npm?

pnpm is more than just a faster dependency manager, it also has MUCH higher safety net by not allowing to install dependency postinstall scripts by default. This is FINALLY after multiple years of waiting coming to npm v12 as well.
But it also does not stop there, pnpm also has a by default configured minimumReleaseAge set to 1440 min, which prevents even renovate installing accidentally early CI runtime attacks.

I hope the reasons above are already enough, but if you even want to force me harder, I will go into rabbit hole and find more.

@Shinigami92 Shinigami92 modified the milestones: v11.0, vAnytime Aug 16, 2026
@ST-DDT

ST-DDT commented Aug 16, 2026

Copy link
Copy Markdown
Member

Nobody is arguing whether you should use pnpm (today you should).
The discussion is whether you must use pnpm with <insert version here>.
I had projects that used other tooling which required npm/yarn "just because" and I hated it.

I personally don't have anything against a somewhat strict pnpm@^v11 or [email protected] (as corepack basically handles it for me), but I would like to avoid getting into situations that get us stuck or "somebody else can clean up that warning/issue later".
Because I tend to be that "someone else", else it gets forgotten/ignored.

Maybe someone should open an issue with renovate so that both versions get updated in tandem.

@Shinigami92
Shinigami92 marked this pull request as draft August 18, 2026 09:03
@Shinigami92
Shinigami92 force-pushed the infra-devengines-packagemanager branch from 1b90106 to 4390d66 Compare August 18, 2026 09:03
@ST-DDT

ST-DDT commented Aug 18, 2026

Copy link
Copy Markdown
Member

I merged #4002 for now, since currently you have to look the package.json yourself.

This can potentially be simplified when this PR lands and thus this PR can change the respective docs as well.

@Shinigami92

Copy link
Copy Markdown
Member Author

blocked by pnpm/pnpm#14009

@Shinigami92 Shinigami92 added the s: on hold Blocked by something or frozen to avoid conflicts label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: infra Changes to our infrastructure or project setup p: 1-normal Nothing urgent s: on hold Blocked by something or frozen to avoid conflicts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants