netsoft-danger 0.3.3 → 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 834689b35350c36d2d44ddf42e4f4063d6a64669eae0b81622bd75a4a84ad521
4
- data.tar.gz: 60f0dce22bea4f15e6c41dca65b68015117580cf2f6b887b96ea7c5e5cf08e52
3
+ metadata.gz: dfb9db9333ed0616edd6d2650f3d39949842ab95f4226ba641d2829f041e3168
4
+ data.tar.gz: faf45b8bf21ecea1afaf661651579c2b732155979ba15947ba26592f446794bf
5
5
  SHA512:
6
- metadata.gz: ba7dd552680963b240de97848204a8da274fcd62826284bad93a906f0d629de0e41f0fc2a72da71590005392e0415a84d5fd27a4d9b726a997d80da9db4242f3
7
- data.tar.gz: fb9da655dc4673612375ae7b0307fd6add7a01922152f08d1f8728550723df6b725150add8f480b7726756c0f986f797e73ac569e67d1c98673b8f72e67afd9c
6
+ metadata.gz: 503763af26af1a7ad5a88c9b76dd345f4b5de9384111be1aaee93bfb78b4f0f970ab77d98440ca87256f65aac2c3d7e105043ad11514edcfd86ec884ae816c1c
7
+ data.tar.gz: f195d074cdc895ae1031fc326e90b6f718fef0f8e335cf15fd09e1341e4fd45ac652ca2e1343ee71fdee4e367ccde3e26a0b1df5493238b2757482a3dd6493aa
@@ -111,6 +111,10 @@ jobs:
111
111
  name: Build gem
112
112
  command: |-
113
113
  gem build *.gemspec
114
+ - run:
115
+ name: Run rubocop
116
+ command: |-
117
+ bundle exec rubocop
114
118
  - run:
115
119
  name: Run Danger
116
120
  command: |-
@@ -124,7 +128,9 @@ jobs:
124
128
  name: Deploy to gem server
125
129
  command: |-
126
130
  ./bin/tag_check.sh
131
+ ./bin/setup-rubygems.sh
127
132
  rm -rf pkg
128
133
  gem install geminabox
129
134
  rake build
130
135
  gem inabox -g ${HUBSTAFF_GEM_SERVER} pkg/*
136
+ gem push pkg/*.gem
@@ -0,0 +1,15 @@
1
+ inherit_gem:
2
+ netsoft-rubocop:
3
+ - default.yml
4
+
5
+ Style/SignalException:
6
+ Exclude:
7
+ - Dangerfile
8
+
9
+ Style/IfUnlessModifier:
10
+ Exclude:
11
+ - Dangerfile
12
+
13
+ Layout/EmptyLineAfterGuardClause:
14
+ Exclude:
15
+ - Dangerfile
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Next]
8
+ ### Added
9
+ ### Changed
10
+ ### Fixed
11
+
12
+ ## [0.3.8]
13
+ ### Added
14
+ - auto add/remove some labels
15
+ - allow conventional commit format for our migration, gemfile, and package.json
16
+ - add support for product review labels
17
+ - add support for renamed code review labels
18
+
19
+ ## [0.3.7]
20
+ ### Fixed
21
+ - require English in the netsoft-circle bin
22
+
23
+ ## [0.3.6]
24
+ ### Added
25
+ - rubocop checks for this gem
26
+ ### Changed
27
+ - allow bundler 1.17.3
28
+
29
+ ## [0.3.5]
30
+ ### Added
31
+ - package.json checks
32
+ ### Fixed
33
+ - requie older version of faraday until octokit is fixed (https://github.com/octokit/octokit.rb/pull/1154)
34
+
data/Dangerfile CHANGED
@@ -1,105 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ def toggle_label(github, label, should_set)
4
+ repo_name = github.pr_json['head']['repo']['full_name']
5
+ pr_number = github.pr_json['number']
6
+ has_label = github.pr_labels.include?(label)
7
+ if should_set && !has_label
8
+ github.api.add_labels_to_an_issue(repo_name, pr_number, label)
9
+ elsif !should_set && has_label
10
+ github.api.remove_label(repo_name, pr_number, label)
11
+ end
12
+ end
13
+
1
14
  # Don't let testing shortcuts get into master by accident
2
- if Dir.exists?('spec')
3
- fail("fdescribe left in tests") if `grep -r -e '\\bfdescribe\\b' spec/ |grep -v 'danger ok' `.length > 1
4
- fail("fcontext left in tests") if `grep -r -e '\\bfcontext\\b' spec/ |grep -v 'danger ok' `.length > 1
5
- fail("fit left in tests") if `grep -r -e '\\bfit\\b' spec/ | grep -v 'danger ok' `.length > 1
6
- fail("ap left in tests") if `grep -r -e '\\bap\\b' spec/ | grep -v 'danger ok' `.length > 1
7
- fail("puts left in tests") if `grep -r -e '\\bputs\\b' spec/ | grep -v 'danger ok' `.length > 1
15
+ if Dir.exist?('spec')
16
+ fail('fdescribe left in tests') if `grep -r -e '\\bfdescribe\\b' spec/ |grep -v 'danger ok' `.length > 1
17
+ fail('fcontext left in tests') if `grep -r -e '\\bfcontext\\b' spec/ |grep -v 'danger ok' `.length > 1
18
+ fail('fit left in tests') if `grep -r -e '\\bfit\\b' spec/ | grep -v 'danger ok' `.length > 1
19
+ fail('ap left in tests') if `grep -r -e '\\bap\\b' spec/ | grep -v 'danger ok' `.length > 1
20
+ fail('puts left in tests') if `grep -r -e '\\bputs\\b' spec/ | grep -v 'danger ok' `.length > 1
8
21
  end
9
22
 
10
- if File.exists?('Gemfile')
23
+ if File.exist?('Gemfile')
11
24
  if `grep -r -e "^ *gem 'hubstaff_[a-z]\\+" Gemfile | grep -e ",.\\+[a-zA-Z]" `.length > 1
12
- fail("[gemfile] Beta hubstaff_* gems are not allowed in master/production")
25
+ fail('gemfile: Beta hubstaff_* gems are not allowed in master/production')
13
26
  end
14
27
  if `grep -r -e "^ *gem 'hubstaff_[a-z]\\+" Gemfile | grep -e ",.\\+'[~>=]\\+.\\+[a-zA-Z]" `.length > 1
15
- fail("[gemfile] Beta hubstaff_* gems should be the exact version")
28
+ fail('gemfile: Beta hubstaff_* gems should be the exact version')
16
29
  end
17
30
  if `grep -r -e "^ *gem 'hubstaff_[a-z]\\+" Gemfile | grep -e ",.\\+[' ][0-9.]\\+'" | grep -v '~>' `.length > 1
18
- fail("[gemfile] Release hubstaff_* gems should be using a ~> version")
31
+ fail('gemfile: Release hubstaff_* gems should be using a ~> version')
19
32
  end
20
33
  end
21
34
 
35
+ if github.branch_for_head.start_with?('security')
36
+ toggle_label(github, 'security', true)
37
+ end
38
+
22
39
  git.commits.each do |c|
23
- short = " ( #{c.sha[0..7]} )"
24
- has_migrations = c.diff_parent.any? {|f| f.path =~ /db\/migrate\// }
25
- has_schema_changes = c.diff_parent.any? {|f| f.path =~ /db\/schema\.rb/ }
26
- has_migration_msg = c.message =~ /^\[migration\]/
27
- no_schema_ok = ENV['DANGER_NO_SCHEMA_OK'] || false
40
+ short = " ( #{c.sha[0..7]} )"
41
+ has_migrations = c.diff_parent.any? { |f| f.path =~ %r{db/migrate/} }
42
+ has_schema_changes = c.diff_parent.any? { |f| f.path =~ %r{db/schema\.rb} }
43
+ old_migration_msg = c.message.start_with?('[migration]')
44
+ has_migration_msg = old_migration_msg || c.message.match?(/\Amigration(\([A-Za-z]+\))?:/)
45
+ no_schema_ok = ENV['DANGER_NO_SCHEMA_OK'] || false
28
46
  if has_migrations || has_schema_changes
29
47
  unless has_migration_msg
30
- fail '[migration] Schema migration commits need to be prefixed with [migration]' + short
48
+ fail 'migration: Schema migration commits needs to be tagged with (migration). e.g. migration(Module): ' + short
49
+ end
50
+ if old_migration_msg
51
+ warn 'migration: Please switch to the new conventional commit format.'
31
52
  end
32
53
  if has_migrations && !has_schema_changes && !no_schema_ok
33
- fail '[migration] Please checkin your schema.rb changes with your migration' + short
54
+ fail 'migration: Please checkin your schema.rb changes with your migration' + short
34
55
  end
35
56
  if !has_migrations && has_schema_changes
36
- warn '[migration] Please checkin your migrations with your schema.rb changes' + short
57
+ warn 'migration: Please checkin your migrations with your schema.rb changes' + short
37
58
  end
38
- if c.diff_parent.any? {|f| !( f.path =~ /db\/migrate\// or f.path =~ /db\/schema.rb/ ) }
39
- fail '[migration] Migration commit contains non-migration changes' + short
59
+ if c.diff_parent.any? { |f| f.path !~ %r{db/migrate/|db/schema\.rb} }
60
+ fail 'migration: Migration commit contains non-migration changes' + short
40
61
  end
62
+
63
+ toggle_label(github, 'run migration', true)
41
64
  elsif has_migration_msg
42
65
  fail '[migration] Migration commit with no migrations!' + short
66
+ else
67
+ toggle_label(github, 'run migration', false)
43
68
  end
44
69
 
45
- has_hubstaff_icon_changes = c.diff_parent.any? {|f| f.path =~ /hubstaff(icons|font)/ || f.path =~ /fontcustom-manifest/ }
70
+ has_hubstaff_icon_changes = c.diff_parent.any? { |f| f.path =~ /hubstaff(icons|font)|fontcustom-manifest/ }
46
71
  if has_hubstaff_icon_changes
47
- if c.diff_parent.any? {|f| !( f.path =~ /hubstaff-(icons|font)/ || f.path =~ /fontcustom-manifest/ ) }
48
- fail '[hubstaff-icons] Put hubstaff-icon changes into their own commit' + short
72
+ if c.diff_parent.any? { |f| !(f.path =~ /hubstaff-(icons|font)/ || f.path =~ /fontcustom-manifest/) }
73
+ fail 'hubstaff-icons: Put hubstaff-icon changes into their own commit' + short
49
74
  end
50
75
  end
51
76
 
52
- has_gemfile_changes = c.diff_parent.any? {|f| f.path =~ /Gemfile/ || f.path =~ /gemspec/ }
53
- has_gemfile_msg = c.message =~ /^\[gemfile\]/
77
+ has_gemfile_changes = c.diff_parent.any? { |f| f.path =~ /Gemfile|gemspec/ }
78
+ old_gemfile_msg = c.message.start_with?('[gemfile]')
79
+ has_gemfile_msg = old_gemfile_msg || c.message.match?(/\Agemfile(\([A-Za-z]+\))?:/)
54
80
  if has_gemfile_changes
55
81
  unless has_gemfile_msg
56
- fail '[gemfile] Gemfile commits need to be prefixed with [gemfile] ' + short
82
+ fail 'gemfile: Gemfile commits needs to be tagged with (gemfile). e.g. gemfile(Module): ' + short
57
83
  end
58
- if c.diff_parent.any? {|f| !( f.path =~ /Gemfile/ || f.path =~ /gemspec/ ) }
59
- fail '[gemfile] Gemfile commit contains non-gemfile changes' + short
84
+ if old_migration_msg
85
+ warn 'gemfile: Please switch to the new conventional commit format.'
60
86
  end
61
- if c.diff_parent.any? {|f| f.path == 'Gemfile.lock' }
62
- unless `grep -e '^ 1.15.2$' Gemfile.lock`.length > 1
63
- fail("[gemfile] Gemfile not bundled with bundler 1.15.2")
87
+ if c.diff_parent.any? { |f| f.path !~ /Gemfile|gemspec/ }
88
+ fail 'gemfile: Gemfile commit contains non-gemfile changes' + short
89
+ end
90
+ if c.diff_parent.any? { |f| f.path == 'Gemfile.lock' }
91
+ unless `grep -E -- '^BUNDLED WITH\s*\n\s+(1\\.15\\.2|1\\.17\\.3)$' Gemfile.lock`.length > 1
92
+ fail('gemfile: Gemfile not bundled with bundler 1.15.2 or 1.17.3')
64
93
  end
65
94
  end
66
95
  elsif has_gemfile_msg
67
- fail '[gemfile] Gemfile commit has no gemfile changes!' + short
96
+ fail 'gemfile: Gemfile commit has no gemfile changes!' + short
97
+ end
98
+
99
+ has_package_changes = c.diff_parent.any? { |f| f.path =~ /package\.json|yarn\.lock/ }
100
+ old_package_msg = c.message.start_with?('[package.json]')
101
+ has_package_msg = old_package_msg || c.message.match?(/\Apackage(\([A-Za-z]+\))?:/)
102
+ if has_package_changes
103
+ unless has_package_msg
104
+ fail 'package: Package.json commits needs to be tagged with package. e.g package(Module): ' + short
105
+ end
106
+ if old_package_msg
107
+ warn 'package: Please switch to the new conventional commit format.'
108
+ end
109
+ if c.diff_parent.any? { |f| f.path !~ /package\.json|yarn\.lock/ }
110
+ fail 'package: Package.json commit contains non-package changes' + short
111
+ end
112
+ elsif has_package_msg
113
+ fail 'package: Pacakge.json commit has no package changes!' + short
68
114
  end
69
115
  end
70
116
 
71
- require 'open-uri'
117
+ if ENV['CIRCLE_TOKEN']
118
+ require 'open-uri'
72
119
 
73
- artifact_url = "https://circleci.com/api/v1.1/project/github/#{ENV['CIRCLE_PROJECT_USERNAME']}/#{ENV["CIRCLE_PROJECT_REPONAME"]}/#{ENV['CIRCLE_BUILD_NUM']}/artifacts?circle-token=#{ENV['CIRCLE_TOKEN']}"
74
- artifacts = JSON.load(open(artifact_url).read).map{|a| a["url"]}
120
+ artifact_url = "https://circleci.com/api/v1.1/project/github/#{ENV['CIRCLE_PROJECT_USERNAME']}/#{ENV['CIRCLE_PROJECT_REPONAME']}/#{ENV['CIRCLE_BUILD_NUM']}/artifacts?circle-token=#{ENV['CIRCLE_TOKEN']}"
121
+ artifacts = JSON.parse(URI.parse(artifact_url).read).map { |a| a['url'] }
75
122
 
76
- jest = artifacts.find{ |artifact| artifact.end_with?('jest/index.html') }
77
- coverage = artifacts.find{ |artifact| artifact.end_with?('coverage/index.html') }
78
- rubocop = artifacts.find{ |artifact| artifact.end_with?('rubocop/report.html') }
79
- eslint = artifacts.find{ |artifact| artifact.end_with?('eslint/report.html') }
80
- rspec_files = artifacts.select{ |artifact| artifact =~ /rspec-(.+)\.html$/ }
123
+ jest = artifacts.find { |artifact| artifact.end_with?('jest/index.html') }
124
+ coverage = artifacts.find { |artifact| artifact.end_with?('coverage/index.html') }
125
+ rubocop = artifacts.find { |artifact| artifact.end_with?('rubocop/report.html') }
126
+ eslint = artifacts.find { |artifact| artifact.end_with?('eslint/report.html') }
127
+ rspec_files = artifacts.select { |artifact| artifact =~ /rspec-(.+)\.html$/ }
81
128
 
82
- {}.tap do |hash|
83
- hash['Ruby coverage report'] = coverage if coverage
84
- hash['RSpec test report'] = rspec_files unless rspec_files.empty?
85
- hash['RuboCop inspection report'] = rubocop if rubocop
86
- hash['ESLint inspection report'] = eslint if eslint
87
- hash['Jest coverage report'] = jest if jest
88
- end.each do |msg, links|
89
- links = [*links]
90
- if links.size == 1
91
- message("[#{msg}](#{links[0]})")
92
- else
93
- r = /rspec-(.+)\.html$/
94
- the_links = links.map do |l|
95
- m = r.match(l)
96
- if m
97
- "[#{m[1]}](#{l})"
98
- else
99
- "[link](#{l})"
100
- end
101
- end.join(', ')
129
+ {}.tap do |hash|
130
+ hash['Ruby coverage report'] = coverage if coverage
131
+ hash['RSpec test report'] = rspec_files unless rspec_files.empty?
132
+ hash['RuboCop inspection report'] = rubocop if rubocop
133
+ hash['ESLint inspection report'] = eslint if eslint
134
+ hash['Jest coverage report'] = jest if jest
135
+ end.each do |msg, links|
136
+ links = [*links]
137
+ if links.size == 1
138
+ message("[#{msg}](#{links[0]})")
139
+ else
140
+ r = /rspec-(.+)\.html$/
141
+ the_links = links.map { |l|
142
+ m = r.match(l)
143
+ if m
144
+ "[#{m[1]}](#{l})"
145
+ else
146
+ "[link](#{l})"
147
+ end
148
+ }.join(', ')
102
149
 
103
- message("#{msg} - #{the_links}")
150
+ message("#{msg} - #{the_links}")
151
+ end
104
152
  end
105
153
  end
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler'
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
6
  desc 'Default: run build.'
5
- task :default => :build
7
+ task default: :build
@@ -1,35 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'thor'
4
5
  require 'fileutils'
6
+ require 'English'
5
7
 
6
- class NetsoftCircle < Thor
8
+ class NetsoftCircle < Thor # :nodoc:
7
9
  desc 'setup', 'Setup Heroku for deployment'
8
10
  def setup
9
-
10
11
  File.open("#{ENV['HOME']}/.netrc", 'a+') do |file|
11
- file << <<-EOF
12
+ file << <<~CONFIG
12
13
 
13
- machine api.heroku.com
14
- login #{ENV['HEROKU_LOGIN']}
15
- password #{ENV['HEROKU_API_KEY']}
16
- machine git.heroku.com
17
- login #{ENV['HEROKU_LOGIN']}
18
- password #{ENV['HEROKU_API_KEY']}
19
- EOF
14
+ machine api.heroku.com
15
+ login #{ENV['HEROKU_LOGIN']}
16
+ password #{ENV['HEROKU_API_KEY']}
17
+ machine git.heroku.com
18
+ login #{ENV['HEROKU_LOGIN']}
19
+ password #{ENV['HEROKU_API_KEY']}
20
+ CONFIG
20
21
  end
21
22
 
22
- Dir.mkdir("#{ENV['HOME']}/.ssh") unless Dir.exists?("#{ENV['HOME']}/.ssh")
23
+ Dir.mkdir("#{ENV['HOME']}/.ssh") unless Dir.exist?("#{ENV['HOME']}/.ssh")
23
24
  File.open("#{ENV['HOME']}/.ssh/config", 'a+') do |file|
24
- file << <<-EOF
25
+ file << <<~CONFIG
25
26
 
26
- VerifyHostKeyDNS yes
27
- StrictHostKeyChecking no
28
- EOF
27
+ VerifyHostKeyDNS yes
28
+ StrictHostKeyChecking no
29
+ CONFIG
29
30
  end
30
31
 
31
32
  system('git config --global url."https://git.heroku.com/".insteadOf heroku:')
32
- exit(1) unless $?.success?
33
+ exit(1) unless $CHILD_STATUS.success?
33
34
  end
34
35
 
35
36
  desc 'merge', 'Merges several simplecov json result files'
@@ -37,6 +38,7 @@ EOF
37
38
  def merge(*files)
38
39
  require 'simplecov'
39
40
  return if files.empty?
41
+
40
42
  results = []
41
43
 
42
44
  files.each do |file|
@@ -54,7 +56,7 @@ EOF
54
56
 
55
57
  desc 'rspec', 'Run rspec'
56
58
  def rspec
57
- system <<-EOF
59
+ system <<~COMMAND
58
60
  bundle _${BUNDLE_VERSION}_ exec rspec \
59
61
  --color \
60
62
  --format RspecJunitFormatter \
@@ -63,20 +65,20 @@ EOF
63
65
  --out $CIRCLE_ARTIFACTS/rspec.html \
64
66
  --format progress \
65
67
  $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
66
- EOF
67
- exit(1) unless $?.success?
68
+ COMMAND
69
+ exit(1) unless $CHILD_STATUS.success?
68
70
  end
69
71
 
70
72
  desc 'rubocop', 'Run rubocop'
71
73
  def rubocop
72
- system <<-EOF
73
- rubocop \
74
+ system <<~COMMAND
75
+ bundle _${BUNDLE_VERSION}_ exec rubocop \
74
76
  --parallel \
75
77
  --format progress \
76
78
  --format html \
77
79
  --out $CIRCLE_ARTIFACTS/rubocop/report.html
78
- EOF
79
- exit(1) unless $?.success?
80
+ COMMAND
81
+ exit(1) unless $CHILD_STATUS.success?
80
82
  end
81
83
 
82
84
  def self.exit_on_failure?
@@ -0,0 +1,3 @@
1
+ mkdir ~/.gem
2
+ echo -e "---\n:rubygems_api_key: $RUBYGEMS_API_KEY" > ~/.gem/credentials
3
+ chmod 0600 ~/.gem/credentials
@@ -15,8 +15,12 @@ if ((danger.github.pr.body || '').length < 5) {
15
15
  fail("Please provide a summary in the Pull Request description");
16
16
  }
17
17
 
18
- if (!labels.includes('review passed')) {
19
- fail("Has not passed code-review");
18
+ if (labels.includes('product review needed')) {
19
+ fail('Has not passed product review');
20
+ }
21
+
22
+ if (!(labels.includes('review passed') || labels.includes('code review passed'))) {
23
+ fail("Has not passed code review");
20
24
  }
21
25
 
22
26
  if (!labels.includes('QA passed')) {
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module NetsoftDanger
2
- VERSION = '0.3.3'.freeze
4
+ VERSION = '0.3.8'
3
5
  end
@@ -1,5 +1,6 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
3
4
  require 'netsoft-danger/version'
4
5
 
5
6
  Gem::Specification.new do |s|
@@ -15,11 +16,15 @@ Gem::Specification.new do |s|
15
16
  s.files = `git ls-files`.split("\n")
16
17
  s.require_paths = ['lib']
17
18
 
18
- s.add_development_dependency 'rake'
19
19
  s.add_runtime_dependency 'danger', '~> 5.0'
20
- s.add_runtime_dependency 'rubocop', '~> 0.74.0'
21
- s.add_runtime_dependency 'rubocop-rails', '~> 2.2.1'
22
- s.add_runtime_dependency 'rubocop-performance', '~> 1.4.1'
23
- s.add_runtime_dependency 'rubocop-rspec', '~> 1.35.0'
20
+ s.add_runtime_dependency 'faraday'
24
21
  s.add_runtime_dependency 'thor'
22
+
23
+ s.add_development_dependency 'rake'
24
+
25
+ s.add_development_dependency 'netsoft-rubocop', '= 1.0.1'
26
+ s.add_development_dependency 'rubocop', '= 0.74.0'
27
+ s.add_development_dependency 'rubocop-performance', '= 1.5.2'
28
+ s.add_development_dependency 'rubocop-rails', '= 2.4.2'
29
+ s.add_development_dependency 'rubocop-rspec', '= 1.38.1'
25
30
  end