Monorepo CI, linting, pre-commits, builds etc., using `mise` tasks.
Project description
misek
Monorepo CI, linting, pre-commits, builds etc., using mise tasks
So you have a monorepo (either monolingual or multilingual). You have various projects (and maybe sub-projects, etc.), each with separate environments, lints (e.g. pre-commits ran locally), tests (e.g. CI ran remotely), build and deploy scripts (e.g. CD ran remotely). And great news, maybe you're already capturing all of these using mise, which is an excellent language-agnostic monorepo tool. (If you're new to mise, then see Why mise? in the FAQ below.)
One missing piece: when you make a change (a commit, a PR, a release), then you need to map changed files into changed projects, so that you can run pre-commits / CI / CD.
misek is a tiny extension (two CLI commands, ~100 lines of code) to mise performing that conceptual conversion, thus providing a way to run:
- pre-commits (format, lints) for just the files that have changes on the most recent commit.
- CI (tests) in a pull request for just the projects that have changes relative to
main. - CD (build, deploy) for just the projects that have just been deployed.
The idea is simple:
(a) specify entry points for lints/tests/etc. using mise's tasks, e.g. add a [tasks.tests] to a project's mise.toml.
(b) use git diff --name-only | misek run tests to walk your monorepo to find and execute all the tests corresponding to your latest changes.
[!TIP] For specifically linting/pre-commits, then equivalent tooling already exists in the form of
prek(the successor topre-commit), which does agit diffand figures out which pre-commits to run.misekexists to extend the same principle to every other kind of task (test, build, deploy, ...), and for this reason we considermise+misekto supersedeprek/pre-commit.
[!NOTE] For single-project repos then
misekdoesn't add much. But in multi-project monorepos thenmisekmakes it possible to handle multi-project cross-cutting changes, and to handle changes within nested projects (e.g. maybe a top-level lint detects and blocks large files, whilst a project-specific lint formats code).
Installation
Add to your top-level mise.toml:
[tools]
"pipx:misek" = "latest"
Usage
CI, tests in pull requests
Add the line git diff main --name-only | misek run tests to your CI. This will run all [task.tests] associated with your changes. You can use a different name than tests if you like.
CD, build after merge
Add the line git diff main --name-only | misek run build to your CD. This will run all [task.build] associated with your changes. You can use a different name than buildif you like.
Add extra task names if e.g. you have separate build and deploy tasks, and would like to invoke both: git diff main --name-only | misek run build deploy.
pre-commits
Run misek install lint to set your git pre-commit hook to run git diff --cached --name-only | misek run lint. On every git commit, this will run all [tasks.lint] associated with your changes. You can specify a different name than lint if you like.
To make this happen automatically, you can have this automatically performed as part of mise install:
[hooks]
postinstall = "misek install lint"
[settings]
experimental = true # to enable hooks.postinstall
jjusers, you should arrange to runjj diff --name-only | misek run lintin whatever way you most prefer. For example, I like to runjj config edit --repo, and add the following block:[aliases] check = ["util", "exec", "--", "bash", "-c", "jj diff --name-only -r 'latest((@-::@) ~ empty())' | misek run lint"]
Manually run lints/tests/etc for just your cwd
You could lint your current working directory with git ls-files --full-name | misek run lint. This is equivalent to running mise run ...all lint tasks, including those defined in parent mise.toml files....
This use-case exemplifies the key conceptual point: when running tasks from mise then we think of them on a per-project basis. When running tasks from misek then we still define them on a per-project basis, but we execute them on a per-file basis.
How does it work? / The algorithm
Click to expand
misek provides two CLI commands:
misek install <task1> <task2> ...
misek run <task1> <task2> ...
The first installs a pre-commit hook that runs git diff --cached --name-only | misek run <task1> <task2> ... on every commit.
The second:
- consumes a list of files from stdin;
- finds all
mise.tomlfiles in all their parent directories (stopping once the monorepo root is found, defined by the presence of a.gitdirectory); - finds all of their tasks with name matching any of
<task1>,<task2>etc. - executes all collected tasks in a single
mise run <task1> <arg1> ::: <task2> <arg2> ..., so that parallelism, dependencies, error handling etc. are all done correctly as permise's usual behaviour.- Each task is passed a single argument, which is the path to a temporary file listing just those entries from stdin that are within its part of the file system.
misek works when using mise with both monorepo and non-monorepo tasks. (In the former case it does a single big mise run call; in latter case it makes a separate mise run call for each mise.toml it finds.)
Examples
See the examples directory for a few full worked examples.
FAQ
Why mise?
To be clear, misek is not affiliated with mise. I'm just a fan.
mise collects together three key things that are especially useful for both single-project repos and monorepos:
- pinning dependencies of core tooling like
uv(Python),npm(JavaScript), etc.- This is like
asdf. - This ensures that your whole team use the same versions of these dependencies.
- This is like
- adding entries to your
$PATHbased on your cwd.- This is like
direnv. - This automatically enables exactly the dependencies you require, regardless of what else is on your system.
- This is like
- defining tasks (with arbitrary names, though 'test', 'build' etc. are common choices), including with dependencies/parallelism between them.
- This is like
makeorjust. - This provides a way to record all the various test/build/etc. scripts that are needed.
- This is like
Importantly, it's also language-agnostic. No special-casing to e.g. the webdev stack.
Together with misek, one then ends up with a unified system that also covers:
pre-commit/prek;- the usual custom CI/CD scripts;
- simple build systems.
When should you not use mise/misek?
The main reason not to use mise is if you need the heavyweight stuff: remote caching or hermiticity. Use Bazel or Buck2 instead.
You can often also get away without mise if you have a monolingual single-project repos, for which you may prefer to stick to just uv (Python) / just npm (JavaScript) / etc.
Otherwise, use mise.
Finally, use misek whenever you have a monorepo (either monolingual or multilingual) that uses mise.
Where does the name misek come from?
misek = mise + k, with the k meant to inspire thoughts of:
prek(which ispre-commit+k);make(which it sounds a bit like);- cute names for bears in Polish and Russian (for which reason I pronounce 'misek' as 'mish-ek' 🐻).
How to best configure mise for use with misek?
Once you start using mise with monorepo tasks, then it will ask you to list the child mise.toml in [monorepo].config_roots.
Personally I think this is a wart (one of the few in mise), as the point of a monorepo is distributed-configuration-by-convention, not centralised declarations.
You can work around this by adding the following to your root mise.toml (up to whatever depth you require):
[monorepo]
config_roots = ["*", "*/*", "*/*/*", "*/*/*/*"]
Hurrah! All possible configs are located (up to some depth).
Versus prek, pre-commit, make, just, Bazel, etc. etc?
It took me a while to realise (despite... working with Blaze at Google...) that tooling like pre-commit and prek belong in the same category of thought as mise, make, Blaze, Buck2, etc.
That is: linting, formatting, tests, builds, deploys, etc., are all special cases of the exact same flow:
git diff --name-onlyto see what changed in this commit / in this PR.- walk up the file tree from all changed files, to look for
.pre-commit-config.yaml/mise.toml/etc., which specify what tasks need to run based on file location. - filter down to which tasks need to be run based on the trigger:
- pre-commit: linting + formatting.
- pre-merge PRs: linting + formatting + tests.
- post-merge PRs: builds + deploys.
- run them!
Honestly what's surprising to me is how few options there seem to be supporting this exact flow, given how ubiquitous this use-case is. All the tooling I'm aware of only seems to handle some slice of this.
- Bazel and Buck2: the heavyweight solutions, the right choice if you're a big org with big needs.
- Dagger: I've looked through the documentation but never managed to get past the boilerplate.
- Earthly: has now shut down.
- GitHub/GitLab:
.github/workflows/.gitlab-ci.ymlare good as a shim to call your 'real' CI scripts, but serious engineering should not be included here, to avoid vendor lock-in. (Also these are terrible YAML-masquerading-as-code.) - moonrepo, turborepo, Nx: largely based around the webdev stack, restrictive in the general case.
- devenv: largely based around the Nix stack, this is again kind of a heavyweight choice.
mise: this does a good job defining tasks ('fancy bash scripts', to quote the author). It doesn't do any of the rest of the flow on its own though.make: is explicitly just a build system (caches based on the presence of files, etc.), but doesn't do the rest.just, taskfile: are both explicitly just task runners, basically superseded bymisetasks if one is already usingmise.pre-commit: superseded byprek.prek: actually does the git-diff > walk through file tree > run tasks flow! This is by far the closest to what's discussed here. However it specifically only really handles pre-commits. Whilst it does have a general concept of stages, (a) I couldn't get these working, (b) these are specifically tied to the git-commit-push-etc model and aren't concepts like 'test' or 'build'.
It's also informative to consider prek/pre-commit's model here, in which the tool tries to install+cache an environment for you. I have come to regard this as an antipattern, as typically we actually want to run in the same environment as the rest of our tooling is using, e.g. in Python this would be the .venv created by uv run. This is another one of the reasons that has pushed me away from prek/pre-commit.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file misek-0.1.0.tar.gz.
File metadata
- Download URL: misek-0.1.0.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f2db063b0297bff99bf79834cefeea314ae7cf291e86567e4dcf767032949b8
|
|
| MD5 |
59ae47f2a7fa23d9fa01c6ba2c29231c
|
|
| BLAKE2b-256 |
90886c099fcc75ddb15e2d87eb7e127188976081360e1f251bd012fb44d2050a
|
File details
Details for the file misek-0.1.0-py3-none-any.whl.
File metadata
- Download URL: misek-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d28bc71d1987915cc5b281fd8bb707a209ddfb1ea3e07a838d2dfa65aee5b296
|
|
| MD5 |
c4b81578a945c699c70d723ef7309095
|
|
| BLAKE2b-256 |
838281c2f1a5eb95749e6958977ce95e37aade7a8a94df4f39cea78e2386578b
|