neetob-ud 0.1.0

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.
Files changed (71) 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 +96 -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 +412 -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/neetob.gemspec +50 -0
  69. data/overcommit.yml +43 -0
  70. data/scripts/delete_unused_assets.rb +67 -0
  71. metadata +187 -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