-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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(import): convert package-lock.json to yarn.lock #5745
Conversation
|
Removed node4 support for this feature at @BYK's kind recommendation. :) |
38% size increase (from 3.95 MB to 5.46 MB) seems pretty substantial... Is all that weight from |
Mostly bluebird, through
And you really don't need pacote here: you should be able to get the package manifest using Yarn's built in whatever-the-thing-is. You should be able to get by with only adding |
I basically agree with everything. While I do this - are there any other unrelated concerns? |
(for the record, the reason pacote is so big is because you are literally pulling in npm's entire networking, caching, and package resolution layer. You're basically pulling in half the npm installer minus the tree calculation and reading logic. pacote does a lot of heavy lifting for being a tiny api :P) |
@zkat - using I removed If anyone wants to comment, give this a go or review, that would be great. |
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.
Left some initial comments. So far it looks good. I think I understand what is going on here and it seems sound. I'll try to actually test it out on a repo later.
In terms of future use, I can see the case where at some point we might want to prompt or auto convert during yarn install
and it might be nice to have some of this as it's own "npm lockfile converter" module, but that is refactoring that could happen later.
src/cli/commands/import.js
Outdated
createLogicalDependencyTree(packageJson: ?string, packageLock: ?string): LogicalDependencyTree { | ||
try { | ||
if (!packageJson || !packageLock) { | ||
throw new Error('files missing'); |
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 string should probably come from reporter.lang
and maybe report what files are expected.
Actually, since their existence is already checked around line 337-ish, do these even need to be 'nullable' types? Although I imagine Flow complains if you change ?string
to just string
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.
Yeah, this is some flow
acrobatics. I don't like it either.
I thought not to bother with reporter.lang
in this case because it will be caught by the below try/catch block by definition. I'll try to work out a better way.
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.
Change createLogicalDependencyTree
to only accept non-nullable types, then use invariant
calls before calling it at line 340 to instruct flow that they cannot be null. Something like this:
invariant(packageJson, 'packageJson should exist');
invariant(packageLock, 'packageJson should exist');
That being said, the condition at line 336 should be enough - maybe the fact that you're storing this inside a variable is throwing Flow off. Maybe you can try to split it?
let importSource = 'node_modules';
if (packageJson && packageLock && semver.satisfies(nodeVersion, '>=5.0.0')) {
importSource = 'package-lock.js';
// ...
}
src/cli/commands/import.js
Outdated
if (importSource === 'package-lock.json') { | ||
this.reporter.info(this.reporter.lang('importPackageLock')); | ||
const tree = this.createLogicalDependencyTree(packageJson, packageLock); | ||
if (this.resolver instanceof ImportPackageResolver) { |
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.
Is there a known case where resolver
isn't ImportPackageResolver
? If so, what would happen? Since the package-lock tree won't be set on the resolver, should this check maybe be part of packageJson && packageLock && semver.satisfies(nodeVersion, '>=5.0.0') ? 'package-lock.json' : 'node_modules';
to prevent it from taking this code path? (if resolver is not instanceof ImportPackageResolver then we build the logical tree and don't use it)
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.
More flow
acrobatics, I'm afraid. :/ I've seen this done elsewhere in the file though. I'm not sure if adding that check below will satisfy flow
, but can give it a shot.
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.
Just use a typecasting expression, or an invariant
🙂
src/cli/commands/import.js
Outdated
while (!path.relative(this.config.cwd, cwd).startsWith('..')) { | ||
const loc = path.join(cwd, 'node_modules', name); | ||
const info = await this.config.getCache(`import-resolver-${loc}`, () => this.resolveLocation(loc)); | ||
if (this.request instanceof ImportPackageRequest && this.request.fixedVersionPattern) { |
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.
Could we refactor these cases into separate functions for readability/clarity? something like
async resolve(): Promise<Manifest> {
if (this.request instanceof ImportPackageRequest && this.request.fixedVersionPattern) {
return await this._resolveFromFixedVersions();
} else {
return await this._resolveFromNodeModules();
}
}
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.
Yes, I like it!
Thanks for the input @rally25rs + @arcanis! |
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.
Tested it out on a couple small projects I had and it seems to work well. Unfortunately all my large projects (>1k packages total) have some yarn-only things going on so I can't npm i
them, but I think it looks solid.
If we can try using invariant
as @arcanis suggested to clean up some of the unnecessary conditionals
We should also update the website docs to explain the logic.
edit
I did manage to get the error
error An unexpected error occurred: "should have a resolved reference".
out of it. I'd share the package.json, but it has some private github repos in it. Let me see if I can narrow it down to something I can share...
@imsnif it looks like this package causes an error:
Using npm v5.8.0 In package-lock.json it looks like a bunch of packages depend on
It looks like |
Yarn doesn't handle bundledDependencies properly, can this be the reason? Theory: since yarn doesn't handle bundled dependencies, it expects these packages in the cache but they are not there so it fails. |
Nice find! @rally25rs, @BYK - So after some digging: I fixed it locally for this test-case by overriding the |
I fixed some of the unnecessary conditionals after the above comments from @rally25rs and @arcanis. I went in a bit of a different direction when creating the dependency tree. In my eyes, this is clearer. What do you think? I also refactored the Finally, I issued a fix for @rally25rs's bug discussed above. Since this bug happened both when importing It essentially has to do with So say we have this pattern in
When we get to Line 239 in 05080da
Now we get the request depA@^1.0.4 pointing at the same manifest in our patterns (version 1.0.4 ). We add it to a delayed queue and sort it out later in order not to create duplication: Lines 244 to 247 in 05080da
The problem is, that we make the assumption that said Manifest will still be in resolver.patterns when we sort it out later here: Line 208 in 05080da
In the normal course of an installation, this assumption works. When importing from a state of fixed versions, we can sometimes have an identical pattern resolving to a different manifest (one that does not satisfy the queued range). In this case, eg. depA@^1.0.0 ===> version: 1.0.3 . And thus, we would throw here: Line 209 in 05080da
I fixed this by overriding the resolveToExisting PackageRequest method for ImportPackageRequest . It essentially does the same as the overridden original method, but if it does not find a resolved pattern, it resolves itself to its own manifest.
Would love to hear additional comments on these fixes or other issues. |
Edit: Scratch that. Don't know what I did, but now the difference is minimal. I think this should be OK to ship 👍 |
This is the first step to addressing: #5654
Overview (TLDR)
At the moment, one is able to
yarn import
usingnode_modules
as the dependency-tree state to create ayarn.lock
file.The feature created by this PR allows one to
yarn import
usingpackage-lock.json
as the dependency-tree state. When one enters theyarn import
command, if apackage-lock.json
file exists, yarn would attempt to import from it. If not, it would import fromnode_modules
normally.How does this work?
A logical dependency tree is created from the
package-lock.json
file, then yarn walks through the dependencies as normal, using the logical tree to determine the package details. Often, this tree will not include the full information needed by yarn (eg. packageshasum
), and for this reason the package manifest is downloaded from the registry. This makes the process a little lengthy, but not terribly so. I deem this acceptable, seeing as this is not an everyday process. Would love to hear your thoughts if you disagree.I opted not to fall back on (or search initially for) the manifest in node_modules, because I felt this would be error prone and introduce non-trivial complexity (eg. corrupted node_modules).
Limitations
Rarely, a
yarn.lock
would not be 100% logically identical to thepackage-lock.json
from which it was created. In all incidents I encountered, this happens because of built-in limitations inyarn
.An example is two different packages with the same semver range string (eg.
^2.0.0
) being resolved inpackage-lock.json
to two different versions (eg.2.0.1
and2.0.2
).Another example is bundled dependencies.
I encourage the readers to test things for themselves (if there is interest, I have a script that logically compares yarn/npm trees - I can clean it up and post it somewhere).
Open Questions / Issues
package-lock.json
import feature and does not address the full concerns raised in the issue linked above. I feel this is a good first step, after which we can continue discussing whetheryarn install
should issue a warning/error if it finds apackage-lock.json
file, and whetherimport
should delete thepackage-lock.json
file after successfully importing. As well as the myriad of other concerns raised there.node_modules
, the user would end up with a fully installednode_modules
folder consistent with theiryarn.lock
file. This is not the case when importing frompackage-lock.json
. One mustyarn install
afteryarn import
ing from apackage-lock.json
. In my eyes, this is a desired behaviour for the conversion (which I see more as a 'surgical fix') even if it is not such a desired behaviour for theimport
command. I chose to give more weight to the former, but am of course open to different approaches.node_modules
folder is created when importing frompackage-lock.json
as mentioned here: extraneous files always checks/cleans node_modules, ignoring --modules-folder specified folder #5419Tests
In the tests for this feature I copied the relevant cases from the existing import tests. I did not (as I initially intended) add tests that make a logical comparison of the created
yarn.lock
against the originatingpackage-lock.json
.The existing cases use file snapshots, and I felt this would be more coherent upon failure. Would love to hear differing opinions though.