Pinned versions

Background

Rush performs a single install for all your projects by creating a fake rush-common project in your common folder that references tarballs containing the dependencies for each project. For example, suppose your rush.json has two projects “project1” and “project2”. The generated file might look like this:

common/temp/package.json

{
  "name": "rush-common",
  "description": "Temporary file generated by the Rush tool",
  "private": true,
  "version": "0.0.0",
  "dependencies": {
    "@rush-temp/project1": "file:./projects/project-1.tgz",
    "@rush-temp/project2": "file:./projects/project-2.tgz",
  }
}

NPM considers each of these “@rush-temp” scoped projects to be a direct dependency for the rush-common project. In general NPM installs a project’s direct dependencies first (at the root of the node_modules tree), and only then proceed to download the indirect dependencies. But since your real project’s direct dependencies are now indirect dependencies for the rush-common project, the npm install behavior could be different.

Suppose project-1/package.json looks like this:

{
  "name": "project-1",
  "version": "1.0.0",
  "dependencies": {
    "library-a": "1.0.1",
    "library-b": "1.1.3"
  }
}

And let’s say library-a (from the internet) looks like this:

{
  "name": "library-a",
  "version": "1.0.1",
  "dependencies": {
    "library-b": "^1.0.0"
  }
}

If you ran a normal npm install in for project-1, you would expect to have a node_modules folder which looks like this, even if library-b@1.4.4 exists in the NPM registry:

node_modules/
  library-a/ (1.0.1)
  library-b/ (1.1.3)

Even though library-b@1.4.4 matches the "^1.0.0" SemVer pattern, NPM doesn’t install it because 1.1.3 (installed by project-1) already satisfies it.

But the common/temp/package.json described above would not guarantee this. Instead, depending on the dependencies of project-2, you could end up with this:

node_modules/
  project-1/
    library-b/ (1.1.3)
  library-a/ (1.0.1)
  library-b/ (1.4.4)

… which is also a valid solution to the SemVer equation. Similar problems can arise when using Rush with NPM’s peer dependencies.

Pinned Versions

To control these effects Rush introduces a concept of “pinned versions”, which are dependencies that get explicitly added to the top-level common/temp/package.json.

You can “pin” a version by adding it to the config file pinned-versions.json. For example:

common/config/rush/pinned-versions.json

{
  "css-loader": "1.2.3"
}

This will cause css-loader to be added to the common/temp/package.json from our above example, like this:

{
  "name": "rush-common",
  "description": "Temporary file generated by the Rush tool",
  "private": true,
  "version": "0.0.0",
  "dependencies": {
    "css-loader": "1.2.3",
    "@rush-temp/project1": "file:./projects/project-1.tgz",
    "@rush-temp/project2": "file:./projects/project-2.tgz",
  }
}

Note: If you are publishing packages, you should be careful about pinning versions in a way that would produce a different result than a person who installs your library normally using NPM.

Implicitly Pinned Versions

In general, you don’t need to pin versions explicitly. In our original example, Rush would probably automatically solve the problem for you using “implicitly pinned versions”. Most likely your common/temp/package.json will already look like this:

{
  "name": "rush-common",
  "description": "Temporary file generated by the Rush tool",
  "private": true,
  "version": "0.0.0",
  "dependencies": {
    "library-a": "~1.0.0",
    "library-b": "1.1.3",
    "@rush-temp/project1": "file:./projects/project-1.tgz",
    "@rush-temp/project2": "file:./projects/project-2.tgz",
  }
}

As long as two projects don’t request incompatible versions of a library, Rush will implicitly pin their versions. But if project1 and project2 were requesting different versions of library-b, then you might need to get involved, and maybe use pinned-versions.json to resolve your issue.