How to use yarn to upgrade node_module subdependencies. image

When might you want to do this?

Example

There is security vulnerability in a node module that is included in the proeject you're working on. Let's pretend that the vulnerability impacts braces versions <=3.0.2. How do you resolve this?

1. Start by running yarn why braces

yarn why is a handy command that can help us figure out why a dependency exists in in your project so let's start with that.

The output of yarn why braces in an example project:

yarn why v1.22.4
[1/4] πŸ€”  Why do we have the module "braces"...?
[2/4] 🚚  Initialising dependency graph...
[3/4] πŸ”  Finding dependency...
[4/4] 🚑  Calculating file sizes...
=> Found "braces@3.0.2"
info Has been hoisted to "braces"
info Reasons this module exists
   - Hoisted from "karma#braces"
   - Hoisted from "ts-loader#micromatch#braces"
   - Hoisted from "karma#chokidar#braces"
   - Hoisted from "copy-webpack-plugin#fast-glob#micromatch#braces"
   - Hoisted from "@types#browser-sync#@types#chokidar#chokidar#braces"
   - Hoisted from "@rails#webpacker#webpack#watchpack#chokidar#braces"
info Disk size without dependencies: "80KB"
info Disk size with unique dependencies: "104KB"
info Disk size with transitive dependencies: "244KB"
info Number of shared dependencies: 4
=> Found "micromatch#braces@2.3.2"
info This module exists because "micromatch" depends on it.
info Disk size without dependencies: "120KB"
info Disk size with unique dependencies: "1.42MB"
info Disk size with transitive dependencies: "4MB"
info Number of shared dependencies: 30
=> Found "chokidar#braces@2.3.2"
info This module exists because "chokidar" depends on it.
info Disk size without dependencies: "76KB"
info Disk size with unique dependencies: "1.38MB"
info Disk size with transitive dependencies: "3.96MB"
info Number of shared dependencies: 30
✨  Done in 0.80s.

2. πŸ”Ž Analyze the output

Look for all the lines that start with "Found". Each of these lines gives us information about a version of braces that this project depends on. In this case we can see that our project depends on braces versions 3.0.2 and 2.3.2. This means that in our project's yarn.lock file we should expect to see 2 entries starting with braces@. One will have its version property set to 3.0.2 and the other, 2.3.2. Here's a full output of these from yarn.lock:

braces@^2.3.1, braces@^2.3.2:
  version "2.3.2"
  resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
  integrity sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=
  dependencies:
    arr-flatten "^1.1.0"
    array-unique "^0.3.2"
    extend-shallow "^2.0.1"
    fill-range "^4.0.0"
    isobject "^3.0.1"
    repeat-element "^1.1.2"
    snapdragon "^0.8.1"
    snapdragon-node "^2.0.1"
    split-string "^3.0.2"
    to-regex "^3.0.1"

braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
  version "3.0.2"
  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
  integrity sha1-NFThpGLujVmeI23zNs2epPiv4Qc=
  dependencies:
    fill-range "^7.0.1"

In our case, the next lines we care about tell us why braces exists via output of the dependency tree leaves that end with braces. The left-most leaves are dependencies we've declared in the project's package.json file.

Looking at a few of these lines we see that:

  • karma has a direct dependency on braces.
  • ts-loader has a dependency on braces via its dependency on micromatch.
  • karma also has a dependency on braces via its dependency on chokidar.
  • etc

3. πŸš€ Update the version of braces that our project requires

Lets assume the maintainer of the braces module has published an updated version (3.0.3) that resolves the vulnerability*, our goal now is to update our project's dependency tree so that it only resolves its braces dependencies to braces@3.0.3.

*If the maintainer hasn't shipped a fix, open an issue on their project repository with details about the vulnerability and the fix.

Here are some options for updating the braces version that our project depends on. I've ordered these according to how effective the solution is, with minimal side-effects.

1. βœ‚οΈπŸŒ³ Prune tree leaves that depend on braces

Depending on how the version constraint on braces is declared* in the package.json file of modules that directly depend on braces (leaves that were to the immediate left of braces in the yarn why output), they (or newer versions of them) may be able to resolve braces to version 3.0.3.

*For more information on dependency version constraints in package.json check out this article.

Case 1: All the package.json files for modules that depend on braces, we see "braces": "^3.0.0". This makes us πŸ˜… (relieved)! This means we can simply delete the 2 entries for braces for our yarn.lock file and then run yarn install. This will install braces@3.0.3 and set it as the version that is depended on.

Case 2: Not all current modules depending on braces can resolve their versions to 3.0.3. We must then see if newer versions of these modules are available that update their braces version constraint to be something that can resolve to 3.0.3. If not, we're a bit stuck. We should create an issue on the module(s) repo for their maintainer(s) and then proceed to #2.

2. ↗️ Upgrade top-level that include braces in their dependency sub-tree

In the case we've been looking at, we can see what top-most dependencies requires braces and can upgrade them. This will result in that package's entire sub-dependency tree getting updated as well. This can be helpful as it may remove the dependency on braces entirely in some cases! If whatever module that depended on braces can be resolved to braces@3.0.3, that version will be installed and an update to yarn.lock will have been made. This process must be repeated for all top-level dependencies that have a sub-tree that has a braces leaf in it (the left-most packages in the output in step 2).

If, after completing this, your yarn.lock file only contains a single braces@ entry with its version set to 3.0.3, AND your project is stable (you've not broken something by upgrading top-level dependencies), commit your change-set, rebuild your project and deploy!! βœ…

If you still have yarn.lock entry for braces <= 3.0.2 then proceed to #3.

3. 😬 Add a "resolutions" entry in package.json

⚠️This should only be done if braces cannot be resolved to version 3.0.3 for some reason. For more things to consider, refer to the yarn "Selective dependency resolutions" documentation for "Why would you want to do this?". Once you're confident this is the only way to proceed, follow the "How to use it?" instructions.