-
Notifications
You must be signed in to change notification settings - Fork 67
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
Fix: Correctly parse v3 lockfiles #1793
Conversation
🦋 Changeset detectedLatest commit: 35ca64c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
return false; | ||
}); | ||
}); | ||
// Prepare the parsed lockfile for access |
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.
This comment seems to be floating away from what it describes
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.
Done
); | ||
let lockDeps: Dependency = {}; | ||
|
||
try { |
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.
The getPackageDependencies
function is very long. You could begin to break it down by moving each of these v1 and v2+ operations to separate functions.
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.
I also recommend splitting these into separate functions - it's unclear to the reader which thing might fail and therefore why the catch block is larger than the try block... consider:
function parseYarnLock(lockFile: string, deps: Dependency): Dependency {
return parseYarnLockV1(lockFile, deps) || parseYarnLockV3(lockFile, deps);
}
function parseYarnLockV1(lockFile: string, deps: Dependency): Dependency | null {
try {
const parsedLockfile = lockfile.parse(lockFile);
return Object.entries(deps).reduce(...);
}
catch (e) { return null }
}
function parseYarnLockV3(lockFile: string, deps: Dependency): Dependency {
return Object.entries(yaml.load(lockFile) as LockFileEntries).reduce(...);
}
These could then be more readily unit/integration tested in isolation.
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.
Done
// Parse: yarn v3 lock comes with entries like "'yargs@npm:^15.0.2, yargs@npm:^15.1.0, yargs@npm:^15.3.1, yargs@npm:^15.4.1'" | ||
Object.entries(parsedLockfile).forEach(([name, { version }]) => { | ||
const entryDependencies = name.split(', '); | ||
dependencyArray.some(([dependencyName, dependencyVersion]) => { |
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.
As you're not actually using the return value of Array.prototype.some
, a for loop with a break statement would be clearer.
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.
I find it quite idiomatic when we ignore the return value (functional breaking loop), but no prob, done.
} catch (e) { | ||
// Try to parse as yaml (v2+) - https://github.com/yarnpkg/yarn/issues/5629#issuecomment-753418765 | ||
const parsedLockfile = yaml.load(lockFile) as LockFileEntries; | ||
const dependencyArray = Object.entries(deps); |
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.
What's the difference between dependencyArray
and Object.entries(parsedLockfile)
? I don't quite understand what this O(n^2)
algo is doing.
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.
dependencyArray
is the array of dependencies as extracted from source (likely very small), while Object.entries(parsedLockfile)
is the array of entries from the lockfile (possibly big). It's quadratic but not as in O(n^2)
, more like O(n*m)
, where m << n
(greatly less than).
We are traversing all the lockfile entries once and, for every entry, split the key in all the dependencies it represents, then match each with the dependencies extracted from source.
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.
Thanks for the explanation. Why do we not need to add the dependencies to the right of the parsedLockfile
entries after a match is found?
When you extract this to a separate function, I'd recommend adding a little context so it's easier for the next person who reads this to get to grips with the why. I'm not familiar with the new lockfile format!
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.
Refactored + added additional comments.
@@ -120,3 +103,46 @@ export async function getPackageDependencies( | |||
); | |||
return { manifest, resolutions }; | |||
} | |||
|
|||
function parseYarnLock(lockFile: string, deps: Dependency): Dependency { | |||
return parseYarnLockV1(lockFile, deps) || parseYarnLockV3(lockFile, deps); |
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.
Since we are explicitly catering to yarn 1 and yarn 3, can we add this to isYarnInstalled
in startupCheck.ts
?
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.
By this, I mean the check for the version of yarn to be explicit with our compatibility
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.
Spoke offline and decided to create a separate ticket to resolve yarn version compatibility
Problem
@yarnpkg/lockfile
is able to parse lockfile v1 format correctly but not lockfile v3 format (it throws immediately)@yarnpkg/lockfile
that we were using error with the lockfile v3 formatSolution
@yarnpkg/lockfile