Currently in our monorepo's rush lockfile, there is no validation or verification of entries in the lockfile. This can lead to the introduction of potential issues such as side-by-side versions or dopplegangers as more developers work in the growing rush workspace.
The objective of this issue and the lockfile linter efforts is to create a configurable linter, similar to tslint or eslint in the sense that individual users of the linter can specify which rules they want to watch for and what sort of issues they want to be warned about. The linter should also be run in a C.I. environment to verify and validate the added entries to the lockfile when PRs are created, to ensure it does not violate any of the specified linting rules.
Some key features:
The system should be user-configurable, so that monorepo maintainers can specify which rules they want enforced in what way.
The linter should work similarly to how eslint or tsconfig work, where certain rules can be specified and configured to the requirements of the repo.
The usability of the linter will depend on two key factors:
The linting output file doesn't get churned too much, and all changes are meaningful (to either the package owner or monorepo maintainers)
Currently this is the main linting feature we are working on developing. It is a way of identifying and saving any side-by-side cluster nodes as well as connected projects. (Below is a handy list of vocabulary that we have been working on to help understand the feature.) This should allow monorepo maintainers to get a high-level overview of the highest priority side-by-side dependencies as well as any projects in the monorepo that are of the highest concern.
Cluster Nodes: A side-by-side dependency with no upstream side-by-side versions.
In the following example below, heft@2 and heft@3.0.7 are cluster nodes because they have no other upstream dependencies that are side-by-side versions. However, eslint@8.35 and eslint@9 are not considered cluster nodes as they have an upstream side-by-side package (in this case the two versions of heft).
We care only about the cluster node, because that is the determining factor that causes other dependencies to become side-by-side versions. Even if these side-by-side versions use the same dependency versions further down the tree, pnpm still has to install dopplegangers for those packages because the root dependency is different. For example, eslint@8.35 and eslint@9 cause dopplegangers (eslint-utils D1 and D2), even though the two versions of eslint may specify the same version of eslint-utils in the package.json. It also is likely to cause other side-by-side versions, such as things@3 and things@4.
Connected Cluster Nodes: A project in the rush workspace that has more than one cluster node dependencies somewhere in the tree. (denoted by allowedConnected in the linting output)
In the following example, App7 has two cluster nodes in its dependency tree (thing@3 and thing@4). These situations present a more serious issue because these are the scenarios that greatly increase the project app's bundle size (bundling 2 versions of thing, as well as 2 versions of all their mismatched dependencies further down the chain.
From inside the linter itself, the data will be represented in a similar format to below. Please note that this would not be the final format that is saved in the repo to compare against any PRs, as it is too detailed and would likely cause a lot of churn. The final file used for comparison will be much simpler (see below)
As this is much simpler of a format, it should prevent any churn in the file unless there is an issue that needs to be addressed (and thus require the monorepo maintainer to investigate & approve)
Trying out the Linter
To try out the lockfile linter in your monorepo, checkout the branch mentioned in the PR (https://github.com/microsoft/rushstack/pull/4094) and run the lockfile explorer application in your monorepo.
These are a set of additional linting rules and configurations that we have considered and will likely add as well in the future. We are also looking for any other linting rules that may be helpful to other monorepos (or that we have not considered!).
Version Sync Lint
Ensure major versions of certain packages match other package versions. For example, ensuring that the major version of the webpack-merge package matches the webpack version.
Singleton Version Lint
Ensure only one version of certain packages exist in the lockfile. For example, we may want to set that we only want to allow one version of tslib in the entire lockfile.
Licensing Lint
It may be helpful to have a linting rule that prevents packages with certain licenses from entering the monorepo.
Summary
Currently in our monorepo's rush lockfile, there is no validation or verification of entries in the lockfile. This can lead to the introduction of potential issues such as side-by-side versions or dopplegangers as more developers work in the growing rush workspace.
The objective of this issue and the lockfile linter efforts is to create a configurable linter, similar to tslint or eslint in the sense that individual users of the linter can specify which rules they want to watch for and what sort of issues they want to be warned about. The linter should also be run in a C.I. environment to verify and validate the added entries to the lockfile when PRs are created, to ensure it does not violate any of the specified linting rules.
Some key features:
The usability of the linter will depend on two key factors:
Current progress is being developed in this PR: https://github.com/microsoft/rushstack/pull/4094
Linting Side-by-Side Clusters
Currently this is the main linting feature we are working on developing. It is a way of identifying and saving any side-by-side cluster nodes as well as connected projects. (Below is a handy list of vocabulary that we have been working on to help understand the feature.) This should allow monorepo maintainers to get a high-level overview of the highest priority side-by-side dependencies as well as any projects in the monorepo that are of the highest concern.
Cluster Nodes: A side-by-side dependency with no upstream side-by-side versions. In the following example below, heft@2 and heft@3.0.7 are cluster nodes because they have no other upstream dependencies that are side-by-side versions. However, eslint@8.35 and eslint@9 are not considered cluster nodes as they have an upstream side-by-side package (in this case the two versions of heft). We care only about the cluster node, because that is the determining factor that causes other dependencies to become side-by-side versions. Even if these side-by-side versions use the same dependency versions further down the tree, pnpm still has to install dopplegangers for those packages because the root dependency is different. For example, eslint@8.35 and eslint@9 cause dopplegangers (eslint-utils D1 and D2), even though the two versions of eslint may specify the same version of eslint-utils in the package.json. It also is likely to cause other side-by-side versions, such as things@3 and things@4.
Connected Cluster Nodes: A project in the rush workspace that has more than one cluster node dependencies somewhere in the tree. (denoted by
allowedConnected
in the linting output) In the following example, App7 has two cluster nodes in its dependency tree (thing@3 and thing@4). These situations present a more serious issue because these are the scenarios that greatly increase the project app's bundle size (bundling 2 versions of thing, as well as 2 versions of all their mismatched dependencies further down the chain.From inside the linter itself, the data will be represented in a similar format to below. Please note that this would not be the final format that is saved in the repo to compare against any PRs, as it is too detailed and would likely cause a lot of churn. The final file used for comparison will be much simpler (see below)
To actually output a file that we can use to keep track of violations, this will be the approximate syntax:
As this is much simpler of a format, it should prevent any churn in the file unless there is an issue that needs to be addressed (and thus require the monorepo maintainer to investigate & approve)
Trying out the Linter
To try out the lockfile linter in your monorepo, checkout the branch mentioned in the PR (https://github.com/microsoft/rushstack/pull/4094) and run the lockfile explorer application in your monorepo.
To run a development feature of rush, you may need to follow the following steps: https://rushjs.io/pages/contributing/#testing-rush-builds
Roadmapped rules
These are a set of additional linting rules and configurations that we have considered and will likely add as well in the future. We are also looking for any other linting rules that may be helpful to other monorepos (or that we have not considered!).
Version Sync Lint Ensure major versions of certain packages match other package versions. For example, ensuring that the major version of the webpack-merge package matches the webpack version.
Singleton Version Lint Ensure only one version of certain packages exist in the lockfile. For example, we may want to set that we only want to allow one version of tslib in the entire lockfile.
Licensing Lint It may be helpful to have a linting rule that prevents packages with certain licenses from entering the monorepo.