【笔记】mise 的环境控制模型与工作流
初识 mise 时,很容易把它理解成“另一个 Node/Python 版本管理器”。这个理解只覆盖了最外层的功能,也解释不了后续一连串问题:use 和 install 为什么同时存在?工具已经安装,为什么命令还不能用?[env] 中的 _.file 是什么?普通 activation 和 shims 都能切换版本,为什么后者支持的功能更少?Tasks 又处在什么位置?
这些问题真正指向的是同一个核心:mise 不只管理工具文件,还要决定当前目录采用哪份配置、工具如何进入执行环境,以及一条命令在哪个时机获得这些上下文。
一句话心智模型是:配置负责声明期望状态,backend 负责把工具安装到本机,activation 或 shim 负责把配置解析结果注入执行环境,run/exec 则为一条显式命令建立完整的 mise 上下文。
flowchart LR
A[全局与项目配置] --> B[配置解析与版本选择]
B --> C[tools]
B --> D[env / vars]
B --> E[tasks]
C --> F[backend 安装工具]
C --> G[PATH activation / shims]
D --> G
C --> H[mise run / exec]
D --> H
E --> H
G --> I[Shell 与普通命令]
H --> J[显式命令或任务]
1. 不要用“激活”概括所有动作
mise 文档和日常交流都会使用 active、activate 等词,但它们可能指向不同层次。只说“这个工具激活了”,很容易把以下动作混为一谈:
| 动作 | 真正含义 | 典型载体 |
|---|---|---|
| 声明 | 希望某个作用域使用哪个工具版本 | mise.toml、全局 config.toml |
| 安装 | 将工具文件下载、编译或放入 mise 数据目录 | mise install、backend |
| 选择 | 根据当前目录和配置层级解析出目标版本 | mise 配置解析器 |
| 注入 | 将工具路径和环境变量放入执行环境 | PATH activation、shim、exec/run |
| 执行 | 启动最终工具或任务 | Shell、真实工具进程 |
例如,node@20 可以已经安装在本机,却没有被任何配置声明;也可以已经写入配置,但对应文件尚未安装;还可以配置和安装都已完成,但当前 Shell 没有接入 mise。三种情况的表现可能都是“node 没按预期工作”,根因却完全不同。
因此,后文尽量使用“写入配置”“安装到本机”“当前配置选中”“注入当前 Shell”这些具体表达,只在语义明确时使用“激活”。
2. Tools 的完整链路
[tools] 是 mise 最常用的入口,但一项工具从名字变成可执行命令,中间仍要经过版本解析、backend 安装和环境注入。
2.1 Tool、backend 与 registry
配置中的 tool 表示需要管理的开发工具或 CLI:
1
2
3
4
[tools]
node = "24"
python = "3.14"
"npm:@openai/codex" = "0.151.0"
mise 不会亲自实现所有生态的安装协议,而是通过 backend 统一适配不同来源:
1
2
3
4
5
node → core backend
npm:prettier → npm backend
pipx:black → pipx backend
cargo:ripgrep → cargo backend
github:owner/project → GitHub backend
backend 负责列出远程版本、解析目标版本、下载或调用外部包管理器、安装工具并暴露可执行文件。registry 则负责把 node、ripgrep 这类简写映射到合适的 backend。显式写出 npm:、pipx:、github: 前缀时,相当于直接指定安装来源。
工具通常按版本分别保存,而不是覆盖系统目录。未覆盖 MISE_DATA_DIR 等目录设置时,默认结构类似:
1
2
$MISE_DATA_DIR/installs/node/20.x.x/
$MISE_DATA_DIR/installs/node/24.x.x/
同一台机器因此可以同时安装多个版本。当前使用哪个版本,不由“最后安装了谁”决定,而由当前目录适用的配置决定。
2.2 install:只保证工具文件存在
mise install 的职责是安装,不负责把一个临时参数写回配置:
1
2
3
4
5
6
7
8
9
10
11
# 安装配置中声明的全部工具
mise install
# 安装配置为当前 node 选中的版本
mise install node
# 额外安装指定版本,但不写入配置
mise install node@20
# 具体 patch 版本同样可以指定
mise install [email protected]
这里最容易出现的误解是“install 不能指定版本”。事实正好相反:mise install [TOOL@VERSION] 明确支持版本参数。它和 use 的关键差别不是能否指定版本,而是是否修改配置。
假设本机执行:
1
mise install node@20
安装目录中会出现 Node 20,但如果当前配置仍声明 Node 24,执行 node 时依然应该得到 Node 24。Node 20 只是【已安装/available】,并没有成为当前作用域【选中的版本/selected】。
反过来,如果配置已经存在:
1
2
[tools]
node = "20"
再执行:
1
mise install
Node 20 安装完成后就能成为当前配置的目标版本。这里不是 install 做了额外“激活”,而是配置早已完成选择,install 只是补齐缺失文件。
2.3 use:写入配置,并补齐安装
mise use 同时完成两个动作:
- 将工具及版本写入目标配置;
- 对尚未安装的版本执行安装。
1
2
3
4
5
6
# 写入当前项目的 mise.toml
mise use node@20
# 写入 mise 的全局配置文件
mise use --global node@24
mise use -g node@24
因此可以把两者记成:
1
2
mise install = 安装,但不改变配置选择
mise use = 写入配置选择 + 必要时安装
不过,“use 安装并激活”仍然是一个不够精确的简写。use 确实让版本进入当前配置,但普通命令能否找到它,还取决于 Shell 是否使用 PATH activation、shims,或者命令是否通过 mise exec/run 启动。
| 命令 | 安装工具 | 写入配置 | 改变当前作用域的版本选择 |
|---|---|---|---|
mise install node@20 | 是 | 否 | 否,除非配置原本已选中它 |
mise use node@20 | 是 | 当前项目 | 是 |
mise use -g node@20 | 是 | 全局配置 | 是,但可被项目配置覆盖 |
3. 配置作用域决定“当前应该用什么”
mise 会同时读取全局配置、父目录配置和项目配置。越接近当前目录的配置通常越具体,可以覆盖更上层的同名工具版本。
1
2
3
4
$MISE_CONFIG_DIR/config.toml 全局默认
父目录/mise.toml 目录树公共配置
项目根目录/mise.toml 项目配置
项目根目录/mise.local.toml 本机项目覆盖
例如全局配置声明:
1
2
[tools]
node = "24"
某个项目声明:
1
2
[tools]
node = "20"
在项目外,Node 24 是默认选择;进入项目后,Node 20 覆盖全局默认。离开项目后再恢复 Node 24。这里没有反复卸载或覆盖文件,变化的只是配置解析结果和最终执行环境。
排查配置来源时,不要只看全局文件,可以直接检查:
1
2
3
4
5
6
7
8
# 查看当前目录实际读取了哪些配置
mise config ls
# 查看 node 的 backend、已安装版本、请求版本、当前版本和配置来源
mise tool node
# 只查看提供 node 配置的来源
mise tool node --config-source
3.1 chezmoi 管理全局配置时,源文件不在目标路径
当 dotfiles 使用 chezmoi 管理 mise 全局配置时,会出现两个不同角色的文件:
1
2
3
4
5
6
7
chezmoi source state
dot_config/mise/config.toml.tmpl
↓ chezmoi 渲染与应用
目标文件
$MISE_CONFIG_DIR/config.toml
↓ mise 读取
工具安装与版本选择
目标配置是生成结果,不是长期事实源。直接运行:
1
mise use -g [email protected]
会修改目标文件,却不会同步修改 chezmoi 模板。下一次 chezmoi apply 可能把这项改动覆盖掉。
因此,当前全局工具工作流应当是:
flowchart LR
A[修改 chezmoi mise 模板] --> B[检查两个 Profile 的渲染]
B --> C[提交并同步 source state]
C --> D[chezmoi diff]
D --> E[chezmoi apply]
E --> F[生成全局 config.toml]
F --> G[mise install]
G --> H[版本与路径验证]
常用命令如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
cd "$(chezmoi source-path)"
# 修改 dot_config/mise/config.toml.tmpl 后先检查目标变化
chezmoi diff
chezmoi apply -v
# 按新配置补齐工具
mise install
# 验证最终解析结果
mise ls
mise tool shellcheck
shellcheck --version
其他机器只需要同步声明,再安装缺失版本:
1
2
3
4
chezmoi update --apply=false
chezmoi diff
chezmoi apply -v
mise install
如果只是试用一个版本,不想立即进入 dotfiles,可以绕开全局配置:
1
2
mise install [email protected]
mise exec [email protected] -- shellcheck --version
验证通过后,再把精确版本写入 chezmoi 模板。
4. [env]、[vars] 与特殊指令表 _
mise 不仅能选择工具,也能为项目建立环境变量。这里最容易迷惑的语法是 _.file = ".env":_ 不是占位符、通配符或名为 _ 的环境变量,而是 mise 在 [env] 和 [vars] 下预留的【特殊指令表】。
4.1 普通键是环境变量
1
2
3
[env]
APP_ENV = "development"
LOG_LEVEL = "debug"
进入完整 mise 执行上下文后,子进程会获得:
1
2
APP_ENV=development
LOG_LEVEL=debug
也可以提供不覆盖外部值的默认值:
1
2
[env]
APP_ENV = { default = "development" }
4.2 _ 下的键是环境构造指令
1
2
3
4
[env]
_.file = ".env"
_.path = "./bin"
_.source = "./scripts/env.sh"
这些配置分别表示:
| 配置 | 作用 |
|---|---|
_.file | 从 dotenv、JSON、YAML 或 TOML 等文件读取变量 |
_.path | 将目录加入执行环境的 PATH |
_.source | 执行 Shell 脚本并读取它产生的环境变化 |
TOML 的点号表示嵌套,因此:
1
2
[env]
_.file = ".env"
等价于:
1
2
[env._]
file = ".env"
mise 选择 _,是因为普通环境变量本来是扁平的键值对,嵌套表不适合作为普通环境变量,正好可以承载“如何构造环境”这类元信息。
相对路径按照声明该配置的 config root 解析。项目 mise.toml 中的:
1
2
[env]
_.file = ".env"
通常指向项目根目录下的 .env。不要把它不加区分地放进全局 mise 配置,期待它自动加载每个项目的 .env;全局配置中的相对路径会以全局配置根为基准。
.env 如果包含凭据,应加入 .gitignore,仓库只提交无秘密的 .env.example。_.source 会执行脚本,能力和风险都高于读取静态 dotenv,只有确实需要 Shell 计算时才使用。
4.3 [vars] 只服务于配置渲染
[vars] 和 [env] 的关键差别是是否导出给子进程:
1
2
3
4
5
6
7
8
9
[vars]
node_version = "24"
build_mode = "release"
[tools]
node = ""
[tasks.build]
run = "pnpm build --mode "
node_version 和 build_mode 可以参与模板渲染,但不会自动成为进程环境变量。需要程序直接读取的值放入 [env];只用于消除 mise 配置重复的值放入 [vars]。
5. Shims 与 PATH activation 的根本差异
普通 activation 和垫片模式都能让 node 指向当前目录所需的版本,但它们不是在同一个时间点做这件事。反复比较各种表面能力后,最稳定的理解是:
两种模式真正不同的是介入点:shim 在具体工具调用时介入,普通 activation 在 Shell 的生命周期节点介入。
也可以称为“环境解析和注入的截止点不同”:普通模式在命令执行前先准备整个 Shell;shim 模式等到某个受管工具真正被调用,才为这次调用准备环境。
5.1 PATH activation:先改造当前 Shell
普通 Zsh 激活方式是:
1
eval "$(mise activate zsh)"
mise 会集成 Zsh 的 prompt 或 chpwd 等 hook。在提示符刷新或目录变化时,它重新解析当前配置,并向当前 Shell 注入:
- 真实工具目录组成的
PATH; [env]声明的环境变量;- 支持的 shell aliases;
enter、leave、cd等 hooks 所需状态。
1
2
3
4
5
6
7
cd project
↓ Zsh 触发 chpwd
mise 重新解析配置
↓
当前 Zsh 的 PATH / env / alias 被更新
↓
后续所有子进程继承新环境
此时执行 node,Shell 通常直接从真实安装目录找到二进制:
1
$MISE_DATA_DIR/installs/node/24.x.x/bin/node
mise 不需要在每一次 node 调用中继续充当代理。
5.2 Shims:在工具入口处拦截
垫片模式的配置是:
1
eval "$(mise activate zsh --shims)"
它的主要作用是把一个稳定目录加入 PATH:
1
$MISE_DATA_DIR/shims
其中的 node、python 等小型入口会拦截具体工具调用:
1
2
3
4
5
当前 Zsh
↓ 执行 node
$MISE_DATA_DIR/shims/node
↓ 读取当前目录配置并构造子进程环境
真实 node
所以即使在一行命令中连续切换目录,shim 也能在每次工具调用时重新按当前目录选择版本:
1
cd project-a && node -v && cd ../project-b && node -v
它不依赖下一次 prompt 是否已经显示,非交互式 Shell、IDE 和脚本也更容易获得稳定的工具入口。
5.3 能力差异来自父子进程边界
Shim 能力较少并不是简单的实现缺陷,而是操作系统进程模型决定的边界:子进程可以继承父进程环境,却不能在退出后反向修改已经运行的父进程。
假设项目配置:
1
2
[env]
APP_ENV = "development"
普通 activation 在当前 Zsh 中完成等价于 export 的操作,因此:
1
2
echo "$APP_ENV"
# development
Shim 模式下,当前 Zsh 没有加载这个变量。只有调用 node shim 时,mise 才能把变量交给即将创建的真实 Node 子进程:
1
2
3
Zsh(没有 APP_ENV)
└── node shim
└── APP_ENV=development node
因此可能出现:
1
2
3
4
5
echo "$APP_ENV"
# 空
node -p 'process.env.APP_ENV'
# development
同理,cd 是 Shell 内建命令,不会启动 node、python 等 shim。单独使用 shims 时,mise 没有机会获知“刚刚进入项目”或“已经离开项目”,所以 enter、leave、cd 和 watch_files 等依赖 Shell 生命周期的 hooks 无法正常触发。preinstall 和 postinstall 由 mise 安装命令自身触发,不依赖目录事件,仍然可以工作。
Alias 也是 Zsh 进程内部维护的状态,不是磁盘上的普通可执行文件。一个晚于 Zsh 启动的 shim 子进程无法回头修改父 Zsh 的 alias 表,这同样不是补几个 shim 文件就能解决的问题。
5.4 两种模式的能力对照
| 关注点 | PATH activation | Shims |
|---|---|---|
| 版本选择时机 | prompt 或目录 hook 更新环境时 | 每次受管工具调用时 |
| 真实工具路径 | 直接进入当前 Shell 的 PATH | PATH 中首先看到 shim |
[env] 对当前 Shell 可见 | 是 | 否,只在 shim/显式 mise 命令的子进程中加载 |
enter/leave/cd hooks | 支持 | 不支持 |
| 动态 shell aliases | 支持的 Shell 中可用 | 不能仅靠 shim 修改父 Shell |
which node | 通常显示真实工具路径 | 显示 shim 路径,应使用 mise which node |
| 非交互式环境 | 需要显式安排加载时机 | 固定 shim 目录更方便 |
一行内多次 cd 后执行工具 | 取决于 Shell 是否有目录 hook | 每次调用时解析,天然适配 |
性能也只是成本支付时机不同:普通 activation 在 prompt 或目录事件发生时运行 mise;shim 在每次受管工具调用时运行 mise。绝大多数场景中只是几毫秒级差异,选择时应优先看环境语义,而不是过早优化。
5.5 run/exec 是第三种显式边界
除了“Shell 级注入”和“工具级拦截”,mise 还提供显式命令边界:
1
2
mise exec -- some-command
mise run some-task
它们会在启动目标命令前一次性加载工具、[env] 和相关上下文:
1
2
3
4
5
当前 Shell
↓ 显式调用 mise exec/run
mise 构造完整执行环境
↓
目标命令或任务及其子进程
这条路径不要求把环境永久注入当前 Shell,也不要求目标命令本身存在 shim。单独使用 shims 时,如果某条命令必须可靠获得项目环境,mise run 和 mise exec 是最明确的入口:
1
2
mise exec -- bash -c 'echo "$APP_ENV"'
mise run dev
如果希望连续操作,也可以启动一个加载了 mise 环境的新 Shell:
1
mise en
这个新 Shell 仍然是当前 Shell 的子进程。退出它以后,外层 Shell 会恢复原状,仍然符合“子进程不能反向修改父进程”的边界。
三种模式可以用“介入点”统一理解:
1
2
3
PATH activation:Shell 生命周期级
shims: 受管工具调用级
run / exec: 显式命令或任务级
5.6 双层激活与配置状态核对
需要同时覆盖非交互式工具入口和完整交互体验时,可以设计【双层激活】:.zshenv 或 profile 层先加载 shims,交互式 .zshrc 再加载普通 activation。
.zshenv 中的非交互基础层是:
1
eval "$(mise activate zsh --shims)"
.zshrc 中的 Linux 交互层是:
1
eval "$(mise activate zsh)"
两层分别解决不同问题:
1
2
3
4
5
6
7
.zshenv --shims
→ 所有 Zsh 都有稳定的工具入口
→ 覆盖非交互式 Shell
.zshrc 普通 activation
→ Linux 交互式 Shell 获得完整 PATH、env 与目录 hooks
→ 真实工具路径优先于 shim
某些兼容性受限或只需要稳定工具入口的环境可以保持 shim-only;需要自动环境变量和目录 hooks 的交互式 Zsh 再通过第二层 activation 补齐能力。这不是重复初始化同一件事,而是分别覆盖不同的 Shell 生命周期。
chezmoi 体系还要继续区分四种状态:
| 状态 | 要检查的对象 |
|---|---|
| 仓库声明态 | dotfiles Git 仓库希望分发什么 |
| chezmoi source state | 当前 chezmoi 实际拿什么生成目标文件 |
| 目标文件状态 | .zshenv、.zshrc 等文件已经落地了什么 |
| 运行态 | 当前 Shell 启动时真正读取了什么 |
仓库已经修改,不代表 chezmoi source state 已经同步;目标文件已经更新,也不会反向改变此前启动的 Shell。判断功能是否生效时,必须沿这四层逐步核对,不能把其中任何一层当成完整现状。
在 shim-only 环境中,稳妥做法是:
1
2
3
4
5
6
# 工具命令可以通过 shim 自动选择版本
node --version
# 依赖完整项目环境的操作使用显式边界
mise run dev
mise exec -- some-command
启用普通 activation 的交互式 Shell 可以直接获得项目 [env]、hooks 和动态 alias;mise run/exec 仍是脚本、CI 和跨环境工作流中更明确的执行入口。
6. Tasks 将工具、环境与命令收敛成项目契约
[tasks] 不是另一个独立世界。它恰好位于前面几层的汇合点:任务在 mise 创建的执行环境中运行,会自动使用项目声明的工具版本和环境变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[tools]
node = "22"
pnpm = "10"
[env]
APP_ENV = "development"
_.file = ".env"
[tasks.dev]
description = "启动开发服务器"
run = "pnpm dev"
[tasks.lint]
description = "检查代码"
run = "pnpm lint"
[tasks.test]
description = "运行测试"
run = "pnpm test"
[tasks.check]
description = "执行提交前检查"
depends = ["lint", "test"]
run = "echo '检查完成'"
常用入口:
1
2
3
4
5
6
7
8
9
# 查看任务
mise tasks
# 运行任务
mise run dev
mise run check
# 简写
mise r check
这里的价值不只是少打几个字符,而是将“需要哪个工具版本、需要哪些变量、执行什么命令”收敛成一个项目级契约。开发者和 CI 都可以使用同一入口,避免 README、package scripts、Makefile 和 CI YAML 各自维护一套略有差异的命令。
6.1 依赖关系与并行执行
1
2
3
4
5
6
7
8
9
[tasks.lint]
run = "pnpm lint"
[tasks.test]
run = "pnpm test"
[tasks.check]
depends = ["lint", "test"]
run = "echo '检查完成'"
mise run check 会先调度 lint 和 test。没有依赖关系阻止时,mise 可以并行执行它们;依赖成功后再运行 check。
6.2 Sources、outputs 与 watch
任务可以声明输入和输出:
1
2
3
4
[tasks.build]
run = "pnpm build"
sources = ["src/**/*", "package.json", "pnpm-lock.yaml"]
outputs = ["dist/**/*"]
当输入没有变化且输出仍有效时,mise 可以跳过重复构建。需要持续监听时:
1
mise watch build
如果 Vite、Next.js、nodemon 等工具已经提供成熟的 watch 模式,应优先复用原生能力,不必为了统一入口再叠加一层重复监听。
6.3 Task 专用工具与文件任务
不需要成为整个项目默认工具的依赖,可以只绑定到任务:
1
2
3
[tasks.build]
tools.rust = "1.96.0"
run = "cargo build"
也可以提前为所有任务准备工具:
1
mise install --include-task-tools
复杂脚本不适合塞进 TOML 字符串时,可以创建 .mise/tasks/build:
1
2
3
4
#!/usr/bin/env bash
set -euo pipefail
pnpm build
随后仍通过统一入口执行:
1
mise run build
对 shim-only 环境而言,Tasks 还有一层额外价值:mise run 本身就是显式环境边界,所以任务能够稳定获得 [tools] 和 [env],不依赖变量是否已经写入父 Zsh。即使交互式 Shell 已经应用完整 activation,Tasks 仍能让开发者、脚本和 CI 使用同一条可复现入口。
7. 其他能力及当前取舍
mise 还提供一些建立在同一配置模型之上的能力。理解它们的位置即可,不需要为了“用全”而全部引入。
7.1 配置环境
项目可以按开发、测试和 CI 拆分:
1
2
3
4
5
mise.toml
mise.development.toml
mise.test.toml
mise.ci.toml
mise.local.toml
例如:
1
MISE_ENV=ci mise run test
mise.local.toml 适合不提交的本机覆盖。只有环境差异真实存在时再拆分,不应一开始就创建一组空文件。
7.2 Hooks 与 shell aliases
Hooks 可以在 enter、leave、cd、preinstall、postinstall 等事件执行动作;shell aliases 可以随项目进入和离开动态创建、删除。两者都更依赖 Shell 级 activation,也都包含隐式行为。
在 shim-only 环境中,关键初始化和构建流程优先写成显式 task:
1
mise run setup
它比“进入目录后自动执行一段脚本”更容易观察、复现和排查。
7.3 mise.lock
mise.lock 可以固定解析后的精确版本,并在 backend 支持时记录下载 URL、校验和等信息。它适合团队和 CI 追求可复现安装的场景。
如果 chezmoi 模板已经固定精确版本,而当前阶段尚不需要下载校验和或跨平台 CI,也可以暂不引入 lockfile。后续需要校验下载产物或减少远程 API 解析时,再统一设计共享 lockfile;不要让每台机器各自生成一份相互漂移的锁文件。
7.4 Bootstrap
新版 mise 的 bootstrap 可以继续管理系统包、用户、服务、仓库、dotfiles、Shell activation 和远程机器。这已经接近完整的机器配置系统,并可能执行高风险或破坏性操作。
当前职责已经明确:
1
2
3
4
chezmoi → dotfiles
apt → Linux 系统组件、共享库和服务
winget → Windows GUI 与原生应用
mise → 用户级开发工具、项目环境和任务
此时再引入 mise bootstrap 会与 chezmoi、apt 和 winget 形成重叠。除非未来决定重新设计整机初始化职责,否则保持现有边界更简单。
8. 面向不同场景的工作流
8.1 新增全局工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
确认工具应归 mise 管理
↓
查询并选择精确版本
↓
修改 chezmoi 的 mise 模板
↓
分别检查 git-bash / linux 渲染
↓
提交 source state
↓
chezmoi diff → apply
↓
mise install
↓
mise tool / version 命令验证
不要只在单机执行 mise use -g 或 mise upgrade,否则声明源仍停留在旧版本。
8.2 初始化新机器
1
2
3
4
5
6
7
# 获取并检查 dotfiles
chezmoi init <dotfiles-repository>
chezmoi diff
chezmoi apply -v
# 按已落地的全局配置安装工具
mise install
chezmoi 决定“应该有什么配置”,mise 决定“怎样获得这些工具”。两者分工明确,初始化顺序也不能颠倒:mise 需要先看到 chezmoi 生成的全局配置,才能按声明安装完整工具集。
8.3 建立项目环境
项目自己的工具、变量和任务进入项目仓库:
1
2
3
4
5
6
7
8
9
10
11
12
13
[tools]
node = "22"
pnpm = "10"
[env]
APP_ENV = { default = "development" }
_.file = ".env"
[tasks.dev]
run = "pnpm dev"
[tasks.check]
run = "pnpm lint && pnpm test"
新成员或 CI 执行:
1
2
mise install
mise run check
本机差异进入 mise.local.toml 或未提交的 .env,不污染公共配置。
8.4 临时验证某个版本
不想修改任何配置时:
1
mise exec node@22 -- node --version
想提前下载,稍后再决定是否采用:
1
2
mise install node@22
mise exec node@22 -- node --version
验证通过后,再通过项目 mise use 或 chezmoi 模板完成长期声明。
8.5 排查“为什么版本不对”
按声明、安装、选择、注入、执行五层依次排查:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 当前读取了哪些配置
mise config ls
# 2. 请求版本、当前版本、配置来源和安装状态
mise tool node
# 3. 真实可执行文件在哪里
mise which node
# 4. Shell 实际首先找到谁
type -a node
# 5. 当前完整 mise 环境
mise env --json
# 6. 综合诊断
mise doctor
Shim 模式下,which node 显示 shim 是正常现象;要找真实路径使用 mise which node。如果配置版本未安装,还要区分自动安装、系统同名命令 fallback 等设置,不能看到 node 能运行就断言 mise 版本已经正确生效。
9. 易混点复习索引
这部分只压缩前文已经建立的理解,用于以后快速定位。
9.1 install 与 use
mise install可以指定版本;mise install node@20完全合法。install负责安装,不把临时指定版本写入配置。use写入项目或全局配置,并在必要时安装。- “安装完成”不等于“当前配置选择了它”。
- “配置选择了它”也不等于“当前 Shell 已经获得完整环境”。
9.2 全局与项目配置
- 全局配置提供默认工具集,项目配置可以覆盖同名版本。
- 切换项目不是反复安装和卸载,而是重新解析配置并改变执行入口。
- chezmoi 管理全局配置时,模板是事实源,mise 的全局配置文件是生成结果。
- 长期变更应修改 chezmoi 模板,再
apply → mise install。
9.3 [env] 中的 _
- 普通键表示要导出的环境变量。
_是特殊指令表,不是环境变量、占位符或通配符。_.file、_.path、_.source表示如何构造环境。_.file = ".env"等价于[env._] file = ".env"。- 相对路径跟随声明它的 config root,不会自动指向所有项目的
.env。
9.4 Shims 与普通 activation
- 普通 activation 在 prompt、
cd等 Shell 生命周期节点介入。 - Shim 在
node、python等具体工具调用时介入。 - 普通模式先修改父 Shell,后续所有子进程继承环境。
- Shim 只能修改即将启动的工具子进程,不能反向修改父 Shell。
- 所以 shims 能切版本,却不能单独提供完整的当前 Shell env、目录 hooks 和动态 aliases。
- 这是进程模型带来的架构取舍,不是简单缺陷。
- “profile 层 shims + 交互式
.zshrc普通 activation”可以组成双层激活,兼顾稳定入口与完整 Shell 能力。 - 仓库已经声明不代表目标文件已经应用;目标文件已更新也不代表当前 Shell 已重新加载。
9.5 run/exec
mise exec为一条显式命令加载工具和环境。mise run为任务及其子进程加载工具和环境。- 单独使用 shims 时,它们是获得完整项目上下文最可靠、最明确的入口。
10. 总结
mise 的各项能力并不是散落的命令集合。它们围绕同一条环境控制链展开:
1
2
3
4
5
6
7
8
9
配置声明期望状态
↓
解析器结合目录层级选择版本、变量和任务
↓
backend 将缺失工具安装到本机
↓
PATH activation、shim 或 run/exec 选择介入点
↓
真实命令在对应环境中执行
理解这条链路后,几个最容易混淆的问题可以统一回答:install 与 use 的差别在于是否修改配置;全局与项目版本的差别在于配置作用域;_ 表示环境构造指令;shims 与普通 activation 的差别则在于介入时机和作用范围。
chezmoi + mise 的组合可以据此保持简单:chezmoi 维护全局工具声明,mise 安装并解析工具;项目用自己的 mise.toml 管理工具、环境和 Tasks;shims 提供稳定工具入口,需要完整交互能力时再用普通 activation 补齐当前 Shell 的环境与生命周期能力。Shim-only 环境通过 mise run/exec 获得完整项目上下文。判断“是否生效”时,要分别核对仓库声明、chezmoi source state、目标文件和正在运行的 Shell。