🔼

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

に公開
2

はじめに

複数の言語を扱うプロジェクトで依存関係の更新作業に時間を取られていませんか?

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)
Python出力 Tauri出力

主な特徴

1. マニフェストを直接更新

cargo updatebundle updateなどはロックファイルの更新が中心ですが、depupはマニフェスト(Cargo.tomlGemfileなど依存定義ファイル)自体を更新します。

# ドライランで確認
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で実装しました。理由は以下の通りです。

  1. クロスプラットフォーム: macOS、Linux、Windowsで動作するバイナリを簡単に生成できる
  2. パフォーマンス: 複数のレジストリAPIへの並行リクエストが高速
  3. シングルバイナリ: ランタイム不要で配布が容易

各レジストリ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の組み合わせや、マイクロサービスで複数言語を扱うプロジェクトで威力を発揮します。

ぜひ試してみてください。フィードバックやコントリビューションも歓迎です。

https://github.com/owayo/depup

Discussion

Naughie(なっふぃ)Naughie(なっふぃ)

おもしろいツールだと思います ✨

ちなみに

解決策として、HTTPクライアント(reqwest)のTLS実装をOpenSSLベースのnative-tlsから、純粋なRust実装のrustls-tlsに切り替えました。

こちらですが,年末にリリースされた reqwest の最新バージョンでは rustls がデフォルトとなったので,単に

Cargo.toml
- reqwest = { version = "0.12", features = ["json"] }
+ reqwest = { version = "0.13", features = ["json"] }

とするだけで大丈夫です……!
実は depup するだけで良かったという悲しいニアミスですね ( ᐡᴗ ̫ ᴗᐡ)

1
owayoowayo

コメントありがとうございます!
なるほど・・・ほんとに年末に出てましたねw
容量節約になりそうなのでreqwestに戻してみます。

1