VCS hooks
VCS hooks (most popular with Git) are a mechanism for running scripts at pre-defined phases in the VCS's lifecycle, most commonly pre-commit, pre-push, or pre-merge. With moon, we provide a built-in solution for managing hooks, and syncing them across developers and machines.
Defining hooks
Hooks can be configured with the vcs.hooks
setting in
.moon/workspace.yml
. This setting requires a map of hook names (in the
format required by your VCS), to a list of arbitrary commands to run within the hook script.
Commands are used as-is and are not formatted or interpolated in any way.
To demonstrate this, let's configure a pre-commit
hook that runs a moon lint
task for affected
projects, and also verifies that the commit message abides by a specified format (using
pre-commit).
vcs:
hooks:
pre-commit:
- 'pre-commit run'
- 'moon run :lint --affected'
All commands are executed from the repository root (not moon's workspace root) and must exist on
PATH
. If moon
is installed locally, you can execute it using a repository relative path, like
./node_modules/@moonrepo/cli/moon
.
Enabling hooks
Hooks are a divisive subject, as some developers love them, and others hate them. Finding a viable solution for everyone can be difficult, so with moon, we opted to support 2 distinct options, but only 1 can be used at a time. Choose the option that works best for your project, team, or company!
If you have existing VCS hooks, back them up as moon's implementation will overwrite them! To migrate your existing hooks, configure them as commands to run.
Automatically for everyone
If you'd like hooks to be enforced for every contributor of the repository, then simply enable the
vcs.syncHooks
setting in
.moon/workspace.yml
. This will automatically generate hook scripts and link
them with the local VCS checkout, everytime a target is ran.
vcs:
hooks: [...]
syncHooks: true
Manually by each developer
If you'd prefer contributors to have a choice in whether or not they want to use hooks, then simply
do nothing, and guide them to run the moon sync hooks
command. This
command will generate hook scripts and link them with the local VCS checkout.
$ moon sync hooks
Disabling hooks
If you choose to stop using hooks, you'll need to cleanup the previously generated hook scripts, and
reset the VCS checkout. To start, disable the vcs.syncHooks
setting.
vcs:
syncHooks: false
And then run the following command, which will delete files from your local filesystem. Every developer that is using hooks will need to run this command.
$ moon sync hooks --clean
How it works
When hooks are enabled, the following processes will take place.
-
The configured hooks will be generated as individual script files in the
.moon/hooks
directory. Whether or not you commit or ignore these script files is your choice. They are written to the.moon
directory so that they can be reviewed, audited, and easily tested, but are required. -
We then sync these generated hook scripts with the current VCS. For Git, we create
.git/hooks
files that execute our generated scripts, using repository relative commands. Any existing VCS hooks will be overwritten.
The .moon/hooks
scripts are generated as Bash scripts (use a .sh
file extension) on Unix, and
PowerShell scripts (use a .ps1
file extension) on Windows.
Git
On Unix based operating systems (Linux, macOS, etc), the .moon/hooks
scripts are executed from
.git/hooks
Bash files. Because of this, bash
should be available on the system (which is
typically the case).
On Windows, things get tricky. Since Git has a requirement that .git/hooks
files must be
extensionless, and older versions of PowerShell require an extension, we have to use a workaround.
To handle this, the .git/hooks
files are Bash-like scripts (that should work on most machines)
that execute .moon/hooks
using the powershell.exe
(or pwsh.exe
) executables. Because of this,
PowerShell must be available on the system.
Examples
Pre-commit
A perfect use case for the pre-commit
hook is to check linting and formatting of the files being
committed. If either of these tasks fail, the commit will abort until they are fixed. Be sure to use
the --affected
option so that we only run on
changed projects!
vcs:
hooks:
pre-commit:
- 'moon run :lint :format --affected --status=staged'
By default this will run on the entire project (all files). If you want to filter it to only the changed files, enable the
affectedFiles
task option.