Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce suspenders:ci generator #1172

Merged
merged 1 commit into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Unreleased
* Introduce `suspenders:email` generator
* Introduce `suspenders:testing` generator
* Introduce `suspenders:prerequisites` generator
* Introduce `suspenders:ci` generator

20230113.0 (January, 13, 2023)

Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ Configures prerequisites. Currently Node.
bin/rails g suspenders:prerequisites
```

### CI

Creates CI files for GitHub Actions.

```
bin/rails g suspenders:ci
```

## Contributing

See the [CONTRIBUTING] document.
Expand Down
52 changes: 52 additions & 0 deletions lib/generators/suspenders/ci_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module Suspenders
module Generators
class CiGenerator < Rails::Generators::Base
include Suspenders::Generators::DatabaseUnsupported
include Suspenders::Generators::Helpers

source_root File.expand_path("../../templates/ci", __FILE__)
desc "Creates CI files for GitHub Actions"

def ci_files
empty_directory ".github/workflows"
template "ci.yml", ".github/workflows/ci.yaml"
template "dependabot.yml", ".github/dependabot.yaml"
end

private

def scan_ruby?
has_gem? "bundler-audit"
end

def scan_js?
File.exist?("bin/importmap") && using_node?
end

def lint?
using_node? && has_gem?("standard") && has_yarn_script?("lint")
end

def using_node?
File.exist? "package.json"
end

def has_gem?(name)
Bundler.rubygems.find_name(name).any?
end

def using_rspec?
File.exist? "spec"
end

def has_yarn_script?(name)
return false if !using_node?

content = File.read("package.json")
json = JSON.parse(content)

json.dig("scripts", name)
end
end
end
end
148 changes: 148 additions & 0 deletions lib/generators/templates/ci/ci.yml.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: CI

on:
pull_request:
push:
branches: [main]

jobs:
<%- if scan_ruby? -%>
scan_ruby:
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 in Ruby dependencies
run: |
bin/rails bundle:audit:update
bin/rails bundle:audit
<% end -%>

<%- if scan_js? -%>
scan_js:
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: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: .node-version

- name: Install modules
run: yarn install

- name: Scan for security vulnerabilities in JavaScript dependencies
run: |
bin/importmap audit
yarn audit
<% end -%>

<%- if lint? -%>
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: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: .node-version

- name: Install modules
run: yarn install

- name: Lint Ruby code for consistent style
run: bin/rails standard

- name: Lint front-end code for consistent style
run: yarn lint
<% end -%>

test:
runs-on: ubuntu-latest

services:
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

# redis:
# image: redis
# ports:
# - 6379:6379
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- name: Install packages
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libvips postgresql-client libpq-dev

- name: Checkout code
uses: actions/checkout@v4

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true

<%- if using_node? -%>
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: .node-version

- name: Install modules
run: yarn install
stevepolitodesign marked this conversation as resolved.
Show resolved Hide resolved
<%- end -%>

- name: Run tests
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432
# REDIS_URL: redis://localhost:6379/0
<%- if using_rspec? -%>
run: bin/rails db:setup spec
<%- else -%>
run: bin/rails db:setup test test:system
<%- end -%>

- name: Keep screenshots from failed system tests
uses: actions/upload-artifact@v4
if: failure()
with:
name: screenshots
<%- if using_rspec? -%>
path: ${{ github.workspace }}/tmp/capybara
<%- else -%>
path: ${{ github.workspace }}/tmp/screenshots
<%- end -%>
if-no-files-found: ignore
7 changes: 7 additions & 0 deletions lib/generators/templates/ci/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
28 changes: 28 additions & 0 deletions lib/suspenders/generators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,33 @@ def api_only_app?
.match?(/^\s*config\.api_only\s*=\s*true/i)
end
end

module DatabaseUnsupported
class Error < StandardError
def message
"This generator requires PostgreSQL"
end
end

extend ActiveSupport::Concern

included do
def raise_if_database_unsupported
if database_unsupported?
raise Suspenders::Generators::DatabaseUnsupported::Error
end
end

private

def database_unsupported?
configuration = File.read(Rails.root.join("config/database.yml"))
configuration = YAML.load(configuration, aliases: true)
adapter = configuration["default"]["adapter"]

adapter != "postgresql"
end
end
end
stevepolitodesign marked this conversation as resolved.
Show resolved Hide resolved
end
end
40 changes: 40 additions & 0 deletions test/generators/suspenders/ci_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "test_helper"
require "generators/suspenders/ci_generator"

module Suspenders
module Generators
class CiGeneratorTest < Rails::Generators::TestCase
include Suspenders::TestHelpers

tests Suspenders::Generators::CiGenerator
destination Rails.root
teardown :restore_destination

test "generates CI files" do
with_database "postgresql" do
run_generator

assert_file app_root(".github/workflows/ci.yaml")
assert_file app_root(".github/dependabot.yaml")
end
end

test "raises if PostgreSQL is not the adapter" do
with_database "unsupported" do
assert_raises Suspenders::Generators::DatabaseUnsupported::Error, match: "This generator requires PostgreSQL" do
run_generator

assert_no_file app_root(".github/workflows/ci.yaml")
assert_no_file app_root(".github/dependabot.yaml")
end
end
end

private

def restore_destination
remove_dir_if_exists ".github"
end
end
end
end
8 changes: 8 additions & 0 deletions test/suspenders/generators_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ class APIAppUnsupportedTest < Suspenders::GeneratorsTest
assert_equal expected, Suspenders::Generators::APIAppUnsupported::Error.new.message
end
end

class DatabaseUnsupportedTest < Suspenders::GeneratorsTest
test "message returns a custom message" do
expected = "This generator requires PostgreSQL"

assert_equal expected, Suspenders::Generators::DatabaseUnsupported::Error.new.message
end
end
end
12 changes: 12 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ def with_test_suite(test_suite, &block)
remove_dir_if_exists "spec"
end

def with_database(database, &block)
backup_file "config/database.yml"
configuration = File.read app_root("config/database.yml")
configuration = YAML.load(configuration, aliases: true)
configuration["default"]["adapter"] = database
File.open(app_root("config/database.yml"), "w") { _1.write configuration.to_yaml }

yield
ensure
restore_file "config/database.yml"
end

def backup_file(file)
FileUtils.copy app_root(file), app_root("#{file}.bak")
end
Expand Down