pnpm, uv, cargo... 言語ごとの依存更新コマンドを統一するCLI「depup」を作った

はじめに
複数の言語を扱うプロジェクトで依存関係の更新作業に時間を取られていませんか?
Node.jsならpnpm update --latest、Pythonならuv lock --upgrade、Rustならcargo update...と言語ごとにコマンドを使い分け、さらにマニフェストファイルを手動で編集する作業は地味に面倒です。
そこで、7言語に対応した依存関係アップデーター「depup」 を作りました。この記事では、depupの特徴と使い方、開発時に工夫した点を紹介します。
対象読者
- 複数言語のプロジェクトを管理している方
- 依存関係の更新作業を効率化したい方
- Tauriなどのマルチ言語フレームワークを使用している方
depupとは
depupは、以下の7言語の依存関係を統一的に更新できるCLIツールです。
| 言語 | マニフェスト | レジストリ |
|---|---|---|
| Node.js | package.json | npm |
| Python | pyproject.toml | PyPI |
| Rust | Cargo.toml | crates.io |
| Go | go.mod | Go Proxy |
| Ruby | Gemfile | RubyGems |
| PHP | composer.json | Packagist |
| Java | build.gradle(.kts) | Maven Central |
出力例
| Python (pyproject.toml) | Tauri (package.json + Cargo.toml) |
|---|---|
![]() |
![]() |
主な特徴
1. マニフェストを直接更新
cargo updateやbundle updateなどはロックファイルの更新が中心ですが、depupはマニフェスト(Cargo.tomlやGemfileなど依存定義ファイル)自体を更新します。
# ドライランで確認
depup -n
# 実際に更新
depup
2. バージョン範囲形式を維持
depupは最新版に更新する際、元の範囲形式を維持します。
例えば以下のように、^1.2.3を^2.0.0に、~1.2.3を~1.3.0に更新する際、元の範囲形式(キャレット、チルダなど)を維持します。
"^1.2.3" → "^2.0.0" # キャレット維持
"~1.2.3" → "~1.3.0" # チルダ維持
">=1.0.0" → ">=2.0.0" # 範囲維持
3. 固定バージョンの自動検出
意図的に固定されたバージョン("1.2.3"や"==1.2.3")はデフォルトでスキップします。
{
"dependencies": {
"lodash": "^4.17.20", // ✅ 更新対象
"legacy-lib": "1.0.0" // ❌ 固定のためスキップ
}
}
4. Ageフィルター
リリースから一定期間経過したバージョンのみに更新することで、不安定な最新版を避けられます。
# 2週間以上経過したバージョンのみに更新
depup --age 2w
5. モノレポ対応
pnpmワークスペースやTauriプロジェクトを自動検出し、関連するすべてのマニフェストを処理します。
使い方
基本的な使い方
# カレントディレクトリの依存関係を更新
depup
# ドライラン(変更せずに確認)
depup -n
# 特定の言語のみ更新
depup --node --python
# 特定のパッケージのみ更新
depup --only lodash --only typescript
# 特定のパッケージを除外
depup --exclude react
出力形式
デフォルトのテキスト出力に加え、JSON形式やdiff形式での出力にも対応しています。
# JSON出力(CI/CD連携向け)
depup --json
# diff形式
depup --diff
diff出力の例
--- package.json
+++ package.json
@@ dependencies @@
- "lodash": "^4.17.20"
+ "lodash": "^4.17.21"
パッケージマネージャとの連携
更新後に自動でインストールコマンドを実行することもできます。
# 更新後にnpm/pnpm/yarn installを実行
depup --node --install
開発時の工夫
Rustで実装した理由
depupはRustで実装しました。理由は以下の通りです。
- クロスプラットフォーム: macOS、Linux、Windowsで動作するバイナリを簡単に生成できる
- パフォーマンス: 複数のレジストリAPIへの並行リクエストが高速
- シングルバイナリ: ランタイム不要で配布が容易
各レジストリAPIの違い
7つの言語に対応するにあたり、各レジストリのAPIの違いに対処する必要がありました。
特に以下の点で工夫が必要でした。
- バージョン形式の違い: semverに準拠していない言語(Python: PEP 440)への対応
- リリース日の取得: APIによって提供される情報が異なる
- レート制限: 各レジストリの制限に配慮した並行リクエスト数の調整
Gradleの変数定義への対応
Java/Gradle対応で特に工夫したのが、変数を使ったバージョン定義への対応です。
// 変数でバージョンを定義
def kotlinVersion = '1.9.0'
def springVersion = '3.2.0'
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.springframework.boot:spring-boot-starter:$springVersion"
}
このパターンでは、dependenciesブロック内のバージョンを直接書き換えるのではなく、変数定義部分を更新する必要があります。正規表現でパターンマッチングを行い、適切な箇所を更新するようにしました。
クロスコンパイルの課題
GitHub Actionsでのクロスコンパイル時、OpenSSLの依存関係で問題が発生しました。
解決策として、HTTPクライアント(reqwest)のTLS実装をOpenSSLベースのnative-tlsから、純粋なRust実装のrustls-tlsに切り替えました。
# 変更前
reqwest = { version = "0.12", features = ["json"] }
# 変更後
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
これにより、システムのOpenSSLに依存せずにクロスコンパイルが可能になりました。
インストール
# Homebrew (macOS/Linux)
brew install owayo/depup/depup
# または Releases からバイナリをダウンロード
まとめ
depupは、複数言語の依存関係更新を一つのコマンドで完結させるツールです。
- 7言語(Node.js、Python、Rust、Go、Ruby、PHP、Java)に対応
- マニフェストファイルを直接更新
- バージョン範囲形式を維持
- 固定バージョンの自動検出
- Ageフィルターでリリース後の安定期間を考慮
- モノレポ(pnpmワークスペース、Tauri)対応
特にTauriのようなNode.js + Rustの組み合わせや、マイクロサービスで複数言語を扱うプロジェクトで威力を発揮します。
ぜひ試してみてください。フィードバックやコントリビューションも歓迎です。


Discussion
おもしろいツールだと思います ✨
ちなみに
こちらですが,年末にリリースされた
reqwestの最新バージョンではrustlsがデフォルトとなったので,単にとするだけで大丈夫です……!
実は
depupするだけで良かったという悲しいニアミスですね ( ᐡᴗ ̫ ᴗᐡ)コメントありがとうございます!
なるほど・・・ほんとに年末に出てましたねw
容量節約になりそうなのでreqwestに戻してみます。