Just: Just a Command Runner
We love just and are using it in all projects now. So great. Our typical justfile has around ~20 rules. Here is an example rule (and helper) to illustrate how we use it in ci:
export PATH := justfile_directory() + "/node_modules/.bin:" + env_var('PATH')
ci:
@just banner yarn install
yarn install
@just banner tsc
tsc --noEmit
@just banner lint
eslint src
prettier --check src
@just banner vitest
vitest --run
@just banner done!
banner *ARGS:
@printf '\e[42;37;1m[%s] %-72s \e[m\n' "$(date +%H:%M:%S)" "{{ARGS}}"
This example is a bit contrived, more typically we would have a rule like "just lint" and you might call it from "just ci".One of the best features is that just always runs from the project root directory. Little things like that add up after you've spent years wrestling with bash scripts.
I've been using just at work and in personal projects for almost a year, and I like it a lot. In particular, its self documentation with `just --list` makes onboarding new folks easy. It's also just a nicer syntax than make.
This is one of the most important pieces of software in my development stack that "just" gets out of the way and does what it's supposed to do. Also has excellent Windows[1] support so I can take it everywhere!
[1]: https://github.com/LGUG2Z/komorebi/blob/master/justfile example justfile on my biggest and most active Windows project- might not seem like a lot but this has probably cumulatively saved me months of time
I'm not a fan. It works well for what it is, but what it is is an additional language to know in a place where you probably already have one lying around.
Also, like make, it encourages an imperative mode for project tooling and I think we should distance ourselves from that a bit further. It's nice that everybody is on the same page about which verbs are available, but those verbs likely change filesystem state among your .gitignored files. And since they're starting from an unknown state you end up with each Just command prefixed by other commands which prepare to run the actual command, so now you're sort of freestyling a package manager around each command in an ad-hoc way when maybe it's automation that deserves to be handled without depending on unspecified state in the project dir.
None of this is Just's fault. This is people using Just poorly. But I do think it (and make) sort of place you on a slippery slope. Wherever possible I'd prefer to reframe whatever needs doing as a build and use something like nix which is less friendly up front, but less surprising later on because you know you're not depending on the outputs of some command that was run once and forgotten about--suddenly a problem because the new guy can't get it to work and nobody else remembers why it works on theirs.
I love just. The main benefit for me at work is that it's much easier to convince others to use, unlike make.
I like make just fine, and it's useful to learn, but it's also a very opaque language to someone who may not even have very much shell experience. I've frequently found Makefiles scattered around a repo – which do still work, to be clear – with no known ownership, the knowledge of their creation lost with the person who wrote them, and subsequently left.
I've used Just at a workplace on a project I didn't start. It seemed slightly simpler than make when putting together task dependencies. But I couldn't figure out what justifies using it over make.
I saw many projects like this a while ago, and, although they all seemed great, I kept wondering why do I need such a complex thing just to save/run a bunch of scripts?
I ended up building my own script runner, fj.sh [1]. It's dead simple, you write your scripts using regular shell functions that accept arguments, and add your script files to your repos. Run with "fj myfunc myarg ...". Installation is basically downloading an executable shell script (fj.sh) and adding it to your PATH. Uninstall by removing it. That's all.
I'm not saying 'just' is bad—it is an awesome, very powerful tool, but you don't always need that much power, so keep an eye on your use case, as always.
[1] github.com/gutomotta/fj.sh
Can anyone with experience with just and tools like npm/yarn explain if there are any benefits to use just instead of codifying commands into the "scripts" field of the package.json? Commands can also be enumerated. How often would I benefit from just's other features?
Task is in a similar problem space.
Unlike Just which clearly states it is not a build system [1], Task can be told about expected files so tasks can be skipped to avoid unnecessary work [2]. So if your task is to build software, IMO make and the others like Task would be better.
If your tasks only care about the success code from a process, and/or are a Rust fan instead of Go, then Just should be fine. Otherwise, for specific use-cases like CI, you are likely already coding in a proprietary YAML/JSON/XML format.
[1] https://github.com/casey/just/blob/e1b85d9d0bc160c1ac8ca3bca...
I'm surprised nobody mentioned Rake yet. Having the full capability of Ruby and whatever gem you want makes it a dream for these kind of tasks. Absolutely love it.
I recently looked at various alternatives to make and landed on https://taskfile.dev/
It handles dependencies and conditions well without needing to be a full blown bash expert.
I've used make for years, even partially wrote my own make interpreter once, I hate it as much as anybody else. But I don't feel confident investing in a new tool that has widespread industry adoption. I wish there was a 'better make' that tries to replace make the same way Zig wants to replace C, where they have great interop and make it easy to rewrite code into the new language.
I started writing my tasks in mise (https://mise.jdx.dev/tasks/) instead of just, but I found that others didn’t want to install it. Something about mise being an all-in-one tool—combining asdf/direnv/virtualenv/global npm/task management—made installing it just for the task feature off-putting. At least that's my theory. So, I’m back to using just. I am happy that there isn't a ton of pushback on adding a justfile here and there. Maybe it’s the name—‘just’ feels lightweight and is known to be fast, so people are cool with it.
From my perspective, Just would be more useful if it had some ability to skip steps where the input hasn't changed.
Like maybe a Justfile's recipe could produce a "<task>.complete" kind of file, and could decide whether to re-run the task based on whether the task's inputs (or its dependencies' inputs).
Also if that sounds like a useful feature, consider using Make.
I see more projects switch to PIXI, another Rust-written piece of software. RERUN was the one I follow the most https://github.com/rerun-io/rerun
It looks like much more than just command runner, but my projects happen to be needing much more than that too.
One reward you get for allowing yourself to become brainwashed by Bazel is you get a pretty nice task runner in every project that you've brought into the fold.
Several comments mention Task/Taskfile already, which is very similar in that you define tasks in YAML.
I think it's worth mentioning Mage/Magefile [1][2] as well, where your tasks are actual Go code. Similar to how Rake is for tasks in Ruby code.
It's useful when you have complex tasks.
It's like using Pulumi instead of Terraform.
I've been using babashka tasks [0] for a while. It has a nice api to run shell commands but it's all clojure based.
Am I missing out on justfiles? It seems to be quite popular among rust/nix circles but I'm afraid it's going to be yet another instance of Greenspun's tenth rule.
I switched from make a while ago because I was using it to run tasks in my Python projects, which doesn't require any of make's build tools.
I didn't like make's complicated syntax either. Everything just makes more sense now.
I already have a command runner, it's called a shell.
Apparently just also needs one to run.
I love `just` and have adopted it universally in all my projects. For what it does, it gets the job done fantastically.
That being said, I found myself needing a tool that builds a DAG of dependent tasks and automatically figures out what can be ran in parallel and what cannot -- obviously you have to spell out all tasks and who depends on what first.
Anybody knows such a tool?
EDIT: Apparently people did not get the hint that I believe `make` is an over-engineered pile of metric tons of legacy and I'll sooner slash my wrists than to learn it in full.
I did mean something ergonomic and easy to read and write. And no I'll never view `make` as such. I tried. Many times. I have better things to do in my life than to memorize exceptions of the exceptions.
Can anyone give me an example where I can actually replace my bash scripts with just? I don't see a point in using it if I can simply write a bash script (at least their examples are very easily replaceable)
I’ve been happy with Just at our workplace. It lets me focus more on the task at hand instead of Conan / Cmake incantations.
It’s consistent, easy to use and maintain, and keeps all relevant operations in one place.
I'm Stockholm syndrome with make at this point. I'm not sure I'd want it any other way.
I used this until AI became good enough. Now, for most purpose, I can just declare what I want to be done/executed and get perfect bash for it. I have a relatively complex Makefile that build graphql schemas and sets them up. It'd have been a no-go given how weird bash syntax is; but now I can get it generated and working from pretty much the first try.
There is lots of bash around and it's a very simple language, so AI models are pretty good at it.
no flags, no parallel tasks, no skipping tasks unless files change, no watching for changes
come see a modern task manager: https://mise.jdx.dev/tasks/
My favorite command runner setup is just a simple bash script and .envrc
I can put my commands in a run file, which source a simple bash script, and use it like:
run do-foo
run build-bar
You can even `run help` to list all available commands.The setup is explained here: https://olivernguyen.io/w/direnv.run/
My favorite entry in this space is Argc. I like it because the only “new syntax” it introduces is metadata comments, and the rest is pure bash. The maintainer is also best-in-class in terms of responsiveness.
Why use this over .sh files?
I use Invoke-Build[1] everywhere and I highly recommend it. It's cross-platform, uses PowerShell so we have serious programming language in the background and is extremely simple yet powerful: dependencies, integrated help, good defaults for error handling and starting directory, vs code support, DOT charts of task dependencies, incremental task, persistent builds, parallel stuff etc. See example usage here [2]
I use it as a somewhat more sane way of collecting my repetitive, project specific commands, without having to rely on shell history.
I'll just plop my project-specific workflows (series of shell commands) into a Justfile (that I don't commit, it's just for me). That allows me to be more rigorous and structured with how I'm iterating on a project.
It has syntax and semantics that are sufficiently saner than make, so I don't need to know a lot to be productive.
If I come back to a project after a couple weeks, I don't need to spelunk shell history. Just --list is enough to get back up to speed with how I was iterating.
Nice. I didn't know about Just.
Just (pun intended) a personal plug: I always liked the Make ease of use and the declarative GH Actions phylosophy. I also like to have the same workflows in local and in my remote CI, so I recently wrote a task runner with the (IMHO) ease of use of Make and GH Actions-like philosophy. It still lacks good docs, but I use it everyday on my projects and works like a charm.
https://github.com/luismedel/bluish/
Some day I need to do a proper Show HN :-)
You didn't mention it's written in Rust. Is that allowed?
I find it more powerful and from a certain point easier to create the tooling using the projects programming language. Every dev should be familiar with that language and ecosystem. E.g. for project that had several tools - Rust (server), .NET and Node (CLI tools) and Svelte (frontend) - I wrote all operational tools in Typescript and run them using Deno. Very clean and powerful (typesafe, composable, Deno std lib). You can add all kind of stuff like timings, logging, checks, whatever ...
Question - mise is also incorporating a command runner. Anyone tried it yet? We love just, of course. Always curious about new tools.
I have been using this for months now - way easier than Taskfile.
The parameter injection and passing to commands was the thing that converted me.
Reliance over a Posix shell basically prevents me from using Just. Using bash from Git on Windows is a very weird choice.
Just recipes accepting command line arts and supporting documentation might be enough to finally push me away from Make.
Seems that I'm the only one who opened up the website and didn't know what was going on. At least two sentences what the "just" is, otherwise it's "if you, you know" and that isn't inviting page.
Justfiles are really awesome for repos where you have to use a bunch of complex, long to type CLI integrations. Especially if you’re using Deno scripts that all have different permission flags…
I am really moving spok
It’s golang based but very like make.
Used it in my graduate internship. It really made using the garbage ASP.NET commands easier. Thanks!
Why is "Just" superior to any other e.g. bash script with a bunch of subcommands?
I'm also using a global justfile (`-g`) [1] to serve as a convenient location to aggregate any convenience functions, as well as call out to any standalone scripts as necessary.
You can also 'convert' all recipes to aliases so you get the best of both worlds, the ability to call with `just -g foo` or `foo`, from anywhere.
The docs example [2] uses a `user` justfile, but the principal is the same for global.
for recipe in `just --justfile ~/.user.justfile --summary`; do
alias $recipe="just --justfile ~/.user.justfile --working-directory . $recipe"
done
Most recently I've started using `fzf` and `bat` to allow interactive selection of recipes with syntax highlighted previews: _choose:
@just -g --summary | \
tr ' ' '\n' | \
sort -r | \
fzf --multi --preview 'just -g --show {} | bat --color=always -l just -pp' | \
xargs just -g
Now with a global `alias ji="just -g _choose"` I can interactively choose a recipe if I need a reminder of what I've set up.This was inspired by the native `--choose` flag which does something similar, but by using `--summary` here, all recipes, including those that take arguments *, are listed, as well as any nested modules.
And because you can use any shebang, you can also write little python scripts to run with `uv`, including those with dependencies [5] declared in the shebang:
# list Cloudflare accounts
accounts:
#!/usr/bin/env -S uv run --script --with cloudflare --python 3.13
from cloudflare import Cloudflare
client = Cloudflare()
accounts = client.accounts.list()
print(accounts)
…here with inline metadata: # list Cloudflare accounts
accounts:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "cloudflare",
# ]
# ///
from cloudflare import Cloudflare
client = Cloudflare()
accounts = client.accounts.list()
print(accounts)
[1] https://just.systems/man/en/global-and-user-justfiles.html
[2] https://just.systems/man/en/global-and-user-justfiles.html#r...
[3] https://just.systems/man/en/selecting-recipes-to-run-with-an...
[4] https://github.com/charmbracelet/gum?tab=readme-ov-file#inpu...
[5] https://docs.astral.sh/uv/guides/scripts/#running-a-script-w...* interactively selected recipes that take arguments won't work by directly passing to `xargs` here, but in some cases where I do want that flexibility I just add a condition in the recipe to prompt for input, with `gum input` [4]. Flexibility. This is a belt and braces approach and only used where necessary as the `fzf` preview will have made it clear that a recipe takes arguments.
[positional-arguments]
foo $bar="":
#!/usr/bin/env bash
if [ -z "$bar" ]; then
bar=$(gum input --placeholder "bar")
fi
echo "looking up $bar"
very neat but already hitting cases where it doesn't play nice with pwsh scripts, even using the shebang. Back to using a dir full of .ps1 files I guess lol
I love just! Any way to avoid remembering things is great.
Just use programming language to build itself, it is even possible with C [0]
[0] https://github.com/tsoding/nob.h
If it is painful, ditch that language.
I built something similar a couple of years back. Glad to see I wasn't alone in my itches
Uh, isn't this just Make? I'd rather people run `make this` and `make that` than install a new tool to do the same damned thing. Sometimes software is just "done" and doesn't need to be reinvented.
I love just, this is such a great piece of software.
I was thinking the other day: why don't we use just instead of Dockerfiles to define containers?
Can you set a variable from one task and use it from another, or is it a bad thing to want this?
use this in my projects and love it
[dead]
[dead]
[flagged]
Nobody: Let's write all of our scripts in YAML
Me: !
We recently switched pgai over to just. And are quite happy so far. The hierarchical nature is quite nice: https://github.com/timescale/pgai
In case you want to run Justfiles in places where you can't install the Just binary (for whatever reason), I wrote a compiler that transforms Justfiles into portable shell scripts that have byte-for-byte identical output in most cases.
https://github.com/jstrieb/just.sh
Previous HN discussion: https://news.ycombinator.com/item?id=38772039