abtion-aid 0.2.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a1339e7c88c58cb0b3e6bd78f80b381433f50075466af838e62b53352ffec23
4
- data.tar.gz: 673ba5a521e3e86853c9acbeaa46d7dccf3000faea06fa47ce5321c0b82f4eca
3
+ metadata.gz: 5a777e8c76409c4bb71e1432027481e6887f6404b538ed81f324af12bcddd799
4
+ data.tar.gz: 16f94988582a60e12c00fed82c7b62f8a159bb4b4be61e8c4a6f5c8052ce222a
5
5
  SHA512:
6
- metadata.gz: eeaebd179113a9cb4ac6f52c1789b1f20286a428c6c0d1bb380322c91519e99663988e403ddb048fea646f923a4f6773560e30a014f69e3e61d0ecde11236701
7
- data.tar.gz: d35bb798022f67a5f91c550cd0b6b526a84c548992454061ae1a7e9b22ba3e4e902331b254dd95faa3c7266621670d9fa0fcf90e37f2a17c73e65c853e5df4fc
6
+ metadata.gz: 174b5404a30f59b4b789f76b7095dffc304251c84f38374a8eea6e2d21be21b4b48a3419364fdf27568e0ab86a39d01f46ab2ce5b3b2ab8be3bb348e8bfb95d8
7
+ data.tar.gz: f4bdc737181cf232024863952dfb11255dbee8df42eadd2b9fb20db445125551799f2456a15fbe2dd4e299110d39437ef6b7ab2ca0318cbe621435adc9435606
data/README.md CHANGED
@@ -34,7 +34,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
34
34
 
35
35
  ## Contributing
36
36
 
37
- Bug reports and pull requests are welcome on GitHub at https://github.com/dbalatero/aid.
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/abtion/aid.
38
38
 
39
39
  ## License
40
40
 
data/aid.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['Abtion', 'David Balatero']
11
11
  spec.email = ['mail@abtion.com']
12
12
 
13
- spec.summary = 'A library to make repo scripts easy and discoverable.'
13
+ spec.summary = 'Abtion\'s development workflow tools'
14
14
  spec.homepage = 'https://github.com/abtion/aid'
15
15
  spec.license = 'MIT'
16
16
 
@@ -27,4 +27,8 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
28
28
  spec.add_development_dependency 'rspec', '~> 3.0'
29
29
  spec.add_development_dependency 'rubocop'
30
+
31
+ spec.add_dependency 'http'
32
+ spec.add_dependency 'slack-notifier'
33
+ spec.add_dependency 'asana'
30
34
  end
@@ -0,0 +1,310 @@
1
+ require "asana"
2
+ require "tempfile"
3
+ require_relative "./shared/git_config"
4
+ require_relative "./shared/git_staged"
5
+
6
+ ASANA_URL_PATTERN = %r{https?://app.asana.com/\d+/(?<project_id>\d+)/(?<task_id>\d+)}.freeze
7
+
8
+
9
+ module Aid
10
+ module Scripts
11
+ class Begin < Aid::Script
12
+ include Aid::GitConfig
13
+ include Aid::GitStaged
14
+
15
+ def self.description
16
+ "Starts a new Asana story"
17
+ end
18
+
19
+ def self.help
20
+ <<~HELP
21
+ Fill me in.
22
+ aid begin:
23
+ Starts a story on Asana, moves it into the Work In Progress column,
24
+ and opens a pull request on GitHub for you.
25
+ Usage:
26
+ aid begin "https://app.asana.com/0/1135298954694367/1135903276459289/f"
27
+ HELP
28
+ end
29
+
30
+ def run
31
+ check_for_hub!
32
+ check_for_hub_credentials!
33
+
34
+ prompt_for_config!(
35
+ "asana.personal-access-token",
36
+ "Enter your personal access token",
37
+ <<~EOF
38
+ 1. Visit https://app.asana.com/0/developer-console
39
+ 2. Click "+ New access token" under "Personal access tokens"
40
+ 3. Give the token a name
41
+ 4. Copy the token and paste it back here.
42
+ EOF
43
+ )
44
+
45
+ prompt_for_config!(
46
+ "github.user",
47
+ "Enter your GitHub username",
48
+ <<~EOF
49
+ 1. Visit https://github.com and sign in
50
+ 2. Find the GitHub username you use and paste it in here
51
+ EOF
52
+ )
53
+
54
+ set_asana_project_id_if_needed!
55
+ check_for_existing_branch!
56
+ ensure_base_branch_is_ok!
57
+
58
+ step "Starting '#{asana_task.name}' on Asana" do
59
+ start_task
60
+ end
61
+
62
+ step "Creating local branch off '#{base_branch}'" do
63
+ check_for_staged_files!
64
+ reset_hard_to_base_branch!
65
+ create_git_branch!
66
+ make_empty_commit!
67
+ end
68
+
69
+ step "Pushing to Github" do
70
+ push_branch_to_github!
71
+ end
72
+
73
+ step "Opening pull request on Github" do
74
+ open_pull_request_on_github!
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def open_pull_request_on_github!
81
+ tempfile = Tempfile.new("begin_pull_request")
82
+
83
+ begin
84
+ tempfile.write(pull_request_description)
85
+ tempfile.close
86
+
87
+ labels = "WIP"
88
+
89
+ url = `hub pr list -h #{current_branch_name} -f '%U'`.strip
90
+ if url.empty?
91
+ url = `hub pull-request -F #{tempfile.path} -l "#{labels}" -d -a #{github_user}`.strip
92
+ end
93
+ puts colorize(:blue, url)
94
+
95
+ # Copy it to your clipboard
96
+ pbcopy_exist = system("command -v pbcopy >/dev/null 2>&1")
97
+ system("echo #{url.inspect} | pbcopy") if pbcopy_exist
98
+
99
+ # Update metadata in .git/config
100
+ result = url.match(%r{pull/(\d+)})
101
+ pull_request_id = result && result[1]
102
+
103
+ git_config("tasks.#{asana_task_id}.url", asana_url)
104
+ git_config("tasks.#{asana_task_id}.name", asana_task.name)
105
+ git_config("tasks.#{asana_task_id}.pull-request-id", pull_request_id)
106
+ ensure
107
+ tempfile.unlink
108
+ end
109
+ end
110
+
111
+ def github_user
112
+ git_config("github.user")
113
+ end
114
+
115
+ def push_branch_to_github!
116
+ silent "git push --force-with-lease -u origin '#{current_branch_name}'"
117
+ end
118
+
119
+ def make_empty_commit!
120
+ msg = "Initial commit for task ##{asana_task_id} [ci skip]"
121
+
122
+ silent("git commit --allow-empty -m #{msg.inspect}")
123
+ end
124
+
125
+ def current_branch_name
126
+ `git rev-parse --abbrev-ref HEAD`.strip
127
+ end
128
+
129
+ def ensure_base_branch_is_ok!
130
+ base_branch
131
+ end
132
+
133
+ def base_branch
134
+ @base_branch ||= prompt_for_base_branch
135
+ end
136
+
137
+ def prompt_for_base_branch
138
+ current_branch = current_branch_name
139
+
140
+ if current_branch != "master"
141
+ print colorize(
142
+ :yellow,
143
+ "Current branch is #{colorize(:blue, current_branch)} - do you want "\
144
+ "to create a branch off that? (y/n) > "
145
+ )
146
+
147
+ answer = STDIN.gets.strip
148
+
149
+ exit if answer[0] != "y"
150
+ end
151
+
152
+ current_branch
153
+ end
154
+
155
+ def story_branch_name
156
+ name = asana_task
157
+ .name
158
+ .gsub(%r{[/.]}, "-")
159
+ .gsub(/\s+/, "-")
160
+ .gsub(/[^\w-]/, "")
161
+ .downcase
162
+ .slice(0, 50)
163
+
164
+ "#{name}-#{asana_task_id}"
165
+ end
166
+
167
+ def start_task
168
+ # Assign task to runner of this script
169
+ user = asana.users.me
170
+ asana_task.update(assignee: user.gid)
171
+
172
+ # Move it to the WIP column
173
+ asana_task.add_project(
174
+ project: asana_project.gid,
175
+ section: asana_wip_section.gid
176
+ )
177
+ end
178
+
179
+ def asana_project
180
+ @asana_project ||= asana.projects.find_by_id(asana_project_id)
181
+ end
182
+
183
+ def asana_task
184
+ @asana_task ||= asana.tasks.find_by_id(asana_task_id)
185
+ end
186
+
187
+ def asana_sections
188
+ asana.sections.find_by_project(project: asana_project.gid)
189
+ end
190
+
191
+ def asana_wip_section
192
+ asana_sections.detect do |task|
193
+ task.name =~ /(Work In Progress|WIP)/i
194
+ end
195
+ end
196
+
197
+ def asana_project_id
198
+ git_config("asana.project-id")&.to_i
199
+ end
200
+
201
+ def set_asana_project_id_if_needed!
202
+ return if asana_project_id
203
+
204
+ result = asana_url
205
+ .match(ASANA_URL_PATTERN)
206
+
207
+ abort help unless result
208
+
209
+ id = result[:project_id].to_i
210
+
211
+ puts "Setting asana.project-id to #{id}"
212
+
213
+ git_config("asana.project-id", id)
214
+ end
215
+
216
+ def asana_task_id
217
+ @asana_task_id ||=
218
+ begin
219
+ result = asana_url
220
+ .match(ASANA_URL_PATTERN)
221
+
222
+ abort help unless result
223
+
224
+ result[:task_id].to_i
225
+ end
226
+ end
227
+
228
+ def asana_url
229
+ argv.first.to_s
230
+ end
231
+
232
+ def asana
233
+ @asana ||=
234
+ Asana::Client.new do |client|
235
+ client.default_headers "asana-enable" => "string_ids,new_sections"
236
+ client.authentication :access_token,
237
+ git_config("asana.personal-access-token")
238
+ end
239
+ end
240
+
241
+ def pull_request_description
242
+ <<~EOF
243
+ #{asana_task.name}
244
+
245
+ #{asana_url}
246
+ EOF
247
+ end
248
+
249
+ def reset_hard_to_base_branch!
250
+ silent "git checkout #{base_branch}",
251
+ "git fetch origin",
252
+ "git reset --hard origin/#{base_branch}"
253
+ end
254
+
255
+ def check_for_existing_branch!
256
+ return unless system "git ls-remote --exit-code --heads origin #{story_branch_name}"
257
+
258
+ print colorize(
259
+ :yellow,
260
+ "The branch for this task (#{story_branch_name}) already exists on origin, \n"\
261
+ "if you continue, it will be reset to master. \n"\
262
+ "Do you want to continue? ? (y/n) > "
263
+ )
264
+
265
+ answer = STDIN.gets.strip.downcase
266
+
267
+ exit if answer[0] != "y"
268
+ end
269
+
270
+ def create_git_branch!
271
+ system! "git checkout -b '#{story_branch_name}'"
272
+ end
273
+
274
+ def check_for_hub_credentials!
275
+ config_file = "#{ENV['HOME']}/.config/hub"
276
+ credentials_exist = File.exist?(config_file) &&
277
+ File.read(config_file).match(/oauth_token/i)
278
+
279
+ unless credentials_exist
280
+ abort <<~EOF
281
+ Your GitHub credentials are not set. Run this command:
282
+
283
+ $ hub pull-request
284
+
285
+ and when prompted for login details, enter them. It will give you
286
+ an error at the end, but you can safely ignore it.
287
+ EOF
288
+ end
289
+ end
290
+
291
+ def command?(name)
292
+ system("which #{name} 2>&1 >/dev/null")
293
+ end
294
+
295
+ def check_for_hub!
296
+ unless command?("hub")
297
+ abort <<~EOF
298
+ You need to install `hub` before you can use this program.
299
+ To fix:
300
+ $ brew install hub
301
+ EOF
302
+ end
303
+ end
304
+
305
+ def silent(*cmds)
306
+ cmds.each { |cmd| system("#{cmd} >/dev/null") }
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,133 @@
1
+ require "fileutils"
2
+
3
+ require_relative './shared/git_config'
4
+ require_relative './shared/git_staged'
5
+ require 'bundler/setup'
6
+
7
+ module Aid
8
+ module Scripts
9
+ class Finish < Aid::Script
10
+ include Aid::GitConfig
11
+ include Aid::GitStaged
12
+
13
+ def self.description
14
+ "Commits what is currently staged with a [finishes] tag"
15
+ end
16
+
17
+ def run
18
+ within_dir(project_root) do
19
+ Bundler.with_clean_env do
20
+ check_for_staged_files!
21
+ check_for_master!
22
+
23
+ clean_up_feature_branch!
24
+ amend_commit_with_finish_message!
25
+ check_for_linter_prehook!
26
+ force_push_to_github!
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def check_for_master!
34
+ return unless `git rev-parse --abbrev-ref HEAD`.chomp == "master"
35
+
36
+ abort colorize(:red, "You are on master, can not aid finish")
37
+ end
38
+
39
+ def clean_up_feature_branch!
40
+ step "Removing any empty commits & pulling in any changes from master" do
41
+ system! "git fetch origin && git rebase -f origin/master"
42
+ end
43
+ end
44
+
45
+ def amend_commit_with_finish_message!
46
+ step "Amending your last commit with finish message" do
47
+ message = amend_message_with_tag(`git log -1 --pretty="format:%B"`.strip)
48
+ template_file = create_template_file
49
+
50
+ begin
51
+ template_file.write(message)
52
+ template_file.close
53
+
54
+ system! "git commit --amend -o -F '#{template_file.path}'"
55
+ ensure
56
+ template_file.close
57
+ template_file.unlink
58
+ end
59
+ end
60
+ end
61
+
62
+ def check_for_linter_prehook!
63
+ step "checking for linter prehook" do
64
+ pre_commit_file_location = ".git/hooks/pre-commit"
65
+ pre_commit_exists = File.exist?(pre_commit_file_location)
66
+
67
+ linter_path = ".git/hooks/rubocop-linter"
68
+ if pre_commit_exists
69
+ if File.readlines(pre_commit_file_location).grep(/rubocop[-]linter/).empty?
70
+ open(pre_commit_file_location, "a") do |f|
71
+ f << "bash #{linter_path}"
72
+ end
73
+ end
74
+ else
75
+ add_rubocop_linter linter_path
76
+ end
77
+ end
78
+ end
79
+
80
+ def add_rubocop_linter(linter_path)
81
+ dirname = File.dirname(linter_path)
82
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
83
+ linter_template = <<~RUBOCOP_LINTER
84
+ #!/bin/bash
85
+ rubocop -a
86
+ RUBOCOP_LINTER
87
+ file = File.open(linter_path, "w+")
88
+ file.puts linter_template
89
+ file.close
90
+ end
91
+
92
+ def force_push_to_github!
93
+ step "Force pushing your branch to origin" do
94
+ system! "git push --force-with-lease origin #{branch_name.inspect}"
95
+ end
96
+ end
97
+
98
+ def amend_message_with_tag(message)
99
+ finish_message = asana_finish_message
100
+ message = message.gsub("\n\n#{finish_message}", "")
101
+
102
+ "#{message}\n\n#{finish_message}"
103
+ end
104
+
105
+ def asana_finish_message
106
+ metadata = [asana_task_name, asana_task_url].compact
107
+ metadata_lines = metadata.join("\n ")
108
+
109
+ "Finishes:\n #{metadata_lines}"
110
+ end
111
+
112
+ def asana_task_name
113
+ git_config("tasks.#{asana_task_id}.name")
114
+ end
115
+
116
+ def asana_task_url
117
+ git_config("tasks.#{asana_task_id}.url")
118
+ end
119
+
120
+ def asana_task_id
121
+ @asana_task_id ||= branch_name&.split("-")&.last
122
+ end
123
+
124
+ def branch_name
125
+ `git rev-parse --abbrev-ref HEAD`.strip
126
+ end
127
+
128
+ def create_template_file
129
+ Tempfile.new("git-commit-template")
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,346 @@
1
+ require "http"
2
+ require "json"
3
+ require "slack-notifier"
4
+ require_relative "./shared/git_config"
5
+ require_relative "./shared/team"
6
+
7
+ module Aid
8
+ module Scripts
9
+ class Review < Aid::Script
10
+ include Aid::GitConfig
11
+
12
+ def self.description
13
+ "Requests a review from one or more team members"
14
+ end
15
+
16
+ def self.help
17
+ <<~HELP
18
+ Usage: $ aid review
19
+ Type aid review from your feature branch and follow the prompts
20
+ HELP
21
+ end
22
+
23
+ def run
24
+ check_for_minimum_version_of_hub!
25
+
26
+ prompt_for_reviewers!
27
+ run_aid_finish_if_needed!
28
+
29
+ change_wip_to_reviewable_label!
30
+ move_asana_task_to_pull_request_column!
31
+
32
+ step "Marking PR ready for review" do
33
+ pull_request.mark_ready_for_review!
34
+ puts "✅ done"
35
+ end
36
+
37
+ add_reviewers_to_pr!
38
+ ping_reviewers_on_slack!
39
+ end
40
+
41
+ private
42
+
43
+ def ping_reviewers_on_slack!
44
+ step "Pinging reviewers on slack" do
45
+ slack.ping(
46
+ slack_message,
47
+ username: "review-bot",
48
+ channel: slack_channel
49
+ )
50
+ end
51
+ end
52
+
53
+ def github_username
54
+ git_config("github.user")
55
+ end
56
+
57
+ def current_user_slack_tag
58
+ member = team.members.find { |member| member.github_username == github_username }
59
+ member&.slack_tag || git_config("user.name")
60
+ end
61
+
62
+
63
+ def slack_message
64
+ user_tags = @reviewers.map(&:slack_tag)
65
+
66
+ <<~MSG
67
+ :pray: *Review request from #{current_user_slack_tag}*
68
+ _#{pull_request.title}_
69
+ #{pull_request.url}
70
+
71
+ Requested: #{user_tags.join(', ')}
72
+ MSG
73
+ end
74
+
75
+ def slack
76
+ @slack ||= Slack::Notifier.new(slack_webhook)
77
+ end
78
+
79
+ def slack_webhook
80
+ "https://hooks.slack.com/services/T02A0DJMA/"\
81
+ "BN49T3ZC0/Q536v8O5pzn1NSqDJ9BpEV3s"
82
+ end
83
+
84
+ def slack_channel
85
+ "#verisure"
86
+ end
87
+
88
+ def add_reviewers_to_pr!
89
+ step "Assigning reviewers on Github" do
90
+ github_client.post(
91
+ "#{api_prefix}/pulls/#{pull_request.id}/requested_reviewers",
92
+ json: {
93
+ reviewers: @reviewers.map(&:github_username)
94
+ }
95
+ )
96
+ end
97
+ end
98
+
99
+ def check_for_minimum_version_of_hub!
100
+ match = `hub --version`.strip.match(/hub version (.+)$/)
101
+ version = Gem::Version.new(match[1])
102
+ min_version = "2.8.4"
103
+
104
+ if version < Gem::Version.new(min_version)
105
+ abort "aid review requires hub at #{min_version} or greater. "\
106
+ "Run `brew upgrade hub` to upgrade."
107
+ end
108
+ rescue StandardError
109
+ abort "Error checking for hub version. Ensure you have it installed with "\
110
+ "$ brew install hub"
111
+ end
112
+
113
+ def run_aid_finish_if_needed!
114
+ branch_commit_logs = `git log --format=full master..`.strip
115
+
116
+ if branch_commit_logs =~ %r{Finishes:.+https://app.asana.com}mi
117
+ step "Doing one last push to GitHub" do
118
+ system! "git push --force-with-lease origin #{current_branch_name}"
119
+ end
120
+ else
121
+ step "Running aid finish..." do
122
+ Finish.run
123
+ end
124
+ end
125
+ end
126
+
127
+ def move_asana_task_to_pull_request_column!
128
+ step "Moving Asana task to Pull Request column" do
129
+ asana_task.add_project(
130
+ project: asana_project.gid,
131
+ section: asana_pr_section.gid
132
+ )
133
+ end
134
+ end
135
+
136
+ def team
137
+ @team ||= Aid::Team.from_yml("#{project_root}/.team.yml")
138
+ end
139
+
140
+ def prompt_for_reviewers!
141
+ puts "Which reviewers do you want to review this PR?"
142
+ puts
143
+
144
+ @reviewers = team.prompt_for_members
145
+ puts
146
+
147
+ abort colorize(:red, "Please select a reviewer from the list") if @reviewers.empty?
148
+ end
149
+
150
+ def change_wip_to_reviewable_label!
151
+ step "Changing WIP to reviewable label" do
152
+ puts "Deleting WIP label..."
153
+
154
+ github_client.delete("#{api_prefix}/issues/#{pull_request.id}/labels/wip")
155
+
156
+ puts "Adding reviewable label..."
157
+
158
+ github_client.post(
159
+ "#{api_prefix}/issues/#{pull_request.id}/labels",
160
+ json: {
161
+ labels: ["reviewable"]
162
+ }
163
+ )
164
+ end
165
+ end
166
+
167
+ def api_prefix
168
+ "https://api.github.com/repos/#{repo_name}"
169
+ end
170
+
171
+ def github_auth_token
172
+ @github_auth_token ||= load_github_auth_token
173
+ end
174
+
175
+ def load_github_auth_token
176
+ credential_file = "#{ENV['HOME']}/.config/hub"
177
+
178
+ abort "No hub credentials in #{credential_file}" unless File.exist?(credential_file)
179
+
180
+ credentials = YAML.safe_load(File.read(credential_file))
181
+ credentials["github.com"][0]["oauth_token"]
182
+ end
183
+
184
+ def github_client
185
+ HTTP.headers("Authorization" => "token #{github_auth_token}")
186
+ end
187
+
188
+ def asana_task_id
189
+ @asana_task_id ||=
190
+ begin
191
+ result = current_branch_name.strip.match(/\d+$/)
192
+ result && result[0]
193
+ end
194
+ end
195
+
196
+ def current_branch_name
197
+ `git rev-parse --abbrev-ref HEAD`.strip
198
+ end
199
+
200
+ def repo_name
201
+ remote_url = `git remote get-url origin`.strip
202
+ result = remote_url.match(%r{github.com[:/](?<repo_name>\w+/\w+)(?:\.git)?})
203
+ result && result[:repo_name]
204
+ end
205
+
206
+ def pull_request
207
+ @pull_request ||= local_github_pull_request ||
208
+ find_remote_github_pull_request
209
+ end
210
+
211
+ def local_github_pull_request
212
+ id = git_config("asana.#{asana_task_id}.pull-request-id")
213
+ title = git_config("asana.#{asana_task_id}.name")
214
+
215
+ return nil unless id && title
216
+
217
+ PullRequest.new(
218
+ id: id,
219
+ title: title,
220
+ repo_name: repo_name
221
+ )
222
+ end
223
+
224
+ def find_remote_github_pull_request
225
+ sep = "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
226
+ cmd = %(hub pr list --format="#PR: %i\nTitle: %t\n\n%b\n#{sep}\n")
227
+
228
+ raw_pull_requests = `#{cmd}`.strip
229
+ pull_requests = raw_pull_requests.split(sep)
230
+ request = pull_requests.detect { |pr| pr.include?(asana_task_id) }
231
+
232
+ abort "Could not find a PR that matches story #{asana_task_id}" unless request
233
+
234
+ id = request.match(/PR: #(\d+)/)[1]
235
+ title = request.match(/Title: (.+?)\n/)[1]
236
+
237
+ PullRequest.new(
238
+ id: id,
239
+ title: title,
240
+ repo_name: repo_name
241
+ )
242
+ end
243
+
244
+ def asana_task
245
+ @asana_task ||= asana.tasks.find_by_id(asana_task_id)
246
+ end
247
+
248
+ def asana_project
249
+ @asana_project ||= asana.projects.find_by_id(asana_project_id)
250
+ end
251
+
252
+ def asana_project_id
253
+ git_config("asana.project-id").to_i
254
+ end
255
+
256
+ def asana_sections
257
+ asana.sections.find_by_project(project: asana_project.gid)
258
+ end
259
+
260
+ def asana_pr_section
261
+ asana_sections.detect do |task|
262
+ task.name =~ /Pull Request/i
263
+ end
264
+ end
265
+
266
+ def asana
267
+ @asana ||=
268
+ Asana::Client.new do |client|
269
+ client.default_headers "asana-enable" => "string_ids,new_sections"
270
+ client.authentication :access_token,
271
+ git_config("asana.personal-access-token")
272
+ end
273
+ end
274
+
275
+ class PullRequest
276
+ attr_reader :id, :title, :url
277
+
278
+ def initialize(id:, title:, repo_name:)
279
+ @id = id
280
+ @title = title
281
+ @url = "https://github.com/#{repo_name}/pull/#{id}"
282
+ end
283
+
284
+ # rubocop:disable Metrics/MethodLength
285
+ def mark_ready_for_review!
286
+ cmd = <<~CMD
287
+ hub api graphql \
288
+ -H "Accept: application/vnd.github.shadow-cat-preview+json" \
289
+ -f query='
290
+ mutation MarkPullRequestReady {
291
+ markPullRequestReadyForReview(
292
+ input: {
293
+ pullRequestId:"#{graphql_id}"
294
+ }
295
+ ) {
296
+ pullRequest {
297
+ isDraft
298
+ number
299
+ }
300
+ }
301
+ }
302
+ '
303
+ CMD
304
+
305
+ `#{cmd}`
306
+ end
307
+
308
+ private
309
+
310
+ def graphql_id
311
+ @graphql_id ||= find_graphql_id
312
+ end
313
+
314
+ def find_graphql_id
315
+ cmd = <<~CMD
316
+ hub api graphql -f query='
317
+ query FindPullRequestId {
318
+ repository(owner:"abtion", name:"verisure") {
319
+ pullRequests(states:OPEN, first: 25) {
320
+ nodes {
321
+ id
322
+ number
323
+ }
324
+ }
325
+ }
326
+ }
327
+ '
328
+ CMD
329
+
330
+ json = `#{cmd}`.strip
331
+ response = JSON.parse(json)
332
+
333
+ pull_requests = response.dig(
334
+ "data",
335
+ "repository",
336
+ "pullRequests",
337
+ "nodes"
338
+ )
339
+
340
+ request = pull_requests.find { |pr| pr["number"].to_s == id.to_s }
341
+ request["id"]
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
@@ -0,0 +1,39 @@
1
+ module Aid
2
+ module GitConfig
3
+ def git_config(key, value = nil)
4
+ if value
5
+ `git config --local --add #{key.inspect} #{value.inspect}`
6
+ else
7
+ git_value = `git config --get #{key.inspect}`.strip
8
+ git_value.empty? ? nil : git_value
9
+ end
10
+ end
11
+
12
+ def prompt_for_config!(key, prompt_msg, remedy)
13
+ value = git_config(key)
14
+
15
+ if value == "" || value.nil?
16
+ puts <<~EOF
17
+ Missing git config "#{key}":
18
+ To find this value:
19
+ #{remedy}
20
+ EOF
21
+
22
+ new_value = prompt(prompt_msg)
23
+
24
+ if new_value.empty?
25
+ abort "Empty value, aborting"
26
+ else
27
+ git_config(key, new_value)
28
+ end
29
+ end
30
+ end
31
+
32
+ def prompt(msg)
33
+ print "#{msg} > "
34
+ value = STDIN.gets.strip
35
+ puts
36
+ value
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ module Aid
2
+ module GitStaged
3
+ def check_for_staged_files!
4
+ file_change_status_codes = "^\s*[MADRCU]"
5
+ return unless system("git status -s | grep #{file_change_status_codes.inspect} >/dev/null 2>&1")
6
+
7
+ abort colorize(:red, "You have modified/staged files, cannot aid")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,60 @@
1
+ module Aid
2
+ class Team
3
+ attr_reader :members
4
+
5
+ def initialize(members)
6
+ @members =
7
+ members.map do |member|
8
+ Member.new(
9
+ member["name"],
10
+ member["github"],
11
+ member["slack"]
12
+ )
13
+ end
14
+ end
15
+
16
+ def self.from_yml(path)
17
+ members = YAML.safe_load(File.read(path))["team"]
18
+
19
+ Team.new(members)
20
+ end
21
+
22
+ def prompt_for_members
23
+ puts "Enter their number(s) below. For multiple team members, enter "\
24
+ "multiple numbers separated by spaces or commas."
25
+ puts
26
+
27
+ members.each.with_index do |member, index|
28
+ puts "#{index + 1}. #{member.name} (@#{member.github_username})"
29
+ end
30
+
31
+ puts
32
+ print "> "
33
+
34
+ numbers = $stdin.gets.strip.split(/[^\d]+/)
35
+
36
+ indexes =
37
+ numbers
38
+ .map { |num| num.to_i - 1 }
39
+ .reject { |num| num < 0 }
40
+
41
+ indexes.map { |index| members[index] }.compact
42
+ end
43
+
44
+ private
45
+
46
+ class Member
47
+ attr_reader :name, :github_username, :slack_user_id
48
+
49
+ def initialize(name, github_username, slack_user_id)
50
+ @name = name
51
+ @github_username = github_username
52
+ @slack_user_id = slack_user_id
53
+ end
54
+
55
+ def slack_tag
56
+ "<@#{slack_user_id}>"
57
+ end
58
+ end
59
+ end
60
+ end
data/lib/aid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aid
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abtion-aid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abtion
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-11-06 00:00:00.000000000 Z
12
+ date: 2019-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -81,6 +81,48 @@ dependencies:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: http
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: slack-notifier
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: asana
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
84
126
  description:
85
127
  email:
86
128
  - mail@abtion.com
@@ -123,10 +165,16 @@ files:
123
165
  - lib/aid/plugins.rb
124
166
  - lib/aid/script.rb
125
167
  - lib/aid/scripts.rb
168
+ - lib/aid/scripts/begin.rb
126
169
  - lib/aid/scripts/doctor.rb
170
+ - lib/aid/scripts/finish.rb
127
171
  - lib/aid/scripts/help.rb
128
172
  - lib/aid/scripts/init.rb
129
173
  - lib/aid/scripts/new.rb
174
+ - lib/aid/scripts/review.rb
175
+ - lib/aid/scripts/shared/git_config.rb
176
+ - lib/aid/scripts/shared/git_staged.rb
177
+ - lib/aid/scripts/shared/team.rb
130
178
  - lib/aid/version.rb
131
179
  homepage: https://github.com/abtion/aid
132
180
  licenses:
@@ -147,9 +195,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
195
  - !ruby/object:Gem::Version
148
196
  version: '0'
149
197
  requirements: []
150
- rubyforge_project:
151
- rubygems_version: 2.7.7
198
+ rubygems_version: 3.0.3
152
199
  signing_key:
153
200
  specification_version: 4
154
- summary: A library to make repo scripts easy and discoverable.
201
+ summary: Abtion's development workflow tools
155
202
  test_files: []