module ReactOnRails::Generators::JsDependencyManager

def add_css_dependencies

def add_css_dependencies
  puts "Installing CSS handling dependencies..."
  return if add_packages(CSS_DEPENDENCIES)
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Failed to add CSS dependencies.
    You can install them manually by running:
      npm install #{CSS_DEPENDENCIES.join(' ')}
  MSG
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Error adding CSS dependencies: #{e.message}
    You can install them manually by running:
      npm install #{CSS_DEPENDENCIES.join(' ')}
  MSG
end

def add_dev_dependencies

def add_dev_dependencies
  puts "Installing development dependencies..."
  # Use Rspack-specific dev dependencies if --rspack flag is set
  dev_deps = if respond_to?(:options) && options&.rspack?
               RSPACK_DEV_DEPENDENCIES
             else
               DEV_DEPENDENCIES
             end
  return if add_packages(dev_deps, dev: true)
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Failed to add development dependencies.
    You can install them manually by running:
      npm install --save-dev #{dev_deps.join(' ')}
  MSG
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Error adding development dependencies: #{e.message}
    You can install them manually by running:
      npm install --save-dev #{dev_deps.join(' ')}
  MSG
end

def add_js_dependencies

def add_js_dependencies
  add_react_on_rails_package
  add_react_dependencies
  add_css_dependencies
  # Rspack dependencies are only added when --rspack flag is used
  add_rspack_dependencies if respond_to?(:options) && options&.rspack?
  # Dev dependencies vary based on bundler choice
  add_dev_dependencies
end

def add_package(package, dev: false)

Returns:
  • (Boolean) - true if successful, false otherwise

Parameters:
  • dev (Boolean) -- Whether to add as dev dependency
  • package (String) -- Package specifier (e.g., "react-on-rails@16.0.0")
def add_package(package, dev: false)
  pj = package_json
  return false unless pj
  begin
    # Ensure package is in array format for package_json gem
    packages_array = [package]
    if dev
      pj.manager.add(packages_array, type: :dev, exact: true)
    else
      pj.manager.add(packages_array, exact: true)
    end
    true
  rescue StandardError
    # Return false to trigger warning in calling method
    false
  end
end

def add_packages(packages, dev: false)

Returns:
  • (Boolean) - true if successful, false otherwise

Parameters:
  • dev (Boolean) -- Whether to add as dev dependencies
  • packages (Array) -- Package names to add
def add_packages(packages, dev: false)
  # Use the add_npm_dependencies helper from GeneratorHelper
  add_npm_dependencies(packages, dev: dev)
end

def add_react_dependencies

def add_react_dependencies
  puts "Installing React dependencies..."
  return if add_packages(REACT_DEPENDENCIES)
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Failed to add React dependencies.
    You can install them manually by running:
      npm install #{REACT_DEPENDENCIES.join(' ')}
  MSG
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Error adding React dependencies: #{e.message}
    You can install them manually by running:
      npm install #{REACT_DEPENDENCIES.join(' ')}
  MSG
end

def add_react_on_rails_package

def add_react_on_rails_package
  # Use exact version match between gem and npm package for stable releases
  # For pre-release versions (e.g., 16.1.0-rc.1), use latest to avoid installing
  # a version that may not exist in the npm registry
  major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
  react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
                         "react-on-rails@#{ReactOnRails::VERSION}"
                       else
                         puts "Adding the latest react-on-rails NPM module. " \
                              "Double check this is correct in package.json"
                         "react-on-rails"
                       end
  puts "Installing React on Rails package..."
  return if add_package(react_on_rails_pkg)
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Failed to add react-on-rails package.
    You can install it manually by running:
      npm install #{react_on_rails_pkg}
  MSG
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Error adding react-on-rails package: #{e.message}
    You can install it manually by running:
      npm install #{react_on_rails_pkg}
  MSG
end

def add_rspack_dependencies

def add_rspack_dependencies
  puts "Installing Rspack core dependencies..."
  return if add_packages(RSPACK_DEPENDENCIES)
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Failed to add Rspack dependencies.
    You can install them manually by running:
      npm install #{RSPACK_DEPENDENCIES.join(' ')}
  MSG
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Error adding Rspack dependencies: #{e.message}
    You can install them manually by running:
      npm install #{RSPACK_DEPENDENCIES.join(' ')}
  MSG
end

def add_typescript_dependencies

def add_typescript_dependencies
  puts "Installing TypeScript dependencies..."
  return if add_packages(TYPESCRIPT_DEPENDENCIES, dev: true)
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Failed to add TypeScript dependencies.
    You can install them manually by running:
      npm install --save-dev #{TYPESCRIPT_DEPENDENCIES.join(' ')}
  MSG
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  Error adding TypeScript dependencies: #{e.message}
    You can install them manually by running:
      npm install --save-dev #{TYPESCRIPT_DEPENDENCIES.join(' ')}
  MSG
end

def install_js_dependencies

def install_js_dependencies
  # Use package_json gem's install method (always available via shakapacker)
  # package_json is guaranteed to be available because:
  # 1. react_on_rails gemspec requires shakapacker
  # 2. shakapacker gemspec requires package_json
  # 3. GeneratorHelper provides package_json method
  pj = package_json
  unless pj
    GeneratorMessages.add_warning("package_json not available, skipping dependency installation")
    return false
  end
  pj.manager.install
  true
rescue StandardError => e
  GeneratorMessages.add_warning(<<~MSG.strip)
    ⚠️  JavaScript dependencies installation failed: #{e.message}
    This could be due to network issues or package manager problems.
    You can install dependencies manually later by running:
    • npm install (if using npm)
    • yarn install (if using yarn)
    • pnpm install (if using pnpm)
  MSG
  false
end

def setup_js_dependencies

def setup_js_dependencies
  add_js_dependencies
  # Always run install to ensure all dependencies are properly installed.
  # The package_json gem's install method is idempotent and safe to call
  # even if packages were already added - it will only install what's needed.
  # This ensures edge cases where package.json was modified but install wasn't
  # run are handled correctly.
  install_js_dependencies
end