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

Selective versions resolutions #68

Merged
merged 8 commits into from
Jul 10, 2017

Conversation

victornoel
Copy link
Contributor

Hi,

Here is an RFC for solving the situation discussed in yarnpkg/yarn#2763

@bestander

the `package.json` file.

ça veut dire qu'on doit pouvoir mettre des specs invalides (wrt vesion) dans
lockfile.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this written in french? Should we translate this part?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha no, sorry, I took some notes while writing and forgot to remove them :P I'm going to amend the commit!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's done, thanks :)

@victornoel victornoel force-pushed the selective-versions-resolutions branch from 5533347 to 98693bd Compare May 21, 2017 09:56
When a nested dependency is being resolved by yarn, if the `resolutions` field
contains a version for this package, then this version is used instead.

All the examples are given with exact dependencies, but note that putting a
Copy link

@sejoker sejoker May 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it make sense to provide separate examples for the resolutions with non-exact specification. The more specific RFC will be defined the better end implementation will be produced at the end.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will work on that soon then :)

My idea is that the way yarn resolves a given dependency is the same as before, so exact or non-exact specifications should be handled the same.
For example, the role of yarn.lock doesn't change, it is completely inferred from the package.json file, including the resolutions field.

But maybe there is some corner cases to be discovered. Im' thinking for example about a situation where a nested dependency has a specification more specific than what is specified in the resolutions field. Intuitively I would say that the resolutions field takes precedence and a warning is printed. I will put that in a more formal way in the document :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's done :)

But the `resolutions` field is always considered by yarn, even if `--flat` is
not specified.

Inceidently, this resolves this strange situation when two developers would be
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incidentally

@sejoker
Copy link

sejoker commented May 21, 2017

@victornoel in yarnpkg/yarn#2763 you mentioned that npm allows overriding dependencies. Could you reference that in RFC? I can't find any documentation on npm website, maybe I missed something. The point is, referencing npm's similar mechanism will increase chances of the RFC to land.

@victornoel
Copy link
Contributor Author

victornoel commented May 21, 2017

@sejoker actually I'm not so sure it is possible with npm, I will look a bit more, but what I originally linked (https://nodejs.org/en/blog/npm/managing-node-js-dependencies-with-shrinkwrap/) is actually closer to the --flat options than to this RFC.

I still remember reading something about that but there is a lot of chance I was wrong.

EDIT: actually it is closer to the lockfile mechanism than to --flat apparently, so it's out of scope I think :)


Should yarn warn the user about an incoherence between an explicit dependency
and a resolution. For example if the user specify a dependency to
`typescript@2.3.2` and the resolutions field contains `typescript@2.3.0`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that Yarn warns when peer dependencies don't match semver I think overridden resolutions should warn as well.
I think this is a temporary measure until the community catches up and fixes issues

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

For sure if the above alternative solution is chosen, this wouldn't make sense.

Should we warn if a resolutions is incompatible, but still upper-bounded?
For example, forcing version `a@2.3` while a dependency needs version `a@2.2`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if semver is violated there should be a warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

"typescript": "2.3.2"
},
"resolutions": {
"@angular/cli": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer nesting to be an option.
JS project, unlike Java, have hundreds of dependencies, it is not rare to have several versions of the same library at different levels and this is fine.
Sometimes I'd need a fine control to override resolution only for a specific dependency path

Copy link
Contributor Author

@victornoel victornoel May 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's exactly why I am not so sure the original proposition is good enough and I added that question at the end :)

Should we merge both of the proposition?

Accepting both resolutions like this:

"typescript": "2.3.2"

and

"@angular/cli": {
    "typescript": "2.3.2"
}

@bestander
Copy link
Member

You need to consider check --verify-tree and check commands.
First one parses package.json files and verifies that they don't conflict with each other.

@bestander
Copy link
Member

Thanks for raising this, @victornoel!

@arcanis
Copy link
Member

arcanis commented May 22, 2017

Three thoughts:

  • Maybe we could put this inside the lockfile instead of the package.json? I assume this resolutions key will have no effect unless it's inside the project root, right (just like we ignore nested yarn.lock)?

  • Maybe we could make the resolutions key a glob pattern, minus the node_modules:

    { "resolutions": { "**/left-pad": "1.0.0", "**/@angular/cli/typescript": "ssh+git://..." } }

    It would be more verbose, but this feature should be a very advanced feature anyway, and should never be used under normal circonstances, so I think it could work.

  • Maybe the resolutions could become a "preference" array, so that :

    { "resolutions": { "typescript": [ "ssh+git://..." ] } }

    Then, when resolving, we would first try the dependencies listed in this array, in the order defined. If they don't match the version specified, then continue just like we currently do. The problem would be finding a good wording for the exotic resolvers (ie dependencies that don't come from the npm registry).

@bestander
Copy link
Member

Maybe we could put this inside the lockfile instead of the package.json?

I think this list will be manually managed and yarn.lock file is automatically generated.
Also IMHO resolutions is very similar dependencies from package.json, they loosely influence resolution step in Yarn install command, yarn.lock definitely describes resolved dependencies after resolution happened.

@victornoel
Copy link
Contributor Author

@arcanis originally it is not intended to be a very advanced feature. It is meant to be used regularly by people. At least in the Maven world, this kind of feature is very useful and thus very used.

Your question raises a point I didn't think of before: is it the case that you can have node_modules directory inside dependencies that are themselves in the node_modules of one of the project dependency?
Something like myproject/node_modules/dep1/node_modules/dep2/node_modules/dep3?

@bestander
Copy link
Member

@victornoel, feel free to start work on the implementation, we can agree on the details how specifiers are implemented in the meantime.

@bestander
Copy link
Member

Something like myproject/node_modules/dep1/node_modules/dep2/node_modules/dep3

That is a legit use case.

@victornoel
Copy link
Contributor Author

feel free to start work on the implementation, we can agree on the details how specifiers are implemented in the meantime.

@bestander I'm not sure I have the skills to implement this, I wouldn't even know where to start…
I can commit to take some time to see how it could be implemented and see if I'm up to the task, but I would need you to give me some guidance on where to start for this to happen.

Something like myproject/node_modules/dep1/node_modules/dep2/node_modules/dep3

That is a legit use case.

It is? Damn, it means that to be complete, we would need to be able to specify resolutions like this then:

"resolutions": {
    "dep1": {
        "dep2": {
            "dep3": "1.0.0"
        }
    }
}

Or use some kind of glob pattern as proposed by @arcanis

@bestander
Copy link
Member

I'm not sure I have the skills to implement this, I wouldn't even know where to start…

Most of resolution stuff is happening here https://github.com/yarnpkg/yarn/blob/master/src/package-resolver.js.
You can also search where semver module is used because your change is going to be about violating semver.

Or use some kind of glob pattern as proposed by @arcanis

For deep trees, which is common, a glob pattern makes sense.
A maybe easier alternative is to override a specific semver with another one, e.g. typescript@>=2.5 < 3 => typescript@4.0.2

@victornoel
Copy link
Contributor Author

A maybe easier alternative is to override a specific semver with another one, e.g. typescript@>=2.5 < 3 => typescript@4.0.2

I'm not very convinced by this approach, I explained why in the RFC (I will update it with all the feedbacks and discussions soon by the way).

@bestander
Copy link
Member

bestander commented May 23, 2017 via email

@victornoel
Copy link
Contributor Author

It just seemed easier to implement for me but I don't insist

I agree it will be easier to implement, I'm more worried about usability and expressiveness :)

"@angular/cli": "1.0.3"
},
"resolutions": {
"typescript": "2.3.2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think it is a bit too brute force to force typescript to 2.3.2 everywhere.
We need to be able to enforce typescript to some subtree.
What do you think of the proposed glob for the resolution path?

The use case I am trying to solve is this:

  • left-pad@2.0.5 is getting released with a bug
  • it is used all over the app dependencies, some of them get locked on @1.0.1 which is fine
  • you want to upgrade jest to next version but some of the subdependencies requires "leftpad@^2.0.0" which will get resolved to "leftpad@2.0.5"
  • you want to force jest's subdependency to leftpad@2.0.4 but don't change force it across the whole app

I would think this could solve it elegantly.

"resolutions": {
  "jest-*/**/left-pad": "1.0.4"
}

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I agree, I was actually thinking of something like that after integrating in my head all the comments and @arcanis glob pattern proposition :)

I'm still processing all that was said and also things to add (check command in particular) to produce an update of the RFC.

@bestander
Copy link
Member

@victornoel, this is one of the best RFC I've seen here, great job!

@victornoel
Copy link
Contributor Author

hehe, cool, thanks!


## Package designation and single `*`

I am not totally convinced that using a single `*` in a package designation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's not do single *

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, cool!

I wonder if the `--flat` option of `install` shouldn't be transformed to
a `flatten` command that would:
1. Fill in the *resolutions* for all nested dependencies.
2. Set the `flat` field in the `package.json`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that idea.
Unlike --production, --flat is an option that you decide for the project early on and stick to it.


## resolutions in dependencies

If a dependency contains resolutions, should it be taken into account?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, same as shrikwraps/lockfiles, every project is responsible for their resolutions.
Also right now this is a Yarn-only feature, we don't really want to get it into the wild yet too much

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree :)

2. `install` should be only about building the `node_modules` directory, not
modifying the the `package.json` IMHO.

If we do that, then the `--flat` option of `add` (and the `flat` option in the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how much --flat affects resolutions feature.
Can we decide on flat in a follow up RFC?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If --flat gives trouble to this feature then let's just throw on --flat if resolutions property is specified

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean:

  1. Currently, --flat is both about populating resolutions field AND taking resolutions field into account when doing install (including install after add).
  2. This RFC is about taking resolutions field into account when doing install (including install after add).
  3. So with this RFC, --flat is only about populating resolutions field.

Thus the question is when should the resolutions field be populated.
Maybe this can simply be managed in another RFC that transforms --flat into:

  • a flatten command that flattens the current dependency tree.
  • a --flat option (for all package.json modifying command such as add) as well flat field to keep the dependency tree flattened at all time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, it seems like I might not have all the context on this one.
I thought that --flat was just enforcing that every dependency is resolved only once so that after hoisting we get a flat structure in node_modules.

I propose leaving --flat behavior out of scope for this RFC and move forward with the implementation if it is possible.
We can follow up with --flat specific right after it.

```

yarn will use `package-d1@3.0.0` both for `package-a` and the nested
dependency `package-a` of `package-c`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? **/package-a/package-d1 doesn't match package-a

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you mean that both /package-a/package-d1 and /package-c/package-a/package-d1 will be fixed at 3.0.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you mean that both /package-a/package-d1 and /package-c/package-a/package-d1 will be fixed at 3.0.0?

yes: /package-a/package-d1 and /package-c/package-a/package-d1 both match **/package-a/package-d1

With the current proposal, the package designation `package-a` is an alias for
`**/package-a`: this means the behaviour of yarn with a project whose
`resolutions` field contains *resolutions* filed by a pre-RFC yarn will be
as expected: the nested dependencies will have the fixed version specified.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I also think we should prevent people from using both pkg-name and glob syntaxes at the same time. If they want the same effect, they should explicitely use **/pkg-name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean:

  1. consider both syntaxes equally valid but prevent the user from mixing them in the same project, or
  2. have a preference for the glob syntax and keep the non-glob syntax only for retro-compatibility purposes?

How would we achieve one or the other btw? Via printing a warning? For 2. I can easily see it happening, but for 1. it would seem strange I think...

@arcanis
Copy link
Member

arcanis commented Jun 30, 2017

This RFC seems to be in a really good shape @victornoel, kudos! 😃 What would you think about implementing a first iteration in Yarn and see how it goes? Would you be willing to do it?

@victornoel
Copy link
Contributor Author

@arcanis last time I checked the code for this task, I got a bit lost in it, I'm worried I won't succeed doing it and I wouldn't want to commit to something I can't do :)
@bestander seemed to know which specific places would be concerned (see #68 (comment)), so, once we consider the set of features to implement fixed, I can look at the code again with @bestander guidance on where to look (I suppose this won't be different from what he envisioned in the above comment) and tell you if I can do it or not :)

I'm going to introduce the changes discussed in last few days very soon and then I will explore the code again!

@bestander
Copy link
Member

@victornoel, you'll have our full support.
Feel free to push some half-baked ideas in and we can have a look.
Maybe you could start with a PR that adds failing and skipped tests that cover the spec?
Then with another go we would just need to comply with the tests

@bestander
Copy link
Member

bestander commented Jul 3, 2017

Personally, I think this is one of the most important features in while for Yarn.
It addresses a huge point of stress for semver packages users.

@victornoel
Copy link
Contributor Author

victornoel commented Jul 3, 2017

Maybe you could start with a PR that adds failing and skipped tests that cover the spec?

Actually I've already started doing that some time ago :) it's a good idea, and like this, worst case scenario is someone will be able to implement the RFC from the failing tests :)

@victornoel
Copy link
Contributor Author

ok, I pushed the last changes, I kept the discussion on the future of --flat in a specific section at the end for record, and later we should make another RFC.

@bestander bestander merged commit fd710e7 into yarnpkg:master Jul 10, 2017
@bestander
Copy link
Member

Great job, @victornoel.
This is going to be huge!

@HaleTom
Copy link

HaleTom commented Oct 6, 2017

Where do I find the official location of this RFC (and its number)?

@bestander
Copy link
Member

@victornoel
Copy link
Contributor Author

@bestander by the way, maybe it should be moved to the implemented sub-folder now, no?

@bestander
Copy link
Member

Of course, do you want to send a quick PR?

@victornoel
Copy link
Contributor Author

ok, I will do that in the next few days! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants