rails-quality-assurance 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4fbc6fbabe01efd826fc05c8a532acc86d0a27b3daa02011637636c8a2cc82b4
4
+ data.tar.gz: 8b5b8f23af17448d99ed7eff051e39d2df7167c5250ad3a1c7ed84faaf167167
5
+ SHA512:
6
+ metadata.gz: caa5a305daad24a7341a93fd599f07e3fcc1a29b7f1f48a49278601e6a7dda3ab8cfece9eb19ad24c932b23f731a51f8c9d1c7e8807aedf6b83a11e9c45bff34
7
+ data.tar.gz: 6eeaf4ad31f53038d9cb23b3d2ec1746fbbc24f01bdce1b315869236f523cba13c4f6d54cb82e67a7d9a4cc7f50a701deb968cc47681aea384a933f911d8dc2d
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-09-14
9
+
10
+ ### Added
11
+
12
+ - Initial release of the Rails Quality Assurance kit.
13
+ - Shared RuboCop configuration for plugins: `rubocop-performance`, `rubocop-rspec`,
14
+ `rubocop-rspec_rails`, `rubocop-capybara`, `rubocop-factory_bot`,
15
+ `rubocop-rubycw`, `rubocop-migration`, `rubocop-rails`.
16
+ - `inherit_mode.merge: [Exclude]` so consuming projects add their own
17
+ exclusions without dropping the shared ones.
18
+ - Reek base configuration with Rails-appropriate defaults.
19
+ - Biome and Stylelint configurations for frontend linting.
20
+ - Single-require test setup via `rails_quality_assurance/all`: SimpleCov
21
+ 100% line & branch coverage with LCOV output, RSpec core defaults, and
22
+ Playwright/Capybara system test driver with parallel worker port
23
+ allocation and failure HTML dumps.
24
+ - Rake tasks for quality checks (`qa:lint`, `qa:frontend`, `spec:parallel`).
25
+ - `rails_quality_assurance:ci` generator installing `bin/ci` and
26
+ `config/ci.rb` for host-level CI execution.
27
+ - Release workflow using RubyGems trusted publishing (OIDC).
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mauricio Zaffari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Rails Quality Assurance
2
+
3
+ Opinionated quality assurance kit for Ruby on Rails applications.
4
+
5
+ `rails-quality-assurance` provides an out-of-the-box, comprehensive quality control setup designed to work with zero-configuration defaults. It integrates the standard quality control suite used across Develoz Rails applications.
6
+
7
+ ## What's Included
8
+
9
+ ### 1. Style & Linting
10
+
11
+ - **RuboCop**: Pre-configured `rubocop.yml` targeting Ruby 4.0 with all standard plugins loaded (`rubocop-rails`, `rubocop-rspec`, `rubocop-rspec_rails`, `rubocop-performance`, `rubocop-capybara`, `rubocop-factory_bot`, `rubocop-rubycw`, `rubocop-migration`).
12
+ - **Reek**: Code smell detection with sensible Rails defaults (`config/reek.yml`).
13
+ - **Flay**: Structural code duplication analysis.
14
+ - **Biome**: Pre-configured `config/biome.json` for fast JavaScript/TypeScript/JSON linting and formatting.
15
+ - **Stylelint**: Pre-configured `config/.stylelintrc.json` with Tailwind CSS support.
16
+
17
+ ### 2. Security
18
+
19
+ - **Brakeman**: Static analysis security vulnerability scanner.
20
+ - **Bundler Audit**: Vulnerability scanner for gem dependencies.
21
+
22
+ ### 3. Testing & Coverage
23
+
24
+ - **RSpec**: Shared helpers and configuration.
25
+ - **Playwright + Capybara**: Remote Chromium headless driver support with auto port assignment for parallel workers and HTML error state dumps on failure (`require 'rails_quality_assurance/playwright'`).
26
+ - **Parallel Tests**: `rake spec:parallel` task with automated system test isolation.
27
+ - **SimpleCov**: 100% line and branch coverage threshold enforcement, LCOV reporting, and parallel process reporting (`require 'rails_quality_assurance/simplecov'`).
28
+
29
+ ### 4. Continuous Integration Harness
30
+
31
+ - **`RailsQualityAssurance::CI`**: Built-in pipeline runner modeled after Rails CI harnesses.
32
+ - **CLI / Executable**: Run with `bundle exec rails-qa` or define your pipeline in `config/ci.rb`.
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ Add to your `Gemfile`:
39
+
40
+ ```ruby
41
+ group :development, :test do
42
+ gem 'rails-quality-assurance', require: false
43
+ end
44
+ ```
45
+
46
+ Then run:
47
+
48
+ ```bash
49
+ bundle install
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Zero-Config Usage
55
+
56
+ ### 1. RuboCop
57
+
58
+ In your `.rubocop.yml`:
59
+
60
+ ```yaml
61
+ inherit_gem:
62
+ rails-quality-assurance: rubocop.yml
63
+
64
+ # Only your application-specific overrides are needed here
65
+ ```
66
+
67
+ ### 2. Single Require for Testing & Coverage (spec/spec_helper.rb)
68
+
69
+ Add a single line at the very top of `spec/spec_helper.rb`:
70
+
71
+ ```ruby
72
+ require 'rails_quality_assurance/all'
73
+ ```
74
+
75
+ That single require sets up:
76
+
77
+ 1. **SimpleCov**: Starts immediately before application code loads, enforcing 100% line & branch coverage thresholds, parallel process aggregation, and LCOV output.
78
+ 2. **RSpec**: Configures mock verification of partial doubles, expectations, and metadata inheritance.
79
+ 3. **Playwright + Capybara System Tests**: Registers the `:remote_playwright` headless driver, defaults system specs to `:rack_test`, escalates to Playwright for `js: true`, dynamically allocates ports for `parallel_tests` workers, and dumps HTML failure snapshots into `tmp/capybara`.
80
+
81
+ *(If you prefer to load them individually, `require 'rails_quality_assurance/simplecov'` and `require 'rails_quality_assurance/playwright'` remain available.)*
82
+
83
+ ### 3. Continuous Integration: `bin/ci`
84
+
85
+ Run the generator to install `bin/ci` and `config/ci.rb`:
86
+
87
+ ```bash
88
+ bin/rails generate rails_quality_assurance:ci
89
+ ```
90
+
91
+ This generates `bin/ci` (executable) and `config/ci.rb` following Develoz standards (matching Pitwall and Develoz B2B).
92
+
93
+ Run CI anytime on the host:
94
+
95
+ ```bash
96
+ bin/ci
97
+ ```
98
+ Or in Docker-based setups:
99
+ ```bash
100
+ bin/run bin/ci
101
+ ```
102
+
103
+ ### 4. Rake Tasks (Optional)
104
+
105
+ The gem also provides tasks if you prefer `rake`:
106
+
107
+ ```bash
108
+ bin/rails qa:lint # Runs RuboCop, Reek, Flay, Brakeman, bundler-audit
109
+ bin/rails qa:frontend # Runs Biome and Stylelint
110
+ bin/rails spec:parallel # Runs RSpec in parallel with system specs isolated
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Development
116
+
117
+ ```bash
118
+ bundle install
119
+ bundle exec rspec
120
+ bundle exec rubocop
121
+ ```
122
+
123
+ ## License
124
+
125
+ Released under the [MIT License](https://opensource.org/license/mit/).
data/config/biome.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
3
+ "assist": {
4
+ "actions": {
5
+ "source": {
6
+ "organizeImports": "on"
7
+ }
8
+ }
9
+ },
10
+ "linter": {
11
+ "enabled": true,
12
+ "rules": {
13
+ "recommended": true,
14
+ "correctness": {
15
+ "noUnusedImports": "warn",
16
+ "noUnusedVariables": "warn"
17
+ },
18
+ "suspicious": {
19
+ "useIterableCallbackReturn": "off",
20
+ "noAssignInExpressions": "off",
21
+ "noDocumentCookie": "off"
22
+ }
23
+ }
24
+ },
25
+ "formatter": {
26
+ "enabled": true,
27
+ "indentStyle": "space",
28
+ "indentWidth": 2,
29
+ "lineWidth": 120
30
+ },
31
+ "javascript": {
32
+ "formatter": {
33
+ "quoteStyle": "double",
34
+ "semicolons": "asNeeded"
35
+ }
36
+ },
37
+ "json": {
38
+ "formatter": {
39
+ "indentWidth": 2
40
+ }
41
+ },
42
+ "files": {
43
+ "includes": [
44
+ "app/javascript/**/*.js",
45
+ "app/javascript/**/*.ts",
46
+ "app/javascript/**/*.jsx",
47
+ "app/javascript/**/*.tsx"
48
+ ]
49
+ }
50
+ }
data/config/reek.yml ADDED
@@ -0,0 +1,33 @@
1
+ ---
2
+ # Base Reek smell detectors for Rails applications
3
+ exclude_paths:
4
+ - app/assets
5
+ - config
6
+ - db
7
+ - lib/tasks
8
+ - vendor
9
+ - spec
10
+ - node_modules
11
+
12
+ detectors:
13
+ IrresponsibleModule:
14
+ enabled: false
15
+ UtilityFunction:
16
+ enabled: false
17
+ ControlParameter:
18
+ enabled: false
19
+ BooleanParameter:
20
+ enabled: false
21
+ FeatureEnvy:
22
+ enabled: false
23
+ TooManyStatements:
24
+ max_statements: 15
25
+ TooManyMethods:
26
+ max_methods: 25
27
+ UncommunicativeVariableName:
28
+ accept:
29
+ - e
30
+ - id
31
+ - f
32
+ - i
33
+ - n
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/base'
5
+
6
+ module RailsQualityAssurance
7
+ module Generators
8
+ # Generates bin/ci and config/ci.rb for host-level CI execution
9
+ class CiGenerator < Rails::Generators::Base
10
+ source_root File.expand_path('templates', __dir__)
11
+
12
+ desc 'Installs bin/ci and config/ci.rb for Rails Continuous Integration'
13
+
14
+ def copy_ci_files
15
+ template 'bin_ci.erb', 'bin/ci'
16
+ chmod 'bin/ci', 0o755
17
+ template 'config_ci.rb.erb', 'config/ci.rb'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../config/boot'
5
+ require 'rails_quality_assurance/ci_runner'
6
+
7
+ CI = ActiveSupport::ContinuousIntegration
8
+ require_relative '../config/ci'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Run using bin/ci
4
+ CI.run do
5
+ step 'Setup', 'bin/setup --skip-server'
6
+
7
+ step 'Style: Ruby', 'bin/rubocop'
8
+ step 'Style: Reek', 'bundle exec rake qa:reek'
9
+ step 'Style: Flay', 'bundle exec rake qa:flay'
10
+
11
+ step 'Security: Gem audit', 'bundle exec rake qa:audit'
12
+ step 'Security: Brakeman', 'bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error'
13
+
14
+ step 'Frontend: Biome', 'npm run biome' if File.exist?('biome.json')
15
+ step 'Frontend: Stylelint', 'npm run stylelint' if File.exist?('.stylelintrc.json')
16
+
17
+ step 'Tests: RSpec (parallel)', 'bin/rails spec:parallel'
18
+
19
+ # Coverage gate: enforced by SimpleCov in spec/spec_helper.rb (100% line + branch)
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 1. Start SimpleCov immediately before any app code is loaded
4
+ require 'rails_quality_assurance/simplecov'
5
+
6
+ # 2. Configure RSpec core settings if RSpec is present
7
+ require 'rails_quality_assurance/rspec'
8
+
9
+ # 3. Configure Capybara + Playwright system test driver
10
+ require 'rails_quality_assurance/playwright'
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ module RailsQualityAssurance
6
+ # Continuous Integration test and lint execution harness
7
+ class CI
8
+ # Represents a single verification step in the pipeline
9
+ class Step
10
+ attr_reader :name, :command
11
+
12
+ def initialize(name, command)
13
+ @name = name
14
+ @command = command
15
+ end
16
+
17
+ def run
18
+ banner("Running: #{name}")
19
+
20
+ system(command)
21
+ exit_code = $CHILD_STATUS&.exitstatus || 1
22
+
23
+ if exit_code.zero?
24
+ puts "✓ #{name} passed"
25
+ else
26
+ puts "✗ #{name} failed (exit code: #{exit_code})"
27
+ Kernel.exit(exit_code)
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.run(&)
33
+ runner = new
34
+ runner.instance_eval(&) if block_given?
35
+ runner.execute
36
+ end
37
+
38
+ def initialize
39
+ @steps = []
40
+ end
41
+
42
+ def step(name, command = nil, &block)
43
+ @steps << [name, command || block]
44
+ end
45
+
46
+ def execute
47
+ banner('Rails Quality Assurance Pipeline')
48
+
49
+ @steps.each do |name, action|
50
+ if action.is_a?(Proc)
51
+ banner("Running: #{name}")
52
+ action.call
53
+ else
54
+ Step.new(name, action).run
55
+ end
56
+ end
57
+
58
+ banner('✓ All Quality Assurance checks passed')
59
+ end
60
+
61
+ private
62
+
63
+ def banner(text)
64
+ separator = '=' * 60
65
+ puts "\n#{separator}"
66
+ puts text
67
+ puts separator
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/ci'
4
+
5
+ # Emulate ActiveSupport::ContinuousIntegration interface if needed
6
+ unless defined?(ActiveSupport::ContinuousIntegration)
7
+ module ActiveSupport
8
+ module ContinuousIntegration
9
+ def self.run(&)
10
+ RailsQualityAssurance::CI.run(&)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsQualityAssurance
4
+ module PlaywrightHelper
5
+ def self.configure!
6
+ require 'capybara'
7
+ require 'capybara-playwright-driver'
8
+
9
+ playwright_url = ENV.fetch('PLAYWRIGHT_URL', nil)
10
+ capybara_port = ENV.fetch('CAPYBARA_SERVER_PORT', 3001)
11
+
12
+ register_capybara_driver(playwright_url)
13
+ configure_rspec(playwright_url, capybara_port) if defined?(RSpec)
14
+ end
15
+
16
+ def self.register_capybara_driver(playwright_url)
17
+ Capybara.register_driver :remote_playwright do |app|
18
+ options = {
19
+ browser_type: :chromium,
20
+ headless: true,
21
+ args: ['--disable-dev-shm-usage'],
22
+ viewport: { width: 1400, height: 1400 },
23
+ screen: { width: 1400, height: 1400 }
24
+ }
25
+
26
+ if playwright_url.present?
27
+ options[:browser_server_endpoint_url] = playwright_url
28
+ elsif local_playwright_cli
29
+ options[:playwright_cli_executable_path] = local_playwright_cli.to_s
30
+ end
31
+
32
+ Capybara::Playwright::Driver.new(app, **options)
33
+ end
34
+ end
35
+
36
+ def self.local_playwright_cli
37
+ return nil unless defined?(Rails)
38
+
39
+ cli_path = Rails.root.join('node_modules/.bin/playwright-core')
40
+ cli_path.executable? ? cli_path : nil
41
+ end
42
+
43
+ def self.configure_rspec(playwright_url, capybara_port)
44
+ RSpec.configure do |config|
45
+ setup_system_hooks(config, playwright_url, capybara_port)
46
+ setup_failure_dump(config)
47
+ end
48
+ end
49
+
50
+ def self.setup_system_hooks(config, playwright_url, capybara_port)
51
+ config.before(type: :system) do
52
+ Capybara.default_max_wait_time = 10
53
+ driven_by :rack_test
54
+ end
55
+
56
+ config.before(type: :system, js: true) do
57
+ PlaywrightHelper.apply_js_system_driver(playwright_url, capybara_port)
58
+ end
59
+ end
60
+
61
+ def self.apply_js_system_driver(playwright_url, capybara_port)
62
+ if playwright_url.present?
63
+ Capybara.server_host = '0.0.0.0'
64
+ base_port = capybara_port.to_i
65
+ test_env_number = (ENV.fetch('TEST_ENV_NUMBER', nil) || '0').to_i
66
+ Capybara.server_port = base_port + test_env_number
67
+
68
+ hostname = Socket.gethostname
69
+ Capybara.app_host = "http://#{hostname}:#{Capybara.server_port}"
70
+ else
71
+ Capybara.server_port = nil
72
+ Capybara.app_host = nil
73
+ end
74
+
75
+ Capybara.current_driver = :remote_playwright
76
+ end
77
+
78
+ def self.setup_failure_dump(config)
79
+ config.after(type: :system) do |example|
80
+ next unless example.exception
81
+ next unless defined?(Rails)
82
+
83
+ session = Capybara.current_session
84
+ next unless session.current_url.to_s.start_with?('http')
85
+
86
+ save_dir = Rails.root.join('tmp/capybara')
87
+ FileUtils.mkdir_p(save_dir)
88
+ slug = example.full_description.parameterize.first(120)
89
+ path = save_dir.join("failure-#{slug}-#{Process.pid}.html")
90
+ File.write(path, session.html)
91
+ rescue StandardError
92
+ # Never fail the suite on diagnostic errors
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/playwright/helper'
4
+ RailsQualityAssurance::PlaywrightHelper.configure!
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module RailsQualityAssurance
6
+ class Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ load File.expand_path('../tasks/quality_assurance.rake', __dir__)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/simplecov/helper'
4
+ require 'rails_quality_assurance/playwright/helper'
5
+
6
+ module RailsQualityAssurance
7
+ module RSpecHelper
8
+ def self.configure!
9
+ return unless defined?(RSpec)
10
+
11
+ RSpec.configure do |config|
12
+ configure_expectations(config)
13
+ configure_mocks(config)
14
+ config.shared_context_metadata_behavior = :apply_to_host_groups
15
+ config.order = :random
16
+ end
17
+ end
18
+
19
+ def self.configure_expectations(config)
20
+ config.expect_with :rspec do |expectations|
21
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
22
+ end
23
+ end
24
+
25
+ def self.configure_mocks(config)
26
+ config.mock_with :rspec do |mocks|
27
+ mocks.verify_partial_doubles = true
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/rspec/helper'
4
+ RailsQualityAssurance::RSpecHelper.configure!
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsQualityAssurance
4
+ module SimpleCovHelper
5
+ def self.configure!
6
+ return if ENV['NO_COVERAGE']
7
+ return if filtered_run? && !ENV['COVERAGE']
8
+
9
+ require 'simplecov'
10
+ require 'simplecov-lcov'
11
+
12
+ configure_lcov_formatter
13
+ SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([
14
+ SimpleCov::Formatter::HTMLFormatter,
15
+ SimpleCov::Formatter::LcovFormatter
16
+ ])
17
+ start_coverage
18
+ end
19
+
20
+ def self.filtered_run?
21
+ return false unless defined?(RSpec)
22
+
23
+ rspec_config = RSpec.configuration
24
+ rspec_config.files_to_run.one? ||
25
+ rspec_config.only_failures? ||
26
+ (rspec_config.inclusion_filter.rules&.size&.> 0)
27
+ end
28
+
29
+ def self.configure_lcov_formatter
30
+ SimpleCov::Formatter::LcovFormatter.config do |formatter|
31
+ formatter.report_with_single_file = true
32
+ formatter.single_report_path = 'public/coverage/lcov.info'
33
+ end
34
+ end
35
+
36
+ def self.start_coverage
37
+ SimpleCov.start 'rails' do
38
+ ENV['SIMPLECOV_INLINE_ASSETS'] = 'true'
39
+ coverage_dir 'public/coverage'
40
+ enable_coverage :branch
41
+
42
+ command_name "rspec#{ENV.fetch('TEST_ENV_NUMBER', nil)}"
43
+
44
+ add_filter '/spec/'
45
+ add_filter '/config/'
46
+ add_filter '/vendor/'
47
+
48
+ SimpleCov.minimum_coverage line: 100, branch: 100
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/simplecov/helper'
4
+ RailsQualityAssurance::SimpleCovHelper.configure!
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/all'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsQualityAssurance
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance/version'
4
+ require 'rails_quality_assurance/ci'
5
+
6
+ module RailsQualityAssurance
7
+ class Error < StandardError; end
8
+
9
+ def self.root
10
+ File.expand_path('..', __dir__)
11
+ end
12
+
13
+ def self.rubocop_config_path
14
+ File.join(root, 'rubocop.yml')
15
+ end
16
+
17
+ def self.reek_config_path
18
+ File.join(root, 'config/reek.yml')
19
+ end
20
+
21
+ def self.biome_config_path
22
+ File.join(root, 'config/biome.json')
23
+ end
24
+
25
+ def self.stylelint_config_path
26
+ File.join(root, 'config/.stylelintrc.json')
27
+ end
28
+ end
29
+
30
+ require 'rails_quality_assurance/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_quality_assurance'
4
+
5
+ namespace :qa do
6
+ desc 'Run all quality assurance checks (RuboCop, Reek, Flay, Brakeman, bundler-audit)'
7
+ task lint: %w[qa:rubocop qa:reek qa:flay qa:brakeman qa:audit]
8
+
9
+ desc 'Run RuboCop'
10
+ task rubocop: :environment do
11
+ sh 'bundle exec rubocop'
12
+ end
13
+
14
+ desc 'Run Reek code smell detection'
15
+ task reek: :environment do
16
+ config = File.exist?('.reek.yml') ? '' : "-c #{RailsQualityAssurance.reek_config_path}"
17
+ sh "bundle exec reek #{config} app lib"
18
+ end
19
+
20
+ desc 'Run Flay structural code duplication analysis'
21
+ task flay: :environment do
22
+ mass = ENV.fetch('FLAY_MASS', '300')
23
+ output = `bundle exec flay --mass #{mass} app lib`
24
+ puts output
25
+ abort 'Flay detected structural duplication' unless output.include?('Total score (lower is better) = 0')
26
+ end
27
+
28
+ desc 'Run Brakeman static security analysis'
29
+ task brakeman: :environment do
30
+ sh 'bundle exec brakeman --quiet --no-pager --exit-on-warn --exit-on-error'
31
+ end
32
+
33
+ desc 'Run Bundler Audit for vulnerable gems'
34
+ task audit: :environment do
35
+ sh 'bundle exec bundle audit check --update'
36
+ end
37
+
38
+ namespace :lint do
39
+ desc 'Run BiomeJS on JS/TS/JSON'
40
+ task biome: :environment do
41
+ config = File.exist?('biome.json') ? '' : "--config-path #{RailsQualityAssurance.biome_config_path}"
42
+ sh "npx @biomejs/biome check #{config}"
43
+ end
44
+
45
+ desc 'Run Stylelint on CSS'
46
+ task stylelint: :environment do
47
+ config = File.exist?('.stylelintrc.json') ? '' : "--config #{RailsQualityAssurance.stylelint_config_path}"
48
+ sh "npx stylelint #{config} \"**/*.css\""
49
+ end
50
+ end
51
+
52
+ desc 'Run frontend linters (Biome + Stylelint)'
53
+ task frontend: %w[qa:lint:biome qa:lint:stylelint]
54
+
55
+ desc 'Run all CI checks'
56
+ task ci: %w[qa:lint qa:frontend spec:parallel]
57
+ end
58
+
59
+ namespace :lint do
60
+ desc 'Alias for qa:lint'
61
+ task all: 'qa:lint'
62
+
63
+ desc 'Run BiomeJS linter'
64
+ task biome: 'qa:lint:biome'
65
+
66
+ desc 'Run Stylelint on CSS'
67
+ task stylelint: 'qa:lint:stylelint'
68
+ end
69
+
70
+ task lint: 'qa:lint'
71
+
72
+ namespace :spec do
73
+ desc 'Run specs in parallel with system specs isolated'
74
+ task parallel: :environment do
75
+ ENV['PARALLEL_TEST_FIRST_IS_1'] ||= 'true'
76
+ parallel_tasks = ENV.fetch('PARALLEL_TEST_PROCESSORS', 3).to_s
77
+ isolate_tasks = ENV.fetch('ISOLATE_SPEC_TASKS', 1).to_s
78
+ test_options = ENV.fetch('PARALLEL_SPEC_OPTIONS', nil)
79
+ raise ArgumentError, 'PARALLEL_TEST_PROCESSORS must be a positive integer' if parallel_tasks.to_i <= 0
80
+
81
+ options = "--isolate --single spec/system/ --isolate-n #{isolate_tasks}"
82
+ options << " --test-options=\"#{test_options}\"" if test_options
83
+ Rake::Task['parallel:create'].invoke(parallel_tasks)
84
+ Rake::Task['parallel:load_schema'].invoke(parallel_tasks)
85
+ Rake::Task['parallel:spec'].invoke(parallel_tasks, nil, nil, options)
86
+ end
87
+ end
data/rubocop.yml ADDED
@@ -0,0 +1,143 @@
1
+ # Develoz Ruby styling.
2
+ #
3
+ # Shared baseline for all Develoz projects. Application-specific tweaks
4
+ # (file excludes, loosened metrics) belong in the project's own
5
+ # .rubocop.yml, which inherits this file.
6
+ #
7
+ # Never remove this line.
8
+ # This merges default exclusions which makes running rubocop much faster.
9
+ inherit_mode:
10
+ merge:
11
+ - Exclude
12
+
13
+ plugins:
14
+ - rubocop-performance
15
+ - rubocop-rspec
16
+ - rubocop-rspec_rails
17
+ - rubocop-capybara
18
+ - rubocop-factory_bot
19
+ - rubocop-rubycw
20
+ - rubocop-migration
21
+ - rubocop-rails
22
+
23
+ AllCops:
24
+ TargetRubyVersion: 4.0
25
+ NewCops: enable
26
+ Exclude:
27
+ - "bin/*"
28
+ - "db/schema.rb"
29
+ - "db/migrate/**/*.rb"
30
+ - "vendor/**/*"
31
+ - "config/locales/**/*.yml"
32
+
33
+ FactoryBot/ExcessiveCreateList:
34
+ MaxAmount: 15
35
+
36
+ Layout/LineLength:
37
+ Max: 120
38
+ AllowedPatterns:
39
+ - !ruby/regexp /^ *# /
40
+
41
+ Migration/AddIndexColumnsCount:
42
+ Enabled: true
43
+ MaxColumnsCount: 4
44
+
45
+ Migration/AddIndexConcurrently:
46
+ Enabled: true
47
+
48
+ Migration/AddIndexDuplicate:
49
+ Enabled: true
50
+
51
+ Migration/Jsonb:
52
+ Enabled: true
53
+
54
+ Naming/VariableNumber:
55
+ Enabled: false
56
+
57
+ Rails/SkipsModelValidations:
58
+ Enabled: false
59
+
60
+ Style/Documentation:
61
+ Enabled: false
62
+
63
+ Style/SafeNavigationChainLength:
64
+ Enabled: false
65
+
66
+ Style/HashSyntax:
67
+ EnforcedShorthandSyntax: always
68
+
69
+ Metrics/AbcSize:
70
+ Max: 35
71
+
72
+ Metrics/ClassLength:
73
+ Max: 150
74
+
75
+ Metrics/MethodLength:
76
+ Max: 30
77
+
78
+ Metrics/BlockLength:
79
+ Exclude:
80
+ - "config/**/*"
81
+ - "lib/tasks/**/*"
82
+
83
+ Metrics/CyclomaticComplexity:
84
+ Max: 10
85
+
86
+ Metrics/PerceivedComplexity:
87
+ Max: 10
88
+
89
+ # RSpec system tests naturally have multiple expectations
90
+ RSpec/MultipleExpectations:
91
+ Max: 5
92
+ Exclude:
93
+ - "spec/system/**/*"
94
+ - "spec/views/**/*"
95
+ - "spec/helpers/**/*"
96
+
97
+ # RSpec system tests can be longer
98
+ RSpec/ExampleLength:
99
+ Max: 15
100
+ Exclude:
101
+ - "spec/system/**/*"
102
+ - "spec/views/**/*"
103
+ - "spec/helpers/**/*"
104
+
105
+ # We might want to check texts in tests
106
+ Rails/I18nLocaleTexts:
107
+ Exclude:
108
+ - "spec/**/*"
109
+
110
+ # Allow more memoized helpers in specs for comprehensive testing
111
+ RSpec/MultipleMemoizedHelpers:
112
+ Max: 10
113
+
114
+ # Allow I18n.locale assignment in test setup blocks
115
+ Rails/I18nLocaleAssignment:
116
+ Exclude:
117
+ - "spec/**/*"
118
+
119
+ # Allow allow_any_instance_of
120
+ RSpec/AnyInstance:
121
+ Enabled: false
122
+
123
+ # Allow expect_any_instance_of
124
+ RSpec/StubbedMock:
125
+ Enabled: false
126
+
127
+ # Allow message chains
128
+ RSpec/MessageChain:
129
+ Enabled: false
130
+
131
+ RSpec/NestedGroups:
132
+ Max: 6
133
+
134
+ # Allow let! setup in specs where roles/associations are needed
135
+ RSpec/LetSetup:
136
+ Exclude:
137
+ - "spec/system/**/*"
138
+ - "spec/requests/**/*"
139
+ - "spec/controllers/**/*"
140
+
141
+ # Capybara negation matcher warnings are false positives for have_no_*
142
+ Capybara/NegationMatcherAfterVisit:
143
+ Enabled: false
metadata ADDED
@@ -0,0 +1,390 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-quality-assurance
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mauricio Zaffari
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rubocop
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 1.85.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 1.85.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: rubocop-capybara
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.22.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 2.22.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: rubocop-factory_bot
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 2.28.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 2.28.0
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop-migration
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.7.1
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.7.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-performance
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 1.26.0
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.26.0
82
+ - !ruby/object:Gem::Dependency
83
+ name: rubocop-rails
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 2.34.0
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 2.34.0
96
+ - !ruby/object:Gem::Dependency
97
+ name: rubocop-rspec
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 3.9.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 3.9.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubocop-rspec_rails
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 2.32.0
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 2.32.0
124
+ - !ruby/object:Gem::Dependency
125
+ name: rubocop-rubycw
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.2.0
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: 0.2.0
138
+ - !ruby/object:Gem::Dependency
139
+ name: brakeman
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '7.0'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '7.0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: bundler-audit
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0.9'
159
+ type: :runtime
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0.9'
166
+ - !ruby/object:Gem::Dependency
167
+ name: flay
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '2.14'
173
+ type: :runtime
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '2.14'
180
+ - !ruby/object:Gem::Dependency
181
+ name: reek
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '6.3'
187
+ type: :runtime
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '6.3'
194
+ - !ruby/object:Gem::Dependency
195
+ name: parallel_tests
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '5.0'
201
+ type: :runtime
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '5.0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: rspec-rails
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '7.0'
215
+ type: :runtime
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '7.0'
222
+ - !ruby/object:Gem::Dependency
223
+ name: simplecov
224
+ requirement: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0.22'
229
+ type: :runtime
230
+ prerelease: false
231
+ version_requirements: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0.22'
236
+ - !ruby/object:Gem::Dependency
237
+ name: simplecov-lcov
238
+ requirement: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '0.8'
243
+ type: :runtime
244
+ prerelease: false
245
+ version_requirements: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ version: '0.8'
250
+ - !ruby/object:Gem::Dependency
251
+ name: capybara
252
+ requirement: !ruby/object:Gem::Requirement
253
+ requirements:
254
+ - - ">="
255
+ - !ruby/object:Gem::Version
256
+ version: '3.40'
257
+ type: :runtime
258
+ prerelease: false
259
+ version_requirements: !ruby/object:Gem::Requirement
260
+ requirements:
261
+ - - ">="
262
+ - !ruby/object:Gem::Version
263
+ version: '3.40'
264
+ - !ruby/object:Gem::Dependency
265
+ name: capybara-playwright-driver
266
+ requirement: !ruby/object:Gem::Requirement
267
+ requirements:
268
+ - - ">="
269
+ - !ruby/object:Gem::Version
270
+ version: 0.5.0
271
+ type: :runtime
272
+ prerelease: false
273
+ version_requirements: !ruby/object:Gem::Requirement
274
+ requirements:
275
+ - - ">="
276
+ - !ruby/object:Gem::Version
277
+ version: 0.5.0
278
+ - !ruby/object:Gem::Dependency
279
+ name: playwright-ruby-client
280
+ requirement: !ruby/object:Gem::Requirement
281
+ requirements:
282
+ - - ">="
283
+ - !ruby/object:Gem::Version
284
+ version: 1.50.0
285
+ type: :runtime
286
+ prerelease: false
287
+ version_requirements: !ruby/object:Gem::Requirement
288
+ requirements:
289
+ - - ">="
290
+ - !ruby/object:Gem::Version
291
+ version: 1.50.0
292
+ - !ruby/object:Gem::Dependency
293
+ name: railties
294
+ requirement: !ruby/object:Gem::Requirement
295
+ requirements:
296
+ - - ">="
297
+ - !ruby/object:Gem::Version
298
+ version: '7.0'
299
+ type: :runtime
300
+ prerelease: false
301
+ version_requirements: !ruby/object:Gem::Requirement
302
+ requirements:
303
+ - - ">="
304
+ - !ruby/object:Gem::Version
305
+ version: '7.0'
306
+ - !ruby/object:Gem::Dependency
307
+ name: rake
308
+ requirement: !ruby/object:Gem::Requirement
309
+ requirements:
310
+ - - "~>"
311
+ - !ruby/object:Gem::Version
312
+ version: '13.0'
313
+ type: :development
314
+ prerelease: false
315
+ version_requirements: !ruby/object:Gem::Requirement
316
+ requirements:
317
+ - - "~>"
318
+ - !ruby/object:Gem::Version
319
+ version: '13.0'
320
+ - !ruby/object:Gem::Dependency
321
+ name: rspec
322
+ requirement: !ruby/object:Gem::Requirement
323
+ requirements:
324
+ - - "~>"
325
+ - !ruby/object:Gem::Version
326
+ version: '3.13'
327
+ type: :development
328
+ prerelease: false
329
+ version_requirements: !ruby/object:Gem::Requirement
330
+ requirements:
331
+ - - "~>"
332
+ - !ruby/object:Gem::Version
333
+ version: '3.13'
334
+ description: 'Unified QA kit for Rails apps: testing, linters, security audits, parallel
335
+ execution, and CI.'
336
+ email:
337
+ - mauricio@develoz.com
338
+ executables: []
339
+ extensions: []
340
+ extra_rdoc_files: []
341
+ files:
342
+ - CHANGELOG.md
343
+ - LICENSE.txt
344
+ - README.md
345
+ - config/biome.json
346
+ - config/reek.yml
347
+ - lib/generators/rails_quality_assurance/ci/ci_generator.rb
348
+ - lib/generators/rails_quality_assurance/ci/templates/bin_ci.erb
349
+ - lib/generators/rails_quality_assurance/ci/templates/config_ci.rb.erb
350
+ - lib/rails_quality_assurance.rb
351
+ - lib/rails_quality_assurance/all.rb
352
+ - lib/rails_quality_assurance/ci.rb
353
+ - lib/rails_quality_assurance/ci_runner.rb
354
+ - lib/rails_quality_assurance/playwright.rb
355
+ - lib/rails_quality_assurance/playwright/helper.rb
356
+ - lib/rails_quality_assurance/railtie.rb
357
+ - lib/rails_quality_assurance/rspec.rb
358
+ - lib/rails_quality_assurance/rspec/helper.rb
359
+ - lib/rails_quality_assurance/simplecov.rb
360
+ - lib/rails_quality_assurance/simplecov/helper.rb
361
+ - lib/rails_quality_assurance/test_helper.rb
362
+ - lib/rails_quality_assurance/version.rb
363
+ - lib/tasks/quality_assurance.rake
364
+ - rubocop.yml
365
+ homepage: https://github.com/develoz-com/rails-quality-assurance
366
+ licenses:
367
+ - MIT
368
+ metadata:
369
+ homepage_uri: https://github.com/develoz-com/rails-quality-assurance
370
+ source_code_uri: https://github.com/develoz-com/rails-quality-assurance
371
+ changelog_uri: https://github.com/develoz-com/rails-quality-assurance/blob/main/CHANGELOG.md
372
+ rubygems_mfa_required: 'true'
373
+ rdoc_options: []
374
+ require_paths:
375
+ - lib
376
+ required_ruby_version: !ruby/object:Gem::Requirement
377
+ requirements:
378
+ - - ">="
379
+ - !ruby/object:Gem::Version
380
+ version: '4.0'
381
+ required_rubygems_version: !ruby/object:Gem::Requirement
382
+ requirements:
383
+ - - ">="
384
+ - !ruby/object:Gem::Version
385
+ version: '0'
386
+ requirements: []
387
+ rubygems_version: 4.0.16
388
+ specification_version: 4
389
+ summary: Opinionated quality assurance kit for Rails applications.
390
+ test_files: []