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
JSONDiffForFile
so that it only warns ondependencies
ordevDependencies
. - Ensure you never add
@types/[module]
todependencies
but only intodevDependencies
. - When a new dependency is added, use a web-service like libraries.io to describe the module inline.
-
Parse the
yarn.lock
file, 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.