use_packs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/bin/packs +10 -0
  4. data/bin/rubocop +29 -0
  5. data/bin/tapioca +29 -0
  6. data/lib/use_packs/cli.rb +127 -0
  7. data/lib/use_packs/code_ownership_post_processor.rb +58 -0
  8. data/lib/use_packs/configuration.rb +61 -0
  9. data/lib/use_packs/default_user_event_logger.rb +7 -0
  10. data/lib/use_packs/logging.rb +37 -0
  11. data/lib/use_packs/per_file_processor_interface.rb +18 -0
  12. data/lib/use_packs/private/file_move_operation.rb +80 -0
  13. data/lib/use_packs/private/interactive_cli/pack_selector.rb +34 -0
  14. data/lib/use_packs/private/interactive_cli/team_selector.rb +35 -0
  15. data/lib/use_packs/private/interactive_cli/use_cases/add_dependency.rb +30 -0
  16. data/lib/use_packs/private/interactive_cli/use_cases/check.rb +25 -0
  17. data/lib/use_packs/private/interactive_cli/use_cases/create.rb +27 -0
  18. data/lib/use_packs/private/interactive_cli/use_cases/get_info.rb +74 -0
  19. data/lib/use_packs/private/interactive_cli/use_cases/interface.rb +34 -0
  20. data/lib/use_packs/private/interactive_cli/use_cases/lint_package_yml.rb +26 -0
  21. data/lib/use_packs/private/interactive_cli/use_cases/make_public.rb +34 -0
  22. data/lib/use_packs/private/interactive_cli/use_cases/move.rb +36 -0
  23. data/lib/use_packs/private/interactive_cli/use_cases/nest.rb +31 -0
  24. data/lib/use_packs/private/interactive_cli/use_cases/query.rb +51 -0
  25. data/lib/use_packs/private/interactive_cli/use_cases/regenerate_rubocop_todo.rb +26 -0
  26. data/lib/use_packs/private/interactive_cli/use_cases/rename.rb +34 -0
  27. data/lib/use_packs/private/interactive_cli/use_cases/update_deprecations.rb +25 -0
  28. data/lib/use_packs/private/interactive_cli/use_cases/validate.rb +25 -0
  29. data/lib/use_packs/private/interactive_cli/use_cases/visualize.rb +44 -0
  30. data/lib/use_packs/private/interactive_cli.rb +52 -0
  31. data/lib/use_packs/private/pack_relationship_analyzer.rb +135 -0
  32. data/lib/use_packs/private/packwerk_wrapper/offenses_aggregator_formatter.rb +34 -0
  33. data/lib/use_packs/private/packwerk_wrapper.rb +71 -0
  34. data/lib/use_packs/private.rb +453 -0
  35. data/lib/use_packs/rubocop_post_processor.rb +67 -0
  36. data/lib/use_packs/spring_command.rb +28 -0
  37. data/lib/use_packs/user_event_logger.rb +259 -0
  38. data/lib/use_packs.rb +298 -0
  39. metadata +351 -0
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Create
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.params(prompt: TTY::Prompt).void }
13
+ def perform!(prompt)
14
+ pack_name = prompt.ask('What should the name of your pack be?', value: 'packs/')
15
+ team = TeamSelector.single_select(prompt)
16
+ UsePacks.create_pack!(pack_name: pack_name, team: team)
17
+ end
18
+
19
+ sig { override.returns(String) }
20
+ def user_facing_name
21
+ 'Create a new pack'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,74 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class GetInfo
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Get info on one or more packs'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ team_or_pack = prompt.select('Do you want info by team or by pack?', ['By team', 'By pack'])
20
+
21
+ if team_or_pack == 'By team'
22
+ teams = TeamSelector.multi_select(prompt)
23
+ selected_packs = ParsePackwerk.all.select do |p|
24
+ teams.map(&:name).include?(CodeOwnership.for_package(p)&.name)
25
+ end
26
+ else
27
+ selected_packs = PackSelector.single_or_all_pack_multi_select(prompt, question_text: 'What pack(s) would you like info on?')
28
+ end
29
+
30
+ inbound_violations = {}
31
+ outbound_violations = {}
32
+ ParsePackwerk.all.each do |p|
33
+ violations_for_pack = ParsePackwerk::DeprecatedReferences.for(p).violations
34
+ violations_for_pack.each do |violation|
35
+ outbound_violations[p.name] ||= []
36
+ outbound_violations[p.name] << violation
37
+ inbound_violations[violation.to_package_name] ||= []
38
+ inbound_violations[violation.to_package_name] << violation
39
+ end
40
+ end
41
+
42
+ puts "You've selected #{selected_packs.count} packs. Wow! Here's all the info."
43
+ all_inbound = T.let([], T::Array[ParsePackwerk::Violation])
44
+ all_outbound = T.let([], T::Array[ParsePackwerk::Violation])
45
+ selected_packs.each do |pack|
46
+ all_inbound += inbound_violations[pack.name] || []
47
+ all_outbound += outbound_violations[pack.name] || []
48
+ end
49
+
50
+ puts "There are #{all_inbound.select(&:privacy?).sum { |v| v.files.count }} total inbound privacy violations"
51
+ puts "There are #{all_inbound.select(&:dependency?).sum { |v| v.files.count }} total inbound dependency violations"
52
+ puts "There are #{all_outbound.select(&:privacy?).sum { |v| v.files.count }} total outbound privacy violations"
53
+ puts "There are #{all_outbound.select(&:dependency?).sum { |v| v.files.count }} total outbound dependency violations"
54
+
55
+ selected_packs.sort_by { |p| -p.directory.glob('**/*.rb').count }.each do |pack|
56
+ puts "\n=========== Info about: #{pack.name}"
57
+ owner = CodeOwnership.for_package(pack)
58
+ puts "Owned by: #{owner.nil? ? 'No one' : owner.name}"
59
+ puts "Size: #{pack.directory.glob('**/*.rb').count} ruby files"
60
+ puts "Public API: #{pack.directory.join('app/public')}"
61
+
62
+ inbound_for_pack = inbound_violations[pack.name] || []
63
+ outbound_for_pack = outbound_violations[pack.name] || []
64
+ puts "There are #{inbound_for_pack.select(&:privacy?).sum { |v| v.files.count }} inbound privacy violations"
65
+ puts "There are #{inbound_for_pack.flatten.select(&:dependency?).sum { |v| v.files.count }} inbound dependency violations"
66
+ puts "There are #{outbound_for_pack.select(&:privacy?).sum { |v| v.files.count }} outbound privacy violations"
67
+ puts "There are #{outbound_for_pack.flatten.select(&:dependency?).sum { |v| v.files.count }} outbound dependency violations"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ module Interface
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ interface!
12
+
13
+ sig { params(base: Class).void }
14
+ def self.included(base)
15
+ @use_cases ||= T.let(@use_cases, T.nilable(T::Array[Class]))
16
+ @use_cases ||= []
17
+ @use_cases << base
18
+ end
19
+
20
+ sig { returns(T::Array[Interface]) }
21
+ def self.all
22
+ T.unsafe(@use_cases).map(&:new)
23
+ end
24
+
25
+ sig { abstract.params(prompt: TTY::Prompt).void }
26
+ def perform!(prompt); end
27
+
28
+ sig { abstract.returns(String) }
29
+ def user_facing_name; end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class LintPackageYml
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.params(prompt: TTY::Prompt).void }
13
+ def perform!(prompt)
14
+ packs = PackSelector.single_or_all_pack_multi_select(prompt, question_text: 'Please select the packs you want to lint package.yml files for')
15
+ UsePacks.lint_package_yml_files!(packs)
16
+ end
17
+
18
+ sig { override.returns(String) }
19
+ def user_facing_name
20
+ 'Lint packs/*/package.yml for one or more packs'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class MakePublic
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Make files or directories public'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ raw_paths_relative_to_root = prompt.multiline('Please copy in a space or new line separated list of files or directories to make public')
20
+ paths_relative_to_root = T.let([], T::Array[String])
21
+ raw_paths_relative_to_root.each do |path|
22
+ paths_relative_to_root += path.chomp.split
23
+ end
24
+
25
+ UsePacks.make_public!(
26
+ paths_relative_to_root: paths_relative_to_root,
27
+ per_file_processors: [UsePacks::RubocopPostProcessor.new, UsePacks::CodeOwnershipPostProcessor.new]
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Move
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.params(prompt: TTY::Prompt).void }
13
+ def perform!(prompt)
14
+ pack = PackSelector.single_pack_select(prompt, question_text: 'Please select a destination pack')
15
+ raw_paths_relative_to_root = prompt.multiline('Please copy in a space or new line separated list of files or directories')
16
+ paths_relative_to_root = T.let([], T::Array[String])
17
+ raw_paths_relative_to_root.each do |path|
18
+ paths_relative_to_root += path.chomp.split
19
+ end
20
+
21
+ UsePacks.move_to_pack!(
22
+ pack_name: pack.name,
23
+ paths_relative_to_root: paths_relative_to_root,
24
+ per_file_processors: [UsePacks::RubocopPostProcessor.new, UsePacks::CodeOwnershipPostProcessor.new]
25
+ )
26
+ end
27
+
28
+ sig { override.returns(String) }
29
+ def user_facing_name
30
+ 'Move files'
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Nest
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.params(prompt: TTY::Prompt).void }
13
+ def perform!(prompt)
14
+ child_pack = PackSelector.single_pack_select(prompt, question_text: 'Please select the pack that will be nested')
15
+ parent_pack = PackSelector.single_pack_select(prompt, question_text: 'Please select the pack that will be the parent')
16
+ UsePacks.move_to_parent!(
17
+ parent_name: parent_pack.name,
18
+ pack_name: child_pack.name,
19
+ per_file_processors: [UsePacks::RubocopPostProcessor.new, UsePacks::CodeOwnershipPostProcessor.new]
20
+ )
21
+ end
22
+
23
+ sig { override.returns(String) }
24
+ def user_facing_name
25
+ 'Nest one pack under another'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ #
8
+ # We have not yet pulled QueryPackwerk into open source, so we cannot include it in this CLI yet
9
+ #
10
+ class Query
11
+ extend T::Sig
12
+ extend T::Helpers
13
+ include Interface
14
+
15
+ sig { override.returns(String) }
16
+ def user_facing_name
17
+ 'Query violations about a pack'
18
+ end
19
+
20
+ sig { override.params(prompt: TTY::Prompt).void }
21
+ def perform!(prompt)
22
+ selection = prompt.select('For one pack or all packs?', ['One pack', 'All packs'])
23
+ if selection == 'All packs'
24
+ # Probably should just make `list_top_dependency_violations` take in an array of things
25
+ # Better yet we might just want to replace these functions with `QueryPackwerk`
26
+ selected_pack = nil
27
+ else
28
+ selected_pack = PackSelector.single_pack_select(prompt).name
29
+ end
30
+
31
+ limit = prompt.ask('Specify the limit of constants to analyze', default: 10, convert: :integer)
32
+
33
+ selection = prompt.select('Are you interested in dependency or privacy violations?', %w[Dependency Privacy], default: 'Privacy')
34
+
35
+ if selection == 'Dependency'
36
+ UsePacks.list_top_dependency_violations(
37
+ pack_name: selected_pack,
38
+ limit: limit
39
+ )
40
+ else
41
+ UsePacks.list_top_privacy_violations(
42
+ pack_name: selected_pack,
43
+ limit: limit
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class RegenerateRubocopTodo
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.params(prompt: TTY::Prompt).void }
13
+ def perform!(prompt)
14
+ packs = PackSelector.single_or_all_pack_multi_select(prompt, question_text: "Please select the packs you want to regenerate `#{RuboCop::Packs::PACK_LEVEL_RUBOCOP_TODO_YML}` for")
15
+ RuboCop::Packs.regenerate_todo(packs: packs)
16
+ end
17
+
18
+ sig { override.returns(String) }
19
+ def user_facing_name
20
+ "Regenerate packs/*/#{RuboCop::Packs::PACK_LEVEL_RUBOCOP_TODO_YML} for one or more packs"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Rename
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Rename a pack'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ prompt.warn(<<~WARNING)
20
+ We do not yet have an automated API for this.
21
+
22
+ Follow these steps:
23
+ 1. Rename the `packs/your_pack` directory to the name of the new pack, `packs/new_pack_name
24
+ 2. Replace references to `- packs/your_pack` in `package.yml` files with `- packs/new_pack_name`
25
+ 3. Rerun `bin/packwerk update-deprecations` to update violations
26
+ 4. Run `bin/codeownership validate` to update ownership information
27
+ 5. Please let us know if anything is missing.
28
+ WARNING
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class UpdateDeprecations
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Run bin/packwerk update-deprecations'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ system('bin/packwerk update-deprecations')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Validate
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Run bin/packwerk validate (detects cycles)'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ system('bin/packwerk validate')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ # typed: strict
2
+
3
+ require 'visualize_packwerk'
4
+
5
+ module UsePacks
6
+ module Private
7
+ module InteractiveCli
8
+ module UseCases
9
+ class Visualize
10
+ extend T::Sig
11
+ extend T::Helpers
12
+ include Interface
13
+
14
+ sig { override.params(prompt: TTY::Prompt).void }
15
+ def perform!(prompt)
16
+ teams_or_packs = prompt.select('Do you want the graph nodes to be teams or packs?', %w[Teams Packs])
17
+
18
+ if teams_or_packs == 'Teams'
19
+ teams = TeamSelector.multi_select(prompt)
20
+ VisualizePackwerk.team_graph!(teams)
21
+ else
22
+ by_name_or_by_owner = prompt.select('Do you select packs by name or by owner?', ['By name', 'By owner'])
23
+ if by_name_or_by_owner == 'By owner'
24
+ teams = TeamSelector.multi_select(prompt)
25
+ selected_packs = ParsePackwerk.all.select do |p|
26
+ teams.map(&:name).include?(CodeOwnership.for_package(p)&.name)
27
+ end
28
+ else
29
+ selected_packs = PackSelector.single_or_all_pack_multi_select(prompt)
30
+ end
31
+
32
+ VisualizePackwerk.package_graph!(selected_packs)
33
+ end
34
+ end
35
+
36
+ sig { override.returns(String) }
37
+ def user_facing_name
38
+ 'Visualize pack relationships'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+
3
+ # https://github.com/piotrmurach/tty-prompt
4
+ require 'tty-prompt'
5
+
6
+ require 'use_packs/private/interactive_cli/team_selector'
7
+ require 'use_packs/private/interactive_cli/pack_selector'
8
+ require 'use_packs/private/interactive_cli/use_cases/interface'
9
+ require 'use_packs/private/interactive_cli/use_cases/create'
10
+ require 'use_packs/private/interactive_cli/use_cases/move'
11
+ require 'use_packs/private/interactive_cli/use_cases/add_dependency'
12
+ require 'use_packs/private/interactive_cli/use_cases/get_info'
13
+ require 'use_packs/private/interactive_cli/use_cases/query'
14
+ require 'use_packs/private/interactive_cli/use_cases/make_public'
15
+ require 'use_packs/private/interactive_cli/use_cases/nest'
16
+ require 'use_packs/private/interactive_cli/use_cases/rename'
17
+ require 'use_packs/private/interactive_cli/use_cases/check'
18
+ require 'use_packs/private/interactive_cli/use_cases/update_deprecations'
19
+ require 'use_packs/private/interactive_cli/use_cases/validate'
20
+ require 'use_packs/private/interactive_cli/use_cases/regenerate_rubocop_todo'
21
+ require 'use_packs/private/interactive_cli/use_cases/lint_package_yml'
22
+ require 'use_packs/private/interactive_cli/use_cases/visualize'
23
+
24
+ module UsePacks
25
+ module Private
26
+ module InteractiveCli
27
+ extend T::Sig
28
+
29
+ sig { params(prompt: T.nilable(TTY::Prompt)).void }
30
+ def self.start!(prompt: nil)
31
+ prompt ||= TTY::Prompt.new(interrupt: lambda {
32
+ puts "\n\nGoodbye! I hope you have a good day."
33
+ exit 1 })
34
+ help_text = '(Press ↑/↓ arrow to move, Enter to select and letters to filter)'
35
+ choice = prompt.select('Hello! What would you like to do?',
36
+ cycle: true,
37
+ filter: true,
38
+ help: help_text,
39
+ show_help: :always,
40
+ per_page: 15) do |menu|
41
+ menu.enum '.'
42
+
43
+ UseCases::Interface.all.each do |use_case|
44
+ menu.choice use_case.user_facing_name, use_case
45
+ end
46
+ end
47
+
48
+ choice.perform!(prompt)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,135 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module PackRelationshipAnalyzer
6
+ extend T::Sig
7
+
8
+ sig do
9
+ params(
10
+ pack_name: T.nilable(String),
11
+ limit: Integer
12
+ ).void
13
+ end
14
+ def self.list_top_privacy_violations(pack_name, limit)
15
+ all_packages = ParsePackwerk.all
16
+ if pack_name.nil?
17
+ to_package_names = all_packages.map(&:name)
18
+ else
19
+ pack_name = Private.clean_pack_name(pack_name)
20
+ package = all_packages.find { |p| p.name == pack_name }
21
+ if package.nil?
22
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
23
+ end
24
+
25
+ to_package_names = [pack_name]
26
+ end
27
+
28
+ violations_by_count = {}
29
+ total_pack_violation_count = 0
30
+
31
+ Logging.section('👋 Hi there') do
32
+ intro = UsePacks.config.user_event_logger.before_list_top_privacy_violations(pack_name, limit)
33
+ Logging.print_bold_green(intro)
34
+ end
35
+
36
+ # TODO: This is a copy of the implementation below. We may want to refactor out this implementation detail before making changes that apply to both.
37
+ all_packages.each do |client_package|
38
+ client_package.violations.select(&:privacy?).each do |violation|
39
+ next unless to_package_names.include?(violation.to_package_name)
40
+
41
+ if pack_name.nil?
42
+ violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
43
+ else
44
+ violated_symbol = violation.class_name
45
+ end
46
+ violations_by_count[violated_symbol] ||= {}
47
+ violations_by_count[violated_symbol][:total_count] ||= 0
48
+ violations_by_count[violated_symbol][:by_package] ||= {}
49
+ violations_by_count[violated_symbol][:by_package][client_package.name] ||= 0
50
+ violations_by_count[violated_symbol][:total_count] += violation.files.count
51
+ violations_by_count[violated_symbol][:by_package][client_package.name] += violation.files.count
52
+ total_pack_violation_count += violation.files.count
53
+ end
54
+ end
55
+
56
+ Logging.print("Total Count: #{total_pack_violation_count}")
57
+
58
+ sorted_violations = violations_by_count.sort_by { |violated_symbol, count_info| [-count_info[:total_count], violated_symbol] }
59
+ sorted_violations.first(limit).each do |violated_symbol, count_info|
60
+ percentage_of_total = (count_info[:total_count] * 100.0 / total_pack_violation_count).round(2)
61
+ Logging.print(violated_symbol)
62
+ Logging.print(" - Total Count: #{count_info[:total_count]} (#{percentage_of_total}% of total)")
63
+
64
+ Logging.print(' - By package:')
65
+ count_info[:by_package].sort_by { |client_package_name, count| [-count, client_package_name] }.each do |client_package_name, count|
66
+ Logging.print(" - #{client_package_name}: #{count}")
67
+ end
68
+ end
69
+ end
70
+
71
+ sig do
72
+ params(
73
+ pack_name: T.nilable(String),
74
+ limit: Integer
75
+ ).void
76
+ end
77
+ def self.list_top_dependency_violations(pack_name, limit)
78
+ all_packages = ParsePackwerk.all
79
+
80
+ if pack_name.nil?
81
+ to_package_names = all_packages.map(&:name)
82
+ else
83
+ pack_name = Private.clean_pack_name(pack_name)
84
+ package = all_packages.find { |p| p.name == pack_name }
85
+ if package.nil?
86
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
87
+ end
88
+
89
+ to_package_names = [pack_name]
90
+ end
91
+
92
+ Logging.section('👋 Hi there') do
93
+ intro = UsePacks.config.user_event_logger.before_list_top_dependency_violations(pack_name, limit)
94
+ Logging.print_bold_green(intro)
95
+ end
96
+
97
+ violations_by_count = {}
98
+ total_pack_violation_count = 0
99
+
100
+ # TODO: This is a copy of the implementation above. We may want to refactor out this implementation detail before making changes that apply to both.
101
+ all_packages.each do |client_package|
102
+ client_package.violations.select(&:dependency?).each do |violation|
103
+ next unless to_package_names.include?(violation.to_package_name)
104
+
105
+ if pack_name.nil?
106
+ violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
107
+ else
108
+ violated_symbol = violation.class_name
109
+ end
110
+ violations_by_count[violated_symbol] ||= {}
111
+ violations_by_count[violated_symbol][:total_count] ||= 0
112
+ violations_by_count[violated_symbol][:by_package] ||= {}
113
+ violations_by_count[violated_symbol][:by_package][client_package.name] ||= 0
114
+ violations_by_count[violated_symbol][:total_count] += violation.files.count
115
+ violations_by_count[violated_symbol][:by_package][client_package.name] += violation.files.count
116
+ total_pack_violation_count += violation.files.count
117
+ end
118
+ end
119
+
120
+ Logging.print("Total Count: #{total_pack_violation_count}")
121
+
122
+ sorted_violations = violations_by_count.sort_by { |violated_symbol, count_info| [-count_info[:total_count], violated_symbol] }
123
+ sorted_violations.first(limit).each do |violated_symbol, count_info|
124
+ percentage_of_total = (count_info[:total_count] * 100.0 / total_pack_violation_count).round(2)
125
+ Logging.print(violated_symbol)
126
+ Logging.print(" - Total Count: #{count_info[:total_count]} (#{percentage_of_total}% of total)")
127
+ Logging.print(' - By package:')
128
+ count_info[:by_package].sort_by { |client_package_name, count| [-count, client_package_name] }.each do |client_package_name, count|
129
+ Logging.print(" - #{client_package_name}: #{count}")
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end