pnpm
pnpm reads the same .npmrc files as npm, in the same order β project, then user, then global
β so most npm instructions apply unchanged. What follows is the short path, and then the
places where pnpm behaves differently and npm's answer will not help you.
Since pnpm 11, minimumReleaseAge defaults to 1440 minutes β one day. pnpm will not
install a version published more recently than that, so a package you just pushed to
Verdaccio looks as if it does not exist:
No matching version found for @my-company/widget@^1.0.0
It is a supply-chain precaution and it is worth keeping for public packages, but it catches everyone who publishes to their own registry and installs it straight away β in CI end-to-end jobs especially. How to exclude your own scope, or switch it off.
Point pnpm at the registryβ
For one project, which is what you usually want, write it next to package.json:
registry=http://localhost:4873/
Or let pnpm write it for you:
pnpm config set registry http://localhost:4873/ --location=project
For every project on the machine, drop --location=project and pnpm writes to your user
configuration instead. Check what is in effect at any point with:
pnpm config get registry
Only your own packagesβ
Sending everything through Verdaccio is fine when it proxies npmjs, but you can also route a single scope and leave the rest alone:
registry=https://registry.npmjs.org/
@my-company:registry=http://localhost:4873/
Now @my-company/* resolves from Verdaccio and everything else from npmjs. This is the
safer default for a team registry: nothing changes for public packages, and a Verdaccio
outage does not stop an install of lodash.
One command onlyβ
pnpm install --registry http://localhost:4873/
Useful for a quick check, but it does not persist, and it does not apply to a
pnpm publish you run afterwards.
Authenticateβ
pnpm login --registry http://localhost:4873/
For a scoped registry, name the scope so the token is stored against the right one:
pnpm login --scope=@my-company --registry http://localhost:4873/
The token lands in your user .npmrc as a host-scoped entry:
//localhost:4873/:_authToken=<token>
In CI, do not run login at all β write that line directly, with the token from a secret:
echo "//localhost:4873/:_authToken=${VERDACCIO_TOKEN}" >> .npmrc
_auth stops working on Verdaccio 7Older recipes put a base64 user:password in _auth. That makes npm send an
Authorization: Basic header, and Verdaccio 7 no longer accepts incoming Basic
authentication β only Bearer tokens. The same .npmrc that publishes fine against 6.x
fails against 7.x with:
npm error code E401
npm error Unable to authenticate, your authentication token seems to be invalid.
Which is misleading: the credentials are correct, the scheme is not accepted. Use
_authToken, which sends a Bearer token and works on both lines.
Note also that npm itself rejects a bare _auth in a project .npmrc
(ERR_INVALID_AUTH) β it has to be host-scoped, //localhost:4873/:_auth=β¦, the same shape
as _authToken.
Publishβ
pnpm publish --registry http://localhost:4873/
Two pnpm-specific notes:
- pnpm runs the
prepublishOnlyandprepackscripts and publishes from a temporary directory. If your package is built from source, that is what you want. - In a workspace,
pnpm publish -rpublishes every package whose version is not yet on the registry, which pairs well with a throwaway Verdaccio for end-to-end testing.
To make sure a private package can never reach npmjs by accident, pin the registry in
package.json β it beats any .npmrc:
{
"publishConfig": {
"registry": "http://localhost:4873/"
}
}
Where pnpm differs from npmβ
Newly published versions can be invisibleβ
This is the one that will confuse you, because nothing is broken:
No matching version found for @my-company/widget@^1.0.0
pnpm can refuse versions that are too new, as a supply-chain precaution, and pnpm 11 turns this on by default with a one-day delay. A package you published seconds ago does not exist as far as the install is concerned.
Check whether it is in effect:
pnpm config get minimumReleaseAge # minutes; 1440 is one day
Keep the protection for third-party packages and exclude your own scope:
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- '@my-company/*'
Or switch it off entirely, which is what a throwaway CI registry wants:
pnpm config set minimumReleaseAge 0 --location=project
There is no command-line flag for this β --minimum-release-age is not an option, and
the npm_config_minimum_release_age environment variable is ignored. It has to go through
the configuration.
Settings live in two filesβ
Registry and authentication stay in .npmrc, the same as npm. pnpm's own settings β
minimumReleaseAge, hoisting, the store location β live in pnpm-workspace.yaml at the root
of the workspace.
You do not have to remember which is which: pnpm config set <key> --location=project routes
each key to the right file. Setting registry writes .npmrc; setting minimumReleaseAge
writes pnpm-workspace.yaml, creating it if needed.
The lockfile records the registryβ
pnpm-lock.yaml stores the resolved URL of every package. A lockfile produced against
npmjs and then installed against Verdaccio, or the other way round, can resolve to hosts you
did not intend. When you change registries for real, delete the lockfile and reinstall.
Troubleshootingβ
Most problems are shared with npm, and the npm troubleshooting list covers them β SSL certificates, mixed registries in a lockfile, and login failures.
The pnpm-specific one is almost always the release-age gate above.