Feedforce Developer Blog

フィードフォース開発者ブログ

モノレポの開発ツールチェーン管理を mise に移行した話

こんにちは、Omni Hub チームの id:voidCaffeLatte です。

Omni Hub は Rust、TypeScript、Terraform などの複数の言語やツールを一つのリポジトリで扱うモノレポです。
2026 年 6 月から開発ツールチェーンの管理を mise へ段階的に移行しました。
本記事ではその構成を紹介します。

次のような方に向けた記事です。

  • モノレポで開発ツールのバージョン管理に悩んでいる方
  • mise の導入を検討している方

背景と課題

Omni Hub の開発で使う多くのツールのバージョン指定が、CI ワークフロー、ローカルの direnv、各種設定ファイルなどに散らばっていました。

この構成では CI とローカルでバージョンがズレても気付けず、更新時は複数箇所の書き換えが必要でした。

また新メンバー向けのセットアップ手順も、ツールごとの導入説明の列挙になっており、ツールが増えるたびに長くなるという問題を抱えていました。

mise とは

mise (読み: meez、ミーズ) は、開発ツールのバージョンを宣言的に管理するツールチェーンマネージャです。
Rust や Node.js といった言語に加えて、Terraform や AWS CLI などの CLI ツールも、バックエンド (aqua、npm、cargo など) 経由で一つの設定ファイルから管理できます。
バージョン管理のほかに、ディレクトリごとの環境変数の読み込みやタスクランナーの機能も備えています。

今回 mise を選んだ理由は次の 3 点です。

  • 言語と CLI ツールを同じ仕組みで管理でき、マルチスタックなモノレポの構成と合う
  • lockfile に対応しており、環境間の再現性を担保できる
  • 環境変数の読み込みにも対応しており、direnv の役割を統合できる

mise.toml によるバージョンの一元管理

開発に使うツールとバージョンは、リポジトリ直下の mise.toml に宣言します。

[tools]
aws = "2.36.18"
...
"npm:@ast-grep/cli" = "0.45"
...
"cargo:diesel_cli" = { version = "2.3", features = "postgres", default-features = false }

バイナリ配布のツールも npm パッケージも cargo でビルドするツールも、一つのファイルに並びます。
diesel_cli のように feature フラグの指定が必要でインストールコマンドが複雑なツールも、宣言に置き換えられます。

Rust と Node.js のバージョンは、mise.toml には書いていません。
rustup などのツールが直接参照する rust-toolchain.toml と .node-version が既にあるため、mise がこれらのファイルを情報源として読む設定を有効にしています。

[settings]
idiomatic_version_file_enable_tools = ["node", "rust", ...]

なお、バージョンは末尾から省略でき、"1.48" のような緩い指定にも対応しています。
省略した部分は解決時に最新のものが選ばれるため ("1.48" なら 1.48 系の最新パッチ)、パッチ更新のたびに mise.toml を書き換えない運用も選べます。

mise.lock によるバージョン固定

前述の緩い指定のようにバージョンを確定させない書き方では、インストールした時期によって実際に入るバージョンが変わってしまいます。
そこで lockfile を有効にし、解決済みバージョンと、バックエンドが対応する場合は取得 URL やチェックサムを mise.lock に固定しました。

[settings]
lockfile = true
lockfile_platforms = ["macos-arm64", "linux-x64", "linux-arm64"]

lockfile_platforms を開発機と CI のプラットフォームに絞っているのは、使わないエントリが lockfile の差分ノイズになるためです。
また CI には mise lock を実行して mise.lock に差分が出たら失敗するジョブを置き、mise.toml との整合を保証しています。

minimum_release_age による公開直後バージョンの回避

あわせて、公開から 3 日以内のバージョンをインストールしない設定も入れました。

[settings]
minimum_release_age = "3d"

npm などで繰り返されているサプライチェーン攻撃では、悪意あるバージョンが公開されてから発覚し取り下げられるまでに数時間から数日かかることが多いです。
根本的な対策ではないものの、公開直後のバージョンを避けることで被害に遭う確率を下げられるため、自衛として入れています。

mise.toml による環境変数管理 (direnv からの移行)

環境変数の読み込みには direnv を使っていましたが、mise の env 機能で .env 系ファイルを読み込めるため、この役割も mise に統合しました。

[env]
_.file = [".env.general", ...]

ここで指定したファイルの内容は、プロジェクトのディレクトリに入るとシェルの環境変数として展開され、そこを離れると解除されます。
使用感は direnv と変わりません。

CI の mise ベースへの刷新

CI ワークフロー内でのバージョン番号のハードコードを廃止し、jdx/mise-action で mise.toml からバージョンを解決するようにしました。
CI とローカルが同じファイルから解決されるため、片方だけを書き換えてズレることがなくなります。

- name: Install tools (mise)
  uses: jdx/mise-action@v4
  with:
    install_args: task cargo:diesel_cli cargo:lcov-summary cargo:cargo-llvm-cov

install_args には、そのジョブが使うツールだけを指定します。
無引数の mise install だと、ジョブに関係ないツールの取得にも時間を使ってしまいます。

また mise-action はインストールしたツールのキャッシュ機能も備えており、2 回目以降の実行ではキャッシュから復元されるため、ジョブのたびにインストールし直す無駄も防げます。

一つつまずいた点として、mise はツールを並列インストールするため、cargo バックエンドのツールが複数あると cargo install が同時に走り、rustup の toolchain 取得が競合することがありました。
mise-action の前に rustup を単発で実行して toolchain を先にプロビジョニングすることで回避しています。

- run: rustup toolchain install  # toolchain を先に確定させる
- uses: jdx/mise-action@v4
  with:
    install_args: cargo:diesel_cli cargo:lcov-summary ...

余談: 個人で使うツールの管理

ここまではリポジトリで共有する設定の話でしたが、mise は個人のツール管理にも使えます。

~/.config/mise/config.toml に書いたツールはディレクトリを問わず有効になるため、リポジトリに関係ない普段使いの CLI ツールもまとめて管理できます。

リポジトリ直下に mise.local.toml を置くと、そのリポジトリでだけ使う個人用のツールや環境変数を追加できます (Omni Hub では .gitignore に追加済みです)。
チーム共有の mise.toml に手を入れずに、各自の好みのツールを同じ仕組みで揃えられます。

おわりに

この移行で、ツールのバージョン指定は mise.toml (と言語用の rust-toolchain.toml、.node-version) に集約され、CI とローカルは常に同じ情報源を参照するようになりました。
また新メンバーのセットアップについても、ツールごとの手順の列挙が「mise を入れて mise install」だけになり、誰がいつ実行しても lockfile どおりの同じバージョン構成が再現されるようになりました。

移行は mise.toml の追加、direnv の統合、CI への展開、lockfile の導入と、1 ヶ月ほどかけて段階的に進めました。
各段階が独立して価値を持つため、途中で止まっても困らない順序になっています。

mise の導入を検討している方の参考になれば幸いです。

▲