Automate project workflows with the command runner Just!
I believe every project should have a CLI built around the standard workflows of developing on the project. Things like:
- Install dependencies
- Run tests
- Run linter and formatters
- Build project
- Start / Stop the docker environment
The reason I think this is important is because it makes a nice consistent and discoverable
entrypoint for understanding how you should work in the project. If you only provide the
instructions in the README then you have to remember to update those docs every time you
add a new command. Those docs aren't easily testable either.
Most of my career the command runner of choice for my projects as been GNU Make but it was
definitely the wrong tool for the job. It is a build tool that I bent into shape to work
as a command runner for me. These days I use the tool just.
Intro to just
Just is a modern command runner with a similar syntax to make
that provides a nice way for building out your project CLI! You create a file named justfile
at the root of your project and then the basic syntax is:
help:
@just --list
# My first command
first:
echo "Any commands you want to run go here!"The first help line defines a command "help" for your CLI and it lists out all the other available
commans. I always put this line first because just runs the first command in the file if a specific
command isn't requested. The output of this file looks like this:
❯ just
Available recipes:
first # My first command
helpHaving help automatically generated is fantastic! Its also really helpful that it adds the comment
to the command so that each command is self-documenting. If you run the first command you'll notice
it also has a feature where it prints out the commands being ran so the user knows exactly what is
happening:
❯ just first
echo "Any commands you want to run go here!"
Any commands you want to run go here!This doesn't always make sense though, so you can quickly remove that behavior by putting an @ in front
of any of the commands, like I did for the help command above. You can also declare dependencies if
you have re-usable parts of your workflow that many of your commands need.
For example, you might want to check versions of things like node and python before running the install
of their dependencies. So you could do something like:
help:
@just --list
node_version := "v17.6.0"
# Verify system dependencies
check-dependencies:
@if [ ! "$(node --version)" = {{ node_version }} ]; \
then \
echo "Missing node version: {{ node_version }}"; \
exit 1; \
fi
# Install frontend
install: check-dependencies
@echo "yarn install"which ends up with a CLI that looks like this:
❯ just
Available recipes:
check-dependencies # Verify system dependencies
help
install # Install frontend
❯ just install
Missing node version: v17.6.0
error: Recipe `check-dependencies` failed on line 12 with exit code 1This opens up a lot of possibilities! In the above justfile you'll notice I'm using a multi-line
command but I have \ at the end of each line. This is because just by default is going to run
each new line in their own shell. So this just makes all those lines run in the same shell.
You do not have to use this syntax though. Just is polyglot and can run commands from any language
you would like.
Polyglot
If you want to use a bash script as one of your commands, you can do so by adding a shebang at the top:
check-dependencies:
#!/usr/bin/env bash
set -euxo pipefail
if [ ! "$(node --version)" = {{ node_version }} ];
then
echo "Missing node version: {{ node_version }}"
exit 1
fiNow the entire command is using a bash script to execute! This gets really interesting if you want to start using things like python, so if you'd like to change the dependency checker above to python:
check-dependencies:
#!/usr/bin/env python3
import subprocess
result = subprocess.run(
['node', '--version'],
stdout=subprocess.PIPE
)
if result != "{{ node_version }}":
print(f"Missing node version: {{ node_version }}")
exit(1)You can even tell just that you want to use a specific language for all commands!
set shell := ["python3", "-c"]This not only affects the commands you have in your recipe but also anything inside backticks! So something like:
`print("Rust is the best programming language")`It would run through python instead of the shell.
Enviornment Files
One of the other modern things just adds to your workflow is the ability to utilize dotenv
files. So for example if you want to define which port you launch your http server on, you can
create a file called .env:
WEBSERVER_PORT=9000and then utilize it in your justfile:
set dotenv-load
http:
@echo "Starting webserver in current directory"
python3 -m http.server $WEBSERVER_PORTWhen you run just http it'll launch the http server on port 9000. One important line
in this file is set dotenv-load, it will not load the .env file without you telling it to.
Don't use language specific scripts!
I'n not a fan of language specific command runners like package.json in the node community.
It always frustrates me when I start working on a project that heavily uses scripts in their
package.json instead of using a real command runner. json is not a great format for writing
discoverable CLI commands. For example if you wanted to write a next.js build script:
"scripts": {
"predeploy": "yarn build && yarn export && touch dist/.nojekyll && echo sontek.net > dist/CNAME",
"deploy": "gh-pages -d dist -t true",
"build": "next build",
"export": "next export -o dist/",
},Combining all those commands is really messy and not easily understandable through yarn run:
❯ yarn run
yarn run v1.22.17
info Commands available from binary scripts: autoprefixer, browserslist, css-blank-pseudo, css-has-pseudo, css-prefers-color-scheme, cssesc, esparse, esvalidate, extract-zip, gh-pages, gh-pages-clean, js-yaml, loose-envify, nanoid, next, prettier, resolve, rimraf, semver, svgo, uvu
info Project commands
- build
next build
- deploy
gh-pages -d dist -t true
- export
next export -o dist/
- predeploy
yarn build && yarn export && touch dist/.nojekyll && echo sontek.net > dist/CNAMEI'd much rather have this:
❯ just
Available recipes:
build # Build frontend assets
deploy # Deploy assets to cloudfront
export # Export to static assets (no SSR)Conclusion
Just is a wonderful tool for building project specific CLIs without much effort. It is
a great replacement for Make if you are using it as a command runner and it has most of the features you'd need.
I recommend adding a justfile to your projects today! If you'd like to see a real world example of how to use just,
you can check out the one I use to maintain my home directory!