diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f6b44e..0d36ad3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog ## master (unreleased) +* Adds Figjam generator. ([@mausamp][]) +* Adds Pronto Generator with Gitlab CI ([@coolprobn][]) +* Adds Rack Mini Profiler generator. ([@mausamp][]) +* Adds VCR generator. ([@TheZero0-ctrl][]) +* Adds Pronto Generator with Gihub Action. ([@TheZero0-ctrl][]) +* Adds Doorkeeper Generator with Devise. ([@TheZero0-ctrl][]) +* Adds Avo generator. ([@mausamp][]) +* Adds Sentry generator. ([@mausamp][]) +* Adds Dotenv generator. ([@mausamp][]) +* Adds Honeybadger generator. ([@mausamp][]) +* Adds Rack CORS generator. ([@mausamp][]) ## 0.13.0 (March 26th, 2024) * Adds Letter Opener generator. ([@coolprobn][]) @@ -84,3 +95,5 @@ [@luathn]: https://github.com/luathn [@coolprobn]: https://github.com/coolprobn [@aadil]: https://github.com/AdilRT +[@mausamp]: https://github.com/mausamp +[@TheZero0-ctrl]: https://github.com/TheZero0-ctrl diff --git a/Gemfile.lock b/Gemfile.lock index 35a2830e..419199f2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - boring_generators (0.12.0) + boring_generators (0.13.0) railties GEM @@ -143,4 +143,4 @@ DEPENDENCIES sqlite3 (~> 1.4) BUNDLED WITH - 2.1.4 + 2.2.33 diff --git a/README.md b/README.md index aee53505..84aba636 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,17 @@ The boring generator introduces following generators: - Install Whenever: `rails generate boring:whenever:install` - Install Rswag: `rails generate boring:rswag:install --rails_port= --authentication_type= --skip_api_authentication= --api_authentication_options= --enable_swagger_ui_authentication=` - Install Webmock: `rails generate boring:webmock:install --app_test_framework=` +- Install Figjam: `rails generate boring:figjam:install` +- Install Pronto with Gitlab CI: `rails generate boring:pronto:gitlab_ci:install` +- Install Pronto with Github Action: `rails generate boring:pronto:github_action:install` +- Install Rack Mini Profiler: `rails generate boring:rack_mini_profiler:install` +- Install VCR: `rails generate boring:vcr:install --testing_framework= --stubbing_libraries=` +- Install Avo: `rails generate boring:avo:install` +- Install Doorkeeper with devise: `rails generate boring:devise:doorkeeper:install` +- Install Sentry: `rails generate boring:sentry:install --use_env_variable --breadcrumbs_logger=` +- Install Dotenv: `rails generate boring:dotenv:install` +- Install Honeybadger: `rails generate boring:honeybadger:install` +- Install Rack CORS: `rails generate boring:rack_cors:install --origins=` ## Screencasts diff --git a/lib/boring_generators/generator_helper.rb b/lib/boring_generators/generator_helper.rb new file mode 100644 index 00000000..491e6f3a --- /dev/null +++ b/lib/boring_generators/generator_helper.rb @@ -0,0 +1,38 @@ +module BoringGenerators + module GeneratorHelper + include Rails::Generators::Actions + + def app_ruby_version + with_ruby_string = `grep "^ruby.*$" Gemfile` || `cat .ruby-version` + + # only keep 3.3.0 + with_ruby_string.gsub(/[^\d\.]/, '').squish + end + + def gem_installed?(gem_name) + gem_regex = /gem.*\b#{gem_name}\b.*/ + File.read("Gemfile").match?(gem_regex) + end + + def bundle_install + Bundler.with_unbundled_env do + run "bundle install" + end + end + + def check_and_install_gem(*args) + gem_name, = args + + gem_file_content_array = File.readlines("Gemfile") + + + gem_exists = gem_file_content_array.any? { |line| line.include?(gem_name) } + + if gem_exists + say "#{gem_name} is already in the Gemfile, skipping it ...", :yellow + else + gem *args + end + end + end +end diff --git a/lib/generators/boring/active_storage/azure/install/install_generator.rb b/lib/generators/boring/active_storage/azure/install/install_generator.rb index afe2539a..0e27eae9 100644 --- a/lib/generators/boring/active_storage/azure/install/install_generator.rb +++ b/lib/generators/boring/active_storage/azure/install/install_generator.rb @@ -2,7 +2,7 @@ module Boring module ActiveStorage module Azure class InstallGenerator < Rails::Generators::Base - desc "Adds ActiveStorage Mircosoft Azure the application" + desc "Adds ActiveStorage Microsoft Azure the application" class_option :skip_active_storage, type: :boolean, aliases: "-s", desc: "Skips running ActiveStorage installer" diff --git a/lib/generators/boring/audit/install/install_generator.rb b/lib/generators/boring/audit/install/install_generator.rb index 560662f3..f2ff16fd 100644 --- a/lib/generators/boring/audit/install/install_generator.rb +++ b/lib/generators/boring/audit/install/install_generator.rb @@ -13,7 +13,7 @@ def add_bullet_gem \n \t# Patch-level verification for Bundler. https://github.com/rubysec/bundler-audit \tgem "bundler-audit", require: false - \t# vulnerabity checker for Ruby itself. https://github.com/civisanalytics/ruby_audit + \t# vulnerability checker for Ruby itself. https://github.com/civisanalytics/ruby_audit \tgem "ruby_audit", require: false RUBY insert_into_file "Gemfile", audit_gems_content, after: /group :development do/ diff --git a/lib/generators/boring/avo/install/install_generator.rb b/lib/generators/boring/avo/install/install_generator.rb new file mode 100644 index 00000000..35949379 --- /dev/null +++ b/lib/generators/boring/avo/install/install_generator.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Boring + module Avo + class InstallGenerator < Rails::Generators::Base + desc 'Adds Avo to the application' + + def add_avo_gem + say 'Adding Avo gem', :green + + Bundler.with_unbundled_env do + run 'bundle add avo' + end + end + + def configure_avo + say 'Setting up Avo', :green + + Bundler.with_unbundled_env do + run 'bundle exec rails generate avo:install' + end + end + end + end +end diff --git a/lib/generators/boring/devise/doorkeeper/install/install_generator.rb b/lib/generators/boring/devise/doorkeeper/install/install_generator.rb new file mode 100644 index 00000000..885c0e0c --- /dev/null +++ b/lib/generators/boring/devise/doorkeeper/install/install_generator.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require 'boring_generators/generator_helper' + +module Boring + module Devise + module Doorkeeper + class InstallGenerator < Rails::Generators::Base + include BoringGenerators::GeneratorHelper + + desc "Adds doorkeeper with devise to the application" + + class_option :model_name, type: :string, aliases: "-m", default: "User", + desc: "Tell us the user model name which will be used for authentication. Defaults to User" + class_option :grant_flows, type: :array, aliases: "-gf", default: %w[authorization_code client_credentials], + enum: %w[authorization_code client_credentials password], + desc: "Tell us the grant flows you want to use separated by space. Defaults to authorization_code and client_credentials" + class_option :api_only, type: :boolean, aliases: "-a", default: false, + desc: "Tell us if you want to setup doorkeeper for API only application. Defaults to false" + class_option :skip_applications_routes, type: :boolean, aliases: "-sr", default: false, + desc: "Tell us if you want to skip adding doorkeeper routes to manage applications. Defaults to false" + class_option :use_refresh_token, type: :boolean, aliases: "-rt", default: false, + desc: "Keep user logged in with refresh tokens. Defaults to false" + + def verify_presence_of_devise_gem + return if gem_installed?("devise") + + say "We couldn't find devise gem. Please configure devise gem and rerun the generator. Consider running `rails generate boring:devise:install` to set up Devise.", + :red + + abort + end + + def verify_presence_of_devise_model + return if File.exist?("app/models/#{options[:model_name].underscore}.rb") + + say "We couldn't find the #{options[:model_name]} model. Maybe there is a typo? Please provide the correct model name and run the generator again.", + :red + + abort + end + + def add_doorkeeper_gem + say "Adding doorkeeper gem", :green + check_and_install_gem("doorkeeper") + bundle_install + end + + def run_doorkeeper_generators + say "Running doorkeeper generators", :green + + Bundler.with_unbundled_env do + run "bundle exec rails generate doorkeeper:install" + run "bundle exec rails generate doorkeeper:migration" + end + end + + def add_doorkeeper_related_association_to_model + model_name = options[:model_name].underscore + say "Adding doorkeeper related associations to the model file app/models/#{model_name}.rb", + :green + model_content = <<~RUBY + has_many :access_grants, + class_name: 'Doorkeeper::AccessGrant', + foreign_key: :resource_owner_id, + dependent: :delete_all # or :destroy if you need callbacks + + has_many :access_tokens, + class_name: 'Doorkeeper::AccessToken', + foreign_key: :resource_owner_id, + dependent: :delete_all # or :destroy if you need callbacks + RUBY + + inject_into_file "app/models/#{model_name}.rb", + optimize_indentation(model_content, 2), + after: "ApplicationRecord\n" + end + + def update_doorkeeper_initializer + say "Updating doorkeeper initializer", :green + + configure_resource_owner_authenticator if options[:grant_flows].include?("authorization_code") + configure_admin_authenticator unless options[:api_only] || options[:skip_applications_routes] + configure_resource_owner_from_credentials if options[:grant_flows].include?("password") + + gsub_file "config/initializers/doorkeeper.rb", + /# grant_flows %w\[authorization_code client_credentials\]/, + "grant_flows %w[#{options[:grant_flows].uniq.join(' ')}]" + + if options[:api_only] + gsub_file "config/initializers/doorkeeper.rb", + /# api_only/, + "api_only" + end + + if options[:skip_applications_routes] + doorkeeper_routes_content = <<~RUBY + use_doorkeeper do + skip_controllers :applications, :authorized_applications + end + RUBY + + gsub_file "config/routes.rb", + /.*use_doorkeeper/, + optimize_indentation(doorkeeper_routes_content, 2) + end + + if options[:use_refresh_token] + uncomment_lines "config/initializers/doorkeeper.rb", + /use_refresh_token/ + end + end + + def update_doorkeeper_migration + say "Updating doorkeeper migration", :green + model_name = options[:model_name].underscore + + uncomment_lines Dir["db/migrate/*_create_doorkeeper_tables.rb"].first, + /add_foreign_key :oauth/ + + gsub_file Dir["db/migrate/*_create_doorkeeper_tables.rb"].first, + //, + ":#{model_name.pluralize}" + + return unless (%w[password client_credentials] & options[:grant_flows]).any? + + gsub_file Dir["db/migrate/*_create_doorkeeper_tables.rb"].first, + /t.text :redirect_uri, null: false/, + "t.text :redirect_uri" + end + + def show_message + return if options[:api_only] || options[:skip_applications_routes] + + model_name = options[:model_name].underscore + admin_authenticator_content = "current_#{model_name} || warden.authenticate!(scope: :#{model_name})" + + say "\nWe've implemented `#{admin_authenticator_content}` in the admin_authenticator block of config/initializers/doorkeeper.rb to manage access to application routes. Please adjust it as necessary to suit your requirements.", + :yellow + end + + private + + def configure_resource_owner_authenticator + model_name = options[:model_name].underscore + resource_owner_authenticator_content = <<~RUBY + resource_owner_authenticator do + current_#{model_name} || warden.authenticate!(scope: :#{model_name}) + end + RUBY + + gsub_file "config/initializers/doorkeeper.rb", + /.*resource_owner_authenticator do\n(?:\s|.)*?end/, + optimize_indentation(resource_owner_authenticator_content, 2) + end + + def configure_admin_authenticator + model_name = options[:model_name].underscore + gsub_file "config/initializers/doorkeeper.rb", + /(?:# admin_authenticator do\n*)((?:\s|.)*?)(?:# end)/, + "admin_authenticator do\n" + "\\1" + "end" + + admin_authenticator_content = "current_#{model_name} || warden.authenticate!(scope: :#{model_name})" + inject_into_file "config/initializers/doorkeeper.rb", + optimize_indentation(admin_authenticator_content, 4), + after: /admin_authenticator do\n/, + force: true + + end + + def configure_resource_owner_from_credentials + model_name = options[:model_name].underscore + resource_owner_for_credentials_content = <<~RUBY + resource_owner_from_credentials do |routes| + #{model_name} = #{options[:model_name]}.find_for_database_authentication(email: params[:email]) + if #{model_name}&.valid_for_authentication? { #{model_name}.valid_password?(params[:password]) } && #{model_name}&.active_for_authentication? + request.env['warden'].set_user(#{model_name}, scope: :#{model_name}, store: false) + #{model_name} + end + end + RUBY + + inject_into_file "config/initializers/doorkeeper.rb", + optimize_indentation(resource_owner_for_credentials_content, 2), + after: /resource_owner_authenticator do\n(?:\s|.)*?end\n/ + end + end + end + end +end diff --git a/lib/generators/boring/dotenv/install/install_generator.rb b/lib/generators/boring/dotenv/install/install_generator.rb new file mode 100644 index 00000000..6eac0989 --- /dev/null +++ b/lib/generators/boring/dotenv/install/install_generator.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Boring + module Dotenv + class InstallGenerator < Rails::Generators::Base + desc 'Adds dotenv gem to the application' + source_root File.expand_path("templates", __dir__) + + def add_dotenv_gem + say 'Adding dotenv gem', :green + + Bundler.with_unbundled_env do + run 'bundle add dotenv-rails --group development' + end + end + + def configure_dotenv_gem + say 'Configuring dotenv gem', :green + + template '.env', '.env' + + unless File.exist?('.gitignore') + create_file '.gitignore' + end + + FileUtils.cp('.env', '.env.sample') + insert_into_file('.gitignore', "\n/.env\n") + end + end + end +end diff --git a/lib/generators/boring/dotenv/install/templates/.env b/lib/generators/boring/dotenv/install/templates/.env new file mode 100644 index 00000000..26ad5aeb --- /dev/null +++ b/lib/generators/boring/dotenv/install/templates/.env @@ -0,0 +1,3 @@ +# Add your environment variables here +# Example: +# SECRET_KEY_BASE=your_secret_key \ No newline at end of file diff --git a/lib/generators/boring/factory_bot/install/install_generator.rb b/lib/generators/boring/factory_bot/install/install_generator.rb index 2614c362..c7517ef7 100644 --- a/lib/generators/boring/factory_bot/install/install_generator.rb +++ b/lib/generators/boring/factory_bot/install/install_generator.rb @@ -14,7 +14,7 @@ class InstallGenerator < Rails::Generators::Base def add_factory_bot_gem log :adding, "FactoryBot" Bundler.with_unbundled_env do - run "bundle add factory_bot_rails --group='developement,test'" + run "bundle add factory_bot_rails --group='development,test'" end end diff --git a/lib/generators/boring/faker/install/install_generator.rb b/lib/generators/boring/faker/install/install_generator.rb index 94b6f2e8..ec8e91be 100644 --- a/lib/generators/boring/faker/install/install_generator.rb +++ b/lib/generators/boring/faker/install/install_generator.rb @@ -9,7 +9,7 @@ class InstallGenerator < Rails::Generators::Base def add_faker_gem log :adding, "faker" Bundler.with_unbundled_env do - run "bundle add faker --group='developement,test'" + run "bundle add faker --group='development,test'" end end end diff --git a/lib/generators/boring/figjam/install/install_generator.rb b/lib/generators/boring/figjam/install/install_generator.rb new file mode 100644 index 00000000..543f6bbc --- /dev/null +++ b/lib/generators/boring/figjam/install/install_generator.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Boring + module Figjam + class InstallGenerator < Rails::Generators::Base + desc 'Adds figjam gem to the app' + + def add_figjam_gem + say 'Adding figjam gem', :green + + Bundler.with_unbundled_env do + run 'bundle add figjam' + end + end + + def configure_figjam + say 'Configuring figjam', :green + + Bundler.with_unbundled_env do + run 'bundle exec figjam install' + end + + FileUtils.cp('config/application.yml', 'config/application.yml.sample') + + unless File.exist?('.gitignore') + create_file '.gitignore' + end + + insert_into_file('.gitignore', "\n/config/application.yml\n") + end + end + end +end diff --git a/lib/generators/boring/honeybadger/install/install_generator.rb b/lib/generators/boring/honeybadger/install/install_generator.rb new file mode 100644 index 00000000..90f81a48 --- /dev/null +++ b/lib/generators/boring/honeybadger/install/install_generator.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Boring + module Honeybadger + class InstallGenerator < Rails::Generators::Base + source_root File.expand_path("templates", __dir__) + desc 'Adds honeybadger to the app' + + class_option :use_env_variable, type: :boolean, aliases: "-ev", + desc: 'Use ENV variable for devise_jwt_secret_key. By default Rails credentials will be used.' + + def add_honeybadger_gem + say 'Adding Honeybadger gem', :green + + Bundler.with_unbundled_env do + run 'bundle add honeybadger' + end + end + + def configure_honeybadger_gem + say 'Setting up Honeybadger', :green + + @api_key = honeybadger_api_key + + template 'honeybadger.yml', 'config/honeybadger.yml' + + show_readme + end + + private + + def show_readme + readme_template = File.read(File.join(self.class.source_root, 'README')) + readme_content = ERB.new(readme_template).result(binding) + say readme_content + end + + def honeybadger_api_key + if options[:use_env_variable] + "ENV['HONEYBADGER_API_KEY']" + else + "Rails.application.credentials.dig(:honeybadger, :api_key)" + end + end + end + end +end diff --git a/lib/generators/boring/honeybadger/install/templates/README b/lib/generators/boring/honeybadger/install/templates/README new file mode 100644 index 00000000..b83e02f9 --- /dev/null +++ b/lib/generators/boring/honeybadger/install/templates/README @@ -0,0 +1,6 @@ +=============================================================================== + +The API key for Honeybadger will be used from <%= @api_key %>. +You can change these values if they don't match with your app. + +=============================================================================== \ No newline at end of file diff --git a/lib/generators/boring/honeybadger/install/templates/honeybadger.yml.tt b/lib/generators/boring/honeybadger/install/templates/honeybadger.yml.tt new file mode 100644 index 00000000..4d988c27 --- /dev/null +++ b/lib/generators/boring/honeybadger/install/templates/honeybadger.yml.tt @@ -0,0 +1,26 @@ +--- +# For more options, see https://docs.honeybadger.io/lib/ruby/gem-reference/configuration + +api_key: <%= @api_key %> + +# The environment your app is running in. +env: "<%= Rails.env %>" + +# The absolute path to your project folder. +root: "<%= Rails.root.to_s %>" + +# Honeybadger won't report errors in these environments. +development_environments: +- test +- development +- cucumber + +# By default, Honeybadger won't report errors in the development_environments. +# You can override this by explicitly setting report_data to true or false. +# report_data: true + +# The current Git revision of your project. Defaults to the last commit hash. +# revision: null + +# Enable verbose debug logging (useful for troubleshooting). +debug: false diff --git a/lib/generators/boring/oauth/google/install/install_generator.rb b/lib/generators/boring/oauth/google/install/install_generator.rb index 38b531fd..2dda7bbe 100644 --- a/lib/generators/boring/oauth/google/install/install_generator.rb +++ b/lib/generators/boring/oauth/google/install/install_generator.rb @@ -14,8 +14,8 @@ class MissingDeviseConfigurationError < StandardError; end desc "Adds Google OmniAuth to the application" source_root File.expand_path("templates", __dir__) - def add_github_omniauth_gem - say "Adding GitHub OmniAuth gem", :green + def add_google_omniauth_gem + say "Adding Google OmniAuth gem", :green Bundler.with_unbundled_env do run "bundle add omniauth-google-oauth2" end diff --git a/lib/generators/boring/pronto/base_generator.rb b/lib/generators/boring/pronto/base_generator.rb new file mode 100644 index 00000000..e2db7263 --- /dev/null +++ b/lib/generators/boring/pronto/base_generator.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'boring_generators/generator_helper' + +module Boring + module Pronto + class BaseGenerator < Rails::Generators::Base + desc "Adds Pronto gem with various extensions" + + class_option :skip_extensions, type: :array, aliases: "-se", + desc: "List of extensions to skip. Available options: brakeman, flay, reek, rubocop", + enum: %w[brakeman flay reek rubocop], + default: [] + + include BoringGenerators::GeneratorHelper + + def add_pronto_gems + say "Adding pronto gems", :green + pronto_gem_content = <<~RUBY + \n + \t# Pronto is a code linter runner that can be used with git and GitHub pull requests + \tgem "pronto" + #{pronto_brakemen_gem_content} + #{pronto_flay_gem_content} + RUBY + insert_into_file "Gemfile", pronto_gem_content + Bundler.with_unbundled_env do + run "bundle install" + end + end + + def pronto_brakemen_gem_content + return unless options[:skip_extensions].exclude?('brakeman') + return if gem_installed?('pronto-brakeman') + + "\tgem \"pronto-brakeman\", require: false" + end + + def pronto_flay_gem_content + return unless options[:skip_extensions].exclude?('flay') + return if gem_installed?('pronto-flay') + + "\tgem \"pronto-flay\", require: false" + end + end + end +end diff --git a/lib/generators/boring/pronto/github_action/install/install_generator.rb b/lib/generators/boring/pronto/github_action/install/install_generator.rb new file mode 100644 index 00000000..90036846 --- /dev/null +++ b/lib/generators/boring/pronto/github_action/install/install_generator.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "generators/boring/pronto/base_generator" +require "boring_generators/generator_helper" + +module Boring + module Pronto + module GithubAction + class InstallGenerator < Boring::Pronto::BaseGenerator + desc "Adds Pronto configurations to Github Action" + source_root File.expand_path("templates", __dir__) + + class_option :ruby_version, type: :string, aliases: "-rv" + + include BoringGenerators::GeneratorHelper + + def add_pronto_configuration_for_github_action + say "Adding Pronto configurations to .github/workflows/pronto.yml", :green + + @ruby_version = options.ruby_version || app_ruby_version + + template("pronto.yml", ".github/workflows/pronto.yml") + end + end + end + end +end diff --git a/lib/generators/boring/pronto/github_action/install/templates/pronto.yml.tt b/lib/generators/boring/pronto/github_action/install/templates/pronto.yml.tt new file mode 100644 index 00000000..65febf00 --- /dev/null +++ b/lib/generators/boring/pronto/github_action/install/templates/pronto.yml.tt @@ -0,0 +1,53 @@ +on: [pull_request] + +name: Pronto + +jobs: + pronto: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + statuses: write + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: <%= @ruby_version %> + - name: Ruby gem cache + uses: actions/cache@v1 + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + - name: Install gems + run: | + bundle config path vendor/bundle + bundle install --jobs 4 --retry 3 + + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: 10.13.0 + - name: Find yarn cache location + id: yarn-cache + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: JS package cache + uses: actions/cache@v1 + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install packages + run: | + yarn install --pure-lockfile + - name: Run Pronto + run: | + PRONTO_PULL_REQUEST_ID="${{ github.event.pull_request.number }}" PRONTO_GITHUB_ACCESS_TOKEN="${{ github.token }}" bundle exec pronto run -f github_status github_pr -c origin/${{ github.base_ref }} diff --git a/lib/generators/boring/pronto/gitlab_ci/install/install_generator.rb b/lib/generators/boring/pronto/gitlab_ci/install/install_generator.rb new file mode 100644 index 00000000..d6083cad --- /dev/null +++ b/lib/generators/boring/pronto/gitlab_ci/install/install_generator.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require "generators/boring/pronto/base_generator" +require "boring_generators/generator_helper" + +module Boring + module Pronto + module GitlabCi + class InstallGenerator < Boring::Pronto::BaseGenerator + desc "Adds Pronto gem with various extensions and configures it for Gitlab CI" + source_root File.expand_path("templates", __dir__) + + class_option :ruby_version, type: :string, aliases: "-rv" + + include BoringGenerators::GeneratorHelper + + def add_configuration + @ruby_version = options.ruby_version || app_ruby_version + + if File.exists?(".gitlab-ci.yml") + add_pronto_configuration + add_lint_stage + show_readme + else + create_gitlab_ci_with_pronto + end + end + + private + def create_gitlab_ci_with_pronto + say "Creating .gitlab-ci.yml with Pronto configurations", :yellow + template ".gitlab-ci.yml", ".gitlab-ci.yml" + end + + def add_pronto_configuration + return if pronto_configuration_exists? + + say "Adding Pronto configurations to .gitlab-ci.yml", :green + inject_into_file ".gitlab-ci.yml", pronto_ci_content, before: /\Z/ + end + + def add_lint_stage + return if lint_stage_exists? + + if stages_exists? + inject_into_file ".gitlab-ci.yml", optimize_indentation("\n- lint", 2).chomp, after: /stages:/ + else + inject_into_file ".gitlab-ci.yml", stages_configuration, before: /pronto:/ + end + end + + def show_readme + readme "README" + end + + def pronto_configuration_exists? + gitlab_ci_file_content["pronto"] + end + + def lint_stage_exists? + gitlab_ci_file_content["stages"] && gitlab_ci_file_content["stages"].include?("lint") + end + + def stages_exists? + gitlab_ci_file_content["stages"] + end + + def gitlab_ci_file_content + @gitlab_ci_file_content ||= YAML.safe_load(File.open(".gitlab-ci.yml"), aliases: true) || {} + end + + def pronto_ci_content + <<~RUBY + pronto: + image: ruby:#{@ruby_version} + stage: lint + only: + # run pronto on merge requests and when new changes are pushed to it + - merge_requests + variables: + PRONTO_GITLAB_API_PRIVATE_TOKEN: $PRONTO_ACCESS_TOKEN + before_script: + # Install cmake required for rugged gem (Pronto depends on it) + - apt-get update && apt-get install -y cmake + # use bundler version same as the one that bundled the Gemfile + - gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" --no-document + - bundle install --jobs $(nproc) + script: + # Pronto fails with the error "revspec 'origin/{target_branch}' because Gitlab fetches changes with git depth set to 20 by default. You can remove this line if you update Gitlab CI setting to clone the full project. + - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + # Run pronto on branch of current merge request + - bundle exec pronto run -f gitlab_mr -c origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME + RUBY + end + + def stages_configuration + <<~RUBY + stages: + - lint + RUBY + end + end + end + end +end diff --git a/lib/generators/boring/pronto/gitlab_ci/install/templates/.gitlab-ci.yml.tt b/lib/generators/boring/pronto/gitlab_ci/install/templates/.gitlab-ci.yml.tt new file mode 100644 index 00000000..0d0b05b0 --- /dev/null +++ b/lib/generators/boring/pronto/gitlab_ci/install/templates/.gitlab-ci.yml.tt @@ -0,0 +1,22 @@ +stages: + - lint + +pronto: + image: ruby:<%= @ruby_version %> + stage: lint + only: + # run pronto on merge requests and when new changes are pushed to it + - merge_requests + variables: + PRONTO_GITLAB_API_PRIVATE_TOKEN: $PRONTO_ACCESS_TOKEN + before_script: + # Install cmake required for rugged gem (Pronto depends on it) + - apt-get update && apt-get install -y cmake + # use bundler version same as the one that bundled the Gemfile + - gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" --no-document + - bundle install --jobs $(nproc) + script: + # Pronto fails with the error "revspec 'origin/{target_branch}' because Gitlab fetches changes with git depth set to 20 by default. You can remove this line if you update Gitlab CI setting to clone the full project. + - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + # Run pronto on branch of current merge request + - bundle exec pronto run -f gitlab_mr -c origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME diff --git a/lib/generators/boring/pronto/gitlab_ci/install/templates/README b/lib/generators/boring/pronto/gitlab_ci/install/templates/README new file mode 100644 index 00000000..a4628036 --- /dev/null +++ b/lib/generators/boring/pronto/gitlab_ci/install/templates/README @@ -0,0 +1,7 @@ + +=============================================================================== + +❗️❗️ +Pronto needs your Private Gitlab API token for posting comments in the merge request. It is configured as "PRONTO_ACCESS_TOKEN" inside .gitlab-ci.yml, don't forget to configure this variable in your Gitlab Project settings before pushing your new changes to git. + +=============================================================================== diff --git a/lib/generators/boring/rack_cors/install/install_generator.rb b/lib/generators/boring/rack_cors/install/install_generator.rb new file mode 100644 index 00000000..b22be41f --- /dev/null +++ b/lib/generators/boring/rack_cors/install/install_generator.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Boring + module RackCors + class InstallGenerator < Rails::Generators::Base + desc 'Adds rack-cors gem to the app' + source_root File.expand_path("templates", __dir__) + + class_option :origins, type: :array, default: ['*'], + desc: 'Specify which origins are allowed to make CORS requests' + + def add_rack_cors_gem + say 'Adding Rack CORS gem', :green + + gem 'rack-cors' + end + + def configure_rack_cors + say 'Configuring Rack CORS', :green + + @origins = options[:origins].map { |origin| "'#{origin}'" }.join(', ') + + template 'cors.rb', 'config/initializers/cors.rb' + show_readme + end + + private + + def show_readme + readme 'README' + end + end + end +end diff --git a/lib/generators/boring/rack_cors/install/templates/README b/lib/generators/boring/rack_cors/install/templates/README new file mode 100644 index 00000000..75cf64ad --- /dev/null +++ b/lib/generators/boring/rack_cors/install/templates/README @@ -0,0 +1,5 @@ +=============================================================================== + +Please restart your rails server for the changes to take effect. + +=============================================================================== \ No newline at end of file diff --git a/lib/generators/boring/rack_cors/install/templates/cors.rb b/lib/generators/boring/rack_cors/install/templates/cors.rb new file mode 100644 index 00000000..b5722646 --- /dev/null +++ b/lib/generators/boring/rack_cors/install/templates/cors.rb @@ -0,0 +1,6 @@ +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins <%= @origins %> + resource '*', headers: :any, methods: [:get, :post, :patch, :put] + end +end \ No newline at end of file diff --git a/lib/generators/boring/rack_mini_profiler/install/install_generator.rb b/lib/generators/boring/rack_mini_profiler/install/install_generator.rb new file mode 100644 index 00000000..0e9b0726 --- /dev/null +++ b/lib/generators/boring/rack_mini_profiler/install/install_generator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Boring + module RackMiniProfiler + class InstallGenerator < Rails::Generators::Base + desc 'Adds rack-mini-profiler to the application' + + def add_rack_mini_profiler_gem + say 'Adding rack-mini-profiler gem', :green + gem_definition = "gem 'rack-mini-profiler', require: false\n" + rack_mini_profiler_gems_content = <<~RUBY + \n + \t# Profiler for your Rails application + \tgem 'rack-mini-profiler', require: false + RUBY + + insert_into_file "Gemfile", rack_mini_profiler_gems_content, after: /group :development do\n/ + + bundle_install + end + + def configure_rack_mini_profiler + say 'Configuring rack mini profiler', :green + run_with_bundler 'bundle exec rails g rack_mini_profiler:install' + end + + private + + def bundle_install + run_with_bundler 'bundle install' + end + + def run_with_bundler(command) + Bundler.with_unbundled_env do + run command + end + end + end + end +end diff --git a/lib/generators/boring/rspec/install/install_generator.rb b/lib/generators/boring/rspec/install/install_generator.rb index 31f68eae..ae442d3b 100644 --- a/lib/generators/boring/rspec/install/install_generator.rb +++ b/lib/generators/boring/rspec/install/install_generator.rb @@ -14,7 +14,7 @@ class InstallGenerator < Rails::Generators::Base def add_rspec_gem log :adding, "rspec-rails" Bundler.with_unbundled_env do - run "bundle add rspec-rails --group='developement,test'" + run "bundle add rspec-rails --group='development,test'" end end diff --git a/lib/generators/boring/sentry/install/install_generator.rb b/lib/generators/boring/sentry/install/install_generator.rb new file mode 100644 index 00000000..932b987b --- /dev/null +++ b/lib/generators/boring/sentry/install/install_generator.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Boring + module Sentry + class InstallGenerator < Rails::Generators::Base + source_root File.expand_path("templates", __dir__) + desc 'Adds Sentry to the app' + + class_option :use_env_variable, type: :boolean, aliases: '-ev', + desc: 'Use ENV variable for Sentry. By default Rails credentials will be used.' + class_option :breadcrumbs_logger, type: :array, aliases: '-bl', default: [:active_support_logger, :http_logger], + desc: 'Set the breadcrumbs logger. By default [:active_support_logger, :http_logger] will be used.' + + def add_sentry_gems + say 'Adding Sentry gem', :green + + Bundler.with_unbundled_env do + run 'bundle add sentry-ruby sentry-rails' + end + end + + def configure_sentry_gem + say 'Configuring Sentry gem', :green + + @sentry_dsn_key = sentry_dsn_key + @breadcrumbs_logger_options = options[:breadcrumbs_logger].map(&:to_sym) + + template 'sentry.rb', 'config/initializers/sentry.rb' + + show_alert_message + end + + private + + def sentry_dsn_key + if options[:use_env_variable] + "ENV['SENTRY_DSN_KEY']" + else + "Rails.application.credentials.dig(:sentry, :dsn_key)" + end + end + + def show_alert_message + say "❗️❗️\nThe DSN key for Sentry will be used from `#{sentry_dsn_key}`. You can change this value if it doesn't match with your app.\n", :yellow + end + end + end +end diff --git a/lib/generators/boring/sentry/install/templates/sentry.rb b/lib/generators/boring/sentry/install/templates/sentry.rb new file mode 100644 index 00000000..32c95936 --- /dev/null +++ b/lib/generators/boring/sentry/install/templates/sentry.rb @@ -0,0 +1,7 @@ +Sentry.init do |config| + config.dsn = <%= @sentry_dsn_key %> + # enable performance monitoring + config.enable_tracing = true + # get breadcrumbs from logs + config.breadcrumbs_logger = <%= @breadcrumbs_logger_options %> +end \ No newline at end of file diff --git a/lib/generators/boring/vcr/install/install_generator.rb b/lib/generators/boring/vcr/install/install_generator.rb new file mode 100644 index 00000000..5a7d7102 --- /dev/null +++ b/lib/generators/boring/vcr/install/install_generator.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +module Boring + module Vcr + class InstallGenerator < Rails::Generators::Base + desc "Adds VCR to the application" + source_root File.expand_path('templates', __dir__) + + class_option :testing_framework, + type: :string, + alias: "-tf", + default: "rspec", + enum: %w[rspec minitest], + desc: "Tell us which test framework you are using. Default to rspec" + + class_option :stubbing_libraries, + type: :array, + alias: "-sl", + default: ['webmock'], + enum: %w[webmock typhoeus faraday excon], + desc: "Tell us stubbing library you want to use seprated by space. Default to webmock" + + def verify_presence_of_rails_helper + return if !rspec? || File.exist?("spec/rails_helper.rb") + + say "We couldn't find spec/rails_helper.rb. Please configure RSpec and rerun the generator. Consider running `rails generate boring:rspec:install` to set up RSpec.", + :red + + abort + end + + def verify_presence_of_test_helper + return if !minitest? || File.exist?("test/test_helper.rb") + + say "We couldn't find test/test_helper.rb. Please configure Minitest and rerun the generator.", + :red + end + + def add_vcr_gem + say "Adding VCR gems to Gemfile", :green + # TODO: Use check_and_install_gem method when it is available + gem 'vcr', group: :test + end + + def add_stubbing_library_gems + say "Adding stubbing library gems to Gemfile", :green + + options[:stubbing_libraries].uniq.each do |stubbing_library| + # TODO: Use check_and_install_gem method when it is available + gem stubbing_library, group: :test + end + end + + def setup_vcr_for_rspec + return unless rspec? + + say "Setting up VCR for RSpec", :green + + @stubbing_libraries = format_stubbing_libraries + + template("rspec/vcr.rb", "spec/support/vcr.rb") + + unless all_support_files_are_required? + inject_into_file "spec/rails_helper.rb", + "require 'support/vcr'\n\n", + before: /\A/ + end + end + + def setup_vcr_for_minitest + return unless minitest? + + say "Setting up VCR for Minitest", :green + + vcr_config = <<~RUBY + require "vcr" + + VCR.configure do |c| + c.cassette_library_dir = "test/vcr" + c.hook_into #{format_stubbing_libraries} + c.ignore_localhost = true + end + RUBY + + + inject_into_file "test/test_helper.rb", + vcr_config, + end: /^end\s*\Z/m + end + + private + + def format_stubbing_libraries + options[:stubbing_libraries] + .map { |stubbing_library| ":#{stubbing_library}" } + .join(', ') + end + + def rspec? + options[:testing_framework].to_s == 'rspec' + end + + def minitest? + options[:testing_framework].to_s == 'minitest' + end + + def all_support_files_are_required? + line_to_check = "Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f }" + rails_file_content_array = File.readlines("spec/rails_helper.rb") + rails_file_content_array.any? do |line| + line !~ /^\s*#/ && + (line.include?(line_to_check) || line.include?(line_to_check.gsub("'", '"'))) + end + end + end + end +end diff --git a/lib/generators/boring/vcr/install/templates/rspec/vcr.rb.tt b/lib/generators/boring/vcr/install/templates/rspec/vcr.rb.tt new file mode 100644 index 00000000..66c9797b --- /dev/null +++ b/lib/generators/boring/vcr/install/templates/rspec/vcr.rb.tt @@ -0,0 +1,8 @@ +require "vcr" + +VCR.configure do |c| + c.cassette_library_dir = "spec/cassettes" + c.hook_into <%= @stubbing_libraries %> + c.configure_rspec_metadata! + c.ignore_localhost = true +end diff --git a/test/generators/avo_install_generator_test.rb b/test/generators/avo_install_generator_test.rb new file mode 100644 index 00000000..1e974fff --- /dev/null +++ b/test/generators/avo_install_generator_test.rb @@ -0,0 +1,33 @@ +# frozen_string-literal: true + +require 'test_helper' +require 'generators/boring/avo/install/install_generator' + +class AvoInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Avo::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + + def destination_root + app_path + end + + def test_should_configure_avo_gem + Dir.chdir(app_path) do + quietly { run_generator } + + assert_gem 'avo' + + assert_file 'config/initializers/avo.rb' do |content| + assert_match(/Avo.configure do |config|/, content) + assert_match(/config.root_path = '\/avo'/, content) + end + + assert_file 'config/routes.rb' do |content| + assert_match(/mount Avo::Engine, at: Avo.configuration.root_path/, content) + end + end + end +end diff --git a/test/generators/devise/doorkeeper_devise_install_generator_test.rb b/test/generators/devise/doorkeeper_devise_install_generator_test.rb new file mode 100644 index 00000000..6246eabf --- /dev/null +++ b/test/generators/devise/doorkeeper_devise_install_generator_test.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/boring/devise/doorkeeper/install/install_generator" + +class DoorkeeperDeviseInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Devise::Doorkeeper::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_exit_if_devise_is_not_installed + assert_raises SystemExit do + quietly { generator.verify_presence_of_devise_gem } + end + end + + def test_should_exit_if_devise_model_is_not_found + assert_raises SystemExit do + quietly { generator.verify_presence_of_devise_model } + end + end + + def test_should_setup_doorkeeper + Dir.chdir(app_path) do + setup_devise + quietly { run_generator } + + assert_gem "doorkeeper" + assert_file "config/locales/doorkeeper.en.yml" + + assert_file "config/initializers/doorkeeper.rb" do |content| + assert_match(/resource_owner_authenticator do/, content) + assert_match(/admin_authenticator do/, content) + assert_match(/current_user || warden.authenticate!(scope: :user)/, content) + assert_no_match(/# admin_authenticator do/, content) + assert_match(/grant_flows %w\[authorization_code client_credentials\]/, content) + end + + assert_migration "db/migrate/create_doorkeeper_tables.rb" do |content| + assert_no_match(/# add_foreign_key :oauth/, content) + assert_no_match(//, content) + assert_match(/add_foreign_key :oauth_access_grants, :users, column: :resource_owner_id/, content) + assert_match(/add_foreign_key :oauth_access_tokens, :users, column: :resource_owner_id/, content) + end + + assert_file "config/routes.rb" do |content| + assert_match(/use_doorkeeper/, content) + end + + assert_file "app/models/user.rb" do |content| + assert_match(/has_many :access_grants/, content) + assert_match(/has_many :access_tokens/, content) + end + end + end + + def test_should_setup_password_grant_flow + Dir.chdir(app_path) do + setup_devise + quietly { run_generator %w[--grant_flows=password] } + + assert_migration "db/migrate/create_doorkeeper_tables.rb" do |content| + assert_match(/t.text :redirect_uri/, content) + end + + assert_file "config/initializers/doorkeeper.rb" do |content| + assert_match(/grant_flows %w\[password\]/, content) + assert_match(/resource_owner_from_credentials do |routes|/, content) + assert_match(/"Please configure doorkeeper resource_owner_authenticator block/, content) + end + end + end + + def test_should_setup_api_only + Dir.chdir(app_path) do + setup_devise + quietly { run_generator %w[--api_only] } + + assert_file "config/initializers/doorkeeper.rb" do |content| + assert_match(/api_only/, content) + assert_no_match(/# api_only/, content) + assert_match(/# admin_authenticator do/, content) + end + end + end + + def test_should_skip_applications_routes + Dir.chdir(app_path) do + setup_devise + quietly { run_generator %w[--skip_applications_routes] } + + assert_file "config/routes.rb" do |content| + assert_match(/use_doorkeeper do/, content) + assert_match(/skip_controllers :applications, :authorized_applications/, content) + end + end + end + + def test_should_use_refresh_token + Dir.chdir(app_path) do + setup_devise + quietly { run_generator %w[--use_refresh_token] } + + assert_file "config/initializers/doorkeeper.rb" do |content| + assert_match(/use_refresh_token/, content) + assert_no_match(/# use_refresh_token/, content) + end + end + end + + private + + def setup_devise(model_name: "User") + Bundler.with_unbundled_env do + `bundle add devise` + end + + create_devise_initializer + create_devise_model(model_name) + end + + def create_devise_initializer + FileUtils.mkdir_p("#{app_path}/config/initializers") + content = <<~RUBY + Devise.setup do |config| + end + RUBY + + File.write("#{app_path}/config/initializers/devise.rb", content) + end + + def create_devise_model(model_name) + FileUtils.mkdir_p("#{app_path}/app/models") + content = <<~RUBY + class #{model_name} < ApplicationRecord + end + RUBY + + File.write("#{app_path}/app/models/#{model_name.underscore}.rb", content) + end +end + diff --git a/test/generators/dotenv_install_generator_test.rb b/test/generators/dotenv_install_generator_test.rb new file mode 100644 index 00000000..81276d6d --- /dev/null +++ b/test/generators/dotenv_install_generator_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'generators/boring/dotenv/install/install_generator' + +class DotenvInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Dotenv::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + + def destination_root + app_path + end + + def test_should_install_dotenv_gem + Dir.chdir(app_path) do + quietly { run_generator } + + assert_gem 'dotenv-rails' + + assert_file '.env' do |content| + assert_match(/# Add your environment variables here/, content) + assert_match(/# SECRET_KEY_BASE=your_secret_key/, content) + end + + assert_file '.env.sample' + + assert_file '.gitignore' do |content| + assert_match(/.env/, content) + end + end + end +end \ No newline at end of file diff --git a/test/generators/figjam_install_generator_test.rb b/test/generators/figjam_install_generator_test.rb new file mode 100644 index 00000000..527ab56f --- /dev/null +++ b/test/generators/figjam_install_generator_test.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'generators/boring/figjam/install/install_generator' + +class FigjamInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Figjam::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + + def destination_root + app_path + end + + def test_should_configure_figjam + Dir.chdir(app_path) do + quietly { run_generator } + + assert_gem 'figjam' + + assert_file 'config/application.yml' do |content| + assert_match(/# Add configuration values here, as shown below/, content) + assert_match(/# GOOGLE_TAG_MANAGER_ID: GTM-12345/, content) + end + + assert_file 'config/application.yml.sample' + + assert_file '.gitignore' do |content| + assert_match(/config\/application.yml/, content) + end + end + end +end diff --git a/test/generators/honeybadger_install_generator_test.rb b/test/generators/honeybadger_install_generator_test.rb new file mode 100644 index 00000000..f76c224b --- /dev/null +++ b/test/generators/honeybadger_install_generator_test.rb @@ -0,0 +1,40 @@ +# fronzen_string_literal: true + +require 'test_helper' +require 'generators/boring/honeybadger/install/install_generator' + +class HoneybadgerInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Honeybadger::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + + def destination_root + app_path + end + + def test_should_configure_honeybadger + Dir.chdir(app_path) do + quietly { run_generator } + + assert_gem 'honeybadger' + + assert_file 'config/honeybadger.yml' do |content| + assert_match(/\napi_key: Rails.application.credentials.dig\(:honeybadger, :api_key\)\n/, content) + end + end + end + + def test_should_use_env_variable_for_api_key + Dir.chdir(app_path) do + quietly { run_generator([destination_root, '--use_env_variable']) } + + assert_gem 'honeybadger' + + assert_file 'config/honeybadger.yml' do |content| + assert_match(/api_key: ENV\['HONEYBADGER_API_KEY'\]\n/, content) + end + end + end +end \ No newline at end of file diff --git a/test/generators/pronto/base_generator_test.rb b/test/generators/pronto/base_generator_test.rb new file mode 100644 index 00000000..5c6b963b --- /dev/null +++ b/test/generators/pronto/base_generator_test.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/boring/pronto/base_generator" + +class ProntoBaseGeneratorTest < Rails::Generators::TestCase + tests Boring::Pronto::BaseGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_install_pronto_with_all_extension_gems + Dir.chdir(app_path) do + quietly { run_generator } + + assert_file "Gemfile" do |content| + assert_match("pronto", content) + assert_match("pronto-brakeman", content) + assert_match("pronto-flay", content) + assert_match("pronto-reek", content) + assert_match("pronto-rubocop", content) + end + end + end + + def test_should_skip_brakeman_extension + Dir.chdir(app_path) do + quietly do + generator({}, ['--extensions_to_skip=brakeman']).add_brakeman_extension + end + + assert_file "Gemfile" do |content| + refute_match("pronto-brakeman", content) + end + end + end + + def test_should_skip_flay_extension + Dir.chdir(app_path) do + quietly do + generator({}, ['--extensions_to_skip=flay']).add_flay_extension + end + + assert_file "Gemfile" do |content| + refute_match("pronto-flay", content) + end + end + end + + def test_should_skip_reek_extension + Dir.chdir(app_path) do + quietly do + generator({}, ['--extensions_to_skip=reek']).add_reek_extension + end + + assert_file "Gemfile" do |content| + refute_match("pronto-reek", content) + end + end + end + + def test_should_skip_rubocop_extension + Dir.chdir(app_path) do + quietly do + generator({}, ['--extensions_to_skip=rubocop']).add_rubocop_extension + end + + assert_file "Gemfile" do |content| + refute_match("pronto-rubocop", content) + end + end + end + + def test_should_skip_multiple_extensions + Dir.chdir(app_path) do + quietly do + generator({}, ['--extensions_to_skip=reek', 'rubocop']).add_rubocop_extension + end + + assert_file "Gemfile" do |content| + refute_match("pronto-reek", content) + refute_match("pronto-rubocop", content) + end + end + end +end diff --git a/test/generators/pronto/github_action_install_generator_test.rb b/test/generators/pronto/github_action_install_generator_test.rb new file mode 100644 index 00000000..d3b244c6 --- /dev/null +++ b/test/generators/pronto/github_action_install_generator_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/boring/pronto/github_action/install/install_generator" + +class ProntoGithubActionInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Pronto::GithubAction::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_configure_pronto_in_github_action_file + Dir.chdir(app_path) do + quietly do + generator.add_pronto_configuration_for_github_action + end + + template_app_ruby_version = `cat .ruby-version` + + assert_file ".github/workflows/pronto.yml" do |content| + assert_match("pronto:", content) + assert_match("ruby-version: #{template_app_ruby_version}", content) + end + end + end + + def test_should_add_correct_ruby_version + Dir.chdir(app_path) do + quietly do + generator({}, [ "--ruby_version=3.0.0" ]) + .add_pronto_configuration_for_github_action + end + + assert_file ".github/workflows/pronto.yml" do |content| + assert_match("ruby-version: 3.0.0", content) + end + end + end +end diff --git a/test/generators/pronto/gitlab_ci_install_generator_test.rb b/test/generators/pronto/gitlab_ci_install_generator_test.rb new file mode 100644 index 00000000..1f3cb3db --- /dev/null +++ b/test/generators/pronto/gitlab_ci_install_generator_test.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/boring/pronto/gitlab_ci/install/install_generator" + +class ProntoGitlabCiInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Pronto::GitlabCi::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_configure_pronto_in_new_gitlab_ci_file + Dir.chdir(app_path) do + quietly { run_generator } + + template_app_ruby_version = `cat .ruby-version` + + assert_file ".gitlab-ci.yml" do |content| + assert_match("stages:", content) + assert_match("pronto:", content) + assert_match("image: ruby:#{template_app_ruby_version}", content) + end + end + end + + def test_should_configure_pronto_in_existing_gitlab_ci_file + Dir.chdir(app_path) do + create_gitlab_ci_file + + quietly { run_generator } + + template_app_ruby_version = `cat .ruby-version` + stages_configuration = <<~RUBY + stages: + - lint + RUBY + + assert_file ".gitlab-ci.yml" do |content| + assert_match(stages_configuration, content) + assert_match("pronto:", content) + assert_match("image: ruby:#{template_app_ruby_version}", content) + end + end + end + + def test_should_add_correct_ruby_image + Dir.chdir(app_path) do + create_gitlab_ci_file + + quietly { run_generator [app_path, "--ruby_version=3.0.0"] } + + assert_file ".gitlab-ci.yml" do |content| + assert_match("image: ruby:3.0.0", content) + end + end + end + + def test_should_append_lint_to_existing_stages + Dir.chdir(app_path) do + create_gitlab_ci_file + inject_stages_to_ci_file + + quietly { run_generator } + + stages_configuration = <<~RUBY + stages: + - lint + - test + RUBY + + assert_file ".gitlab-ci.yml" do |content| + assert_match(stages_configuration, content) + end + end + end + + private + + def create_gitlab_ci_file + `touch #{app_path}/.gitlab-ci.yml` + end + + def inject_stages_to_ci_file + stages_configuration = <<~RUBY + stages: + - test + RUBY + + `echo "#{stages_configuration}\n" >> #{app_path}/.gitlab-ci.yml` + end +end diff --git a/test/generators/rack_cors_install_generator_test.rb b/test/generators/rack_cors_install_generator_test.rb new file mode 100644 index 00000000..8fd1a5bd --- /dev/null +++ b/test/generators/rack_cors_install_generator_test.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'generators/boring/rack_cors/install/install_generator' + +class RackCorsInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::RackCors::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_configure_rack_cors + Dir.chdir(app_path) do + quietly { run_generator } + + assert_gem 'rack-cors' + + assert_file 'config/initializers/cors.rb' do |content| + assert_match(/Rails.application.config.middleware.insert_before 0, Rack::Cors do/, content) + assert_match(/origins '*'/, content) + end + end + end + + def test_should_configure_custom_origins + Dir.chdir(app_path) do + quietly { run_generator %w[--origins=localhost:3000 example.com] } + + assert_file 'config/initializers/cors.rb' do |content| + assert_match(/Rails.application.config.middleware.insert_before 0, Rack::Cors do/, content) + assert_match(/origins 'localhost:3000', 'example.com'/, content) + end + end + end +end diff --git a/test/generators/rack_mini_profiler_install_generator_test.rb b/test/generators/rack_mini_profiler_install_generator_test.rb new file mode 100644 index 00000000..c9333025 --- /dev/null +++ b/test/generators/rack_mini_profiler_install_generator_test.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'generators/boring/rack_mini_profiler/install/install_generator' + +class RackMiniProfilerGeneratorTest < Rails::Generators::TestCase + tests Boring::RackMiniProfiler::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + + def destination_root + app_path + end + + def test_should_configure_rack_mini_profiler_gem + Dir.chdir(app_path) do + quietly { run_generator } + + assert_file 'Gemfile' do |content| + assert_match(/gem 'rack-mini-profiler', require: false/, content) + end + + assert_file 'config/initializers/rack_mini_profiler.rb' do |content| + assert_match(/if Rails\.env\.development\?/, content) + assert_match(/Rack::MiniProfilerRails.initialize!\(Rails.application\)/, content) + end + end + end +end diff --git a/test/generators/sentry_install_generator_test.rb b/test/generators/sentry_install_generator_test.rb new file mode 100644 index 00000000..4a2ca49e --- /dev/null +++ b/test/generators/sentry_install_generator_test.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'generators/boring/sentry/install/install_generator' + +class SentryInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Sentry::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_configure_sentry_gem + Dir.chdir(app_path) do + quietly { run_generator } + + assert_gem 'sentry-ruby' + assert_gem 'sentry-rails' + + assert_file 'config/initializers/sentry.rb' do |content| + assert_match(/config.dsn = Rails.application.credentials.dig\(:sentry, :dsn_key\)/, content) + assert_match(/config.breadcrumbs_logger = \[:active_support_logger, :http_logger\]\n/, content) + end + end + end + + def test_should_configure_dsn_key_as_env_variable + Dir.chdir(app_path) do + quietly { run_generator [destination_root, '--use_env_variable'] } + + assert_gem 'sentry-ruby' + assert_gem 'sentry-rails' + + assert_file 'config/initializers/sentry.rb' do |content| + assert_match(/config.dsn = ENV\['SENTRY_DSN_KEY'\]\n/, content) + end + end + end + + def test_should_configure_custom_breadcrumbs_logger + Dir.chdir(app_path) do + quietly { run_generator [destination_root, "--breadcrumbs_logger=http_logger"]} + + assert_gem 'sentry-ruby' + assert_gem 'sentry-rails' + + assert_file 'config/initializers/sentry.rb' do |content| + assert_match(/config.breadcrumbs_logger = \[:http_logger\]\n/, content) + end + end + end +end diff --git a/test/generators/vcr/vcr_install_generator_test.rb b/test/generators/vcr/vcr_install_generator_test.rb new file mode 100644 index 00000000..4dc6a5dd --- /dev/null +++ b/test/generators/vcr/vcr_install_generator_test.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require "test_helper" +require "generators/boring/vcr/install/install_generator" + +class VcrInstallGeneratorTest < Rails::Generators::TestCase + tests Boring::Vcr::InstallGenerator + setup :build_app + teardown :teardown_app + + include GeneratorHelper + include ActiveSupport::Testing::Isolation + + def destination_root + app_path + end + + def test_should_exit_if_rails_helper_is_not_present + assert_raises SystemExit do + quietly { run_generator } + end + end + + def test_should_configure_vcr + Dir.chdir(app_path) do + configure_rspec + quietly { run_generator } + + assert_gem "vcr" + assert_gem "webmock" + assert_file "spec/support/vcr.rb" do |content| + assert_match(/require "vcr"/, content) + assert_match(/VCR.configure do |c|/, content) + assert_match(/c.hook_into :webmock/, content) + assert_match(/c.configure_rspec_metadata!/, content) + end + + assert_file 'spec/rails_helper.rb' do |content| + assert_match(/require 'support\/vcr'/, content) + end + end + end + + def test_should_configure_vcr_for_minitest + Dir.chdir(app_path) do + quietly { run_generator %w[--testing_framework=minitest] } + + assert_file "test/test_helper.rb" do |content| + assert_match(/require "vcr"/, content) + assert_match(/VCR.configure do |c|/, content) + assert_match(/c.hook_into :webmock/, content) + assert_no_match(/c.configure_rspec_metadata!/, content) + end + end + end + + def test_should_configure_vcr_with_correct_stubbing_libraries + Dir.chdir(app_path) do + configure_rspec + quietly { run_generator %w[--stubbing_libraries=webmock typhoeus] } + + assert_gem "webmock" + assert_gem "typhoeus" + + assert_file "spec/support/vcr.rb" do |content| + assert_match(/c.hook_into :webmock, :typhoeus/, content) + end + end + end + + private + + def configure_rspec + Bundler.with_unbundled_env do + `bundle add rspec-rails --group development,test` + end + + create_rails_helper + end + + def create_rails_helper + FileUtils.mkdir_p("#{app_path}/spec") + content = <<~RUBY + RSpec.configure do |config| + config.use_transactional_fixtures = true + end + RUBY + + File.write("#{app_path}/spec/rails_helper.rb", content) + end +end