release_robot 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d6a538b348c00e6432db77a517a6b137abf87085
4
+ data.tar.gz: 851612ac8cb53d5803ed37cbb04395af2e640445
5
+ SHA512:
6
+ metadata.gz: 29fd114c621cf47d88ffe255cdec15db430375516c721160d30002449ebf8031073381c3a680133e0755e233e922966fb78b1d86013bdb09a0e3413ea55a7fee
7
+ data.tar.gz: 96c91c028f7c655fdbfa369523f340854170f374060012e7061d90a3a6289c69cfbbc0dcb15b640ba523dcc6627fe57c4740f7dff51370e0e4765139800ce165
@@ -0,0 +1,50 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
@@ -0,0 +1,58 @@
1
+ Contributing
2
+ ============
3
+
4
+ We love pull requests from everyone. By participating in this project, you agree
5
+ to abide by the [contributor covenant].
6
+
7
+ [contributor covenant v1.4]: http://contributor-covenant.org/version/1/4/
8
+
9
+ Here's a quick guide for contributing:
10
+
11
+ 1. Fork the repo.
12
+
13
+ 2. Run the tests. We only take pull requests with passing tests, and it's great
14
+ to know that you have a clean slate: `bundle && bundle exec rake`
15
+
16
+ 3. Add a test for your change. Only refactoring and documentation changes
17
+ require no new tests. If you are adding functionality or fixing a bug, we need
18
+ a test!
19
+
20
+ 4. Make the test pass.
21
+
22
+ 5. Push to your fork and submit a pull request.
23
+
24
+ At this point you're waiting on us. We like to at least comment on, if not
25
+ accept, pull requests within seven business days. We may suggest some changes or improvements or
26
+ alternatives.
27
+
28
+ Some things that will increase the chance that your pull request is accepted,
29
+ taken straight from the Ruby on Rails guide:
30
+
31
+ * Use Rails idioms and helpers
32
+ * Include tests that fail without your code, and pass with it
33
+ * Update the documentation, the surrounding one, examples elsewhere, guides,
34
+ whatever is affected by your contribution
35
+
36
+ Running Tests
37
+ -------------
38
+
39
+ release_robot uses [cucumber]() for its unit tests. If you submit
40
+ tests that are not written for cucumber without a very good reason, you
41
+ will be asked to rewrite them before we'll accept.
42
+
43
+ ### To run a full test suite:
44
+
45
+ bundle exec rake
46
+
47
+ This will run RSpec and Cucumber against all version of Rails
48
+
49
+ ### To run single test file
50
+
51
+ bundle exec rake test TEST=test/test_foobar.rb
52
+
53
+ Syntax
54
+ ------
55
+
56
+ Please follow the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide).
57
+ Rubocop will be used on Pull Requests. If you submit code that deviates from this style,
58
+ you will be asked to justify it, and may be asked to change it.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2017 Mammoth HR
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ # release_robot
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ $ rake install
7
+ $ release_robot
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ This requires your Github username and password to authenticate and requires that you *do not* have 2-factor auth setup for Github. The gem will prompt you for those credentials on first run and store them in `~/.release_robot_settings.yml`.
13
+
14
+ ## What it does
15
+
16
+ Currently, this gem will scan all repositories in the `MammothHR` Github account, collect any Pull Requests that are labeled with "Ready for Thursday Release" or "Ready for Immediate Release," change the base branch to `weekly-release`, determine the build status from Travis, parse any Podio URLs from the Pull Request body, and print out two summaries:
17
+ 1. A verbose summary for posting to #releases channel, which shows the PR title, URL, Podio URL(s) if any, and the build status.
18
+ 2. A terse summary with today's date and a bulleted list of PR titles and the repo to which they belong.
19
+
20
+ ## Next steps to automate
21
+
22
+ - post reminder about updating versions in package.json if an addon is updated
23
+ - tag repos with the correct tag
24
+ - For the most part, for Thursday releases, this will be the next minor version (i.e. if the last tag was v2.13.2, then next is v2.13.3).
25
+ - For hrsc and hrsc-ember, their tags should always be in sync
26
+ - merge PRs with green builds (perhaps a separate script)
27
+ - delete branches after merge
28
+ - check if there's a staging branch for merged branches and either
29
+ - include that in the notes, or
30
+ - auto teardown
31
+ - mark Podio tasks as Complete after deploy (perhaps a separate script)
32
+ - skip CI for all but the last merge to `weekly-release`
33
+ - Can be done by adding 'skip ci' to the merge commit (in the web interface, this can be done when “Confirm Merge” comes up; unsure about API)
34
+ - figure out a standard way to define pre- or post-deploy steps so those can be included in the release prep somehow
35
+
36
+ *"Nice to have" Slack integrations:*
37
+ These would require setting up a slack bot server to receive Github webhooks
38
+ - Post to #code-review-requests when the label "Needs Code Review" is added (https://developer.github.com/v3/activity/events/types/#labelevent)
39
+ - Approved PR adds the :white_check_mark: reaction to the link posted in #code-review-requests (https://developer.github.com/v3/activity/events/types/#pullrequestreviewevent)
40
+ - Merged PR automatically adds the :merge: reaction to the link posted in #code-review-requests (https://developer.github.com/v3/activity/events/types/#pullrequestevent)
@@ -0,0 +1,21 @@
1
+ require 'rake/testtask'
2
+ require 'bundler/gem_tasks'
3
+
4
+ desc 'Run in IRB for debugging'
5
+ task :console do
6
+ require 'irb'
7
+ require 'irb/completion'
8
+ require 'pp'
9
+ require 'yaml'
10
+ require 'octokit'
11
+ Dir[File.expand_path "lib/**/*.rb"].each{|file| require_relative file }
12
+ ARGV.clear
13
+ IRB.start
14
+ end
15
+
16
+ Rake::TestTask.new do |t|
17
+ t.libs << 'test'
18
+ t.test_files = FileList['test/**/*_test.rb']
19
+ end
20
+
21
+ task default: :test
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'release_robot'
4
+ require 'release_robot/version'
5
+ require 'yaml'
6
+
7
+ ReleaseRobot.run(ARGV)
@@ -0,0 +1,134 @@
1
+ require 'optparse'
2
+ require 'optparse/date'
3
+ require 'ostruct'
4
+ require 'json'
5
+ require 'octokit'
6
+ require 'highline/import'
7
+ require 'release_robot/main'
8
+ require 'release_robot/printer'
9
+
10
+ module ReleaseRobot
11
+ class << self
12
+ def run(args)
13
+ options = ReleaseRobot.parse(args)
14
+
15
+ create_settings_file_if_nonexistent
16
+
17
+ fetch_envars_from_config
18
+
19
+ missing_envars = get_missing_envars
20
+
21
+ write_missing_envars(missing_envars) if missing_envars.any?
22
+
23
+ robot = ReleaseRobot::Main.new
24
+ pull_requests = robot.start
25
+ client = robot.client
26
+
27
+ ReleaseRobot::Printer.new(pull_requests, client).print_all
28
+ end
29
+
30
+ def parse(args)
31
+ options = OpenStruct.new
32
+
33
+ opt_parser = OptionParser.new do |opts|
34
+ opts.separator ''
35
+ opts.banner = 'Usage: release_robot [options]'
36
+
37
+ opts.separator ''
38
+ opts.separator 'Common options:'
39
+
40
+ opts.on_tail('-h', '--help', 'Show this message') do
41
+ puts opts
42
+ exit
43
+ end
44
+
45
+ opts.on_tail('-v', '--version', 'Show version') do
46
+ puts ReleaseRobot::VERSION
47
+ exit
48
+ end
49
+ end
50
+
51
+ opt_parser.parse!(args)
52
+ options
53
+ end
54
+
55
+ def get_missing_envars
56
+ missing_envars = {}
57
+
58
+ ReleaseRobot.envars.each do |key|
59
+ next if ENV[key]
60
+ missing_envars[key] = get_envar(key)
61
+ end
62
+
63
+ return missing_envars
64
+ end
65
+
66
+ def get_envar(key)
67
+ if key =~ /GITHUB_PASSWORD/
68
+ env_value = ask("Enter your #{key}: ") { |q| q.echo = "*" }
69
+ else
70
+ print "Enter your #{key}: "
71
+ env_value = gets.chomp
72
+ end
73
+ env_value.strip! unless should_not_strip?(key)
74
+ if env_value.length == 0
75
+ puts 'Invalid input. This is a required field.'
76
+ exit
77
+ end
78
+ env_value
79
+ end
80
+
81
+ def should_not_strip?(key)
82
+ false
83
+ end
84
+
85
+ def fetch_envars_from_config
86
+ return unless envars = YAML.load_file(settings_file_path)
87
+ envars.each_pair do |key, value|
88
+ value.strip! unless should_not_strip?(key)
89
+ ENV[key.upcase] = value
90
+ end
91
+ end
92
+
93
+ def write_missing_envars(missing_envars={})
94
+ puts "\nTo avoid entering setup information each time, the following configuration has been stored in `#{settings_file_path}`:"
95
+ missing_envars.each_pair do |key, value|
96
+ if key =~ /password|token/i
97
+ puts "\t#{key}=[FILTERED]"
98
+ else
99
+ puts "\t#{key}=#{value}"
100
+ end
101
+
102
+ data = YAML.load_file(settings_file_path) || {}
103
+ ENV[key.upcase] = data[key.downcase] = value
104
+ File.open(settings_file_path, 'w') { |f| YAML.dump(data, f) }
105
+ end
106
+ end
107
+
108
+ def create_settings_file_if_nonexistent
109
+ File.new(settings_file_path, "w+") unless File.file?(settings_file_path)
110
+ end
111
+
112
+ def settings_file_path
113
+ File.join(ENV['HOME'], '.release_robot_settings.yml')
114
+ end
115
+
116
+ def root
117
+ File.dirname __dir__
118
+ end
119
+
120
+ def envars
121
+ envars_help.keys
122
+ end
123
+
124
+ def envars_help
125
+ {
126
+ 'GITHUB_USERNAME' =>
127
+ "Your username for github.com\n\n",
128
+
129
+ 'GITHUB_PASSWORD' =>
130
+ "Your password for github.com\n\n",
131
+ }
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,88 @@
1
+ module ReleaseRobot
2
+ class Main
3
+ RELEASE_BASE_BRANCH = 'weekly-release'.freeze
4
+ THURSDAY_LABEL = 'Ready for Thursday Release'.freeze
5
+ IMMEDIATE_LABEL = 'Ready for Immediate Release'.freeze
6
+
7
+ def start
8
+ repos.each do |repo|
9
+ next unless repo.owner.login == 'MammothHR'
10
+
11
+ repo_name = repo.full_name
12
+ puts "Fetching issues for #{repo_name}"
13
+
14
+ all_issues = labels.map do |label|
15
+ client.list_issues(repo_name, labels: label)
16
+ end.flatten
17
+
18
+ all_issues.each do |issue|
19
+ collect_pull_requests(repo_name, issue)
20
+ end
21
+ end
22
+
23
+ return pull_requests
24
+ end
25
+
26
+ def labels
27
+ [THURSDAY_LABEL, IMMEDIATE_LABEL]
28
+ end
29
+
30
+ def client
31
+ @client ||= Octokit::Client.new(
32
+ login: ENV['GITHUB_USERNAME'],
33
+ password: ENV['GITHUB_PASSWORD']
34
+ )
35
+ rescue => ex
36
+ puts "Failed: #{ex}"
37
+ puts '(Do you have the right Github username and password stored in'
38
+ puts 'GITHUB_USERNAME and GITHUB_PASSWORD?)'
39
+ end
40
+
41
+ def repos
42
+ @repos ||= client.repos(owner: 'MammothHR')
43
+ end
44
+
45
+ def pull_requests
46
+ @pull_requests ||= {
47
+ 'success' => [],
48
+ 'pending' => [],
49
+ 'failure' => []
50
+ }
51
+ end
52
+
53
+ def collect_pull_requests(repo_name, issue)
54
+ print "- Determining build status for #{issue.number}"
55
+ pull_request = client.pull_request(repo_name, issue.number)
56
+
57
+ # Build status
58
+ status = client.combined_status(repo_name, pull_request.head.sha)
59
+
60
+ change_base(repo_name, pull_request)
61
+
62
+ sort_issue_by_status(issue, status.state, repo_name)
63
+ end
64
+
65
+ def change_base(repo_name, pull_request)
66
+ client.update_pull_request(
67
+ repo_name,
68
+ pull_request.number,
69
+ nil,
70
+ nil,
71
+ base: RELEASE_BASE_BRANCH
72
+ )
73
+ rescue Octokit::UnprocessableEntity => ex
74
+ puts "Error occurred when attempting to change base branch to #{RELEASE_BASE_BRANCH}:"
75
+ puts ex.message
76
+ end
77
+
78
+ def sort_issue_by_status(issue, status, repo_name)
79
+ case status
80
+ when 'success' then pull_requests['success'] << [repo_name, issue]
81
+ when 'pending' then pull_requests['pending'] << [repo_name, issue]
82
+ when 'failure' then pull_requests['failure'] << [repo_name, issue]
83
+ end
84
+
85
+ print " -- #{status}\n"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,60 @@
1
+ module ReleaseRobot
2
+ class Printer
3
+ PODIO_URL_REGEX = /https:\/\/podio.com\/hranswerlink-8ee92nawfl\/issue-tracker\/apps\/product-feedback\/items\/\d+/
4
+
5
+ attr_accessor :pull_requests, :client
6
+
7
+ def initialize(pull_requests, client)
8
+ @pull_requests = pull_requests
9
+ @client = client
10
+ end
11
+
12
+ def print_all
13
+ print_prep_list
14
+ print_deploy_list
15
+ end
16
+
17
+ def print_prep_list
18
+ print_title 'Prep list for #releases'
19
+
20
+ puts "For today's release:\n\n"
21
+
22
+ pull_requests.each_pair do |status, issues|
23
+ issues.each do |(repo_name, issue)|
24
+ puts issue.title
25
+ puts issue.html_url
26
+ podio_urls(repo_name, issue).each { |url| puts url }
27
+ puts "*Build #{status}*"
28
+ puts
29
+ end
30
+ end
31
+ end
32
+
33
+ def print_deploy_list
34
+ print_title 'List for #deploys'
35
+
36
+ puts Date.today.strftime('%D')
37
+
38
+ pull_requests.each_pair do |_, issues|
39
+ issues.each do |(repo_name, issue)|
40
+ slug = repo_name.gsub('MammothHR/', '')
41
+
42
+ puts "(#{slug}) #{issue.title}"
43
+ end
44
+ end
45
+ end
46
+
47
+ def print_title(title)
48
+ puts
49
+ puts '-' * 50
50
+ puts title
51
+ puts '-' * 50
52
+ puts
53
+ end
54
+
55
+ def podio_urls(repo_name, issue)
56
+ pr = client.pull_request(repo_name, issue.number)
57
+ pr.body.scan PODIO_URL_REGEX
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module ReleaseRobot
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'release_robot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'release_robot'
8
+ spec.version = ReleaseRobot::VERSION
9
+ spec.authors = ['Mark J. Lehman']
10
+ spec.email = ['markopolo@gmail.com']
11
+ spec.description = %q{Automate release tasks}
12
+ spec.summary = %q{Automate tasks surrounding releasing and deploying new code, informing stakeholders, and getting feedback.}
13
+ spec.homepage = 'https://github.com/MammothHR/release_robot'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 2.0'
22
+ spec.add_development_dependency 'pry', '~> 0'
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'rake', '~> 10.5'
25
+
26
+ spec.add_runtime_dependency 'octokit', '~> 4.6.2', '>= 4.6.0'
27
+ spec.add_runtime_dependency 'highline', '~> 1.7.0', '>= 1.7.0'
28
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: release_robot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Mark J. Lehman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: octokit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 4.6.2
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 4.6.0
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: 4.6.2
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 4.6.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: highline
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.7.0
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.7.0
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 1.7.0
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 1.7.0
95
+ description: Automate release tasks
96
+ email:
97
+ - markopolo@gmail.com
98
+ executables:
99
+ - release_robot
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - CONTRIBUTING.md
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - bin/release_robot
110
+ - lib/release_robot.rb
111
+ - lib/release_robot/main.rb
112
+ - lib/release_robot/printer.rb
113
+ - lib/release_robot/version.rb
114
+ - release_robot.gemspec
115
+ homepage: https://github.com/MammothHR/release_robot
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '2.0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.6.11
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Automate tasks surrounding releasing and deploying new code, informing stakeholders,
139
+ and getting feedback.
140
+ test_files: []