DEV Community

Cover image for Automatically Install NPM Dependencies on Git Pull
Chris
Chris

Posted on • Updated on

Automatically Install NPM Dependencies on Git Pull

UPDATE: I released a npm package for this script: git-pull-run. Please report any issues or suggestions to improve on GitHub.

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
Enter fullscreen mode Exit fullscreen mode

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")")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (14)

Collapse
sso profile image
Sall

Nice one. I am looking for solutions on how to improve:

Your opinions, suggestions would be highly appreciated.

Collapse
prabhakarnikhil14 profile image
prabhakarnikhil14

Can't we just run npm install on each pull

Collapse
zirkelc profile image
Chris Author

You mean always running install without checking for lock file changes? Of course that’s an option. I’m using npm’s postinstall lifecycle hook to set up the dev environment (generate code, deploy changes, etc.). So avoiding unnecessary installs is important to me.

Collapse
felipekm profile image
Kautzmann

Hi there, nice article!
Do we need to setup this husky hook into package.json?

Collapse
zirkelc profile image
Chris Author

No, Husky in the latest version v7 doesn't work npm scripts anymore. Just follow these steps and it create a .husky folder in your repo. Then add a post-merge file with gist snippet above.

Collapse
aybee5 profile image
Ibrahim Abdullahi Aliyu

This is pretty cool, can it be a package one can just install?

Collapse
zirkelc profile image
Chris Author

I’m working on a npm package :)

Collapse
zirkelc profile image
Chris Author

I released a first version: npmjs.com/package/git-pull-run

I'd like to hear your thoughts! :-)

Collapse
gaardsholt profile image
Lasse Gaardsholt

Shouldn't you run npm ci instead of npm install ?
Won't npm install try to update your package.json?

Collapse
zirkelc profile image
Chris Author

If the semver versions in package.json and package-lock.json fit with each other, npm install should behave identically to npm ci.

For more information check this comment by the creator of npm ci: dev.to/zkat/comment/epbj

Collapse
sushruth profile image
Sushruth Shastry

Good point! Another way we are solving it at work is by using yarn pnp with zero installs. It's not bulletproof but surely helps a lot.

Collapse
zirkelc profile image
Chris Author

I’d like to use yarn pnp but it’s not widely supported yet. For example Typescript doesn’t support pnp at the moment (it’s being patched into TS by yarn during install). Also bundlers like webpack didn’t handle the lock file at root level correctly the last time I checked.

Collapse
arpitprod profile image
arpitprod

If we are using yarn then How to do same thing ?

Collapse
zirkelc profile image
Chris Author

That should be easy with changing the regex expression from package-lock.json to yarn.lock: PACKAGE_LOCK_REGEX="(^packages\/.*\/yarn\.lock)|(^yarn\.lock)" and running yarn install instead of npm install inside the hook.