160

Is there an npm equivalent of the yarn resolutions functionality? There is no mention of it in the npm package.json docs.

For example, I want to install lerna@3.3.2 and one of its dependencies (@lerna/publish) at 3.3.2 as well. Currently doing that with yarn like so, but would prefer to use npm and not manually change package-lock.json or anything dodgy like that.

"devDependencies": {
  "lerna": "3.3.2",
},
"resolutions": {
  "@lerna/publish": "3.3.2"
}
BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
adanilev
  • 3,008
  • 3
  • 15
  • 20
  • Do you know about `npm shrinkwrap` ? This might work for you. See: https://docs.npmjs.com/cli/shrinkwrap – Consta Gorgan Nov 29 '18 at 15:20
  • Does this answer your question? [How do I override nested NPM dependency versions?](https://stackoverflow.com/questions/15806152/how-do-i-override-nested-npm-dependency-versions) – Henke Apr 06 '23 at 13:17

5 Answers5

114

Since NPM 8.3 the equivalent to yarn resolutions is called overrides.

To make sure the package foo is always installed as version 1.0.0 no matter what version your dependencies rely on:

{
   "overrides": {
     "foo": "1.0.0"
   }
 }

Documentation: https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides

Why and how the feature was added it's discussed in the RFC and discussion on the topic was indexed on this github issue

fernandopasik
  • 9,565
  • 7
  • 48
  • 55
  • 1
    This answer helped me to solve https://github.com/andrewrk/node-s3-client/issues/228 An npm package whose github repo was archived referenced an old npm package causing errors when upgrading to a newer version of NodeJS. https://github.com/andrewrk/node-s3-client/commit/9a1b386020049ecf5d5c8ec70817afd7b6241fed https://github.com/andrewrk/node-s3-client/blame/master/package.json#L40 the offending packaged was updating in the repo however a new version was not pushed to npm, so the npm package was essentially broken forever. – Steven Sep 08 '22 at 15:48
65

This does not seem to be supported by npm natively, however this package aims to add this functionality:

https://github.com/rogeriochaves/npm-force-resolutions

Julien
  • 5,243
  • 4
  • 34
  • 35
  • 6
    @Gajus: [This commit message](https://github.com/rogeriochaves/npm-force-resolutions/commit/68cc61f3f1594fc2875b6092a409d25ebada3b52) suggests it should now work on npm 7. The corresponding version of `npm-force-resolutions` is `0.0.10`. – zb226 Mar 22 '21 at 16:12
  • 2
    2022, it **is supported**: https://stackoverflow.com/a/67397982/1115187 – maxkoryukov Jul 17 '22 at 06:12
59

is due to support overrides, which is equivalent to yarn's resolutions.

For more information about the current RFC status:

https://github.com/npm/rfcs/blob/latest/accepted/0036-overrides.md

Gajus
  • 69,002
  • 70
  • 275
  • 438
20

As far as I can tell, npm-force-resolutions does not work with v7. The package-lock.json format changed in v7 and npm-force-resolutions no longer updates the relevant fields.

However, it is relatively easy to write a script to restrict your dependency tree to only 1 version of a package, e.g.

#!/usr/bin/env node

/* eslint-disable unicorn/prevent-abbreviations */
/* eslint-disable import/unambiguous */
/* eslint-disable import/no-commonjs */
/* eslint-disable node/shebang */

const fs = require('fs').promises;
const path = require('path');

const main = async (resolutions) => {
  const packageLockFilePath = path.resolve(__dirname, '../package-lock.json');

  for (const [name, version] of Object.entries(resolutions)) {
    const packageLock = JSON.parse(await fs.readFile(packageLockFilePath));

    const packagePaths = Object.keys(packageLock.packages);

    const deletePaths = [];

    for (const packagePath of packagePaths) {
      if (packagePath.endsWith('/' + name)) {
        if (packageLock.packages[packagePath].version !== version) {
          deletePaths.push(packagePath);
        }
      }
    }

    for (const packagePath of deletePaths) {
      for (const deletePath of deletePaths) {
        if (packagePath === deletePath || packagePath.startsWith(deletePath + '/')) {
          // eslint-disable-next-line fp/no-delete
          delete packageLock.packages[packagePath];
        }
      }
    }

    await fs.writeFile(
      packageLockFilePath,
      JSON.stringify(packageLock, null, '  '),
    );
  }
};

main(require('../package.json').resolutions);

This script simply removes all links to dependencies that do not satisfy resolutions defined in package.json.

To execute the script, just add it to package.json scripts and define resolutions field, e.g.

{
  "scripts": {
    "postinstall": "node bin/fix-package-lock.js"
  },
  "resolutions": {
    "webpack": "5.6.0"
  }
}

resolutions is simply a map of package names and the exact versions of those packages that should be kept in the dependency tree, i.e. the above configuration will remove all versions of that are not 5.6.0. As long you install webpack@5.6.0 version as a dependency of the project you are working with, this will guarantee that all packages load the same version of webpack.

Gajus
  • 69,002
  • 70
  • 275
  • 438
7

For anyone reading this, overrides is now a supported feature of npm since version 8.3.0 :party:

Danny North
  • 301
  • 3
  • 5