use_packwerk 0.55.0 → 0.57.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/bin/packs +5 -0
  3. data/lib/use_packwerk/cli.rb +10 -10
  4. data/lib/use_packwerk/code_ownership_post_processor.rb +3 -3
  5. data/lib/use_packwerk/configuration.rb +18 -14
  6. data/lib/use_packwerk/default_user_event_logger.rb +7 -0
  7. data/lib/use_packwerk/logging.rb +1 -2
  8. data/lib/use_packwerk/per_file_processor_interface.rb +1 -2
  9. data/lib/use_packwerk/private/file_move_operation.rb +9 -11
  10. data/lib/use_packwerk/private/interactive_cli/pack_selector.rb +34 -0
  11. data/lib/use_packwerk/private/interactive_cli/team_selector.rb +35 -0
  12. data/lib/use_packwerk/private/interactive_cli/use_cases/add_dependency.rb +30 -0
  13. data/lib/use_packwerk/private/interactive_cli/use_cases/create.rb +27 -0
  14. data/lib/use_packwerk/private/interactive_cli/use_cases/get_info.rb +74 -0
  15. data/lib/use_packwerk/private/interactive_cli/use_cases/interface.rb +34 -0
  16. data/lib/use_packwerk/private/interactive_cli/use_cases/make_public.rb +34 -0
  17. data/lib/use_packwerk/private/interactive_cli/use_cases/move.rb +36 -0
  18. data/lib/use_packwerk/private/interactive_cli/use_cases/nest.rb +31 -0
  19. data/lib/use_packwerk/private/interactive_cli/use_cases/query.rb +51 -0
  20. data/lib/use_packwerk/private/interactive_cli/use_cases/rename.rb +34 -0
  21. data/lib/use_packwerk/private/interactive_cli/use_cases/update_deprecations.rb +25 -0
  22. data/lib/use_packwerk/private/interactive_cli/use_cases/validate.rb +25 -0
  23. data/lib/use_packwerk/private/interactive_cli.rb +48 -0
  24. data/lib/use_packwerk/private/pack_relationship_analyzer.rb +24 -108
  25. data/lib/use_packwerk/private.rb +126 -161
  26. data/lib/use_packwerk/rubocop_post_processor.rb +17 -17
  27. data/lib/use_packwerk/user_event_logger.rb +265 -0
  28. data/lib/use_packwerk.rb +38 -76
  29. metadata +74 -14
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+
3
+ module UsePackwerk
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
+ UsePackwerk.list_top_dependency_violations(
37
+ pack_name: selected_pack,
38
+ limit: limit
39
+ )
40
+ else
41
+ UsePackwerk.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,34 @@
1
+ # typed: strict
2
+
3
+ module UsePackwerk
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 UsePackwerk
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
+ `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 UsePackwerk
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
+ `bin/packwerk validate`
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strict
2
+
3
+ # https://github.com/piotrmurach/tty-prompt
4
+ require 'tty-prompt'
5
+
6
+ require 'use_packwerk/private/interactive_cli/team_selector'
7
+ require 'use_packwerk/private/interactive_cli/pack_selector'
8
+ require 'use_packwerk/private/interactive_cli/use_cases/interface'
9
+ require 'use_packwerk/private/interactive_cli/use_cases/create'
10
+ require 'use_packwerk/private/interactive_cli/use_cases/move'
11
+ require 'use_packwerk/private/interactive_cli/use_cases/add_dependency'
12
+ require 'use_packwerk/private/interactive_cli/use_cases/get_info'
13
+ require 'use_packwerk/private/interactive_cli/use_cases/query'
14
+ require 'use_packwerk/private/interactive_cli/use_cases/make_public'
15
+ require 'use_packwerk/private/interactive_cli/use_cases/nest'
16
+ require 'use_packwerk/private/interactive_cli/use_cases/rename'
17
+ require 'use_packwerk/private/interactive_cli/use_cases/update_deprecations'
18
+ require 'use_packwerk/private/interactive_cli/use_cases/validate'
19
+
20
+ module UsePackwerk
21
+ module Private
22
+ module InteractiveCli
23
+ extend T::Sig
24
+
25
+ sig { void }
26
+ def self.start!
27
+ prompt = TTY::Prompt.new(interrupt: lambda {
28
+ puts "\n\nGoodbye! I hope you have a good day."
29
+ exit 1 })
30
+ help_text = '(Press ↑/↓ arrow to move, Enter to select and letters to filter)'
31
+ choice = prompt.select('Hello! What would you like to do?',
32
+ cycle: true,
33
+ filter: true,
34
+ help: help_text,
35
+ show_help: :always,
36
+ per_page: 15) do |menu|
37
+ menu.enum '.'
38
+
39
+ UseCases::Interface.all.each do |use_case|
40
+ menu.choice use_case.user_facing_name, use_case
41
+ end
42
+ end
43
+
44
+ choice.perform!(prompt)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -8,72 +8,28 @@ module UsePackwerk
8
8
  sig do
9
9
  params(
10
10
  pack_name: T.nilable(String),
11
- limit: Integer,
11
+ limit: Integer
12
12
  ).void
13
13
  end
14
14
  def self.list_top_privacy_violations(pack_name, limit)
15
15
  all_packages = ParsePackwerk.all
16
- if !pack_name.nil?
16
+ if pack_name.nil?
17
+ to_package_names = all_packages.map(&:name)
18
+ else
17
19
  pack_name = Private.clean_pack_name(pack_name)
18
- package = all_packages.find { |package| package.name == pack_name }
20
+ package = all_packages.find { |p| p.name == pack_name }
19
21
  if package.nil?
20
- raise StandardError.new("Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`")
22
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
21
23
  end
22
24
 
23
- pack_specific_content = <<~PACK_CONTENT
24
- You are listing top #{limit} privacy violations for #{pack_name}. See #{UsePackwerk.config.documentation_link} for other utilities!
25
- Pass in a limit to display more or less, e.g. `bin/use_packwerk list_top_privacy_violations #{pack_name} -l 1000`
26
-
27
- This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
28
- Anything not in #{pack_name}/app/public is considered private API.
29
- PACK_CONTENT
30
-
31
25
  to_package_names = [pack_name]
32
- else
33
- pack_specific_content = <<~PACK_CONTENT
34
- You are listing top #{limit} privacy violations for all packs. See #{UsePackwerk.config.documentation_link} for other utilities!
35
- Pass in a limit to display more or less, e.g. `bin/use_packwerk list_top_privacy_violations #{pack_name} -l 1000`
36
-
37
- This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
38
- Anything not in pack_name/app/public is considered private API.
39
- PACK_CONTENT
40
-
41
- to_package_names = all_packages.map(&:name)
42
26
  end
43
27
 
44
28
  violations_by_count = {}
45
29
  total_pack_violation_count = 0
46
30
 
47
31
  Logging.section('👋 Hi there') do
48
- intro = <<~INTRO
49
- #{pack_specific_content}
50
-
51
- When using this script, ask yourself some questions like:
52
- - What do I want to support?
53
- - What do I *not* want to support?
54
- - What is considered simply an implementation detail, and what is essential to the behavior of my pack?
55
- - What is a simple, minimialistic API for clients to engage with the behavior of your pack?
56
- - How do I ensure my public API is not coupled to specific client's use cases?
57
-
58
- Looking at privacy violations can help guide the development of your public API, but it is just the beginning!
59
-
60
- The script will output in the following format:
61
-
62
- SomeConstant # This is the name of a class, constant, or module defined in your pack, outside of app/public
63
- - Total Count: 5 # This is the total number of uses of this outside your pack
64
- - By package: # This is a breakdown of the use of this constant by other packages
65
- # This is the number of files in this pack that this constant is used.
66
- # Check `packs/other_pack_a/deprecated_references.yml` under the '#{pack_name}'.'SomeConstant' key to see where this constant is used
67
- - packs/other_pack_a: 3
68
- - packs/other_pack_b: 2
69
- SomeClass # This is the second most violated class, constant, or module defined in your pack
70
- - Total Count: 2
71
- - By package:
72
- - packs/other_pack_a: 1
73
- - packs/other_pack_b: 1
74
-
75
- Lastly, remember you can use `bin/use_packwerk make_public #{pack_name}/path/to/file.rb` to make your class, constant, or module public API.
76
- INTRO
32
+ intro = UsePackwerk.config.user_event_logger.before_list_top_privacy_violations(pack_name, limit)
77
33
  Logging.print_bold_green(intro)
78
34
  end
79
35
 
@@ -81,10 +37,11 @@ module UsePackwerk
81
37
  all_packages.each do |client_package|
82
38
  PackageProtections::ProtectedPackage.from(client_package).violations.select(&:privacy?).each do |violation|
83
39
  next unless to_package_names.include?(violation.to_package_name)
40
+
84
41
  if pack_name.nil?
85
42
  violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
86
43
  else
87
- violated_symbol = "#{violation.class_name}"
44
+ violated_symbol = violation.class_name
88
45
  end
89
46
  violations_by_count[violated_symbol] ||= {}
90
47
  violations_by_count[violated_symbol][:total_count] ||= 0
@@ -104,8 +61,8 @@ module UsePackwerk
104
61
  Logging.print(violated_symbol)
105
62
  Logging.print(" - Total Count: #{count_info[:total_count]} (#{percentage_of_total}% of total)")
106
63
 
107
- Logging.print(" - By package:")
108
- count_info[:by_package].sort_by{ |client_package_name, count| [-count, client_package_name] }.each do |client_package_name, count|
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|
109
66
  Logging.print(" - #{client_package_name}: #{count}")
110
67
  end
111
68
  end
@@ -120,76 +77,35 @@ module UsePackwerk
120
77
  def self.list_top_dependency_violations(pack_name, limit)
121
78
  all_packages = ParsePackwerk.all
122
79
 
123
- if !pack_name.nil?
80
+ if pack_name.nil?
81
+ to_package_names = all_packages.map(&:name)
82
+ else
124
83
  pack_name = Private.clean_pack_name(pack_name)
125
- package = all_packages.find { |package| package.name == pack_name }
84
+ package = all_packages.find { |p| p.name == pack_name }
126
85
  if package.nil?
127
- raise StandardError.new("Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`")
86
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
128
87
  end
129
88
 
130
- pack_specific_content = <<~PACK_CONTENT
131
- You are listing top #{limit} dependency violations for #{pack_name}. See #{UsePackwerk.config.documentation_link} for other utilities!
132
- Pass in a limit to display more or less, e.g. `bin/use_packwerk list_top_dependency_violations #{pack_name} -l 1000`
133
-
134
- This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
135
- Anything not in #{pack_name}/app/public is considered private API.
136
- PACK_CONTENT
137
-
138
89
  to_package_names = [pack_name]
139
- else
140
- pack_specific_content = <<~PACK_CONTENT
141
- You are listing top #{limit} dependency violations for all packs. See #{UsePackwerk.config.documentation_link} for other utilities!
142
- Pass in a limit to display more or less, e.g. `use_packwerk list_top_dependency_violations #{pack_name} -l 1000`
143
-
144
- This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
145
- Anything not in pack_name/app/public is considered private API.
146
- PACK_CONTENT
147
-
148
- to_package_names = all_packages.map(&:name)
149
90
  end
150
91
 
151
- violations_by_count = {}
152
- total_pack_violation_count = 0
153
-
154
92
  Logging.section('👋 Hi there') do
155
- intro = <<~INTRO
156
- #{pack_specific_content}
157
-
158
- When using this script, ask yourself some questions like:
159
- - What do I want to support?
160
- - What do I *not* want to support?
161
- - Which direction should a dependency go?
162
- - What packs should depend on you, and what packs should not depend on you?
163
- - Would it be simpler if other packs only depended on interfaces to your pack rather than implementation?
164
-
165
- Looking at dependency violations can help guide the development of your public API, but it is just the beginning!
166
-
167
- The script will output in the following format:
168
-
169
- SomeConstant # This is the name of a class, constant, or module defined in your pack, outside of app/public
170
- - Total Count: 5 # This is the total number of unstated uses of this outside your pack
171
- - By package: # This is a breakdown of the use of this constant by other packages
172
- # This is the number of files in this pack that this constant is used.
173
- # Check `packs/other_pack_a/deprecated_references.yml` under the '#{pack_name}'.'SomeConstant' key to see where this constant is used
174
- - packs/other_pack_a: 3
175
- - packs/other_pack_b: 2
176
- SomeClass # This is the second most violated class, constant, or module defined in your pack
177
- - Total Count: 2
178
- - By package:
179
- - packs/other_pack_a: 1
180
- - packs/other_pack_b: 1
181
- INTRO
93
+ intro = UsePackwerk.config.user_event_logger.before_list_top_dependency_violations(pack_name, limit)
182
94
  Logging.print_bold_green(intro)
183
95
  end
184
96
 
97
+ violations_by_count = {}
98
+ total_pack_violation_count = 0
99
+
185
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.
186
101
  all_packages.each do |client_package|
187
102
  PackageProtections::ProtectedPackage.from(client_package).violations.select(&:dependency?).each do |violation|
188
103
  next unless to_package_names.include?(violation.to_package_name)
104
+
189
105
  if pack_name.nil?
190
106
  violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
191
107
  else
192
- violated_symbol = "#{violation.class_name}"
108
+ violated_symbol = violation.class_name
193
109
  end
194
110
  violations_by_count[violated_symbol] ||= {}
195
111
  violations_by_count[violated_symbol][:total_count] ||= 0
@@ -208,8 +124,8 @@ module UsePackwerk
208
124
  percentage_of_total = (count_info[:total_count] * 100.0 / total_pack_violation_count).round(2)
209
125
  Logging.print(violated_symbol)
210
126
  Logging.print(" - Total Count: #{count_info[:total_count]} (#{percentage_of_total}% of total)")
211
- Logging.print(" - By package:")
212
- count_info[:by_package].sort_by{ |client_package_name, count| [-count, client_package_name] }.each do |client_package_name, count|
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|
213
129
  Logging.print(" - #{client_package_name}: #{count}")
214
130
  end
215
131
  end