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
feat!: lockfile format v6 #5810
Conversation
I like this! 🎉
The new format definitely looks more clear to me too. It's also nice to be able to see exactly what peer dependencies were resolved to make a package different. That's a great usability improvement, even for someone familiar with the hashes introduced in pnpm v7 like me. One nice property of the existing format was that the hashed path could be searched in I wonder if there's a way we can get the best of both worlds? What if we noted a way to identify where a package was hardlinked into /ts-node@10.9.1(@types/node@14.18.36)(typescript@4.9.3):
installDirectory: ts-node@10.9.1_xl7wyiapi7jo5c2pfz5vjm55na Or perhaps just the peers hash: /ts-node@10.9.1(@types/node@14.18.36)(typescript@4.9.3):
peersHash: xl7wyiapi7jo5c2pfz5vjm55na The extra field makes |
peerSepIndex = version.indexOf('(') | ||
if (peerSepIndex !== -1) { | ||
peersSuffix = version.substring(peerSepIndex) | ||
version = version.substring(0, peerSepIndex) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zkochan This does not work for patched dependencies. _
is used to separate version and patch hash: /ts-node/10.9.1_k5brw22k7hadiw3hedogf4eiee(@swc/core@1.3.27)(@types/node@18.11.18)(typescript@4.9.4)
I believe this should fix it, but I am not familiar with the codebase to know if it might affect anything else:
peerSepIndex = version.indexOf('(') | |
if (peerSepIndex !== -1) { | |
peersSuffix = version.substring(peerSepIndex) | |
version = version.substring(0, peerSepIndex) | |
} | |
peerSepIndex = version.indexOf('(') | |
if (peerSepIndex !== -1) { | |
peersSuffix = version.substring(peerSepIndex) | |
version = version.substring(0, peerSepIndex) | |
} | |
let patchSepIndex = version.indexOf("_"); | |
if(patchSepIndex !== -1) { | |
version = version.substring(0, patchSepIndex); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion didn't quite work either. Here is a PR that fixes the isssue: #5964
Add support for pnpm lockfile v6 (pnpm/pnpm#5810) Notable changes in the lockfile v6: - Lockfile keys are encoded as `/name@version` as opposed to `/name/version` - Specifiers are inline with resolved versions as opposed to being in their own field - Packages with multiple peer dependencies no longer have the peer dependencies hashed Notes: - We currently just attempt to parse the v5 structure and if that fails we attempt v6. We do this since lockfile version can be decided by a combination of `pnpm` version and `.npmrc` contents. In the future once `pnpm@8` is widely used we can switch the ordering of this if it becomes a performance bottle neck - A lot of the implementation was copy/pasta for the *slightly* different structure. I could have gone the route of making most of these functions work on `map[string]interface{}`, but I don't love doing type assertions all over the place.
### Description Fixes #4133 (huge thanks to @shawnmcknight for the reproduction) The crux of the issue was that if a dependency is patched and peer dependencies than both get encoded into the lockfile key. We can't construct a full expected lockfile key from the patch entry anymore as the patch entry doesn't contain information about peer dependencies. To circumvent this we now scan through the entire package list, parse the key, extract the patch hash (if present), and then check if it matches a patch listed in `patchedDependencies`. Notes for reviewers: - A large portion of this PR is actually porting the lockfile key parsing logic that pnpm itself uses so we can extract the individual pieces that we care about. This logic is in a slightly messy state as the pnpm codebase is currently in a state where it supports v5 and v6 lockfiles. As mentioned in pnpm/pnpm#5810 once pnpm@8 lands this code will start to be refactored. I tried to keep the Go port as 1:1 as possible in hopes to make future maintenance easier. - This PR includes new logic for v6 as pnpm now encodes patches in lockfile keys: pnpm/pnpm#5979 ### Testing Instructions Added new packages to the pnpm lockfile fixtures that are patched *and* have peer dependencies. Also verify that the changes work as expected with the provided repro: - `make turbo` - Clone https://github.com/shawnmcknight/turbo-pnpm-patch-repro - Remove private dependency: `pushd packages/test && pnpm rm @sheet/core && popd` - `turbo_dev --skip-infer prune --scope=test` - Verify that `out/pnpm-lock.yaml` contains a `patchedDependencies` section with both `@types/jsonwebtoken@8.5.9` and `moleculer@0.14.28` as entries
### Description #### Problem When updating the internal pnpm lockfile to [v6](pnpm/pnpm#5810) ci stopped working because it couldn't parse the lockfile correctly via `pnpm i --frozen-lockfile --ignore-scripts`. #### Solution By updating pnpm to 8.2.0 we can solve this issue, and turborepo needs to be updated as well to understand the new lockfiles otherwise it crashes while trying to parse them. <!-- Provide a URL to a live deployment where we can test your PR. If a demo isn't possible feel free to omit this section. --> ### Type of Change - [ ] New Example - [ ] Example updates (Bug fixes, new features, etc.) - [x] Other (changes to the codebase, but not to examples)
This new lockfile format will be opt-in in pnpm@7. pnpm@8 will only be able to convert the old lockfile format to the new one. So we'll be able to refactor the code in v8.
The primary reason for the lockfile format change is to get rid of the hashes in package keys. When a package in the dependency graph has peer dependencies, the names and versions of these peer dependencies are added to its pass.
For example, in this case,
babel-preset-jest
has@babel/core
in its peer dependencies and it got resolved from@babel/core@7.20.7
pnpm/pnpm-lock.yaml
Line 9583 in 044cfe5
However, when there are many peer dependencies and the key become long, we use a hashing algorithm to create a hash from the names and versions of the resolved peer dependencies. Like in this case:
pnpm/pnpm-lock.yaml
Line 16463 in 044cfe5
This is needed because we use this key as the directory name inside
node_modules/.pnpm
. The name of the directory shouldn't become too long or otherwise there will be issues on Windows. Unfortunately, these hashes confuse our users. They don't understand what they mean and why they exist. It is also unclear why the hashes change during code reviews.Hence, in the new lockfile format we won't hash long keys in the lockfile. But we will still hash the directory names inside
node_modules/.pnpm
. Also, we will use a more readable format for the key. The above keys will look like this: