├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── massa ├── config └── default_tools.yml ├── lib ├── massa.rb └── massa │ ├── analyzer.rb │ ├── cli.rb │ ├── templates │ └── massa.yml │ ├── tool.rb │ └── version.rb ├── massa.gemspec ├── readme ├── massa-v.gif └── massa.gif └── spec ├── bin └── massa_spec.rb ├── massa_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /Gemfile.lock 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-rspec 2 | 3 | AllCops: 4 | TargetRubyVersion: 2.7 5 | 6 | Metrics/MethodLength: 7 | Max: 20 8 | 9 | Metrics/AbcSize: 10 | Max: 25 11 | 12 | Style/Documentation: 13 | Enabled: false 14 | 15 | Layout/LineLength: 16 | Max: 100 17 | 18 | Layout/EmptyLinesAroundArguments: 19 | Enabled: false 20 | 21 | Layout/EmptyLinesAroundAttributeAccessor: 22 | Enabled: true 23 | 24 | Layout/SpaceAroundMethodCallOperator: 25 | Enabled: true 26 | 27 | Lint/RaiseException: 28 | Enabled: true 29 | 30 | Lint/StructNewOverride: 31 | Enabled: true 32 | 33 | Style/ExponentialNotation: 34 | Enabled: true 35 | 36 | Style/HashEachMethods: 37 | Enabled: true 38 | 39 | Style/HashTransformKeys: 40 | Enabled: true 41 | 42 | Style/HashTransformValues: 43 | Enabled: true 44 | 45 | Style/SlicingWithRange: 46 | Enabled: true 47 | 48 | RSpec/DescribeClass: 49 | Exclude: 50 | - spec/bin/**/* 51 | 52 | RSpec/ExampleLength: 53 | Exclude: 54 | - spec/bin/**/* 55 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | sudo: false 4 | 5 | cache: bundler 6 | 7 | script: 8 | - bundle exec rubocop 9 | - bundle exec rspec 10 | 11 | rvm: 12 | - 2.7 13 | 14 | addons: 15 | code_climate: 16 | repo_token: 8e68857c02b2124104896d9178dc6ab310e143de880e9662d8c5dd9362081c9b 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer. All complaints will be reviewed 39 | and investigated and will result in a response that is deemed necessary and 40 | appropriate to the circumstances. Maintainers are obligated to maintain 41 | confidentiality with regard to the reporter of an incident. 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 44 | version 1.3.0, available at 45 | [http://contributor-covenant.org/version/1/3/0/][version] 46 | 47 | [homepage]: http://contributor-covenant.org 48 | [version]: http://contributor-covenant.org/version/1/3/0/ 49 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in massa.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Lucas Caton 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Massa has been retired 🌅 2 | 3 | Check out [this blog](https://www.lucascaton.com/2024/05/22/how-to-automatically-run-automated-tests-and-code-analyzers-via-git-hooks) 4 | post for a better replacement. 5 | 6 | --- 7 | --- 8 | --- 9 | 10 | # 👌 Massa 11 | 12 | [![Gem Version](https://badge.fury.io/rb/massa.svg)](https://rubygems.org/gems/massa) 13 | [![Dependency Status](https://gemnasium.com/badges/github.com/lucascaton/massa.svg)](https://gemnasium.com/github.com/lucascaton/massa) 14 | [![Build Status](https://travis-ci.org/lucascaton/massa.svg?branch=main)](https://travis-ci.org/lucascaton/massa) 15 | [![Test Coverage](https://codeclimate.com/github/lucascaton/massa/badges/coverage.svg)](https://codeclimate.com/github/lucascaton/massa/coverage) 16 | [![Code Climate](https://codeclimate.com/github/lucascaton/massa/badges/gpa.svg)](https://codeclimate.com/github/lucascaton/massa) 17 | 18 | It's not rare to have Ruby (and Rails) projects becoming hard to maintain and less fun after a while. 19 | This gem helps you to keep or increase the quality, good practices and security of your projects. 20 | 21 | **Massa** can run in your CI using different code analyzers tools along with automated tests, instead of running only your automated tests. 22 | 23 | Ie.: Instead of: 24 | 25 | $ bundle exec rubocop && bundle exec brakeman -Aqz && bundle exec rails_best_practices && bundle exec rspec && karma start --single-run --browsers PhantomJS && etc 26 | 27 | You will only need: 28 | 29 | $ bundle exec massa 30 | 31 | You can either use only the [default tools](https://github.com/lucascaton/massa/blob/main/config/default_tools.yml) or define your own by using a [simple config file](https://github.com/lucascaton/massa#usage). 32 | 33 | ![massa](https://raw.githubusercontent.com/lucascaton/massa/main/readme/massa.gif) 34 | 35 | Verbose mode: 36 | 37 | ![massa-v](https://raw.githubusercontent.com/lucascaton/massa/main/readme/massa-v.gif) 38 | 39 | ## Installation 40 | 41 | Add the following lines to your application's Gemfile: 42 | 43 | ```ruby 44 | group :development, :test do 45 | gem 'massa' 46 | end 47 | ``` 48 | 49 | And then execute: 50 | 51 | $ bundle 52 | 53 | ## Usage 54 | 55 | Generate a config file (optional): 56 | 57 | $ bundle exec massa -g 58 | 59 | This will generate a `config/massa.yml` file, which you can customize. 60 | 61 | Then, run: 62 | 63 | $ bundle exec massa 64 | 65 | It's recommended to use `-V` (or `--verbose`) flag when running it in a CI: 66 | 67 | $ bundle exec massa -V 68 | 69 | ## About the gem name 70 | 71 | **"Massa"** is a 🇧🇷 Portuguese slang which means **"awesome"**, so once you add it to your project, it becomes "massa"! 72 | 73 | ## Contributing 74 | 75 | [Bug reports](https://github.com/lucascaton/massa/issues) and [pull requests](https://github.com/lucascaton/massa/pulls) are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 76 | 77 | ## License 78 | 79 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 80 | 81 | ## Copyright 82 | 83 | Copyright (c) Lucas Caton. 84 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /bin/massa: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | require 'massa' 6 | 7 | $LOAD_PATH.push File.expand_path('../lib', __dir__) 8 | 9 | options = {} 10 | 11 | OptionParser.new do |opts| 12 | opts.banner = 'Usage: massa [options]' 13 | 14 | opts.on('-g', '--generate-config', 'Generate config file') do 15 | template = File.expand_path('../lib/massa/templates/massa.yml', __dir__) 16 | config_file = 'config/massa.yml' 17 | 18 | FileUtils.mkdir_p(File.dirname(config_file)) 19 | FileUtils.cp(template, "#{Dir.pwd}/#{config_file}") 20 | Massa::CLI.colorize :default, 'File generated: ', :green, "#{config_file}\n" 21 | exit 22 | end 23 | 24 | opts.on('-h', '--help', 'Display this help') do 25 | puts opts 26 | puts 27 | puts 'You can read the official documentation here:' 28 | puts 'https://github.com/lucascaton/massa#massa' 29 | puts 30 | exit 31 | end 32 | 33 | opts.on('-v', '--version', 'Display version') do 34 | require 'massa/version' 35 | puts Massa::VERSION 36 | exit 37 | end 38 | 39 | opts.on('-V', '--verbose', 'Run verbosely') do |v| 40 | options[:verbose] = v 41 | end 42 | end.parse! 43 | 44 | begin 45 | Massa::Analyzier.run!(options) 46 | rescue Gem::LoadError 47 | Massa::CLI.colorize :red, "¯\\_(ツ)_/¯ 'massa' gem is not in your Gemfile." 48 | exit 1 49 | end 50 | -------------------------------------------------------------------------------- /config/default_tools.yml: -------------------------------------------------------------------------------- 1 | rubocop: 2 | description: 'Rubocop' 3 | command: 'bundle exec rubocop' 4 | required: true 5 | 6 | haml_lint: 7 | description: 'HAML lint' 8 | command: 'bundle exec haml-lint app/views' 9 | required: true 10 | 11 | brakeman: 12 | description: 'Brakeman (security vulnerability scanner)' 13 | command: 'bundle exec brakeman -Aqz5' 14 | required: true 15 | 16 | rails_best_practices: 17 | description: 'Rails Best Practices' 18 | command: 'bundle exec rails_best_practices' 19 | required: true 20 | 21 | i18n-tasks: 22 | description: 'I18n translations' 23 | command: 'bundle exec i18n-tasks missing' 24 | required: false 25 | 26 | rspec-rails: 27 | description: 'RSpec' 28 | command: 'RAILS_ENV=test bundle exec rspec' 29 | required: true 30 | -------------------------------------------------------------------------------- /lib/massa.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Massa 4 | end 5 | 6 | require 'massa/cli' 7 | require 'massa/tool' 8 | require 'massa/analyzer' 9 | -------------------------------------------------------------------------------- /lib/massa/analyzer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'ostruct' 5 | require 'yaml' 6 | require 'English' 7 | 8 | module Massa 9 | class Analyzier 10 | attr_reader :verbose 11 | 12 | alias verbose? verbose 13 | 14 | def self.run!(options) 15 | new(**options).run! 16 | end 17 | 18 | def initialize(verbose: false) 19 | @verbose = verbose 20 | end 21 | 22 | def run! 23 | Massa::Tool.list.each do |tool| 24 | Massa::CLI.colorize :blue, "➙ #{tool.description}" 25 | 26 | next if tool.gem? && !gem_installed?(tool) 27 | 28 | execute(tool) 29 | end 30 | 31 | Massa::CLI.colorize :green, '~(‾▿‾)~ All good!' 32 | end 33 | 34 | def gem_installed?(tool) 35 | return true if `gem query -i #{tool.name}`.chomp == 'true' 36 | 37 | Massa::CLI.colorize :yellow, "༼ つ ◕_◕ ༽つ '#{tool.name}' gem is not installed" 38 | 39 | tool.required? ? exit(1) : false 40 | end 41 | 42 | def execute(tool) 43 | command_output = '' 44 | 45 | if verbose? 46 | system(tool.command) 47 | else 48 | IO.popen(tool.command, err: %i[child out]) { |io| command_output = io.read } 49 | end 50 | 51 | return if $CHILD_STATUS.success? 52 | 53 | Massa::CLI.colorize :red, "¯\\_(ツ)_/¯ #{tool.description} failed:" 54 | Massa::CLI.colorize :yellow, "$ #{tool.command}" 55 | 56 | puts command_output if command_output.to_s != '' 57 | 58 | exit 1 if tool.required? 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/massa/cli.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Massa 4 | class CLI 5 | class << self 6 | def colorize(*args) 7 | print "\n" 8 | args.each_slice(2) { |color, string| print "\e[#{code(color)}m#{string}\e[0m" } 9 | print "\n" 10 | end 11 | 12 | def code(color) 13 | { red: 31, green: 32, yellow: 33, blue: 34, pink: 35, light_blue: 36 }[color] || 0 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/massa/templates/massa.yml: -------------------------------------------------------------------------------- 1 | # By leaving this file empty, massa will run all default tools. You can check them here: 2 | # https://github.com/lucascaton/massa/blob/main/config/default_tools.yml 3 | # Otherwise you can define your own tools like in the following examples: 4 | 5 | # rubocop: 6 | # description: 'Rubocop' 7 | # command: 'bundle exec rubocop' 8 | # required: true 9 | # 10 | # haml_lint: 11 | # description: 'HAML lint' 12 | # command: 'bundle exec haml-lint app/views' 13 | # required: true 14 | # 15 | # brakeman: 16 | # description: 'Brakeman (security vulnerability scanner)' 17 | # command: 'bundle exec brakeman -Aqz4' 18 | # required: true 19 | # 20 | # rails_best_practices: 21 | # description: 'Rails Best Practices' 22 | # command: 'bundle exec rails_best_practices' 23 | # required: true 24 | # 25 | # i18n-tasks: 26 | # description: 'I18n translations' 27 | # command: 'bundle exec i18n-tasks missing' 28 | # required: false 29 | # 30 | # es_lint: 31 | # description: 'ES lint' 32 | # command: 'npm run eslint --ext js .' 33 | # gem: false 34 | # required: true 35 | # 36 | # karma: 37 | # description: 'Karma (JS specs)' 38 | # command: 'karma start --single-run --browsers PhantomJS' 39 | # required: true 40 | # gem: false 41 | # 42 | # rspec-rails: 43 | # description: 'RSpec' 44 | # command: 'RAILS_ENV=test bundle exec rspec' 45 | # required: true 46 | -------------------------------------------------------------------------------- /lib/massa/tool.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Massa 4 | class Tool 5 | attr_reader :name, :description, :gem, :command, :required 6 | 7 | def initialize(name, attributes) 8 | @name = name 9 | @description = attributes['description'] 10 | @gem = attributes['gem'].nil? ? true : attributes['gem'] 11 | @command = attributes['command'] 12 | @required = attributes['required'].nil? ? true : attributes['required'] 13 | end 14 | 15 | alias required? required 16 | alias gem? gem 17 | 18 | class << self 19 | def list 20 | tools = custom_tools.any? ? custom_tools : default_tools 21 | 22 | tools.map { |name, attributes| new(name, attributes) } 23 | end 24 | 25 | private 26 | 27 | def default_tools 28 | @default_tools ||= YAML.load_file(config_file_from_gem) 29 | end 30 | 31 | def custom_tools 32 | # Returns an empty hash if config file is empty 33 | @custom_tools ||= YAML.load_file(config_file_from_project) || {} 34 | 35 | # When there is no config file in the project 36 | rescue Errno::ENOENT 37 | {} 38 | end 39 | 40 | def config_file_from_gem 41 | File.expand_path('../../config/default_tools.yml', __dir__) 42 | end 43 | 44 | def config_file_from_project 45 | "#{Dir.pwd}/config/massa.yml" 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/massa/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Massa 4 | VERSION = '0.6.0' 5 | end 6 | -------------------------------------------------------------------------------- /massa.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'massa/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'massa' 9 | spec.version = Massa::VERSION 10 | spec.authors = ['Lucas Caton'] 11 | 12 | spec.summary = 'Keep the quality, good practices and security of Rails projects.' 13 | spec.description = 'Keep the quality, good practices and security of Rails projects.' 14 | spec.homepage = 'https://github.com/lucascaton/massa' 15 | spec.license = 'MIT' 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(readme|spec)/}) } 18 | spec.bindir = 'bin' 19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 20 | spec.require_paths = ['lib'] 21 | 22 | spec.required_ruby_version = '>= 2.7' 23 | 24 | spec.add_development_dependency 'bundler' 25 | spec.add_development_dependency 'codeclimate-test-reporter' 26 | spec.add_development_dependency 'pry' 27 | spec.add_development_dependency 'rake' 28 | spec.add_development_dependency 'rspec' 29 | spec.add_development_dependency 'rubocop' 30 | spec.add_development_dependency 'rubocop-rspec' 31 | # spec.add_development_dependency 'simplecov' 32 | end 33 | -------------------------------------------------------------------------------- /readme/massa-v.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucascaton/massa/5399b033d38ba317e24ddfde9c5bb7742a5f70d9/readme/massa-v.gif -------------------------------------------------------------------------------- /readme/massa.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lucascaton/massa/5399b033d38ba317e24ddfde9c5bb7742a5f70d9/readme/massa.gif -------------------------------------------------------------------------------- /spec/bin/massa_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | RSpec.describe 'bin/massa' do 6 | %w[-h --help].each do |option| 7 | describe(option) do 8 | it 'displays massa help' do 9 | output = `#{File.expand_path('../../bin/massa', __dir__)} #{option}`.chomp 10 | expect(output).to eql("Usage: massa [options] 11 | -g, --generate-config Generate config file 12 | -h, --help Display this help 13 | -v, --version Display version 14 | -V, --verbose Run verbosely 15 | 16 | You can read the official documentation here: 17 | https://github.com/lucascaton/massa#massa 18 | ") 19 | end 20 | end 21 | end 22 | 23 | %w[-v --version].each do |option| 24 | describe(option) do 25 | it 'displays massa version' do 26 | output = `#{File.expand_path('../../bin/massa', __dir__)} #{option}`.chomp 27 | expect(output).to eql(Massa::VERSION) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/massa_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Massa do 6 | it 'has a version number' do 7 | expect(Massa::VERSION).not_to be nil 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 4 | 5 | require 'massa/version' 6 | require 'massa' 7 | --------------------------------------------------------------------------------