Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have `pnpm install` install everything exactly to the specs of the pnpm-lock file?

If you connect a Github project to a product like Cloudflare pages or Vercel, commits to the remote repo trigger new builds. These builds will run the appropriate install and build commands.

I haven't updated a site in months. But major changes have come to dependencies being used, and it's causing me so many headaches to try and go through one-by-one and address each and every issue that has surfaced.

I'm using pnpm, is there anyway I can have pnpm install look at the existing pnpm-lock.yaml so I can eventually build a project that is entirely the same as a previous build I had 6 months ago?

I just want to edit some text on my site and not have to make all these updates. I tried "freezing" the versions of all my dependencies and dev dependencies in package.json by removing instances of ^ to match what I see in my lock file, but that didn't work.

like image 531
user1087973 Avatar asked Sep 12 '25 08:09

user1087973


1 Answers

Package Management with PNPM

See also Why does "npm install" rewrite package-lock.json?

Semver

The semver specification explains how to use semantic versioning though you can probably skip to the npm docs.

As you probably know the numbers are in the form major.minor.patch. If you don't mind which patch release you have as long as it is the specified major and minor version you can use the ~ prefix. Similarly, to allow any minor version use ^.

Walkthrough

Inital Setup

pnpm init

pnpm add express

The package.json will contain (at time of writing):

"express": `"^4.18.2"`

A pnpm-lock.yaml is also created:

specifiers:
  express: ^4.18.2

dependencies:
  express: 4.18.2
express -> '.pnpm/[email protected]/node_modules/express'/

Using pnpm install

Giving it a first run without changing anything produces:

$ pnpm install
Lockfile is up to date, resolution step is skipped
Already up to date
Done in 653ms

Now if I change package.json to be exactly v4.16.0 we shall see an update to pnpm-lock.yaml

specifiers:
  express: 4.16.0

dependencies:
  express: 4.16.0

Adding the patch wildcard ~4.16.0 and running pnpm install again gives:

specifiers:
  express: ~4.16.0

dependencies:
  express: 4.16.0

Note that the install version did not change. If I delete the node_modules/ directory and reinstall, still no change.

Ok, now try updating the minor version in package.json to ~4.17.0.

specifiers:
  express: ~4.17.0

dependencies:
  express: 4.17.3

This time it did update the dependency and installed the latest patch version but did install the exact major and minor version. If you think about what the ~ means then this is expected.

The specifiers section in the lock file is just what we specify as the dependency in the package.json file. The dependencies section in the lock file should reflect the version that is installed, or will be installed.

If I delete the node_modules/ folder and pnpm install again then we still have 4.17.3.

Explanation

What confuses a lot of people about pnpm install/npm install is how the lock-file works with the semver specifier:

The installed version listed as a dependency in the lockfile must be compatible with the version specified in the package file.

  • If it is compatible, no changes will be made.

  • If it is incompatible, then the latest compatible version will be installed.

Perhaps because sometimes it seems to install the latest version, and not othertimes, the behaviour is not clear. To state this again, changes will only be made when there is an incompatibility between the packge version and lockfile version. The lockfile dependency never has the ~ or ^ wildcards because only one version is actually installed and that's what the lockfile is supposed to track.

Using --frozen-lockfile in a CI environment

The docs for pnpm install describe how the install will fail if the lockfile is out of sync or needs updating.

Changing the package.json back to ~4.16.0 and then doing the install:

$ pnpm install --frozen-lockfile
Lockfile is up to date, resolution step is skipped
 ERR_PNPM_OUTDATED_LOCKFILE  Cannot install with "frozen-lockfile" because pnpm-lock.yaml is not up to date with package.json

Note that in CI environments this setting is true by default. If you still need to run install in such cases, use "pnpm install --no-frozen-lockfile"

In fact, even if I specify the installed version exactly 4.17.3, because it differs to the specifier ~4.17.0, then it will err. The package.json and pnpm-lock.yaml are out of sync even though the version are compatible.

Finally I will make our package compatible with the latest version that was installed with the first pnpm add express command. To do this I use the minor version wildcard ^4.0.0 and unfreeze the lockfile with pnpm install --no-frozen-lockfile.

specifiers:
  express: ^4.0.0

dependencies:
  express: 4.17.3

While the specifier is updated to match the package file, the version is not chaged; it is compatible.

Running pnpm install --frozen-lockfile will work again, but not update the installed version.

Conclusion

In a normal environment the lockfile will determine the exact version installed unless it is not compatible with the package file, in which case it will install the lastest version specified by the package file.

In a CI environment the lockfile will not by default be updated and will need to be compatible with the package file for installs to occur.

If you want the latest version specified pnpm update will do the update to the lastest compatible version given in the package file.

Disclaimer

I've tested out everything here but it is complex and I have limited experience using pnpm in a real CI environment.

like image 193
Andy2K11 Avatar answered Sep 13 '25 22:09

Andy2K11