Mise ate my Makefile


Every project starts the same. You need Ruby 3.2.1 but have 2.7. You need Node 20 but have 18. Someone wrote a Makefile that assumes GNU make but you’re on BSD. The scripts/ folder has 47 shell scripts and nobody remembers what half of them do.
I found mise. It fixed all of this.
mise started as a Rust rewrite of asdf. Then it absorbed make’s job too. Now it’s the one tool I install on every machine.
Here’s what my typical project setup looked like before:
.ruby-version for rbenv.nvmrc for nodeMakefile with 20 targetsscripts/ with random shell scripts.env.example that nobody updatesHere’s what it looks like now:
# .mise.toml
[tools]
ruby = "3.2.1"
node = "20.11.0"
python = "3.12"
[tasks.test]
run = "bundle exec rspec && npm test"
description = "Run all tests"
[tasks.deploy]
run = "kubectl apply -f k8s/"
depends = ["test", "build"]
[env]
DATABASE_URL = "postgresql://localhost/myapp_dev"
RAILS_ENV = "development"
One file. Everything works.
This is where mise gets interesting. You don’t need exact command names.
$ mise run test # runs the test task
$ mise run tset # still runs test (typo forgiven)
$ mise run tst # yep, runs test
$ mise run deploy # runs deploy task
$ mise run dply # runs deploy
The fuzzy matching is smart. It weighs:
I tested this with 30+ tasks in one project. It still found the right one 90% of the time with 3-4 characters.
Instead of polluting the root with scripts/, mise looks in .mise/tasks/:
.mise/
└── tasks/
├── db-reset.sh
├── cache-clear.sh
└── logs-tail.sh
Any executable in there becomes a task. No registration. No config.
$ mise run db-reset # runs .mise/tasks/db-reset.sh
$ mise run cache # fuzzy matches to cache-clear.sh
Shell scripts stay shell scripts. But now they’re discoverable:
$ mise tasks
cache-clear Clear all caches
db-reset Reset database to clean state
logs-tail Tail production logs
test Run all tests
deploy Deploy to production
mise includes age encryption support. Not bolted on. Built in.
# .mise.toml
[env]
DATABASE_URL = "postgresql://localhost/dev"
API_KEY = "age:SECRET_ENCRYPTED_STRING"
Set it up once:
$ mise decrypt .mise.toml
Enter passphrase:
$ export API_KEY="actual-secret-key"
$ mise encrypt .mise.toml
Your secrets are in the repo but encrypted. CI/CD gets the age key. Developers get the age key. Random GitHub scrapers get nothing.
This is where it gets smooth. In Zed, I set up task shortcuts:
// .zed/tasks.json
{
"tasks": {
"test": {
"command": "mise run test",
"cwd": "$WORKSPACE_ROOT"
},
"deploy": {
"command": "mise run deploy",
"cwd": "$WORKSPACE_ROOT"
}
}
}
Now cmd-shift-t opens the task picker. Type “te”, hit enter. Tests run. The AI assistant sees the output inline. Fixes the code. Reruns the test. No context switching.
mise isn’t perfect. Here’s what I hit:
deploy-staging and deploy-production, typing deploy might pick wrong. Be specific or rename.What I gained:
mise install and mise run setupWhat I paid:
If you’re moving an existing project:
curl https://mise.jdx.dev/install.sh | shmise install.mise/tasks/ graduallyStart with the most-used tasks. Leave the weird legacy stuff in make until later.
After migrating 12 projects:
.mise.toml, not .mise/config.yaml. TOML is cleaner for this..mise/tasks/ as shell scripts first. Move to inline tasks only when needed.db-reset, cache-clear, test-unit. Makes fuzzy matching more predictable.mise replaced my entire project automation stack. The Rust rewrite isn’t just faster. It’s more thoughtful. Fuzzy matching, encrypted env vars, task discovery. These aren’t features. They’re fixes for real pain.
Every new project starts with .mise.toml now. Setup takes 5 minutes instead of an hour. New developers don’t message me asking how to run tests. They just run mise tasks and figure it out.
That’s the tool working.