use_packs 0.0.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.
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