Detect when node_modules are out of sync
Remember those moments when you have those weird issues, and all that was needed was a clean node_modules install
4 min read
4 min read
main
branch or jump to some feature branch and you start getting weird errors. Until only later you recognize you forgot to execute an npm install
. Let’s look into how we can avoid that.
Actually, if you’re using Webstorm you might be on the safe side becaus it’ll show you a nice little notification whenever it detects that node_modules
are out of date. But not everyone uses Webstorm. (Personally big VSCode fan but currently kinda jumping back and forth between the two).
So, Emma just tweeted about exactly this problem, which reminded me of a script.
When you sit around for 20 minutes trying to figure out why your local environment isn't working... and then you realize you forgot to npm install after rebasing on master.
— Emma Bostian (@EmmaBostian) August 27, 2020
Is it the weekend yet?
The strategy is basically to check whether the package.json
changed between the current and previous Git head.
This could be expressed with the following git command
$ git diff-tree -r --name-only --no-commit-id <previous-head> <current-head>
As an example. If I jump from my main
branch (or master
) to my my-cool-feature
branch, I could execute
$ git diff-tree -r --name-only --no-commit-id main HEAD
.gitlab-ci.yml
angular.json
apps/myapp/myapp-e2e/src/app.e2e-spec.ts
apps/myapp/myapp-e2e/src/stackblitz.e2e-spec.ts
apps/myapp/myapp-e2e/tsconfig.json
libs/myapp/util/src/lib/stackblitz/stackblitz-filelist.ts
libs/myapp/util/src/lib/stackblitz/stackblitz-writer.ts
nx.json
package-lock.json
package.json
As you can see we get a list of files which we can now parse for the appearance of package.json
.
So let’s implement it as a Node.js script.
First step is to execute our git
command, which we can do with shelljs.
#!/usr/bin/env node
const shell = require('shelljs');
// we'll get these via the command line args
const [
NODE_PATH,
SCRIPT_PATH,
PREVIOUS_HEAD,
CURRENT_HEAD,
ISBRANCH,
] = process.argv;
// get a list of change files as a string
let changedFiles = shell.exec(
'git diff-tree -r --name-only --no-commit-id ' +
PREVIOUS_HEAD +
' ' +
CURRENT_HEAD
);
shelljs
has a method exec(...)
that allows to directly issue the git command and get the output as a string. Note we get PREVIOUS_HEAD
and CURRENT_HEAD
which are needed for the git
command to work properly. More about that later.
Once we have the changed files, we can verify whether it contains package.json
...
if (changedFiles.includes('package.json')) {
// print to the user that he/she should exec an npm install
// (or even just do the npm install automatically)
}
To make it a bit nicer, add some colors. I place the script in some tools
folder within my repo.
Here’s the entire script:
// tools/node-modules-check.js
#!/usr/bin/env node
const shell = require('shelljs');
const colors = require('colors');
const fs = require('fs');
const [
NODE_PATH,
SCRIPT_PATH,
PREVIOUS_HEAD,
CURRENT_HEAD,
ISBRANCH,
] = process.argv;
let changedFiles = shell.exec(
'git diff-tree -r --name-only --no-commit-id ' +
PREVIOUS_HEAD +
' ' +
CURRENT_HEAD
);
if (changedFiles.includes('package.json')) {
let msg = 'package.json changed: ';
// personalize it based on whether the user uses
// yarn or npm
if (fs.existsSync('yarn.lock')) {
msg += 'Please run "yarn install"';
} else {
msg += 'Please run "npm install"';
}
// some message coloring & design ;)
let width = 80;
console.log(
colors.bold.inverse.yellow(
[
'='.repeat(width),
' '.repeat(width),
msg.padStart(msg.length + (width - msg.length) / 2).padEnd(width, ' '),
' '.repeat(width),
'='.repeat(width),
].join('\n')
)
);
}
The easiest way to install Git hooks is Husky. Follow the setup instructions in their README. Most commonly you simply add some special nodes in your package.json
, like
// package.json
{
"husky": {
"hooks": {
"pre-commit": "npm test",
"pre-push": "npm test",
"...": "..."
}
}
}
or you create a .huskyrc
file, which I did for our example here:
hooks:
"post-checkout": "cross-env-shell node tools/node-modules-check.js $HUSKY_GIT_PARAMS"
I’m using the post-checkout
hook, which means it runs every time you checkout something, whether it is a new branch, a new commit etc. Which is exactly what we want, right?
See the script in action on this GitHub repo. Clone the repo and switch between it’s master
and feature/cowsay
branch.