class GithubWorkflow::Cli

Constants

GITHUB_CONFIG
PROCEED_TEXT

Public Instance Methods

alert(message) click to toggle source
# File lib/github_workflow/cli.rb, line 447
def alert(message)
  say_status("ALERT", message, :red)
end
branch_name_for_issue_number() click to toggle source
# File lib/github_workflow/cli.rb, line 350
def branch_name_for_issue_number
  issue = JSON.parse(github_client.get("repos/#{user_and_repo}/issues/#{issue_id}").body)
  "#{issue['number']}_#{issue['title'].strip.downcase.gsub(/[^a-zA-Z0-9]/, '_').squeeze("_")}"
end
cleanup() click to toggle source
# File lib/github_workflow/cli.rb, line 126
def cleanup
  say_info("Checking merge status")
  merged_pr_branches
  puts "\n"

  if merged_pr_branches.any?
    say_info("Are you sure you want to delete the branches with checkmarks?")

    if yes?(PROCEED_TEXT)
      pass("Deleting Branches")

      merged_pr_branches.each do |branch|
        `git branch -D #{branch}`
      end
    else
      failure("Cleanup aborted")
    end
  else
    failure("No merged branches to delete")
  end
end
commits_for_range() click to toggle source
# File lib/github_workflow/cli.rb, line 406
def commits_for_range
  JSON.parse(github_client.get("repos/#{user_and_repo}/compare/#{options[:commit_range]}").body)
end
convert_issue_to_pr() click to toggle source
# File lib/github_workflow/cli.rb, line 317
def convert_issue_to_pr
  github_client.post(
    "repos/#{user_and_repo}/pulls",
    JSON.generate(
      {
        head: current_branch,
        base: options[:base_branch] || "main",
        issue: issue_number_from_branch.to_i
      }
    )
  ).tap do |response|
    if response.success?
      pass("Issue converted to Pull Request")
      say_info(JSON.parse(response.body)["url"])
    else
      alert("An error occurred when creating PR:")
      alert("#{response.status}: #{JSON.parse(response.body)['message']}")
    end
  end
end
create_and_start() click to toggle source
# File lib/github_workflow/cli.rb, line 120
def create_and_start
  ensure_github_config_present
  create_issue
end
create_branch() click to toggle source
# File lib/github_workflow/cli.rb, line 185
def create_branch
  `git checkout -b #{branch_name_for_issue_number} main`
end
create_issue() click to toggle source
# File lib/github_workflow/cli.rb, line 293
def create_issue
  github_client.post(
    "repos/#{user_and_repo}/issues",
    JSON.generate(
      {
        title: options[:name]
      }
    )
  ).tap do |response|
    if response.success?
      pass("Issue created")
      @issue_id = JSON.parse(response.body)["number"]
      start
    else
      alert("An error occurred when creating issue:")
      alert("#{response.status}: #{JSON.parse(response.body)['message']}")
    end
  end
end
create_issue_from_trello_card() click to toggle source
# File lib/github_workflow/cli.rb, line 197
def create_issue_from_trello_card
  say_info("Creating issue")

  issue_params = {
    title: trello_card.name,
    body: issue_body_from_trello_card,
    assignees: [current_github_username],
    labels: trello_card.labels.map(&:name)
  }

  response = JSON.parse(github_client.post("repos/#{user_and_repo}/issues", issue_params.to_json).body)

  @issue_id = response["number"]

  github_client.post("/repos/#{user_and_repo}/issues/#{@issue_id}/comments", { body: trello_card.short_url }.to_json)
  trello_card.add_attachment response["html_url"]
end
create_pr() click to toggle source
# File lib/github_workflow/cli.rb, line 67
def create_pr
  ensure_github_config_present
  ensure_origin_exists
  convert_issue_to_pr
end
current_branch() click to toggle source
# File lib/github_workflow/cli.rb, line 346
def current_branch
  `git rev-parse --abbrev-ref HEAD`.chomp
end
current_github_username() click to toggle source
# File lib/github_workflow/cli.rb, line 243
def current_github_username
  JSON.parse(github_client.get("user").body)["login"]
end
deploy_notes() click to toggle source
# File lib/github_workflow/cli.rb, line 168
def deploy_notes
  puts formatted_deploy_notes
end
ensure_github_config_present() click to toggle source
# File lib/github_workflow/cli.rb, line 264
def ensure_github_config_present
  if project_config.nil? || (JSON.parse(GITHUB_CONFIG).keys - project_config.keys).any?
    failure("Please add `.github_workflow` file containing:\n#{GITHUB_CONFIG}")
  end
end
ensure_origin_exists() click to toggle source
# File lib/github_workflow/cli.rb, line 189
def ensure_origin_exists
  Open3.capture2("git rev-parse --abbrev-ref --symbolic-full-name @{u}").tap do |_, status|
    unless status.success?
      failure("Upstream branch does not exist. Please set before creating pull request. E.g., `git push -u origin branch_name`")
    end
  end
end
failure(message) click to toggle source
# File lib/github_workflow/cli.rb, line 459
def failure(message)
  say_status("FAIL", message, :red)
  exit
end
formatted_deploy_notes() click to toggle source
# File lib/github_workflow/cli.rb, line 427
def formatted_deploy_notes
  pull_request_in_commit_range.map do |pr|
    deploy_note = pr["body"].to_s.split("**Deploy Note:**")[1].to_s.split(/\n/)[0].to_s

    if deploy_note.length > 0
      "- #{deploy_note}"
    else
      %Q{Missing deploy note: #{pr["title"]}}
    end
  end.unshift("[DaisyBill]")
end
get_issue(id) click to toggle source
# File lib/github_workflow/cli.rb, line 173
def get_issue(id)
  JSON.parse(github_client.get("repos/#{user_and_repo}/issues/#{id}").body)
end
get_pr(id) click to toggle source
# File lib/github_workflow/cli.rb, line 177
def get_pr(id)
  JSON.parse(github_client.get("repos/#{user_and_repo}/pulls/#{id}").body)
end
get_prs_list() click to toggle source
# File lib/github_workflow/cli.rb, line 181
def get_prs_list
  JSON.parse(github_client.get("repos/#{user_and_repo}/pulls?per_page=100").body)
end
github_client() click to toggle source
# File lib/github_workflow/cli.rb, line 355
def github_client
  Faraday.new(url: "https://api.github.com") do |faraday|
    faraday.request   :url_encoded
    faraday.adapter   Faraday.default_adapter
    faraday.authorization :Bearer, oauth_token
  end
end
info() click to toggle source
# File lib/github_workflow/cli.rb, line 106
def info
  ensure_github_config_present
  puts get_issue(issue_number_from_branch)["body"]
end
init_trello() click to toggle source
# File lib/github_workflow/cli.rb, line 270
def init_trello
  Trello.configure do |config|
    config.developer_public_key = project_config["trello_key"]
    config.member_token = project_config["trello_token"]
  end
end
issue_body_from_trello_card() click to toggle source
# File lib/github_workflow/cli.rb, line 215
def issue_body_from_trello_card
  [trello_card.desc, trello_deploy_note, trello_product_review_type, trello_pm].compact.join("\n\n")
end
issue_id() click to toggle source
# File lib/github_workflow/cli.rb, line 313
def issue_id
  @issue_id ||= options[:issue_id]
end
issue_number_from_branch() click to toggle source
# File lib/github_workflow/cli.rb, line 338
def issue_number_from_branch
  current_branch.split("_").first.tap do |issue_number|
    if !issue_number
      failure("Unable to parse issue number from branch. Are you sure you have a branch checked out?")
    end
  end
end
merged_pr_branches() click to toggle source
# File lib/github_workflow/cli.rb, line 391
def merged_pr_branches
  @merged_pr_branches ||=
    pr_branches.map do |branch|
      id = branch.split("_")[0].to_i
      merged = !!get_pr(id)["merged"]
      print merged ? "✅ " : "❌ "
      puts " #{branch}"
      merged ? branch : nil
    end.compact
end
oauth_token() click to toggle source
# File lib/github_workflow/cli.rb, line 281
def oauth_token
  project_config["oauth_token"]
end
open() click to toggle source
# File lib/github_workflow/cli.rb, line 112
def open
  ensure_github_config_present
  response = JSON.parse(github_client.get("repos/#{user_and_repo}/issues/#{issue_number_from_branch}").body)
  `/usr/bin/open -a "/Applications/Google Chrome.app" '#{response["html_url"]}'`
end
pass(message) click to toggle source
# File lib/github_workflow/cli.rb, line 455
def pass(message)
  say_status("OK", message, :green)
end
platform() click to toggle source
# File lib/github_workflow/cli.rb, line 44
def platform
  ensure_github_config_present
  init_trello
  set_trello_card(type: :platform)
  create_issue_from_trello_card
  stash
  rebase_main
  create_branch
  stash_pop
end
pr_branches() click to toggle source
# File lib/github_workflow/cli.rb, line 402
def pr_branches
  `git branch`.gsub(" ", "").split("\n").select { |br| br.match /^[0-9]/ }
end
project_config() click to toggle source
# File lib/github_workflow/cli.rb, line 277
def project_config
  @project_config ||= JSON.parse(File.read(".github_workflow")) rescue nil
end
pull_request_in_commit_range() click to toggle source
# File lib/github_workflow/cli.rb, line 410
def pull_request_in_commit_range
  pr_ids = commits_for_range["commits"].map do |commit|
    commit.dig("commit", "message").to_s.match(/(?<=\[#)\d{4,5}(?=\])/).to_s.to_i
  end.uniq.compact

  prs = pr_ids.map do |id|
    say_info("Fetching Pull Request ##{id}")
    pr = github_client.get("repos/#{user_and_repo}/pulls/#{id}")

    if pr.status == 404
      JSON.parse(github_client.get("repos/#{user_and_repo}/issues/#{id}").body)
    else
      JSON.parse(pr.body)
    end
  end
end
push_and_pr() click to toggle source
# File lib/github_workflow/cli.rb, line 75
def push_and_pr
  ensure_github_config_present
  push_and_set_upstream
  convert_issue_to_pr
end
push_and_set_upstream() click to toggle source
# File lib/github_workflow/cli.rb, line 289
def push_and_set_upstream
  `git rev-parse --abbrev-ref HEAD | xargs git push origin -u`
end
rebase_main() click to toggle source
# File lib/github_workflow/cli.rb, line 363
def rebase_main
  say_info("Fetching changes and rebasing main")

  if success?("git pull origin main:main --rebase")
    pass("Fetched and rebased")
  else
    failure("Failed to fetch or rebase")
  end
end
reviews() click to toggle source
# File lib/github_workflow/cli.rb, line 149
def reviews
  ensure_github_config_present
  reviewers = get_prs_list.map { |pr| pr["requested_reviewers"].map { |r| r["login"] } }.flatten
  reviewer_counts = reviewers.group_by { |i| i }.map { |k, v| [k, v.count] }

  if reviewer_counts.any?
    table = Terminal::Table.new(style: { width: 50 }) do |table_rows|
      table_rows << ["Username", "Requested PRs Count"]
      table_rows << :separator
      reviewer_counts.each { |reviewer_count| table_rows << reviewer_count }
    end

    puts table
  end
end
say_info(message) click to toggle source
# File lib/github_workflow/cli.rb, line 451
def say_info(message)
  say_status("INFO", message, :black)
end
set_trello_card(type:) click to toggle source
# File lib/github_workflow/cli.rb, line 247
def set_trello_card(type:)
  say_info("Fetching trello card")

  trello_board =
    if type
      Trello::Board.find(project_config["trello_#{type}_board_id"])
    else
      Trello::Board.find(project_config["trello_board_id"])
    end

  @trello_card = trello_board.find_card(options["card_number"].to_i)
end
start() click to toggle source
# File lib/github_workflow/cli.rb, line 57
def start
  ensure_github_config_present
  stash
  rebase_main
  create_branch
  stash_pop
end
stash() click to toggle source
# File lib/github_workflow/cli.rb, line 373
def stash
  `git diff --quiet`

  if !$?.success?
    say_info("Stashing local changes")
    `git stash --quiet`
    @stashed = true
  end
end
stash_pop() click to toggle source
# File lib/github_workflow/cli.rb, line 383
def stash_pop
  if @stashed
    say_info("Stash pop")
    `git stash pop`
    nil
  end
end
status() click to toggle source
# File lib/github_workflow/cli.rb, line 82
def status
  ensure_github_config_present
  ensure_origin_exists
  response = JSON.parse(github_client.get("repos/#{user_and_repo}/statuses/#{current_branch}").body)

  if response.empty?
    alert "No statuses yet.  Have you pushed your branch?"
  else
    table = Terminal::Table.new(style: { width: 80 }) do |table_rows|
      table_rows << %w(CI Status Description)
      table_rows << :separator

      response.map { |status| status["context"] }.uniq.map do |status|
        response.select { |st| st["context"] == status }.sort_by { |st| st["updated_at"] }.last
      end.each do |status|
        table_rows << %w(context state description).map { |key| status[key] }
      end
    end

    puts table
  end
end
success?(command) click to toggle source
# File lib/github_workflow/cli.rb, line 439
def success?(command)
  IO.popen(command) do |output|
    output.each { |line| puts line }
  end

  $?.success?
end
trello() click to toggle source
# File lib/github_workflow/cli.rb, line 31
def trello
  ensure_github_config_present
  init_trello
  set_trello_card(type: nil)
  create_issue_from_trello_card
  stash
  rebase_main
  create_branch
  stash_pop
end
trello_card() click to toggle source
# File lib/github_workflow/cli.rb, line 260
def trello_card
  @trello_card
end
trello_deploy_note() click to toggle source
# File lib/github_workflow/cli.rb, line 227
def trello_deploy_note
  custom_field = trello_card.custom_field_items.detect { |cfi| cfi.custom_field.name == "Deploy Note" }

  if custom_field.present?
    "**Deploy Note:** #{custom_field.value['text']}"
  end
end
trello_pm() click to toggle source
# File lib/github_workflow/cli.rb, line 219
def trello_pm
  custom_field = trello_card.custom_field_items.detect { |cfi| cfi.custom_field.name == "PM" }

  if custom_field.present?
    "**Responsible:** #{custom_field.option_value['text']}"
  end
end
trello_product_review_type() click to toggle source
# File lib/github_workflow/cli.rb, line 235
def trello_product_review_type
  custom_field = trello_card.custom_field_items.detect { |cfi| cfi.custom_field.name == "Product Review" }

  if custom_field.present?
    "**Product Review:** #{custom_field.option_value['text']}"
  end
end
user_and_repo() click to toggle source
# File lib/github_workflow/cli.rb, line 285
def user_and_repo
  project_config["user_and_repo"]
end