rogeriochaves / npm-force-resolutions

Force npm to install a specific transitive dependency version
567 stars 28 forks source link

Support for multiple versions of same package #32

Open jcardali opened 3 years ago

jcardali commented 3 years ago

I've run into the situation several times now when I want to add a resolution for a package, but there are multiple major versions present. For example:

> npm ls y18n
├─┬ @graphql-codegen/cli@1.21.3
│ └─┬ yargs@16.2.0
│   └── y18n@5.0.5
├─┬ concurrently@5.3.0
│ └─┬ yargs@13.3.2
│   └── y18n@4.0.0
└─┬ jest@26.6.3
  └─┬ jest-cli@26.6.3
    └─┬ yargs@15.4.1
      └── y18n@4.0.0  deduped

If I then add the following resolution:

  "resolutions": {
    "y18n": "~4.0.1",
  }

I now get:

 > npm ls y18n
├─┬ @graphql-codegen/cli@1.21.3
│ ├── y18n@4.0.1  extraneous
│ └─┬ yargs@16.2.0
│   └── y18n@4.0.1  invalid
├─┬ concurrently@5.3.0
│ └─┬ yargs@13.3.2
│   └── y18n@4.0.1
└─┬ jest@26.6.3
  └─┬ jest-cli@26.6.3
    └─┬ yargs@15.4.1
      └── y18n@4.0.1

That invalid error is the problem, because yargs@16.20 requires y18n of ^5.0.5. The inverse would happen if the resolution was instead set to ~5.0.5.

It would be great if support could be added for multiple versions in resolutions like so:

  "resolutions": {
    "y18n": "~4.0.1 || ~5.0.5"
  }

Which matches the syntax for dependencies: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#dependencies

OR

  "resolutions": {
    "y18n": ["~4.0.1", "~5.0.5"]
  }

Which in this case would yield:

 > npm ls y18n
├─┬ @graphql-codegen/cli@1.21.3
│ ├── y18n@4.0.1  extraneous
│ └─┬ yargs@16.2.0
│   └── y18n@5.0.5
├─┬ concurrently@5.3.0
│ └─┬ yargs@13.3.2
│   └── y18n@4.0.1 
└─┬ jest@26.6.3
  └─┬ jest-cli@26.6.3
    └─┬ yargs@15.4.1
      └── y18n@4.0.1
bones418 commented 3 years ago

When force changing any transitive dependency there is the possibility that we break the parent dependency.

I think we can do better. The README for this module states: "The use case for this is when there is a security vulnerability and you MUST update a nested dependency". So let's keep that in mind and only make the necessary updates to remove the security vulnerability.

Users should be able to enter a minimum version required in the resolutions block. For example, I require version 2.4.0 of package x. If a transitive dependency for package x is 2.1.6 (or anything below 2.4) then it gets bumped to 2.4. Otherwise, if a transitive dependency for package x is 2.7.0 (or anything greater than 2.4) it is not changed. This will keep the blast radius as small as possible!

For example:

> npm ls y18n
├─┬ @graphql-codegen/cli@1.21.3
│ └─┬ yargs@16.2.0
│   └── y18n@5.0.5
├─┬ concurrently@5.3.0
│ └─┬ yargs@13.3.2
│   └── y18n@4.0.0
└─┬ jest@26.6.3
  └─┬ jest-cli@26.6.3
    └─┬ yargs@15.4.1
      └── y18n@4.0.0  deduped

And then I add

"resolutions": {
    "y18n": ">=4.0.1"
}

Which would result in:

> npm ls y18n
├─┬ @graphql-codegen/cli@1.21.3
│ └─┬ yargs@16.2.0
│   └── y18n@5.0.5 <== left alone
├─┬ concurrently@5.3.0
│ └─┬ yargs@13.3.2
│   └── y18n@4.0.1 <== bumped from 4.0.0 to 4.0.1
└─┬ jest@26.6.3
  └─┬ jest-cli@26.6.3
    └─┬ yargs@15.4.1
      └── y18n@4.0.1  <== bumped from 4.0.0 to 4.0.1
jcardali commented 3 years ago

@bones418 What if there was a 3.x or 2.x nested version of y18n?

For my particular use case, y18n has vulnerabilities before versions 3.2.2, 4.0.1, and 5.0.5

bones418 commented 3 years ago

@jcardali agreed, your project could contain nested versions of a package where multiple major versions are vulnerable and need to be bumped. I also suppose the entirety of some 5.x.x release could be vulnerable while a 4.x.x release is not, which means there'd be cases where you do want to explicitly downgrade the package.

Suppose resolutions is

  "resolutions": {
    "y18n": "~4.0.1 || ~5.0.5"
  }

I imagine that means:

I still think a simple approach of taking max(nested dependency version, minimum required version) as a quick win to reduce blast radius and address 80% of use cases. Perhaps they should be separate issues though.

ryan-smylski commented 3 years ago

Since this was built to be similar to yarn's selective dependency resolutions, could it not follow their lead for specifying which packages to update https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-selective-versions-resolutions.md#package-designation

cronon commented 2 years ago

I faced a need for it recently as well. I think first very easy step could be adding support for a js config file. For instance the command would run npm-force-resolutions --config resolutions.js and resolutions.js

module.exports = {
map: function(packageName, currentVersion) {
    if (packageName === lodash) return '4.17.2'
    else if (packageName === 'yargs') {
         if (currentVersion === '5.0.2') return '5.0.2'
         else return '4.0.1'
    }
    else return currentVersion;
}
}

Despite usage is not that convenient, but since implementation and API for that is much simpler, it can be done much faster and therefore be used much sooner

sunil-lulla commented 1 year ago

hey folks, what's the update for this? Are we doing this - my use case is also same for this vulnerability - https://github.com/advisories/GHSA-76p3-8jx3-jpfq.

The Above JS file scheme is great, will add lot of extensibility?

@jcardali Hi Mate, how did you handled the above use case then, any thoughts?

kiloc commented 1 year ago

i use the fixed my issue

"resolutions": {
    "core-js@>2.0.0 <3.0.0": "^2.6.0",
    "core-js@>3.0.0": "^3.30.0"
  }
"core-js@>2.0.0 <3.0.0@^2.6.0", core-js@>3.0.0@^3.30.0, core-js@^3.11.0, core-js@^3.30.0, core-js@^3.6.4, core-js@^3.8.3:
  version "3.30.0"
  resolved "http://xxx/core-js/-/core-js-3.30.0.tgz#64ac6f83bc7a49fd42807327051701d4b1478dea"
  integrity sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==

core-js@^2.4.0, core-js@^2.5.0:
  version "2.6.12"
  resolved "http://xxx/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
  integrity sha1-2TM9+nsGXjR8xWgiGdb2kIWcwuw=

https://github.com/yarnpkg/rfcs/blob/master/implemented/0000-selective-versions-resolutions.md#mapping-version-specifications-as-well-as-packages-name