Automatically Install NPM Dependencies on Git Pull
Tags: npm, node, git, github
Published: 2021-12-27 14:47:50

> UPDATE: I released a npm package for this script: [git-pull-run](https://www.npmjs.com/package/git-pull-run). Please report any issues or suggestions to improve on [GitHub](https://github.com/zirkelc/git-pull-run/issues).

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](https://git-scm.com/docs/githooks) and a tool like [Husky](https://typicode.github.io/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](https://typicode.github.io/husky/#/?id=manual).

## 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:

```sh
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.

```sh
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. 

```sh
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. 
{% gist 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. 

```sh
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.