Railsジェネレータがロックファイルから自動でパッケージマネージャを検出

rails/rails

Rails 8.1では、actiontext:installrails generate channelなどのジェネレータが、プロジェクトのロックファイルを検出して適切なJavaScriptパッケージマネージャを自動選択するようになりました。

背景

これまでRailsのジェネレータはyarnをハードコードしており、npmやpnpm、bunを使用しているプロジェクトでは、ジェネレータ実行後に生成されたファイルを手動で修正する必要がありました。#47416で報告されていたこの問題に対応し、#56636でロックファイルベースの自動検出機能が実装されています。

技術的な変更

検出ロジックの実装

新しく追加されたRails::Generators::JsPackageManagerモジュールが、以下の優先順位でパッケージマネージャを検出します:

  1. bun.lockbまたはbun.config.jsが存在 → bun
  2. pnpm-lock.yamlが存在 → pnpm
  3. package-lock.jsonが存在 → npm
  4. 上記のいずれも存在しない → yarn(デフォルト)
module Rails
  module Generators
    module JsPackageManager
      MANAGERS = {
        bun: {
          add: "bun add %s",
          install: "bun install --frozen-lockfile",
          lockfile: "bun.lockb",
          audit: nil
        },
        pnpm: {
          add: "pnpm add %s",
          install: "pnpm install --frozen-lockfile",
          lockfile: "pnpm-lock.yaml",
          audit: "pnpm audit"
        },
        npm: {
          add: "npm install %s",
          install: "npm ci",
          lockfile: "package-lock.json",
          audit: "npm audit"
        },
        yarn: {
          add: "yarn add %s",
          install: "yarn install --immutable",
          lockfile: "yarn.lock",
          audit: "yarn audit"
        }
      }.freeze

      def self.detect(root)
        if root.join("bun.lockb").exist? || root.join("bun.config.js").exist?
          :bun
        elsif root.join("pnpm-lock.yaml").exist?
          :pnpm
        elsif root.join("package-lock.json").exist?
          :npm
        else
          :yarn
        end
      end

      def package_manager
        @package_manager ||= JsPackageManager.detect(project_root)
      end

      def package_add_command(package)
        config = MANAGERS[package_manager]
        config[:add] % package
      end
    end
  end
end

ジェネレータの変更

ActionTextとActionCableのジェネレータが、このモジュールをincludeして統一的にパッケージマネージャを扱うようになりました。

変更前(ActionTextインストーラ):

def install_editor
  editor = options[:editor]
  say "Installing #{editor} JavaScript dependency", :green
  if using_bun?
    run "bun add #{editor}"
  elsif using_node?
    run "yarn add #{editor}"
  end
end

変更後:

include Rails::Generators::JsPackageManager

def install_editor
  return unless using_js_runtime?

  editor = options[:editor]
  say "Installing #{editor} JavaScript dependency", :green
  run package_add_command(editor)
end

各パッケージマネージャに応じて、以下のコマンドが実行されます:

  • bun: bun add trix
  • pnpm: pnpm add trix
  • npm: npm install trix
  • yarn: yarn add trix

Rakeタスクの更新

yarn:installタスクも同様に更新され、javascript:installタスクとして汎用化されています。

namespace :javascript do
  desc "Install all JavaScript dependencies"
  task :install do
    valid_node_envs = %w[test development production]
    node_env = ENV.fetch("NODE_ENV") do
      valid_node_envs.include?(Rails.env) ? Rails.env : "production"
    end

    manager = Rails::Generators::JsPackageManager.detect(Rails.root)
    config = Rails::Generators::JsPackageManager::MANAGERS[manager]

    system({ "NODE_ENV" => node_env }, config[:install], exception: true)
  rescue Errno::ENOENT
    $stderr.puts "#{manager} failed to execute."
    $stderr.puts "Ensure #{manager} is installed and available in PATH."
    exit 1
  end
end

namespace :yarn do
  task install: "javascript:install"
end

後方互換性のため、yarn:installjavascript:installのエイリアスとして残されています。

設計判断

ロックファイルベースの検出

PR内では、環境変数や設定ファイルでパッケージマネージャを指定する方法も検討されましたが、ロックファイルの存在チェックが最もシンプルで確実な方法として採用されています。プロジェクトにpackage.jsonが存在しても、実際にどのマネージャを使っているかはロックファイルを見ることで判断できます。

bunの検出にbun.config.jsを含める理由

jsbundling-railsを使用しているプロジェクトでは、まだbun.lockbが生成されていない段階でbun.config.jsが存在するケースがあるため、両方をチェックしています。これは元のコードで実装されていた判定ロジックを引き継いだものです。

パッケージマネージャごとのコマンド差異

各パッケージマネージャで、CI環境での再現性を重視したコマンドが選択されています:

  • bun/pnpm: --frozen-lockfileでロックファイルの更新を禁止
  • npm: npm cipackage-lock.jsonを厳密に再現
  • yarn: --immutableでロックファイルの変更を禁止(Yarn 2+)

これにより、ジェネレータ実行時に意図しないロックファイルの更新が発生しません。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成はTitle, Context, Technical Detail, Design Insightの全要素を含み、非常に明確です。カスタムMarkdown構文(ファイル名付きコードブロック、GitHubリンク)も正しく使用されており、ガイドラインに完全に準拠しています。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ⚠ WARNING

技術的な正確性と表現の適切性

Diff情報が提供されていないため、記事内のコード引用と実際の変更との完全な照合はできませんでした。しかし、引用されているコードは構文的に正しく、PRの目的と技術的に整合性が取れています。技術用語や説明の正確性は高いです。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ⚠ WARNING

元のPR情報との一致度

記事の主要な主張はPRのタイトルと一致しています。しかし、PRのDescriptionが提供されていないため、「設計判断」セクションで言及されている代替案の検討経緯など、一部の記述がPR情報から直接裏付けられませんでした。ただし、内容は技術的に妥当な推測の範囲内です。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除