Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not possible to have a reproducible build with yarn #8682

Closed
drrnkin opened this issue Aug 30, 2021 · 5 comments · May be fixed by jamiecool/yarn#362, jamiecool/yarn#363, jamiecool/yarn#364, jamiecool/yarn#365 or jamiecool/yarn#366

Comments

@drrnkin
Copy link

drrnkin commented Aug 30, 2021

Yarn will always accept the latest patch release of a package, ignoring the version in the lock file. That means that if a breaking change is introduced in a patch release by mistake, there is simply no way to sync down an old branch in git and reproduce the build, without painstaking manual intervention. It also means that developers may experience a bug in a package (or not) depending on when they sync, despite having identical code.

To see the issue, clone https://github.com/drrnkin/yarn-8682. There's a yarn folder and an npm folder. Running "yarn" in the yarn folder will get version 4.1.4 of kleur rather than the specified 4.1.3 (note: nothing wrong with kleur; just a simple example). In the npm folder, run "npm install" and the expected version 4.1.3 will be retrieved.

In our case, a breaking change in a package was accidentally released as a patch release, that broke our build, though the version was correctly specified in the yarn.lock file.

I have read through #4147 and #4379 which are similar but not identical. I think a non-breaking change would be to add a flag like --exact-lockfile which means to respect the lockfile and attempt no upgrades.

I see from the bug template that only critical fixes will be made to 1.x, but there are many companies for which switching to 2.x is a substantial undertaking and not something that can be done in the near future, so I hope this will be considered.

At the very least, the documentation should be updated to remove the claim that builds are reproducible, because they currently are not. For example:
https://classic.yarnpkg.com/en/docs/migrating-from-npm

When other people start using Yarn instead of npm, the yarn.lock file will ensure that they get precisely the same dependencies as you have.

https://classic.yarnpkg.com/en/docs/cli/install/

If you need reproducible dependencies, which is usually the case with the continuous integration systems, you should pass --frozen-lockfile flag.

If you pass --frozen-lockfile, then the CI build will break when a new patch release is available (with the error "Your lockfile needs to be updated, but yarn was run with --frozen-lockfile"). We don't want a CI build to fail completely; we just want to to build with the specified versions in the lock file.;

There's also https://classic.yarnpkg.com/blog/2016/11/24/lockfiles-for-all/, but since it's a blog post I doubt anything can be done about the incorrect claim.

Also, since package authors are people and they can make mistake, it’s possible for them to publish an accidental breaking change in a minor or patch version. If you install this breaking change when you don’t intend to it could have bad consequences like breaking your app in production.
Lockfiles lock the versions for every single dependency you have installed. This prevents “Works On My Machine” problems, and ensures that you don’t accidentally get a bad dependency.

That last example is precisely what happened to us, and finding that only led to confusion.

Thanks. I hope that something can be done in the 1.x code line.

@FlorianWendelborn
Copy link

FlorianWendelborn commented Sep 19, 2021

As far as I can tell this is working as intended. You're telling yarn explicitly that you want to have minor updates with ^4.1.3 instead of 4.1.3 in your package.json

Additionally, the yarn.lock clearly states that it will install 4.1.4:

kleur@^4.1.3:
  version "4.1.4"

As a side-note: using zip files makes it harder to review your code. I was on mobile and it took me like 15 minutes to figure out a way to open the yarn.lock on iOS. A git repo or a GitHub gist is much easier

@drrnkin
Copy link
Author

drrnkin commented Sep 20, 2021

Apologies, I think it must have updated the lock file from 4.1.3 to 4.1.4 as I was double checking and I didn't notice (but this is what I don't want it to do BTW, and npm doesn't do this). I'll create an updated gist very shortly.

@drrnkin
Copy link
Author

drrnkin commented Sep 20, 2021

I updated the original comment to reference https://github.com/drrnkin/yarn-8682 rather than a zip file, and I corrected yarn's lock file that was accidentally updated. The key take away is, imagine this was created when 4.1.3 was current, and now 4.1.4 is available and causes a breakage (nothing is truly wrong with kleur, just an example). With npm, the lock file is respected and we get the same version we did originally. With yarn, default behaviour is to update the lock file and and give us 4.1.4. Passing --pure-lockfile doesn't update it but still gives us 4.1.4. Passing --frozen-lockfile aborts. Unlike npm, there's no option in which the lock file is respected to give us version 4.1.3 that we need and specified.

This is critically important for older branches of the consuming application that might reference older versions of packages. Sometimes we need to build an old version and have it in exactly the same state as the day it was made.

I understand that we have a ^ in the package.json, and we want that behaviour when we're explicitly adding packages, but for legacy branches, the lock file should actually lock versions. The package.json is identical in each of the two folders, and npm respected the lock file while yarn did not.

@FlorianWendelborn
Copy link

FlorianWendelborn commented Sep 20, 2021

Did you manually create this lockfile? Because the only thing that happens for me is that the lockfile is fixed when first yarn install-ing due to kleur@4.1.3: not matching the desired kleur@^4.1.3: (from your package.json).

Here’s what needs to be fixed in the yarn.lock to make the example as expected. Please note that after changing this, yarn doesn’t save a new (fixed) lockfile anymore. That part of the behavior is most likely on your reproduce, not on yarn itself though. Yarn simply sees that you have a new dependency kleur@^4.1.3 that doesn’t match any of the known ones (kleur@4.1.3) and thus auto-fixes the yarn.lock for you — upgrading to the latest patch as requested by ^

-kleur@4.1.3:
+kleur@^4.1.3:

After fixing this line in yarn.lock running yarn install does not upgrade to 4.1.4:

~/C/y/yarn ❯❯❯ (main +*=) yarn install
yarn install v1.22.10
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

✨  Done in 0.25s.
~/C/y/yarn ❯❯❯ (main +*=) cat node_modules/kleur/package.json | grep version
  "version": "4.1.3",

Also, I’m using yarn since years and never had the issue you’re describing that yarn updates the lockfile on its own just because a new version is available, so are you sure that there isn’t someone on your team that manually touches the yarn.lock or introduces other changes to package.json? I’m also using yarn install --frozen-lockfile in CI pipelines since ages and haven’t ever had a pipeline that aborted because upgrades are available — even though LOTS of packages are out-of-date by now.


Actually, from your repro, it seems possible that you’re trying to add the ^ to your package.json, and are surprised that yarn install changes the lockfile. This happens because of the diff I posed above.

If that is actually your goal on the project where you’re reproducing this from, then you can attempt to manually change this in the lockfile as well, that should work for you.

@drrnkin
Copy link
Author

drrnkin commented Sep 20, 2021

I didn't hand-edit the lock file, but I did hand-edit the package.json. To simulate adding the latest kleur back when the latest was 4.1.3, I used yarn add kleur@4.1.3, and then added the ^ from package.json. I should have mentioned that, but you've correctly discerned that.

So now I understand what yarn was doing! Since the package and the lock file didn't match exactly, it attempted to update the lock file. And while doing so, it decided to fetch the latest (4.1.4). The key thing here is that without a mismatch, there's no attempt to patch and no upgrade. With your explanation, I tracked down what happened on my team. Someone specified a specific version of a package when upgrading, noticed the ^ was missing from package.json, and manually added it back in. That's the first mistake. The second mistake is that a new breaking change of that referenced package was released as a patch release. So those two mistakes combined to cause a breakage.

Your help has been invaluable to sorting this out. Thank you very much! I'll close this bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment