neetob-ud 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +10 -0
  3. data/.env +1 -0
  4. data/.rubocop.yml +596 -0
  5. data/.ruby-version +1 -0
  6. data/.semaphore/semaphore.yml +30 -0
  7. data/CHANGELOG.md +40 -0
  8. data/CODE_OF_CONDUCT.md +84 -0
  9. data/Gemfile +28 -0
  10. data/Gemfile.lock +250 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +396 -0
  13. data/Rakefile +16 -0
  14. data/config/secrets.yml +0 -0
  15. data/data/branch-protection-rules.json +25 -0
  16. data/data/config-vars-audit.json +18 -0
  17. data/data/config-vars-list.json +1 -0
  18. data/data/github-labels.json +197 -0
  19. data/env.sample +1 -0
  20. data/exe/neetob +25 -0
  21. data/install.sh +21 -0
  22. data/lib/neetob/cli/base.rb +96 -0
  23. data/lib/neetob/cli/fetchorupdate_repos/execute.rb +55 -0
  24. data/lib/neetob/cli/github/auth.rb +134 -0
  25. data/lib/neetob/cli/github/base.rb +42 -0
  26. data/lib/neetob/cli/github/commands.rb +54 -0
  27. data/lib/neetob/cli/github/issues/commands.rb +51 -0
  28. data/lib/neetob/cli/github/issues/create.rb +42 -0
  29. data/lib/neetob/cli/github/issues/list.rb +94 -0
  30. data/lib/neetob/cli/github/labels/commands.rb +65 -0
  31. data/lib/neetob/cli/github/labels/delete.rb +46 -0
  32. data/lib/neetob/cli/github/labels/delete_all.rb +50 -0
  33. data/lib/neetob/cli/github/labels/list.rb +38 -0
  34. data/lib/neetob/cli/github/labels/show.rb +39 -0
  35. data/lib/neetob/cli/github/labels/update.rb +42 -0
  36. data/lib/neetob/cli/github/labels/upsert.rb +64 -0
  37. data/lib/neetob/cli/github/login.rb +16 -0
  38. data/lib/neetob/cli/github/make_pr/base.rb +72 -0
  39. data/lib/neetob/cli/github/make_pr/commands.rb +37 -0
  40. data/lib/neetob/cli/github/make_pr/compliance_fix.rb +49 -0
  41. data/lib/neetob/cli/github/make_pr/script.rb +55 -0
  42. data/lib/neetob/cli/github/protect_branch.rb +48 -0
  43. data/lib/neetob/cli/github/search.rb +38 -0
  44. data/lib/neetob/cli/heroku/access/add.rb +38 -0
  45. data/lib/neetob/cli/heroku/access/commands.rb +41 -0
  46. data/lib/neetob/cli/heroku/access/list.rb +36 -0
  47. data/lib/neetob/cli/heroku/access/remove.rb +38 -0
  48. data/lib/neetob/cli/heroku/commands.rb +28 -0
  49. data/lib/neetob/cli/heroku/config_vars/audit.rb +64 -0
  50. data/lib/neetob/cli/heroku/config_vars/base.rb +19 -0
  51. data/lib/neetob/cli/heroku/config_vars/commands.rb +49 -0
  52. data/lib/neetob/cli/heroku/config_vars/list.rb +56 -0
  53. data/lib/neetob/cli/heroku/config_vars/remove.rb +39 -0
  54. data/lib/neetob/cli/heroku/config_vars/upsert.rb +80 -0
  55. data/lib/neetob/cli/heroku/execute.rb +37 -0
  56. data/lib/neetob/cli/local/commands.rb +19 -0
  57. data/lib/neetob/cli/local/ls.rb +29 -0
  58. data/lib/neetob/cli/sub_command_base.rb +17 -0
  59. data/lib/neetob/cli/ui.rb +41 -0
  60. data/lib/neetob/cli/users/audit.rb +121 -0
  61. data/lib/neetob/cli/users/commands.rb +30 -0
  62. data/lib/neetob/cli/users/commits.rb +171 -0
  63. data/lib/neetob/cli.rb +42 -0
  64. data/lib/neetob/exception_handler.rb +58 -0
  65. data/lib/neetob/utils.rb +16 -0
  66. data/lib/neetob/version.rb +5 -0
  67. data/lib/neetob.rb +10 -0
  68. data/overcommit.yml +43 -0
  69. data/scripts/delete_unused_assets.rb +67 -0
  70. metadata +186 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ module Access
9
+ class List < Base
10
+ attr_accessor :apps, :sandbox
11
+
12
+ def initialize(apps, sandbox = false)
13
+ super()
14
+ @apps = apps
15
+ @sandbox = sandbox
16
+ end
17
+
18
+ def run
19
+ matching_apps = find_all_matching_apps(apps, :heroku, sandbox)
20
+ matching_apps.each do |app|
21
+ ui.info("\n Users for \"#{app}\" app\n")
22
+ users = `heroku access -a #{app}`
23
+ unless $?.success?
24
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
25
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
26
+ next
27
+ end
28
+
29
+ ui.success(users)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ module Access
9
+ class Remove < Base
10
+ attr_accessor :apps, :users_to_be_removed, :sandbox
11
+
12
+ def initialize(apps, users_to_be_removed = [], sandbox = false)
13
+ super()
14
+ @apps = apps
15
+ @users_to_be_removed = users_to_be_removed
16
+ @sandbox = sandbox
17
+ end
18
+
19
+ def run
20
+ matching_apps = find_all_matching_apps(apps, :heroku, sandbox)
21
+ matching_apps.each do |app|
22
+ `heroku access -a #{app}`
23
+ unless $?.success?
24
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
25
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
26
+ next
27
+ end
28
+
29
+ users_to_be_removed.each do |user|
30
+ ui.info(`heroku access:remove #{user} -a #{app}`)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "config_vars/commands"
5
+ require_relative "access/commands"
6
+ require_relative "execute"
7
+
8
+ module Neetob
9
+ class CLI
10
+ module Heroku
11
+ class Commands < Thor
12
+ desc "execute", "Run the given Heroku CLI command or Rails console command on Heroku apps"
13
+ option :command, type: :string, aliases: "-c", required: true, desc: "Command to run on Heroku apps"
14
+ option :rails, type: :boolean, aliases: "-r", default: false, desc: "Use this flag to run the given command in the Rails console"
15
+ option :apps, type: :array, aliases: "-a", required: true, desc: "Heroku app names. Can be matched using the '*' wildcard. Example: \"neeto*\" \"neeto-cal-web-staging\""
16
+ def execute
17
+ Execute.new(options[:apps], options[:command], options[:rails], options[:sandbox]).run
18
+ end
19
+
20
+ desc "config_vars", "Interact with the config(env) variables on Heroku apps"
21
+ subcommand "config_vars", ConfigVars::Commands
22
+
23
+ desc "access", "Interact with the users of the Heroku apps"
24
+ subcommand "access", Access::Commands
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "terminal-table"
5
+
6
+ require_relative "base"
7
+
8
+ module Neetob
9
+ class CLI
10
+ module Heroku
11
+ module ConfigVars
12
+ class Audit < Base
13
+ attr_accessor :apps, :expected_config_vars_json_file_path, :sandbox
14
+
15
+ def initialize(apps, expected_config_vars_json_file_path = "", sandbox = false)
16
+ super()
17
+ @apps = apps
18
+ @expected_config_vars_json_file_path = expected_config_vars_json_file_path
19
+ @sandbox = sandbox
20
+ end
21
+
22
+ def run
23
+ matching_apps = find_all_matching_apps(apps, :heroku, sandbox)
24
+ table_rows = []
25
+ expected_config = read_json_file(expected_config_vars_json_file_path || default_config_vars_json_file_path)
26
+ return unless expected_config
27
+
28
+ matching_apps.each do |app|
29
+ actual_config = `heroku config -a #{app} --json`
30
+ unless $?.success?
31
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
32
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
33
+ next
34
+ end
35
+
36
+ actual_config_json = JSON.parse(actual_config)
37
+ table_rows.concat(compare_config_and_build_rows(app, expected_config, actual_config_json))
38
+ end
39
+ table = Terminal::Table.new headings: ["App", "Config var", "Expected", "Result"], rows: table_rows
40
+ ui.success(table)
41
+ end
42
+
43
+ private
44
+
45
+ def compare_config_and_build_rows(app, expected_config, actual_config)
46
+ expected_config.map do |key, val|
47
+ row = [app, key.to_s]
48
+ row << (value_set_dynamically_by_heroku?(val) ? "Heroku has dynamically set the value" : val.to_s)
49
+ row << (actual_config[key] == val ? "✅" : (actual_config[key] || "No such key"))
50
+ end
51
+ end
52
+
53
+ def value_set_dynamically_by_heroku?(value)
54
+ value == { "isDynamic" => true }
55
+ end
56
+
57
+ def default_config_vars_json_file_path
58
+ File.expand_path("../../../../../data/config-vars-audit.json", __dir__)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ module ConfigVars
9
+ class Base < CLI::Base
10
+ attr_accessor :client
11
+
12
+ def initialize
13
+ super()
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ require_relative "list"
6
+ require_relative "audit"
7
+ require_relative "upsert"
8
+ require_relative "remove"
9
+ require_relative "../../sub_command_base"
10
+
11
+ module Neetob
12
+ class CLI
13
+ module Heroku
14
+ module ConfigVars
15
+ class Commands < SubCommandBase
16
+ desc "list", "List all the config variables in the Heroku apps"
17
+ option :apps, type: :array, aliases: :a, required: true, desc: "Heroku app names. Can be matched using the '*' wildcard. Example: \"neeto*\" \"neeto-cal-web-staging\""
18
+ option :keys, type: :array, desc: "Array of config vars that needs to be listed."
19
+ option :path, type: :string, desc: "The JSON file path which has config vars that needs to be listed"
20
+ def list
21
+ List.new(options[:apps], options[:keys], options[:path], options[:sandbox]).run
22
+ end
23
+
24
+ desc "audit", "Verify whether config vars set in Heroku apps matches against the conditions specified in JSON file."
25
+ option :apps, type: :array, aliases: :a, required: true, desc: "Heroku app names. Can be matched using the '*' wildcard. Example: \"neeto*\" \"neeto-cal-web-staging\""
26
+ option :path, type: :string, desc: "The JSON file path from which config vars will be compared"
27
+ def audit
28
+ Audit.new(options[:apps], options[:path], options[:sandbox]).run
29
+ end
30
+
31
+ desc "upsert", "Update and insert the config vars in Heroku apps"
32
+ option :apps, type: :array, aliases: :a, desc: "Heroku app names. Can be matched using the '*' wildcard. Example: \"neeto*\" \"neeto-cal-web-staging\""
33
+ option :path, type: :string, aliases: :p, desc: "The JSON file path from which config vars will be upserted"
34
+ option :path_with_project_keys, type: :string, desc: "The JSON file path with the project names as the key containing required config vars for each project."
35
+ def upsert
36
+ Upsert.new(options[:apps], options[:path], options[:path_with_project_keys], options[:sandbox]).run
37
+ end
38
+
39
+ desc "remove", "Remove the config vars from Heroku apps"
40
+ option :apps, type: :array, aliases: :a, required: true, desc: "Heroku app names. Can be matched using the '*' wildcard. Example: \"neeto*\" \"neeto-cal-web-staging\""
41
+ option :keys, type: :array, required: true, desc: "Array of config vars to be removed."
42
+ def remove
43
+ Remove.new(options[:apps], options[:keys], options[:sandbox]).run
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ module ConfigVars
9
+ class List < Base
10
+ attr_accessor :apps, :sandbox, :keys, :required_config_vars_file_path
11
+
12
+ def initialize(apps, keys = [], required_config_vars_file_path = "", sandbox = false)
13
+ super()
14
+ @apps = apps
15
+ @sandbox = sandbox
16
+ @keys = keys
17
+ @required_config_vars_file_path = required_config_vars_file_path
18
+ end
19
+
20
+ def run
21
+ matching_apps = find_all_matching_apps(apps, :heroku, sandbox)
22
+ matching_apps.each do |app|
23
+ ui.info("\n Config of #{app}\n")
24
+ config = `heroku config -a #{app} --json`
25
+ unless $?.success?
26
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
27
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
28
+ next
29
+ end
30
+ table = Terminal::Table.new headings: table_columns, rows: filter_config(config)
31
+ ui.success(table)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def table_columns
38
+ ["Key", "Value"]
39
+ end
40
+
41
+ def filter_config(config)
42
+ parsed_config = JSON.parse(config)
43
+ if keys.nil? && required_config_vars_file_path.nil?
44
+ return parsed_config
45
+ end
46
+
47
+ required_config = keys || read_json_file(required_config_vars_file_path)
48
+ required_config.map do |key|
49
+ [key, parsed_config[key]]
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ module ConfigVars
9
+ class Remove < Base
10
+ attr_accessor :apps, :keys, :sandbox
11
+
12
+ def initialize(apps, keys = [], sandbox = false)
13
+ super()
14
+ @apps = apps
15
+ @keys = keys
16
+ @sandbox = sandbox
17
+ end
18
+
19
+ def run
20
+ matching_apps = find_all_matching_apps(apps, :heroku, sandbox)
21
+ matching_apps.each do |app|
22
+ `heroku access -a #{app}`
23
+
24
+ unless $?.success?
25
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
26
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
27
+ next
28
+ end
29
+
30
+ keys.each do |key|
31
+ `heroku config:unset #{key} -a #{app}`
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ module ConfigVars
9
+ class Upsert < Base
10
+ attr_accessor :apps, :required_config_vars_json_file_path,
11
+ :required_config_vars_with_project_keys_json_file_path, :sandbox
12
+
13
+ def initialize(apps, required_config_vars_json_file_path,
14
+ required_config_vars_with_project_keys_json_file_path, sandbox = false)
15
+ super()
16
+ @apps = apps
17
+ @required_config_vars_json_file_path = required_config_vars_json_file_path
18
+ @required_config_vars_with_project_keys_json_file_path =
19
+ required_config_vars_with_project_keys_json_file_path
20
+ @sandbox = sandbox
21
+ end
22
+
23
+ def run
24
+ check_the_given_arguments
25
+ inform_about_current_working_mode(sandbox) if apps.nil?
26
+ apps_to_be_updated = apps.nil? ?
27
+ read_json_file(required_config_vars_with_project_keys_json_file_path) :
28
+ find_all_matching_apps(apps, :heroku, sandbox)
29
+ inform_about_default_config_vars_file
30
+
31
+ apps_to_be_updated.each do |app|
32
+ required_config = apps.nil? ? app[1] : read_json_file(required_config_vars_json_file_path || default_config_vars_upsert_file_path)
33
+ app = app[0] if apps.nil?
34
+
35
+ if apps.nil? && sandbox && app != "neeto-dummy"
36
+ ui.error("The \"#{app}\" app is not available in sandbox mode.")
37
+ next
38
+ end
39
+
40
+ upsert_config_variables(app, required_config)
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def upsert_config_variables(app, vars)
47
+ `heroku access -a #{app}`
48
+ unless $?.success?
49
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
50
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
51
+ return
52
+ end
53
+
54
+ vars.each do |key, val|
55
+ `heroku config:set #{key}='#{val}' -a #{app}`
56
+ end
57
+ end
58
+
59
+ def check_the_given_arguments
60
+ if (!apps.nil? && !required_config_vars_with_project_keys_json_file_path.nil?) ||
61
+ (apps.nil? && required_config_vars_with_project_keys_json_file_path.nil?)
62
+ ui.error("Please provide either apps or path to the config file with project keys")
63
+ exit
64
+ end
65
+ end
66
+
67
+ def default_config_vars_upsert_file_path
68
+ File.expand_path("../../../../../data/config-vars-upsert.json", __dir__)
69
+ end
70
+
71
+ def inform_about_default_config_vars_file
72
+ if !apps.nil? && required_config_vars_with_project_keys_json_file_path.nil? && required_config_vars_json_file_path.nil?
73
+ ui.info("Upserting config vars from the \"neetob/data/config-vars-upsert.json\" file")
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Heroku
8
+ class Execute < Base
9
+ attr_accessor :apps, :command_to_execute, :should_run_in_rails_console, :sandbox
10
+
11
+ def initialize(apps, command_to_execute, should_run_in_rails_console = false, sandbox = false)
12
+ super()
13
+ @apps = apps
14
+ @command_to_execute = command_to_execute
15
+ @should_run_in_rails_console = should_run_in_rails_console
16
+ @sandbox = sandbox
17
+ end
18
+
19
+ def run
20
+ matching_apps = find_all_matching_apps(apps, :heroku, sandbox)
21
+ rails_runner_command = "heroku run rails runner '#{command_to_execute}'"
22
+ heroku_command = should_run_in_rails_console ? rails_runner_command : command_to_execute
23
+ matching_apps.each do |app|
24
+ ui.info("\n Working on #{app}\n")
25
+ output = `#{heroku_command} -a #{app}`
26
+ unless $?.success?
27
+ ui.error("There is a problem in accessing the app with name \"#{app}\" in your account.")
28
+ ui.error("Please check the specified app name and ensure you're authorized to view that app.")
29
+ next
30
+ end
31
+ ui.success(output)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "ls"
5
+
6
+ module Neetob
7
+ class CLI
8
+ module Local
9
+ class Commands < Thor
10
+ desc "ls", "List the files in local neeto repos"
11
+ option :apps, type: :array, aliases: "-a", required: true, desc: "Neeto app names. Can be matched using the '*' wildcard. Example: \"neeto*\" \"neeto-cal-web\""
12
+ option :dir, type: :string, aliases: "-d", desc: "Mention the directory you want to list"
13
+ def ls
14
+ Ls.new(options[:apps], options[:dir], options[:sandbox]).run
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Local
8
+ class Ls < Base
9
+ attr_accessor :sandbox, :apps, :dir
10
+
11
+ def initialize(apps, dir, sandbox = false)
12
+ super()
13
+ @apps = apps
14
+ @dir = dir
15
+ @sandbox = sandbox
16
+ end
17
+
18
+ def run
19
+ matching_apps = find_all_matching_apps(apps, :github, sandbox, true)
20
+ matching_apps.each do |app|
21
+ app_name = app.split("/")[1]
22
+ ui.info("\nListing files from #{app_name}/#{dir}\n")
23
+ ui.info(`cd ./#{app_name} && ls #{dir}`)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ class SubCommandBase < Thor
6
+ def self.banner(command, namespace = nil, subcommand = false)
7
+ "#{basename} #{subcommand_prefix} #{command.usage}"
8
+ end
9
+
10
+ def self.subcommand_prefix
11
+ self.name
12
+ .split("::")[2..-2]
13
+ .map { |name| name.gsub(%r{^[A-Z]}) { |match| match[0].downcase } }
14
+ .map { |name| name.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" } }
15
+ .join(" ")
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ 4# frozen_string_literal: true
4
+
5
+ require "thor"
6
+
7
+ module Neetob
8
+ class CLI
9
+ class UI
10
+ attr_accessor :shell
11
+
12
+ def initialize
13
+ @shell = Thor::Base.shell.new
14
+ end
15
+
16
+ def say(statement, color = Thor::Shell::Color::YELLOW)
17
+ shell.say(statement, color)
18
+ end
19
+
20
+ def ask(question, echo = true)
21
+ shell.ask(question, echo: echo)
22
+ end
23
+
24
+ def yes?(question)
25
+ shell.yes?(question)
26
+ end
27
+
28
+ def error(statement)
29
+ shell.say(statement, Thor::Shell::Color::RED)
30
+ end
31
+
32
+ def success(statement)
33
+ shell.say(statement, Thor::Shell::Color::GREEN)
34
+ end
35
+
36
+ def info(statement)
37
+ shell.say(statement)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Neetob
6
+ class CLI
7
+ module Users
8
+ class Audit < Base
9
+ attr_accessor :sandbox
10
+
11
+ def initialize(sandbox = false)
12
+ super()
13
+ @sandbox = sandbox
14
+ end
15
+
16
+ def run
17
+ users_data = users_for_all_available_apps
18
+ users = extract_values_from_user_data(users_data)
19
+ email_repos_map = create_email_repos_map(users)
20
+ unique_users = users.uniq { |user| [user[0], user[1]] }.sort
21
+ ui.info("\nUsers with multiple emails:")
22
+ find_users_with_multiple_emails(unique_users.transpose, email_repos_map)
23
+ ui.info("Users with emails having 3rd party domains:")
24
+ find_users_with_invalid_emails(unique_users.transpose, email_repos_map)
25
+ end
26
+
27
+ private
28
+
29
+ def users_for_all_available_apps
30
+ find_all_matching_apps(["*"], :github, sandbox, true).map do |app|
31
+ clone_repo_and_get_users(app).split("\n").map { |user| "#{user} #{app}" }
32
+ end
33
+ end
34
+
35
+ def extract_values_from_user_data(users)
36
+ users.flatten.map do |user|
37
+ values = user.strip.split(" ")[1..-1]
38
+ [values[0..-3].join(" "), clean_email(values[-2]), clean_repo_name(values[-1])]
39
+ end
40
+ end
41
+
42
+ def clone_repo_and_get_users(app)
43
+ app_name_without_org_suffix = app.split("/")[1]
44
+ `git -C ./#{app_name_without_org_suffix} shortlog -sne`
45
+ end
46
+
47
+ def create_email_repos_map(users)
48
+ map = {}
49
+ users.each do |user|
50
+ email = user[1]
51
+ repo = user[2]
52
+ map[email] = [] unless map.key?(email)
53
+ map[email].push(repo)
54
+ end
55
+ map
56
+ end
57
+
58
+ def find_users_with_multiple_emails(users, email_repos_map)
59
+ user_emails = {}
60
+ users[0].each_with_index do |name, index|
61
+ email = users[1][index]
62
+ user_emails[name] = [] unless user_emails.key?(name)
63
+ user_emails[name].push(email)
64
+ end
65
+ user_with_multiple_emails = user_emails.filter { |_, val| val.length > 1 }
66
+ table_rows = create_rows_and_add_repos(user_with_multiple_emails, email_repos_map)
67
+ create_and_show_table(table_rows, table_columns)
68
+ end
69
+
70
+ def find_users_with_invalid_emails(users, email_repos_map)
71
+ user_with_invalid_emails = {}
72
+ users[1].each_with_index do |email, index|
73
+ name = users[0][index]
74
+ if invalid_email?(email)
75
+ user_with_invalid_emails[name] = [] unless user_with_invalid_emails.key?(name)
76
+ user_with_invalid_emails[name].push(email)
77
+ end
78
+ end
79
+ table_rows = create_rows_and_add_repos(user_with_invalid_emails, email_repos_map)
80
+ create_and_show_table(table_rows, table_columns)
81
+ end
82
+
83
+ def table_columns
84
+ [:Name, :Email, :Repo]
85
+ end
86
+
87
+ def create_rows_and_add_repos(users, email_repos_map)
88
+ users.map do |name, emails|
89
+ repos = emails.map { |email| email_repos_map[email] }
90
+ [name, emails.join(", "), join_and_truncate_repos(repos)]
91
+ end
92
+ end
93
+
94
+ def create_and_show_table(data, column_names)
95
+ table = Terminal::Table.new headings: column_names, rows: data
96
+ ui.error(table)
97
+ end
98
+
99
+ def join_and_truncate_repos(repos)
100
+ repos.flatten.uniq.join(",").truncate(35)
101
+ end
102
+
103
+ def clean_email(email)
104
+ email.gsub(/<|>/, "")
105
+ end
106
+
107
+ def clean_repo_name(repo)
108
+ repo.gsub("bigbinary/", "")
109
+ end
110
+
111
+ def invalid_email?(email)
112
+ !allowed_email_domains.any? { |domain| email.downcase.include?(domain) }
113
+ end
114
+
115
+ def allowed_email_domains
116
+ ["github", "gmail", "bigbinary"]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end