Danger + Dependencies
#Before we get started
This tutorial continues after “Getting Started” - so you should have seen Danger comment on your PRs.
#Keeping on top of your dependencies
Building pretty-much anything in the node ecosystem involves using using external dependencies. In an ideal situation you want to use as few dependencies as possible, and get the most use out of them. Remember that you are shipping your dependencies too, so you are responsible for them to your end-users.
The numerical scale of dependencies can make it tough to feel like you own your entire stack. So let’s try and use Danger to give us more insight into changes related to our dependencies.
#Lockfiles
The simplest rule, which we can evolve, is that any time your package.json changes you probably want a change to the
yarn.lock or shrinkwrap.json file. Yes, not every change to the package.json represents
a dependency update but we’re starting simple. You start off your Dangerfile like this:
import { danger, fail, warn } from "danger"
import includes from "lodash.includes"
const hasPackageChanges = includes(danger.git.modified_files, "package.json")
const hasLockfileChanges = includes(danger.git.modified_files, "yarn.lock")
if (hasPackageChanges && !hasLockfileChanges) {
warn("There are package.json changes with no corresponding lockfile changes")
}
This uses lodash’s _.includes() function to see if danger.git.modified_files includes the package, but not the
lockfile.
#Vetting New Dependencies
This works, and for a while, this is enough. Time passes and you hear about a node module with a
CVE against it, let’s call it "spaced-between", you want to ensure it isn’t added as a
dependency.
There are two aspects that you consider:
- Keeping track of changes to
dependencies(for noted dependencies) - Reading the lockfile for the dependency (for transitive dependencies)
#Keeping track of changes to dependencies
We can use danger.git.JSONDiffForFile to understand the changes to a JSON file during code review. Note: it returns a
promise, so we’ll need to use schedule to make sure it runs async code correctly in Peril.
const blacklist = "spaced-between"
schedule(async () => {
const packageDiff = await danger.git.JSONDiffForFile("package.json")
if (packageDiff.dependencies) {
const newDependencies = packageDiff.dependencies.added
if (includes(newDependencies, blacklist)) {
fail(`Do not add ${blacklist} to our dependencies, see CVE #23")
}
}
})
So for example with a diff of package.json where spaced-between is added:
{
"dependencies": {
"commander": "^2.9.0",
"debug": "^2.6.0"
+ "spaced-between": "^1.1.1",
"typescript": "^2.2.1",
},
}
JSONDiffForFile will return an object shaped like this:
{
dependencies {
added: ["chalk"],
removed: [],
after: { commander: "^2.9.0", debug: "^2.6.0", spaced-between: "^1.1.1", typescript: "^2.2.1" },
before: { commander: "^2.9.0", debug: "^2.6.0", typescript: "^2.2.1" },
}
}
Danger can then look inside the added keys for your blacklisted module, and fail the build if it is included.
#Parsing the lockfile
You can trust that this dependency is going to be added directly to your project without it being highlighted in code
review, but you can’t be sure that any updates to your dependency tree won’t bring it in transitively. A transitive
dependency is one that comes in as a dependency of a dependency, one which isn’t added to packages.json but is in
node_modules. So you’re going to look at a simple rule that parses the text of the file for your blacklisted module.
import fs from "fs"
import includes from "lodash.includes"
const blacklist = "spaced-between"
const lockfile = fs.readFileSync("yarn.lock").toString()
if (includes(lockfile, blacklist)) {
const message = `${blacklist} was added to our dependencies, see CVE #23`
const hint = `To find out what introduced it, use \`yarn why ${blacklist}\`.`
fail(`${message}<br/>${hint}`)
}
Note the use of readFileSync, as Danger is running as a script you’ll find it simpler to use the synchronous methods
when possible. You could improve the above rule by making danger run yarn why spaced-between and outputting the text
into the messages. We do this in the danger repo with child-process and execSync.
#Building from here
This should give you an idea on how to understand changes to your node_modules, from here you can create any rules you
want using a mix of JSONDiffForFile, fs.readFileSync and child_process.execSync. Here are a few ideas to get you
started:
- Convert the check for the package and lockfile to use
JSONDiffForFileso that it only warns ondependenciesordevDependencies. - Ensure you never add
@types/[module]todependenciesbut only intodevDependencies. - When a new dependency is added, use a web-service like libraries.io to describe the module inline.
-
Parse the
yarn.lockfile, to say how many transitive dependencies are added on every new dependency. - When a dependency is removed, and no other dependencies are added, do a thumbs up 👍.
Got improvements? Help improve this document via sending PRs.
