每个项目都有一些高频命令:构建、测试、部署、格式化、初始化环境。这些命令要么太长记不住,要么有执行顺序的依赖,要么散落在 README 的某个角落里,每次看文档才知道怎么跑。

just 解决这个问题:把这些命令固化成一个可发现、可复用、可交给 CI / Agent 执行的命令菜单。

1. 为什么不只在 README 里写

README 解释背景和动机,justfile 执行操作——两者分工不同,不应该混淆。

README 里的命令有三个缺陷:容易过时(代码改了,文档没改)、参数容易出错(复制粘贴时少了个 flag)、无法被工具直接执行(CI 脚本、Agent 必须解析自然语言文档)。

justfile 里的命令有名字、有依赖关系、有默认值、可以接参数——它是可执行的文档。

2. just vs Makefile

Makefile 能做同样的事,但有三个让人头疼的问题:

  • 语法晦涩$@$<$^ 等自动变量,不查文档几乎看不懂
  • Tab 陷阱:缩进必须是 Tab 而不是 Space,混用会报莫名其妙的错误
  • 跨平台差异:GNU Make 和 BSD Make 行为不一致,macOS 默认是 BSD Make

just 的定位很清晰:专注做命令菜单这一件事,不是构建系统,没有文件时间戳比较,没有隐式规则——只是有名字的命令块,执行顺序由依赖声明决定。

3. 基本语法

justfile 放在项目根目录,文件名就叫 justfile(无扩展名)。

最小示例

# 默认 recipe:直接运行 just 时执行
default:
    just --list
 
# 构建
build:
    cargo build --release
 
# 测试
test:
    cargo test
 
# 带依赖:先 lint 和 test,再 build
ci: lint test build
 
# 带参数(有默认值)
serve port="8000":
    python -m http.server {{ port }}
 
# 运行时:just serve 用默认端口,just serve 3000 指定端口

关键语法点

语法含义
recipe-name:定义一个 recipe
recipe: dep1 dep2recipe 的前置依赖(先执行 dep1 和 dep2)
arg="default"带默认值的参数
{{ arg }}在命令里引用参数
@echo "..."@ 前缀不打印命令本身,只打印输出
# 注释just --list 里显示为描述

查看所有可用 recipe

just --list
# 输出:
# build    构建项目
# ci       lint + test + build
# serve    启动本地服务器
# test     运行测试

just --list 是项目命令的自文档。新人第一次接触项目时,这一个命令告诉他能做什么。

4. 完整示例

一个典型前端项目的 justfile:

# 命令菜单(直接运行 just 显示)
default:
    just --list
 
# 安装依赖和工具
bootstrap:
    npm install
    npx playwright install
 
# 开发服务器
dev:
    npm run dev
 
# 运行测试
test:
    npm test
 
# 代码检查
lint:
    npx eslint src/ --max-warnings 0
 
# 格式化
fmt:
    npx prettier --write "src/**/*.{js,ts,tsx}"
 
# 构建(先 lint 和 test)
build: lint test
    npm run build
 
# 清理产物
clean:
    rm -rf dist/ .next/ node_modules/.cache/
 
# 部署(先构建)
deploy: build
    rsync -avz ./dist/ deploy@prod:/var/www/app/
    @echo "Deployed to production"
 
# 检查所有(CI 入口)
ci: lint test build

执行 just deploy 时,just 会自动先跑 buildbuild 又会先跑 linttest——依赖链自动解析。

5. 全局 justfile

除了项目级 justfile,还可以有全局 justfile:

~/.config/just/justfile

全局 recipe 在任何目录都可以调用,适合放系统维护类操作:

# 更新 Homebrew 和所有包
update:
    brew update && brew upgrade
 
# 同步 dotfiles
sync:
    chezmoi apply --verbose
 
# 查看 dotfiles 变更
diff:
    chezmoi diff
 
# 清理系统缓存
clean:
    brew cleanup
    npm cache clean --force

项目里的 justfile 优先级高于全局,两者可以共存。

6. 与工具链集成

CI/CD 共用入口:CI 跑 just test,本地也跑 just test,保证两者执行的是完全相同的命令——环境差异来源减少。

# GitHub Actions
jobs:
  test:
    steps:
      - uses: extractions/setup-just@v2
      - run: just ci

pre-commit hook:在 .git/hooks/pre-commit 里调用 just lint,统一本地和 CI 的代码检查标准:

#!/bin/sh
just lint

编辑器集成:VS Code 的 Task Runner 可以直接列出 justfile 里的 recipe;Neovim 可以用 :terminal just test 直接运行。

AI Agent 友好:任务名称清晰、参数明确、输出稳定的 justfile,让 Agent 不需要猜命令——它可以直接 just test 验证修复是否生效,just lint 检查代码质量,just build 确认构建无误。这是在 AI 辅助开发工作流里 justfile 越来越重要的原因。

7. 设计原则

项目常用操作要有名字linttestbuildfmtbootstrapdeploy 这些动词应该能被快速发现,不需要翻 README。

危险动作显式命名:删除数据、部署到生产、覆盖数据库这类操作,不要用无害的名字掩盖,必要时加确认步骤:

# 危险:部署到生产前要求确认
deploy-prod:
    @echo "⚠️  即将部署到生产环境,确认?(y/n)"
    @read ans && [ "$$ans" = "y" ] || exit 1
    just _do-deploy-prod
 
_do-deploy-prod:
    rsync -avz ./dist/ prod-server:/var/www/

README 和 justfile 分工:README 写背景、依赖和注意事项;justfile 固化稳定命令。不要在 justfile 里写大量注释解释”为什么”——那应该在 README 或 ADR 里。

CI 和本地共用:如果两者执行不同的命令,最终会出现”本地通过 CI 失败”的情况。共用 just ci 作为单一真相源。