sferik/rails_admin

View on GitHub
lib/generators/rails_admin/install_generator.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require 'rails/generators'
require 'rails_admin/version'
require File.expand_path('utils', __dir__)

module RailsAdmin
  class InstallGenerator < Rails::Generators::Base
    source_root File.expand_path('templates', __dir__)
    include Generators::Utils::InstanceMethods

    argument :_namespace, type: :string, required: false, desc: 'RailsAdmin url namespace'
    class_option :asset, type: :string, required: false, default: nil, desc: 'Asset delivery method [options: webpacker, webpack, sprockets, importmap, vite]'
    desc 'RailsAdmin installation generator'

    def install
      if File.read(File.join(destination_root, 'config/routes.rb')).include?('mount RailsAdmin::Engine')
        display "Skipped route addition, since it's already there"
      else
        namespace = ask_for('Where do you want to mount rails_admin?', 'admin', _namespace)
        route("mount RailsAdmin::Engine => '/#{namespace}', as: 'rails_admin'")
      end
      if File.exist? File.join(destination_root, 'config/initializers/rails_admin.rb')
        insert_into_file 'config/initializers/rails_admin.rb', "  config.asset_source = :#{asset}\n", after: "RailsAdmin.config do |config|\n"
      else
        template 'initializer.erb', 'config/initializers/rails_admin.rb'
      end
      display "Using [#{asset}] for asset delivery method"
      case asset
      when 'webpack'
        configure_for_webpack
      when 'importmap'
        configure_for_importmap
      when 'webpacker'
        configure_for_webpacker5
      when 'vite'
        configure_for_vite
      when 'sprockets'
        configure_for_sprockets
      else
        raise "Unknown asset source: #{asset}"
      end
    end

  private

    def asset
      return options['asset'] if options['asset']

      if defined?(Webpacker)
        'webpacker'
      elsif Rails.root.join('webpack.config.js').exist?
        'webpack'
      elsif Rails.root.join('config/importmap.rb').exist?
        'importmap'
      elsif defined?(ViteRuby)
        'vite'
      else
        'sprockets'
      end
    end

    def configure_for_sprockets
      gem 'sassc-rails'
    end

    def configure_for_webpacker5
      run "yarn add rails_admin@#{RailsAdmin::Version.js}"
      template 'rails_admin.webpacker.js', 'app/javascript/packs/rails_admin.js'
      template 'rails_admin.scss.erb', 'app/javascript/stylesheets/rails_admin.scss'
      # To work around https://github.com/railsadminteam/rails_admin/issues/3565
      add_package_json_field('resolutions', {'rails_admin/@fortawesome/fontawesome-free' => '^5.15.0'})
    end

    def configure_for_vite
      vite_source_code_dir = ViteRuby.config.source_code_dir
      run "yarn add rails_admin@#{RailsAdmin::Version.js} sass"
      template('rails_admin.vite.js', File.join(vite_source_code_dir, 'entrypoints', 'rails_admin.js'))
      @fa_font_path = '@fortawesome/fontawesome-free/webfonts'
      template('rails_admin.scss.erb', File.join(vite_source_code_dir, 'stylesheets', 'rails_admin.scss'))
    end

    def configure_for_webpack
      run "yarn add rails_admin@#{RailsAdmin::Version.js}"
      template 'rails_admin.js', 'app/javascript/rails_admin.js'
      webpack_config = File.join(destination_root, 'webpack.config.js')
      marker = %r{application: ["']./app/javascript/application.js["']}
      if File.exist?(webpack_config) && File.read(webpack_config) =~ marker
        insert_into_file 'webpack.config.js', %(,\n    rails_admin: "./app/javascript/rails_admin.js"), after: marker
      else
        say 'Add `rails_admin: "./app/javascript/rails_admin.js"` to the entry section in your webpack.config.js.', :red
      end
      setup_css({'build' => 'webpack --config webpack.config.js'})
    end

    def configure_for_importmap
      run "yarn add rails_admin@#{RailsAdmin::Version.js}"
      template 'rails_admin.js', 'app/javascript/rails_admin.js'
      require_relative 'importmap_formatter'
      add_file 'config/importmap.rails_admin.rb', ImportmapFormatter.new.format
      setup_css
    end

    def setup_css(additional_script_entries = {})
      gem 'cssbundling-rails'
      rake 'css:install:sass'

      @fa_font_path = '.'
      template 'rails_admin.scss.erb', 'app/assets/stylesheets/rails_admin.scss'
      asset_config = %{Rails.application.config.assets.paths << Rails.root.join("node_modules/@fortawesome/fontawesome-free/webfonts")\n}
      if File.exist? File.join(destination_root, 'config/initializers/assets.rb')
        append_to_file 'config/initializers/assets.rb', asset_config
      else
        add_file 'config/initializers/assets.rb', asset_config
      end
      add_package_json_field('scripts', additional_script_entries.merge({'build:css' => 'sass ./app/assets/stylesheets/rails_admin.scss:./app/assets/builds/rails_admin.css --no-source-map --load-path=node_modules'}), <<~INSTRUCTION)
        Taking 'build:css' as an example, if you're already have application.sass.css for the sass build, the resulting script would look like:
          sass ./app/assets/stylesheets/application.sass.scss:./app/assets/builds/application.css ./app/assets/stylesheets/rails_admin.scss:./app/assets/builds/rails_admin.css --no-source-map --load-path=node_modules
      INSTRUCTION
    end

    def add_package_json_field(name, entries, instruction = nil)
      display "Add #{name} to package.json"
      package = begin
        JSON.parse(File.read(File.join(destination_root, 'package.json')))
      rescue Errno::ENOENT, JSON::ParserError
        {}
      end
      if package[name] && (package[name].keys & entries.keys).any?
        say <<~MESSAGE, :red
          You need to merge "#{name}": #{JSON.pretty_generate(entries)} into the existing #{name} in your package.json.#{instruction && "\n#{instruction}"}
        MESSAGE
      else
        package[name] ||= {}
        entries.each do |entry, value|
          package[name][entry] = value
        end
        add_file 'package.json', "#{JSON.pretty_generate(package)}\n"
      end
    end
  end
end