春江暮客

春江暮客的个人学习分享网站

Use just to Manage Project Commands: Turn Repeated Scripts into a Runnable Menu

2026-07-27 Technology

After a project has been maintained for a while, a small problem often appears: important commands live in memory, README fragments, shell history, CI files, and chat messages.

just is useful for this. It is not a build system. It is a project command runner: you write common commands in a justfile, then run stable entry points such as just test, just run, and just deploy. This tutorial uses a minimal Python project to show a workflow you can copy directly.

By the end, you will have:

  1. A basic working justfile
  2. A run command with arguments
  3. A set of check, test, and cleanup commands
  4. Direct fixes for common errors

When this is useful

just is especially useful for these projects:

  1. The README contains many repeated commands
  2. Local development, testing, formatting, and build commands are stable
  3. Teammates often ask how to run the project
  4. You do not want a complex build system for simple commands
  5. You want CI and local development to reuse the same command entry points

If a project has only one script, running that script directly is enough. If the project already has npm scripts, a Makefile, or CI configuration, just can still be a clearer local entry point.

Method 1: Install and verify just

On macOS, use Homebrew:

brew install just

If the machine already has the Rust toolchain, use Cargo:

cargo install just

If you do not want to depend on a system package manager on a server, install a prebuilt binary into ~/bin:

mkdir -p ~/bin
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/bin
export PATH="$PATH:$HOME/bin"

Verify the installation:

just --version
just --help

If you see the version and help output, you can start writing a justfile.

Method 2: Create a reusable justfile

First create a minimal Python project:

mkdir just-demo
cd just-demo

Write the main program:

cat > greet.py <<'EOF'
from __future__ import annotations

import sys


def greet(name: str) -> str:
    return f"hello, {name}"


if __name__ == "__main__":
    name = sys.argv[1] if len(sys.argv) > 1 else "world"
    print(greet(name))
EOF

Write a standard-library test:

cat > test_greet.py <<'EOF'
import unittest

from greet import greet


class GreetTest(unittest.TestCase):
    def test_greet(self) -> None:
        self.assertEqual(greet("bobobk"), "hello, bobobk")


if __name__ == "__main__":
    unittest.main()
EOF

Now create the justfile:

cat > justfile <<'EOF'
python := "python3"

default:
    @just --list

run name="world":
    {{python}} greet.py "{{name}}"

test:
    {{python}} -m unittest -v

syntax:
    {{python}} -m compileall -q .

check: syntax test

clean:
    rm -rf __pycache__ .pytest_cache .ruff_cache
EOF

This file does a few things:

  1. default: lists all commands when you run just
  2. run name="world": defines an argument with a default value
  3. test: runs standard-library tests
  4. syntax: performs a Python syntax check
  5. check: syntax test: chains multiple steps together
  6. clean: removes local cache directories

Method 3: Run commands and override arguments

List available commands first:

just

You should see output similar to this:

Available recipes:
    check
    clean
    default
    run name="world"
    syntax
    test

Run with the default argument:

just run

Output:

hello, world

Pass an argument:

just run bobobk

Output:

hello, bobobk

Run the full check:

just check

The output runs the syntax check first, then the tests. If one step fails, the later commands will not hide the failure as a success.

If you want to temporarily use another Python command, override the variable:

just python=python3.12 test

This is better for temporary debugging than asking everyone to edit the justfile.

Method 4: Use just in a real project

A real project can start with this kind of justfile:

python := "python3"
src := "app"

default:
    @just --list

install:
    uv sync

dev:
    uv run fastapi dev {{src}}/main.py

fmt:
    uvx ruff format .

lint:
    uvx ruff check .

test:
    uv run pytest -q

check: fmt lint test

build:
    docker build -t my-app:local .

The value of this file is not shorter commands. The value is a stable entry point. After cloning the project, a new teammate does not need to guess which README paragraph is current. They can run:

just

Then choose from the list:

just install
just dev
just check

CI can reuse the same entry point:

- name: Check project
  run: just check

This keeps local and CI commands from drifting apart.

Validation

Use these commands to confirm that the example project works:

just --list
just run Alice
just test
just check

Expected result:

hello, Alice

The test output should include something like:

test_greet (test_greet.GreetTest.test_greet) ... ok

If these commands run, just, the justfile, argument passing, and recipe dependencies are working.

Troubleshooting

1. Error: No justfile found

The current directory and its parent directories do not contain a justfile.

Check your location:

pwd
ls -la

If the file is not in the current project, move to the project root:

cd /path/to/project
just --list

2. Error: Unknown recipe

The recipe name is misspelled, or that recipe is not defined.

List all commands first:

just --list

Then copy the recipe name from the list instead of typing it from memory.

3. A variable disappears on the next line

In a normal recipe, each line is passed to the shell separately. Do not write this:

bad:
    TOKEN=demo
    echo "$TOKEN"

A safer option is to keep it on one line:

good:
    TOKEN=demo && echo "$TOKEN"

If the logic is longer, use a shebang recipe:

script:
    #!/usr/bin/env bash
    set -euo pipefail
    TOKEN=demo
    echo "$TOKEN"

4. An argument contains spaces

Quote argument substitutions. For example:

run name:
    python3 greet.py "{{name}}"

Then run:

just run "Bob Lee"

Otherwise, the shell may split one argument into multiple arguments.

Summary

The most practical value of just is turning common project commands into a discoverable menu. It will not replace every build tool, but it is a good fit for unifying local development, testing, formatting, and cleanup entry points.

Start with five recipes: default, install, dev, test, and check. Once those commands are stable, gradually add deployment, builds, data imports, and log triage tasks.

友情链接

其它