What
Mise is a tool to manage dev tools and environment variables, which is also a task runner. If you happen to know French, mise-en-place is a culinary idiom which makes perfect sense here.
It supercharges development by utilizing several other powerful tools, including pipx/uv (uvx), cargo-binstall, ubi, npm/bun and many others. This post focuses solely on the linter part, which I implicityly mentioned in 「完美」软件的必要不充分条件.
Why
Before you ask why, let me show you the right XKCD myself:
Many reasons behind the scene end up being this: to metigate supply chain attack.1
- Many people are still concerned about XZ backdoor last year while I’m unaffected as I use musl Void, Rocky patches it within a few hours and other distros I use do not use SystemD.
- Recently a bunch of malicious VSCodium (VSC for short) plugins are discovered after being undetected for months, and I’m still unaffected as I don’t use any of them.
- Microsoft also had many serious security incidents these years, SolarWinds, CrowdStrike, SMB 0day (EventLogCrasher), .theme 0day (CVE-2024-21320), just to name a few. And as my windows-vhdboot are mostly offline and highly customized, none of these hacks work for me.
Also, nowadays vibe coding is becoming more and more popular. You can’t just resist the temptation as others would yield to it, eventually. In this regard, linter/formatter/static analyzer raises the bar and ensures the minimum quality of the code. Just treat LLM like any other outsourcing cow-orkers, you have to put trust (albeit minimum) in it, but also verify the output.
Ok, I seem to get off-topic on Linter, let’s get back to the point. Actually, the real question is how ultimate it can be.
I mean, if you ever use Trunk Code Quality,2 which works over 100 linters and formatters, it does not make sense to do it yourself…Unless you discover that it does not work for non-git environment3 and it occupies gigabytes of space which hardly gets cleaned, and it works in a hacky way by injecting environment variables,4 just like nvm.
Also, GitHub has a much less powerful (CI only) linter named, uhh, super-linter. Ok, I’m off-topic again.
To put it simply, I need a ultimate linter that is capable of:
- Add as many linters/formatters/whatever tools as I want, without asking third party for approval5
- Work locally and offline, after initial setup6
- Work everywhere, for a glimpse, check 「完美」软件的必要不充分条件#难点
- Integrate well with existing awesome tools
- Offer isolation between different sets of toolchain
- Use better format for configuration files other than silly YAML
And mise can handle all of these, almost. Well, I believe #TLDR is all I need to convince you.
How
Now that I mentioned zizmor in footnote, I’ll just use it as an example.
Install
As the docs say, zizmor is available within several packaging ecosystems, which makes it a perfect example to show the power of mise.
Naturally you wanna install it via cargo install
since it’s a Rust program. Or if you happen to know cargo-binstall, it can also be used here:7
# boring cargo install
cargo install zizmor
# cargo-binstall
cargo-binstall zizmoor
# mise
# of course you need to first install it
mise use -g cargo-binstall
# boring setting once for all
mise settings set cargo.binstall true
mise use -g cargo:zizmor
# or cool one-liner to temporarily enable cargo-binstall
mise use -g cargo:zizmor[binstall=false]
Also, zizmor is available at PyPI. If you know pipx or uv, they are all usable:
pip install zizmor
-pipx install zizmor
+mise use -g pipx:zizmor
-uv tool install zizmor
+mise use -g uv
+mise settings set pipx.uvx true
+mise use -g pipx:zizmor
It’s also possible to achieve this:
mise use -g ubi:uutils/coreutils
mise use -g npm:vercel
Mise supports many other backends so you can choose the way you like for a specific tool. And as it can be isolated with configuration files, you can install zizmor in Python projects with uv and Rust ones with cargo-binstall.
Lint
It’s probably slightly off-topic as it’s actually the realm of mise tasks.
If you want to use installed tools with mise tasks, the file should be like this:
[tasks."lint:ci"]
description = "lint GitHub Actions"
run = """
#!/usr/bin/env bash
yamllint --strict .github
actionlint
zizmor .github/workflows
"""
Shell shebang is used here to show that it’s hackable. You can also use file tasks instead of TOML tasks to handle complicated logic.
And you can run tasks in group with wildcards:
[tasks."lint:py"]
...
[tasks."lint:js"]
...
[tasks."lint:lua"]
...
[tasks.lint]
depends = ["lint:*"]
Now simply run mise run lint
and it would trigger all linting tasks.
TLDR
This is a snippet (read: snapshot) of my current linter. See #List for brief explanation.
As my definition of linter is rather broad, even compiler, code formatter, and VSC plugins are included.
Language | CLI | VSC |
---|---|---|
Ansible | ansible-lint | redhat.ansible |
Bash | shellcheck, shfmt | ShellCheck |
C/C++ | clang-format | clangd + CodeLLDB |
GitHub Action | actionlint, zizmor (+yamllint) | / |
IaC | gitleaks (+trufflehog) + trivy | / |
JavaScript/CSS/JSON | biome | Biome |
Lua | StyLua | Lua + Lua Debug + StyLua |
Markdown | markdownlint | markdownlint + Markdown All in One |
Nginx | gixy | nginx.conf hint |
Python | ruff (all rules) + mypy | Python + Python Debugger + Ruff |
R | mise-r, lintr | R + R Debugger |
reStructuredText | doc8 | / |
SQL | sqlfluff | sqlfluff.vscode-sqlfluff |
SystemD | systemd-analyze + systemdlint | hangxingliu.vscode-systemd-support |
TOML | taplo | Even Better TOML |
YAML | yamllint | redhat.vscode-yaml |
Usage | CLI | VSC |
---|---|---|
Format | EditorConfig | EditorConfig for VS Code |
Package | mise | Mise VSCode |
CJK | AutoCorrect | / |
En | Vale + vim spellcheck | / |
RCS | v.i. | / |
{Less,Un}interested or unique:
- AHK: AHK++ (
mark-wiemer.vscode-autohotkey-plus-plus
), VSC only - Fortran: gfortran/flang (LLVM20+)
- Graphviz:
joaompinto.vscode-graphviz
, VSC only - HTML:
vscode.html-language-features
- Mermaid:
bierner.markdown-mermaid
, VSC only - V: v-analyzer (CLI & VSC)
- Zig:
ziglang.vscode-zig
- ActionScript, Berry, coq, Crystal, Haskell, Helm, Janet, Java, Lean, Perl, PHP, PostScript, Raku, Ruby, Tcl (Expect), Visual Basic
RCS (revision control system), c.f. git:
- git core: alias, bundle, config
- git extension: git-lfs, git-sizer (trunks)
- git related: git-cliff (changelog), onefetch (metadata), lazygit (TUI)
- github-cli: dlvhdr/gh-dash, meiji163/gh-notify, Vinfall/gh-repo-size
List
(I doubt if I have enough energy to finish this part, TBA for now.)
Most, I mean, MOST, CI/CD workflows are done in an extremely fragile manner. Endless vulnerabilities, silly chains of GitHub Action, outdated & bloated dependencies, any of these can suddenly make your build insecure and buggy. That’s why IaC (Infrastructure as Code) static analysis tools exist and zizmor has a pedantic persona to warn against unpinned-uses. ↩︎
Formerly Trunk Check ↩︎
which is impossible as their free usage licensing logic depends on SVN, aka. Git ↩︎
Mise uses a similar approach but you can avoid it by adding shims to PATH. ↩︎
If they get approved at all, that is. Despite the name, super-linter does not support many modern alternatives and only runs in CI. That’s what people call vendor lock-in. Also, even powerful Trunk requires manual implementation of integration before adopting a new tool. ↩︎
This basically rules out every enterprise solution, no fund wasted, lucky ↩︎
although I’m not sure if quickinstall has pre-built binary for it, which is the whole point of cargo-binstall. ↩︎