Course Builder个人学习工作台 · 本地模式
mise 实战:从工具版本到可复现 CI

M003:读懂有效配置:层级、覆盖与团队边界

目标:先预测、再验证 global、parent、project 与 local 配置如何共同产生最终值。

0–7 分钟:最终配置不是某一个文件

mise 会从当前目录向上发现配置,并把较广范围的值与更具体目录的值合并。更靠近当前目录的配置可以覆盖父目录中的同名键。[S03]

先记住两种不同的合并方式:

  • [tools]、[env] 这类键值配置可以按键继承与覆盖。例如父目录有 Python,子目录增加 Node,最终可能同时存在。
  • 同名 task 通常是一个完整定义;子目录重写 tasks.test 时,不要假设父任务中的字段会自动递归保留。

mise.local.toml 处于很高优先级,设计用途是本机、不提交的覆盖。它适合个人路径和临时偏好,不适合团队必须拥有的工具或任务。[S03]

7–20 分钟:做一个可重复的三层实验

这次不用猜你的真实 global 配置。我们创建 parent、project、local 三层,并在执行前写出预测。

步骤 1:创建目录结构

rm -rf /tmp/mise-config-lab
mkdir -p /tmp/mise-config-lab/project
cd /tmp/mise-config-lab

创建 parent 配置:

cat > mise.toml <<'EOF'
[tools]
python = "3.11"

[env]
ENV_SOURCE = "parent"
PARENT_ONLY = "yes"

[tasks.describe]
run = 'printf "task=parent ENV_SOURCE=%s\n" "$ENV_SOURCE"'
EOF

创建 project 配置:

cat > project/mise.toml <<'EOF'
[tools]
python = "3.12"
node = "22"

[env]
ENV_SOURCE = "project"
PROJECT_ONLY = "yes"

[tasks.describe]
run = 'printf "task=project ENV_SOURCE=%s\n" "$ENV_SOURCE"'
EOF

创建只在本机存在的 local 覆盖:

cat > project/mise.local.toml <<'EOF'
[env]
ENV_SOURCE = "local"
EOF

printf 'mise.local.toml\n' > project/.gitignore
cd project

步骤 2:运行前先预测

不要先执行命令。复制并填写:

项目我的预测理由
Pythonparent 还是 project 更具体?
Node哪一层声明了它?
PARENT_ONLY未冲突的键会怎样?
PROJECT_ONLY哪一层提供?
ENV_SOURCElocal 的优先级如何?
describe 输出同名 task 取哪一个完整定义?

步骤 3:查看“加载了谁”

mise config

预期观察:输出包含 parent、project 和 local 配置,且能看出优先级顺序。若只看到一个文件,先检查当前目录和文件名,不要直接修改结论。

步骤 4:查看最终 tools 与 env

mise ls --current
mise exec -- sh -c 'printf "ENV_SOURCE=%s PARENT_ONLY=%s PROJECT_ONLY=%s\n" "$ENV_SOURCE" "$PARENT_ONLY" "$PROJECT_ONLY"'

预期结果:Python 由 project 覆盖为 3.12 选择器,Node 来自 project;两个 *_ONLY 都保留;ENV_SOURCE 最终为 local。

步骤 5:验证 task 的替换边界

mise run describe

预期打印 task=project ENV_SOURCE=local。这同时说明两件事:同名 task 使用 project 定义,而运行环境仍是合并后的最终 env。

步骤 6:做一次反事实验证

mv mise.local.toml mise.local.toml.disabled
mise exec -- sh -c 'printf "ENV_SOURCE=%s\n" "$ENV_SOURCE"'
mv mise.local.toml.disabled mise.local.toml

预期值从 local 退回 project。这一步比只看最终输出更强,因为它证明了覆盖来自哪一层。

20–26 分钟:把“值”追溯到“所有者”

现在为每个有效值画一条来源链:

Python: parent 3.11 → project 3.12(最终)
ENV_SOURCE: parent → project → local(最终)
PARENT_ONLY: parent(保留)
describe task: parent 定义 → project 整体替换(最终)

再用下面规则决定文件归属:

内容推荐位置原因
团队统一工具、任务、非敏感默认值提交的 mise.tomlfresh checkout 必须恢复
开发者个人绝对路径、临时覆盖被忽略的 mise.local.toml不应强加给团队
真实 secret外部 secret store 或被忽略来源不进入 Git 历史
项目要求的最低 mise 能力提交配置中的 min_version让不兼容尽早失败

常见失败与下一步:

  • 预测与结果不同:先看 mise config 的实际文件列表,再检查是否有真实 global 或更高优先级环境配置。
  • local 文件被 Git 跟踪:仅添加 .gitignore 不能移除已经跟踪的文件;先确认没有 secret,再按团队流程从索引移除。
  • 子任务丢失父任务字段:这是把 task 当递归 map 合并的典型错误;在子层写出完整 task 定义。
  • 只保存 mise.toml 截图:它看不到 parent/global/local;同时保存加载列表和最终值。

26–30 分钟:检索问题

问题:为什么只贴当前目录的 mise.toml 不能解释所有有效值?

作答后查看参考答案

mise 会发现并合并 global、父目录、项目、环境专用和 local 等配置。最终值可能继承自更广层,也可能被更具体层覆盖;必须同时查看加载文件、合并规则和最终输出。

情境题:一位开发者需要把本机 SDK 路径改成 $HOME/sdk。应放在共享 mise.toml 还是 mise.local.toml?

作答后查看参考答案

通常放在被忽略的 mise.local.toml,因为绝对个人路径无法被其他机器复用。若团队确实需要统一 SDK,应提交可移植的工具声明或相对路径,而不是提交某人的主目录。

通过标准

  • 在执行前完成覆盖预测,并用实际输出逐项核对。
  • 能区分 tools/env 的按键合并与同名 task 的整体替换。
  • 能为至少三个最终值指出来源链。
  • 共享配置、个人覆盖与 secret 的边界清楚。

Sources used

  • S02 jdx/mise, Dev Tools.
  • S03 jdx/mise, Configuration.