Rush StackShopBlogEvents
Skip to main content

Installation variants

Sometimes you may want to build your entire monorepo using a modified set of dependencies. For example, suppose you just finished upgrading to a major new release of a framework, but you intend to maintain compatibility with the previous release during a transition period. Your developers should use the new variant for their everyday work, but for PR builds, you want your CI job to build the entire repo twice -- once with the old variant, and once with the new variant.

It seems like you could solve this by writing a simple script to search+replace the versions in your package.json files, but you'll quickly encounter other files that are affected:

  • shrinkwrap file: builds won't be deterministic unless you maintain separate shrinkwrap files for the two variants
  • common-versions.json: the preferredVersions or allowedAlternativeVersions may need to be different for the two variants
  • pnpmfile.js: if you have workarounds for poorly behaved legacy packages, they may need to be different for the two variants

This problem seems to requires a separate, parallel set of configuration files. Starting with Rush 5.4.0, there's now an out-of-box solution for this problem.

Setting up a variant

Suppose "widget-sdk" is a hypothetical library that just shipped a major new release 3, and we've upgraded to version 3, but we want to maintain compatibility with version 2. We can indicate this using a loose SemVer range our package.json file:

libraries/my-controls/package.json

{
"name": "my-controls",
"version": "1.0.0",
"description": "An example library project",
"license": "MIT",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"scripts": {
"build": "node_modules/.bin/my-build"
},
"dependencies": {
"widget-sdk": "^2.3.4 || ^3.0.2"
},
"devDependencies": {
"my-toolchain": "^1.0.0",
"typescript": "^3.0.3"
}
}

The "^2.3.4 || ^3.0.2" range indicates that our library will accept widget-sdk version 2.x (but not older than 2.3.4) or version 3.x (but not older than 3.0.2). When you run rush update, you will normally get the latest compatible version. How to build and test with the older version 2? Let's set up a variant!

1. Define your variant. In the rush.json config file, we add a definition like this:

*rush.json excerpt*

  "variants": [
// {
// /**
// * The folder name for this variant.
// */
// "variantName": "example-variant",
//
// /**
// * An informative description
// */
// "description": "Build this repo using the previous release of the SDK"
// }

{
"variantName": "old-widget-sdk",
"description": "Build this repo using version 2 of the widget-sdk"
}
],

2. Copy the config files. To get your variant started, copy your existing config files from common/config/rush to your variant folder common/config/rush/variants/old-widget-sdk. Three config files are currently supported (others may be added in the future):

  • shrinkwrap.yaml, npm-shrinkwrap.json, or yarn.lock depending on your package manager
  • common-versions.json
  • pnpmfile.js if you are using PNPM as your package manager

Be sure to add the copied files to Git:

git add .
git commit -m "Creating a new variant"

3. Override the dependency versions for the variant. For this example, we will downgrade widget-sdk to use version 2.x. This can be done by using Rush's preferred versions feature. We'll use a wildcard so that rush update --full still picks up minor/patch releases:

*common-versions.json excerpt*

  /**
* A table that specifies a "preferred version" for a dependency package. The "preferred version"
* is typically used to hold an indirect dependency back to a specific version, however generally
* it can be any SemVer range specifier (e.g. "~1.2.3"), and it will narrow any (compatible)
* SemVer range specifier. See the Rush documentation for details about this feature.
*/
"preferredVersions": {

/**
* When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo,
* instead of the latest version.
*/
// "some-library": "1.2.3"

"widget-sdk": "^2.3.9"
},

Note that ^2.3.9 satisfies the SemVer range ^2.3.4 || ^3.0.2 that we specified in package.json above. (If this was not true, then the preferred version would not have any effect!)

4. Install your variant and test it. Let's start by running rush update to install the new set of dependency versions:

rush update --full --variant old-widget-sdk

This will update the file common/config/rush/old-widget-sdk/shrinkwrap.yaml, install those dependencies in common/temp/node_modules, and link each project to use those dependencies. The rush install command also supports the --variant option. Your CI job can use this when it builds with the old widget-sdk release.

Now you can build and test your variant:

rush rebuild

👉 If you get tired of typing --variant, you can also use the RUSH_VARIANT environment variable to specify the variant name.

5. Restoring the original state. When you're done testing your variant, you can return to the original state by running rush install without the --variant option. We call this the "default variant", because it's the same default behavior as a repo that didn't define any variants:

# Restore the original state by omitting "--variant":
rush install

Tip: If you forget which variant is active, you can look in the common/temp/current-variant.json file. If you open this file in a text editor, you should see a line like this:

{
"variant": "old-widget-sdk"
}