春江暮客

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

用 just 管理项目命令:把常用脚本整理成可执行菜单

2026-07-27 技术

项目维护久了,经常会出现一个小问题:命令都在脑子里,或者散落在 README、shell 历史记录、CI 配置和同事的聊天记录里。

just 适合解决这个问题。它不是构建系统,而是一个项目命令运行器:你把常用命令写进 justfile,之后用 just testjust runjust deploy 这类固定入口执行。本文用一个最小 Python 项目演示一套可以直接复制的工作流。

完成后,你会得到:

  1. 一个基础可用的 justfile
  2. 带参数的运行命令
  3. 一组检查、测试和清理命令
  4. 常见报错的直接修复方法

适合什么场景

just 特别适合这些项目:

  1. README 里有很多重复命令
  2. 本地开发、测试、格式检查、构建命令比较固定
  3. 团队成员经常问“这个项目怎么跑”
  4. 你不想为了简单命令引入复杂构建系统
  5. 你希望 CI 和本地尽量复用同一套命令

如果项目只有一个脚本,直接运行脚本就够了。如果项目已经有 npm scriptsMakefile 或 CI 配置,just 也可以作为更清晰的本地入口。

方法 1:安装并验证 just

macOS 可以用 Homebrew:

brew install just

已经有 Rust 工具链的机器,可以用 Cargo:

cargo install just

如果服务器上不想依赖包管理器,也可以把预编译版本安装到 ~/bin

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

验证安装:

just --version
just --help

能看到版本号和帮助信息,就可以开始写 justfile

方法 2:创建一个可复用 justfile

先准备一个最小 Python 项目:

mkdir just-demo
cd just-demo

写入主程序:

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

写入一个标准库测试:

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

现在创建 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

这个文件做了几件事:

  1. default:直接运行 just 时列出所有命令
  2. run name="world":定义一个带默认值的参数
  3. test:运行标准库测试
  4. syntax:做一次 Python 语法检查
  5. check: syntax test:把多个步骤串起来
  6. clean:清理本地缓存目录

方法 3:运行和覆盖参数

先列出可用命令:

just

预期会看到类似输出:

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

运行默认参数:

just run

输出:

hello, world

传入参数:

just run bobobk

输出:

hello, bobobk

运行完整检查:

just check

输出会先执行语法检查,再执行测试。如果其中一步失败,后续命令不会继续把结果伪装成成功。

如果你想临时换 Python 命令,可以覆盖变量:

just python=python3.12 test

这比让每个人手动改 justfile 更适合临时调试。

方法 4:把 just 用在真实项目里

一个真实项目的 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 .

这类文件的价值不是命令更短,而是入口更稳定。新同事拉代码后,不需要猜 README 哪一段是最新的,先执行:

just

再根据列表执行:

just install
just dev
just check

CI 里也可以复用同一个入口:

- name: Check project
  run: just check

这样本地和 CI 的命令就不容易分叉。

验证

用下面几条命令确认示例项目正常:

just --list
just run Alice
just test
just check

预期结果:

hello, Alice

测试部分应该出现类似:

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

如果这些命令都能运行,说明 justjustfile、参数传递和命令依赖都已经工作。

常见问题

1. 报错:No justfile found

原因是当前目录和上级目录里都没有 justfile

先确认位置:

pwd
ls -la

如果文件不在当前项目里,切到项目根目录:

cd /path/to/project
just --list

2. 报错:Unknown recipe

说明命令名写错了,或者对应 recipe 没有定义。

先列出所有命令:

just --list

再复制列表里的 recipe 名称执行,避免凭记忆输入。

3. 变量在下一行丢失

just 的普通 recipe 每一行都会交给 shell 执行。不要写成这样:

bad:
    TOKEN=demo
    echo "$TOKEN"

更稳的做法是放在同一行:

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

如果逻辑比较长,用 shebang recipe:

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

4. 参数里有空格

参数替换时要加引号。比如:

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

然后这样执行:

just run "Bob Lee"

否则 shell 可能会把一个参数拆成多个参数。

总结

just 最实用的价值,是把项目里的常用命令变成一个可发现的菜单。它不会替代所有构建工具,但很适合统一本地开发、测试、格式检查和清理入口。

建议从 5 个 recipe 开始:defaultinstalldevtestcheck。等这些命令稳定后,再把部署、构建、数据导入、日志排查等操作逐步加进去。

友情链接

其它