diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2506772337..60a56f97ad 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add GitHub CI files for dependabot, brakeman, rubocop, and running tests by default. Can be skipped with --skip-ci. + + *DHH* + * Add brakeman gem by default for static analysis of security vulnerabilities. Allow skipping with --skip-brakeman option. *vipulnsward* diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index ef2ca10aac..fd9dbe7d77 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -106,6 +106,9 @@ def self.add_shared_options_for(name) class_option :skip_brakeman, type: :boolean, default: nil, desc: "Skip brakeman setup" + class_option :skip_ci, type: :boolean, default: nil, + desc: "Skip GitHub CI files" + class_option :dev, type: :boolean, default: nil, desc: "Set up the #{name} with Gemfile pointing to your Rails checkout" @@ -393,6 +396,10 @@ def skip_brakeman? options[:skip_brakeman] end + def skip_ci? + options[:skip_ci] + end + class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) def initialize(name, version, comment, options = {}, commented_out = false) super diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 7c4d3d576c..2a0d9dcebf 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -82,6 +82,12 @@ def dockerfiles chmod "bin/docker-entrypoint", 0755 & ~File.umask, verbose: false end + def cifiles + empty_directory ".github/workflows" + template "github/ci.yml", ".github/workflows/ci.yaml" + template "github/dependabot.yml", ".github/dependabot.yaml" + end + def rubocop template "rubocop.yml", ".rubocop.yml" end @@ -377,6 +383,11 @@ def create_rubocop_file build(:rubocop) end + def create_cifiles + return if skip_ci? + build(:cifiles) + end + def create_config_files build(:config) end diff --git a/railties/lib/rails/generators/rails/app/templates/github/ci.yml.tt b/railties/lib/rails/generators/rails/app/templates/github/ci.yml.tt new file mode 100644 index 0000000000..09b9637c35 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/github/ci.yml.tt @@ -0,0 +1,105 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: +<%- unless skip_brakeman? -%> + scan: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for security vulnerabilities + run: bin/brakeman +<% end -%> +<%- unless skip_rubocop? -%> + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop +<% end -%> + + test: + runs-on: ubuntu-latest + + <%- if options[:database] == "sqlite3" -%> + # services: + # redis: + # image: redis + # ports: + # - 6379:6379 + # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + <%- else -%> + services: + <%- if options[:database] == "mysql" || options[:database] == "trilogy" -%> + mysql: + image: mysql + env: + MYSQL_ALLOW_EMPTY_PASSWORD: true + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + <%- elsif options[:database] == "postgresql" -%> + postgres: + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3 + <%- end -%> + + # redis: + # image: redis + # ports: + # - 6379:6379 + # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + + <%- end -%> + steps: + - name: Install packages + run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable <%= (dockerfile_deploy_packages + [build_package_for_database]).join(" ") %> + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Run Tests + env: + RAILS_ENV: test + <%- if options[:database] == "mysql" || options[:database] == "trilogy" -%> + DATABASE_URL: mysql2://127.0.0.1:3306 + <%- elsif options[:database] == "postgresql" -%> + DATABASE_URL: postgres://postgres:postgres@localhost:5432 + <%- end -%> + # REDIS_URL: redis://localhost:6379/0 + run: bin/rails db:setup test test:system diff --git a/railties/lib/rails/generators/rails/app/templates/github/dependabot.yml b/railties/lib/rails/generators/rails/app/templates/github/dependabot.yml new file mode 100644 index 0000000000..452ebb342b --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5967611b50..62ebd8f480 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -653,13 +653,25 @@ def test_brakeman_is_skipped_if_required end def test_both_brakeman_and_rubocop_binstubs_are_skipped_if_required - puts destination_root run_generator [destination_root, "--skip-brakeman", "--skip-rubocop"] assert_no_file "bin/rubocop" assert_no_file "bin/brakeman" end + def test_inclusion_of_ci_files + run_generator + assert_file ".github/workflows/ci.yml" + assert_file ".github/dependabot.yml" + end + + def test_ci_files_are_skipped_if_required + run_generator [destination_root, "--skip-ci"] + + assert_no_file ".github/workflows/ci.yml" + assert_no_file ".github/dependabot.yml" + end + def test_usage_read_from_file assert_called(File, :read, returns: "USAGE FROM FILE") do assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc