.justfile

Recently I started using just to improve and simplify the organization and execution of various scripts in my projects. There is the official Just Programmer's Manual available online, which you may want to check out.

When building client-server applications (e.g. SPAs), I usually have a project structure like this:

root/
  |- .env
  |- .env.template
  |- docker-compose.base.yml
  |- docker-compose.dev.yml
  |- docker-compose.prod.yml
  |- docker-compose.ci.yml
  |- ...
  |- frontend/
    |- package.json
    ...
  |- backend/
    |- pyproject.toml
    ...

And then I could run script like:

docker compose up -d  # `COMPOSE_FILE` is set in `.env`
cd frontend
npm i
cd ../backend
uv sync

However, when projects grew, so did the number of available scripts but not the amount of documentation of said scripts. At work I often introduce and onboard new developers and having multiple scripts and different locations doesn't make this easier. Additionally, in our work environment we have both Windows and Linux machines, which leads to further cluttering.

Example

set dotenv-load := true
set windows-shell := ["cmd.exe", "/c"]
set shell := ["bash", "-c"]


[private]
[windows]
[working-directory("backend")]
make_venv:
    uv sync

[private]
[linux]
[working-directory("backend")]
make_venv:
    #!/usr/bin/env bash
    if [[ -z ${WSLENV+x} ]]; then
        uv sync
        source .venv/bin/activate
    else
        echo We are in WSL!
        VENV_PATH=~/virtualenvs/berichtstool/.venv
        uv venv --allow-existing $VENV_PATH
        source ${VENV_PATH}/bin/activate
        uv sync --active
    fi
    echo "venv: $VIRTUAL_ENV"
    deactivate


[working-directory("backend/abacus")]
generate_abacus_api: make_venv
    python generate.py abacus2023.yaml


[linux]
install_ldap_deps:
    apt install python3-dev ldap-utils libldap2-dev libsasl2-dev

[working-directory("backend")]
translate: make_venv
    just run-in-venv python manage.py makemessages --locale de_CH --no-obsolete

# [private] hides this recipe from the `just --list` output.
# This is our helper for running commands within the venv.
[private]
[windows]
[working-directory("backend")]
run-in-venv +command:
    set ENV_FILE=../.env && .venv\Scripts\activate && {{command}}

[private]
[linux]
[working-directory("backend")]
run-in-venv +command: make_venv
    #!/usr/bin/env bash
    # Using a shebang ensures all lines run in the same shell,
    # so the `source` command affects the subsequent lines.
    set -e

    # This incorporates your specific logic for native Linux vs. WSL
    if [[ -z ${WSLENV+x} ]]; then
        source .venv/bin/activate
    else
        echo "Activating WSL venv..."
        source ~/virtualenvs/berichtstool/.venv/bin/activate
    fi

    export ENV_FILE=${ENV_FILE:-../.env}

    echo ENV_FILE: $ENV_FILE

    # Finally, execute the command that was passed as an argument
    {{command}}

[private]
[working-directory("frontend")]
npm-run +script:
    npm run {{script}}

[doc("Generate the API client in the frontend. This first exports the OpenAPI schema in the backend.")]
update-api:
    just run-in-venv uv run task generate_api
    just run-in-venv openapi-generator-cli generate -i schema.json -g typescript-fetch -o ../frontend/src/api --enable-post-process-file --strict-spec true -c api-generator.typescript-fetch.additionalProperties.json
    git add frontend/src/api
    just npm-run lint
    just npm-run typecheck

[doc("Install all (including dev) dependencies for the frontend and the backend.")]
[working-directory("frontend")]
setup: make_venv
    npm i

[working-directory("backend")]
lock-deps-backend:
    uv lock

[working-directory("frontend")]
lock-deps-frontend:
    npm i --package-lock-only

[doc("Update the lockfiles for the frontend (`package-lock.json`) and the backend (`uv.lock`)")]
lock: lock-deps-backend lock-deps-frontend

[working-directory("backend")]
seed_db:
    just run-in-venv python manage.py seed mini_icf demo2

[working-directory("backend")]
lint-backend:
    just run-in-venv task lint

[working-directory("frontend")]
lint-frontend:
    npm run lint

lint: lint-backend lint-frontend