12k
All articles

mise 入门:多语言版本管理器

了解 mise 如何按项目管理 Node 和 Python 版本,处理现有版本文件、锁定工具、设置环境变量,并在本地和 CI 中运行一致的构建。

OpenReplay Team
OpenReplay Team
mise 入门:多语言版本管理器

mise 是一个命令行工具,可以按目录安装和切换 Node、Python、Ruby 等运行时的版本,版本信息从项目根目录下的 mise.toml 文件中读取。

如果你的仓库同时包含一个 Vite 前端和一个在构建前生成内容的 Python 脚本,那你很可能在同时使用 nvm 和 pyenv,并寄希望于每位队友的 shell 环境都与 CI 保持一致。而通常总有一方对不上。

如需对比 mise、nvm、pyenv、rustup 和 SDKMAN!,请参阅 5 Version Managers Every Developer Should Know。本文不再罗列功能,而是以一个仓库为例,依次讲解 cd 切换目录、现有的 .nvmrc 文件、队友首次克隆、CI、跨两种运行时的构建任务以及环境变量。

核心要点

  • 启用 shell 激活后,进入包含 mise.toml 的目录时,该项目固定的运行时版本会被加入 PATH;离开目录后则恢复之前的版本。
  • mise 默认忽略 .nvmrc 和 .python-version,除非通过 idiomatic_version_file_enable_tools 设置按工具逐一开启。
  • mise run 会自行加载项目的工具和环境变量,并默认安装缺失的工具,因此无需 shell 激活即可在 CI 中使用。
  • jdx/mise-action@v4 会安装 mise 以及 mise.toml 中声明的工具。如果仓库中提交了 mise.lock,则会使用 --locked 安装。
  • [env] 表是会被提交的明文内容,用于存放配置,而非密钥。

示例项目:带 Python 构建步骤的 Vite 前端

示例仓库 docs-preview 包含两种运行时、一个配置文件和一个工作流:

docs-preview/
├── content/*.md
├── scripts/build_content.py
├── src/
├── package.json          # vite in devDependencies
├── mise.toml
└── .github/workflows/build.yml

该 Python 脚本只使用标准库,负责将 Markdown 文件转换为供前端导入的 JSON:

# scripts/build_content.py
import json, os, pathlib

src = pathlib.Path(os.environ.get("CONTENT_DIR", "content"))
out = pathlib.Path("src/generated/content.json")
out.parent.mkdir(parents=True, exist_ok=True)

items = [{"slug": p.stem, "body": p.read_text(encoding="utf-8")}
         for p in sorted(src.glob("*.md"))]
out.write_text(json.dumps(items, indent=2), encoding="utf-8")
print(f"wrote {len(items)} items to {out}")

以下是完整的 mise.toml,后续各节都会引用它:

[tools]
node = "24"
python = "3.14"

[settings]
lockfile = true

[env]
CONTENT_DIR = "content"
VITE_SITE_NAME = "Docs Preview"

[tasks.install]
description = "Install JS dependencies"
run = "npm ci"

[tasks."build:content"]
description = "Convert content files to JSON for the frontend"
run = "python scripts/build_content.py"

[tasks.build]
description = "Generate content, then build the Vite app"
depends = ["install", "build:content"]
run = "npx vite build"

[tools] 表使用的是版本前缀(Node 24 LTS、Python 3.14)。下文将介绍的锁文件会记录它们实际解析到的精确补丁版本。

进入和离开目录时发生了什么

启用 mise 激活后,进入包含 mise.toml 的目录时,该项目固定的运行时会被加入 PATH;离开目录时,则恢复之前生效的版本。只要将 mise activate 写入 shell 启动文件,每次切换目录时 mise 都会自动切换工具和环境变量。

cd ~
node --version        # whatever your system or nvm provides
cd ~/code/docs-preview
node --version        # v24.x from mise.toml
python3 --version     # Python 3.14.x
echo "$CONTENT_DIR"   # content

mise 切换版本的时机取决于所用的 shell。根据 shims 文档,bash、zsh、fish 和 xonsh 在执行 cd 后立即切换;其他 shell 则要等到下一次出现提示符时才切换。

固定的版本尚未安装时

在一台全新的机器上,在项目目录中运行 mise install 即可,这是最明确、最可预期的解决方式。任务也能自动处理这种情况:mise tasks 会使用项目的工具和环境变量运行,而默认开启的 auto_install 设置会在调用 mise run 时自动安装缺失的已配置工具。如果依赖 shims,Python 需要特别留意。设置参考文档指出,当 shim 无法解析到 mise 管理的版本时(例如关闭了自动安装),它会回退到 PATH 中其他位置的第一个同名程序。对于 python3 这种操作系统自带的工具,这意味着你固定的版本根本不会运行,而且不会有任何报错。

若只想在某一个终端中固定不同的版本,可以使用 mise shell node@<version>。前提是该 shell 已激活 mise。

现有的 .nvmrc 和 .python-version 文件

mise 不会读取 .nvmrc 或 .python-version,除非通过 idiomatic_version_file_enable_tools 为每个工具单独开启。mise FAQ 确认这类惯用版本文件(idiomatic version files)默认处于关闭状态,这一默认行为自 mise 2025.10.0 起生效。开启方式如下:

[settings]
idiomatic_version_file_enable_tools = ["node", "python"]

配置文档将 package.json 列为 mise 为 node 读取的文件之一,因此开启 node 后也可能会读取该文件。如需跳过某个文件,可在 idiomatic_version_file_disable_files 设置中添加 tool:filename 形式的条目,例如 node:package.json。

当 mise.toml 已固定两种运行时后,.nvmrc 只需为仍在使用 nvm 的队友保留,并确保两个文件中的版本一致。如需回顾 nvm 如何处理该文件,请参阅在 macOS、Linux 和 Windows 上使用 nvm 切换 Node 版本的指南。

团队协作与 CI

笔记本电脑和 CI runner 读取的是同一个 mise.toml,但将工具加入 PATH 的方式不同:

笔记本电脑GitHub Actions
工具加入 PATH 的方式shell 中的激活钩子jdx/mise-action 安装 mise 及固定版本的工具
版本来源mise.toml + mise.lock相同文件
构建命令mise run buildmise run build

队友首次克隆仓库:

  1. 安装 mise:curl https://mise.run | sh
  2. 将 eval "$(~/.local/bin/mise activate zsh)"(或对应你所用 shell 的命令)添加到 shell 的 rc 文件中。
  3. 审阅 mise.toml,然后运行 mise install。
  4. 运行 mise run build。

由于配置文件可以执行代码,mise 的信任模型需要格外注意。根据入门指南,在非 CI 且未启用 paranoid 模式的情况下,任何作用于项目的命令(如 mise install、mise exec 或 mise run)都会自动信任当前生效的配置。因此在运行这些命令之前,请先审阅 mise.toml;如需显式信任某个配置,请使用 mise trust。

锁定精确版本。 锁文件 mise.lock 可通过 mise lock 或上文所示的 lockfile = true 设置生成。请将其提交到仓库。使用 mise install --locked 时,锁文件中缺失的条目会直接报错,从而确保 mise 不会悄悄选用新版本。

CI 工作流:

# .github/workflows/build.yml
name: build
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: jdx/mise-action@v4
      - run: mise run build

mise-action 步骤会安装 mise 以及 mise.toml 中的工具,因此工作流使用的版本与开发者本地完全一致。默认情况下,它会运行 mise install(install: true)并缓存工具(cache: true)。当存在 mise.lock 时,它会使用 --locked 安装。它还会自动传入 ${{ github.token }}。在笔记本电脑上,如果安装时遇到速率限制,可以设置 GITHUB_TOKEN 或 MISE_GITHUB_TOKEN,详见 GitHub tokens 页面。

出现问题时,可以用以下三个命令缩小排查范围:

症状命令
激活或安装状态异常mise doctor
似乎应用了错误的配置文件mise config ls
选中了错误的版本mise ls --current

实战任务:跨两种运行时的 mise run build

在 mise.toml 中定义的任务运行时,项目固定的工具和 [env] 变量都已生效。这意味着即使没有 shell 激活,mise run build 在笔记本电脑和 CI 中使用的也是相同的 Node 和 Python。

depends 键会让 build 等待其列出的所有任务完成后再执行,从而确保 Python 步骤在 Vite 读取之前已写好 src/generated/content.json。被列出的任务之间可以并发运行。任务文档说明,前置任务可能并行执行,它们在 depends 中的顺序并不代表执行顺序。在本例中这是安全的,因为 npm ci 写入的是 node_modules,而 Python 脚本写入的是 src/generated,两个步骤互不干扰对方的输出。如果某个步骤需要使用另一个步骤的输出,请让它直接依赖该步骤。

mise tasks          # lists install, build:content, build
mise run build      # npm ci + Python step, then vite build

根据运行任务指南,默认的并行任务上限为 8 个。

使用 mise 管理项目级环境变量

[env] 表中的值以明文形式存在于已提交的文件中。mise 的环境变量功能属于配置管理,而非密钥管理。在本例中,Python 脚本通过 os.environ 读取 CONTENT_DIR,前端则读取 import.meta.env.VITE_SITE_NAME。

VITE_ 前缀让 [env] 明文值的风险变得具体可见。Vite 环境变量文档指出,所有以 VITE_ 开头的变量都会被打包进客户端 bundle,其值会在构建时直接写入代码。因此,网站一旦上线,mise.toml 中的任何 VITE_ 值都是公开的。

缺失的构建时变量并不会导致构建失败,而是会生成一个请求错误 URL 的 bundle。会话回放(Session Replay)可以从用户视角呈现这一问题:你会看到请求被发往一个 undefined 的 base URL,进而追溯到构建时所处的环境。

什么时候值得使用 mise?

如果项目只需要 Node,nvm 加上 .nvmrc 已经足够,mise 并非必需,nvm 相关指南已对这种配置做了充分介绍。当一个仓库需要固定两种或以上的运行时,并且 CI 必须与每台开发机保持一致时,就值得引入 mise。若想在现有仓库中尝试,只需添加一个包含 [tools] 版本固定的 mise.toml,运行 mise lock,提交这两个文件,然后将 CI 的构建步骤改为 mise run build 即可。

常见问题

既然 mise.toml 会被提交,密钥应该放在哪里?

可以将密钥放在已加入 gitignore 的 mise.local.toml 中,或者在 mise.toml 中以 required = true 声明它们,这样 mise 会检查该变量是否已设置,而无需存储其值。添加 redact = true 可以在捕获的任务输出中遮蔽这些值,但 mise env 仍会以明文打印它们。对于必须提交到仓库的密钥,mise 可以为 mise exec、任务和 shell 激活解密使用 age 加密的 SOPS 文件(实验性功能)。

mise 能读取 asdf 的 .tool-versions 文件吗?

可以。与 .nvmrc 不同,mise 无需任何额外设置即可读取 .tool-versions。在 Unix 系统上,它还可以运行 asdf 的 shell 插件,但并不保证所有 asdf 命令或插件都能正常工作。如果同一目录中的 mise.toml 和 .tool-versions 声明了同一个工具,则以 mise.toml 为准。mise 文档建议不要在 asdf 和 mise 之间共享安装目录,因此切换后请运行 mise install。

为什么编辑器或脚本使用的 Node 版本与终端不同?

编辑器、git hooks 和调度程序通常不会加载交互式 shell 的 rc 文件,因此 mise 的激活钩子根本不会运行。解决方法是将 mise activate zsh --shims(或对应你所用 shell 的命令)添加到编辑器会读取的登录配置文件中,例如 ~/.zprofile,同时在 ~/.zshrc 中保留普通的 mise activate。对于脚本,请改用 mise exec -- command 或 mise run。shim 模式会跳过部分激活功能,例如大多数钩子。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.