🏡 Coding in a monorepo
At Escape, we use a mono-repository strategy. We have multiple independent projects, with their own source code, documentation, CI configuration, or quality enforcement all living under the same Git repository.
We have several projects using different languages and technologies overall. Nonetheless, we have remained consistent about their quality enforcement, meaning here that every project has a working CI for linting/testing.
Conveniently, we leverage git hooks to ensure that the code we commit will not fail the CI because of linting issues.
🪝 Git hooks
In a nutshell, git hooks are small shell scripts being fired at various steps of the git lifecycle. We focused on pre-commit hooks, being executed before we actually create a new local commit.
🤝 Sharing Git hooks
Git hooks are stored under the .git folder of a repository and thus are not supposed to be versioned.
The actual process of committing git hooks scripts is not a consensus at all. Some teams will do it, others will not. Both sides have valid arguments to my mind, and the point of this article is not to discuss this topic.
At Escape, we decided to share some parts of our hooks configuration, as a convenience for failing before the CI in case of linting issues.
There are a few tools for sharing git hooks in a team of developers. These will:
- centralize the hook configuration in some JSON/YAML versioned file
- provide a nice UI for the scripts to run when doing Git operations
- provide a convenient way to install them when cloning the repository
Historically, we started working on Python and thus used pre-commit for managing our hooks. We finally realized that the tool is not well suited for multi-language monorepos. Our key issues with pre-commit were the following:
- If you have any linting tool that is not python-based, you might have a hard time
- If you manage multiple projects, every hook will be centralized in a single file, thus becoming quickly heavy to manipulate, with weird pattern matching to maintain.
- Dependencies will be stored in a single environment for all of the monorepo's projects, easily leading to time-consuming and boring situations where your linting command is okay, but the pre-commit hook fails because of different versions. It is also not trivial to use different versions of the same tool for different sub-projects.
- We quickly spent too much time in the documentation of the pre-commit version of tools we already know how to use.
✅ Pinpointing our needs
All of this left us with the feeling of tools being tweaked to make them work for monorepos, but definitely not designed for them. However, it also led us to some key points that we wanted to find in our git hook manager.
- Automated execution filtering
- Environment consistency and partitioning
- Language agnosticism
- Extensive and customizable
✨ Presenting Mookme, our homemade git hooks manager ✨
This not-very-good experience led us to some key features that we wanted to find in a hook manager designed for monorepos. Eventually, we built our own open-source tool, called Mookme.
Mookme is an npm package, but it doesn't require to live in a node_modules folder. You can simply install it globally or locally on your project.
npm install @escape.tech/mookme
🦾 Automated execution filtering
In the context of monorepo, a lot of actions will be scoped to projects, the execution of hooks is one, and we wanted this to be handled automatically.
Hooks should be declared on a per sub-project basis and fired if and only if a file related to this sub-project is modified during the commit.
Mookme automatically lists your staged files when committing, and fires only the relevant hooks.
👮 Environment consistency and partitioning
Every project needs to have a specific dependency environment. Hooks should be declared and used in the context of the project using them. If you use Python, then the sub-project's virtualenv should be used. If you use Node.js, the sub-project's node_modules should be used
Using mookme, every sub-project declares a list of commands to execute, in a JSON file. These commands are executed with the subproject as the current working directory, allowing you to write hooks as if you were in a single repository.
🌗 Language agnosticism
Git hooks have been designed to run on every repository regardless of their language, environment our dependencies. It is nothing more than a shell script or a command line, and we lacked this flexibility in other tools.
🪜 Get started using Mookme
Installing Mookme on your project is a matter of two commands. You firstly install the Mookme CLI.
npm -g install @escape.tech/mookme
Then, you have to configure your monorepo so that Mookme can run across it when committing.
npx mookme init
The first configuration step is to declare the folder where the different sub-projects are located. It can be the root folder (leave this empty then) or another name like projects or packages.
You will then be prompted for folders to select. You can select multiple sub-projects.
You can repeat this step for handling subfolders as well
Finally, you can review the changes being made by Mookme, and finish the configuration.
We are proud to introduce you to this new tool and hope you will want to use it for managing Git hooks on your monorepos! Mookme is under active development, meaning that issues or feature requests are warmly received ☺️.
Many thanks for reading this presentation, you can browse our documentation or give us stars on the repository!