This lesson preview is part of the Bundling and Automation in Monorepos course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Bundling and Automation in Monorepos, plus 90+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Bundling and Automation in Monorepos
  • [00:00 - 00:15] In this lesson, we're going to set up automated git hooks that are going to improve our Monorepo workflow. There is a lot that we could do with git hooks, but we are going to start with making our repository install packages automatically.

    [00:16 - 00:28] We will handle switching branches, merging, and rebasing. All these operations can potentially leave you with out of date dependencies, so we're going to automate the process of installing them.

    [00:29 - 01:09] We will also write the necessary optimizations to skip this when it's not needed. We're going to use a tool called Lefthook. It is a flexible git hooks manager that allows us to define hooks in a declarative and reusable way. I'm going to begin by installing "lefthook" as a dev dependency in our Monorepo root. After installing, I'm going to go to our package.json and add a "prepare" script that runs the Lefthook installation process whenever we run "pnpm install". I'm going to run "pnpm install" and Lefthook is going to run, see that there is no configuration and create an empty lefthook.yaml config file.

    [01:10 - 01:36] I'm going to open this file and we can see that it comes with some example usage documentation. You can look through this on your own, but for now I'm going to delete it, and then I'm going to create my first hook. I will create a "post-checkout" hook that runs "pnpm install" every time we run the "git checkout" command, I'm going to commit my changes and then I'm going to create a new branch using "git checkout -b new-branch".

    [01:37 - 01:52] This also switches us to this new branch and it's going to execute the post-checkout hook. I'm going to switch back to the main branch using the "git switch" command, and it's still going to run our post-checkout hook and run "pnpm install" for us.

    [01:53 - 01:59] This is a good start and we get part of the behavior that we want. "pnpm install" is run every time we switch branches.

    [02:00 - 02:19] However, it's a bit naïve and it's going to run in cases where we don't want it to. For example, if I were to run "git checkout README.md", which is a file in our repository, it's still going to run "pnpm install" for us. We need to go to the git hooks documentation to understand why this happens.

    [02:20 - 02:34] The post-checkout documentation shows that it's run both when we switch branches as well as when we checkout files. It also points out that it gives us some parameters that allow us to distinguish between these two cases.

    [02:35 - 02:45] So to do that, I'm going to change our Lefthook configuration. Instead of using an inline command, I'm going to set up Lefthook to run a script that we're going to write.

    [02:46 - 02:56] I'm going to call this script "pnpm-install-on-branch-switch.sh. And I'm going to say that this script is run by "bash" using the "runner" parameter.

    [02:57 - 03:14] You can run scripts in different languages like Node.js or Ruby. However, for our needs, bash is going to be the most appropriate option. Next, I'm going to tell Lefthook to create folders for the post-checkout hook by running "pnpm lefthook add post-checkout --dirs".

    [03:15 - 03:24] We can see that it created ".lefthook" and then post-checkout folder. Inside that folder I'm going to create my bash script.

    [03:25 - 03:38] "pnpm-install-on-branch-switch.sh". I have prepared the script ahead of time, so let's quickly go through it. The documentation for git hooks post-checkout tells us that it's given three parameters.

    [03:39 - 04:15] The first one is the previous HEAD, the second one is the new HEAD, and the third one is a flag that tells us if we were doing a file checkout or a branch switch. Then we check for an empty branch name, which happens when we're in a detached head and we wouldn't want to be running "pnpm install". And finally, we get the list of the files that were changed with all this information. At the end, we can run "pnpm install" only when we are doing a branch switch and we're not in a detached yet, and the pnpm-lock.yaml file has changed.

    [04:16 - 04:35] Now "pnpm install" will correctly only run when actually needed. I'm going to go back to the command line, run "pnpm lefthook install" and now our hook is ready to go and if I've done everything correctly, I can switch to a new branch and it breaks.

    [04:36 - 04:39] I have a typo in my script. Let me go back and quickly fix that.

    [04:40 - 05:00] Go out and try again. And now I can switch between the main branch and the new branch without "pnpm install" being run, because there is no change to our pnpm-lock.yaml file. But let's see the case where we do want it to run. I'm going to commit my changes, and then I'm going to create a branch called "branch-with-new-package".

    [05:01 - 05:06] I'm going to add React here just as an example. And then I'm going to commit into this new branch.

    [05:07 - 05:34] And now when I run "git switch main" it's going to correctly run "pnpm install" and remove React because the main branch doesn't have react. When I go back to branch-with-new-package, it's going to correctly run again and install React. Now that we have the post-checkout case handled, I'm going to quickly add the other two cases we need to handle, which is post-rewrite and post-merge.

    [05:35 - 05:52] post-rewrite is executed after "git rebase", while post-merge is executed after "git merge" or "git pull --merge". In both of these cases, we can get new packages from the remote branch that we are updating from, so we should be running "pnpm install".

    [05:53 - 06:32] The final change I want to make to my Lefthook file is to add an option of "output" that just outputs the status rather than the more verbose output that Lefthook has by default. So let's write the actual scripts for these two configs that we added to Lefthook. I will drop down to the command line, run "pnpm lefthook add post-rewrite --dirs" and "pnpm lefthook add post-merge --dirs", so we have the folders created for us, and then I'm going to vim into .lefthook/post-rewrite/pnpm-install-on-rebase.sh. I'm going to paste in the script that I prepared ahead of time.

    [06:33 - 06:50] And similarly to the post-checkout script, this script gets a first parameter that's going to say either "amend" or "rebase" based on the type of rewrite that's being executed. Amending a commit is still a type of rewrite, but in that case, we know that we don't need to run "pnpm install".

    [06:51 - 07:11] I'm also going to create a list of changed files between the original HEAD and the new HEAD of git. Finally, if this is a "rebase" and pnpm-lockfile.yaml has been changed, then we're going to run "pnpm install". Similarly, I'm going to create .lefthook/post-merge/pnpm-install-on-merge.sh.

    [07:12 - 07:30] The post-merge hook doesn't have any parameters, but we can still create a list of changed files based on what was before and after the merge, and only the run "pnpm install" if pnpm-lock.yaml was changed. Let's quickly look through the changes that we've made.

    [07:31 - 08:00] We've added the three scripts that run on our git hooks. We've added our Lefthook config file, and we've made very minor changes to our package.json to run "lefthook install" on "prepare" and to add "lefthook" as a dependency. We have now automated away a chore and a potential source of everyday problems, offloading the installation of packages to git hooks instead.