Automatically Install NPM Dependencies on Git Pull

// #git#github#node#npm // 15 comments

Together with my team I've been working on one project repository with multiple packages - a monorepo. Of course, we're using Git and branches, so there is almost no friction between us. Except when it comes to dependencies - in our case npm dependencies - but I guess it also holds true for other environments. When I pull the latest changes on my current branch or I switch between different branches, I have to be aware if the package-lock.json (lock file) has been changed. If so, I have to run npm install to make sure my dependencies are up to date with the latest changes. Otherwise, I might run into hard-to-find bugs where the current development works on some machine but not on the other due to outdated dependencies.

Hooks To The Rescue

We're already using a pre-commit hook to automatically run linting and formatting on git commit. That's quite easy with Git Hooks and a tool like Husky. Fortunately, Git also supports a post-merge hook that runs after a git pull is done on a local repository. This is exactly the point in time where we need to update our dependencies to see if they have changed. For detailed steps on how to get started with hooks, I recommend following this guide.

Detect Changes

When we git pull the latest changes, we need a list of all changed files. If this list contains a package-lock.json, we need to run npm install to update our dependencies. If we working on a monorepo with multiple packages as in my case, we need to run it for each changed package. The following git diff prints the list of changed files:

git diff --name-only HEAD@{1} HEAD

With a simple regular expression, we can filter all paths containing a package-lock.json file. I did put the regex into the PACKAGE_LOCK_REGEX variable, because this part must be changed depending on the actual project structure. It contains a matching group (i.e. the first pair of parentheses) that starts with packages/, because in our monorepo all packages live under this directory (except for development dependencies which live at project root directory). The result of regex filter is saved as array into the PACKAGES variable.

IFS=$'\\n' PACKAGE_LOCK_REGEX="(^packages\\/.*\\/package-lock\\.json)|(^package-lock\\.json)" PACKAGES=("$(git diff --name-only HEAD@{1} HEAD | grep -E "$PACKAGE_LOCK_REGEX")")

Run Install

Finally, we need to run npm install for each changed package. As Git runs on the project root directory and the changed files are actually paths to lock files, we must change the directory before running install. With $(dirname package) we can easily extract the directories from the path.

if [[ ${PACKAGES[@]} ]]; then for package in $PACKAGES; do echo "📦 $package was changed. Running npm install to update your dependencies..." DIR=$(dirname package) cd "$DIR" && npm install done fi

Post Merge Hook

All the above snippets can be combined into the following shell script, that is going to be executed by Husky as post-merge hook. https://gist.github.com/zirkelc/eb281b5b1958c2cd4d844c527b69c2c8

The file must be saved as post-merge (no .sh extension) inside the .husky folder. I'm running on macOS with zsh as default shell (see shebang #!/bin/zsh) and it's working. However, I didn't test it with bash, so there might be some changes necessary if you run a different shell.

Test It

In order to verify if the hook is working, we can reset the current local branch to a previous state (e.g. rewind 20 commits) and then pull the changes back.

git reset --hard HEAD~20 && git pull

If the package-lock.json has been changed in one of the commits, the hook will print a nice little message for each lock file and it will automatically run npm install for us. If you use Git-integration of Editors like VSCode, you need to check the output of the Git log in order see what's going on.