packs 0.0.6 → 0.0.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +120 -4
  3. data/bin/packs +10 -0
  4. data/bin/rubocop +29 -0
  5. data/bin/tapioca +29 -0
  6. data/lib/packs/cli.rb +164 -0
  7. data/lib/packs/code_ownership_post_processor.rb +58 -0
  8. data/lib/packs/configuration.rb +61 -0
  9. data/lib/packs/default_user_event_logger.rb +7 -0
  10. data/lib/packs/logging.rb +37 -0
  11. data/lib/packs/per_file_processor_interface.rb +18 -0
  12. data/lib/packs/private/file_move_operation.rb +80 -0
  13. data/lib/packs/private/interactive_cli/file_selector.rb +26 -0
  14. data/lib/packs/private/interactive_cli/pack_selector.rb +55 -0
  15. data/lib/packs/private/interactive_cli/team_selector.rb +58 -0
  16. data/lib/packs/private/interactive_cli/use_cases/add_dependency.rb +30 -0
  17. data/lib/packs/private/interactive_cli/use_cases/check.rb +25 -0
  18. data/lib/packs/private/interactive_cli/use_cases/create.rb +27 -0
  19. data/lib/packs/private/interactive_cli/use_cases/get_info.rb +37 -0
  20. data/lib/packs/private/interactive_cli/use_cases/interface.rb +34 -0
  21. data/lib/packs/private/interactive_cli/use_cases/lint_package_todo_yml_files.rb +25 -0
  22. data/lib/packs/private/interactive_cli/use_cases/lint_package_yml_files.rb +26 -0
  23. data/lib/packs/private/interactive_cli/use_cases/make_public.rb +30 -0
  24. data/lib/packs/private/interactive_cli/use_cases/move.rb +32 -0
  25. data/lib/packs/private/interactive_cli/use_cases/move_to_parent.rb +31 -0
  26. data/lib/packs/private/interactive_cli/use_cases/query.rb +51 -0
  27. data/lib/packs/private/interactive_cli/use_cases/rename.rb +25 -0
  28. data/lib/packs/private/interactive_cli/use_cases/update.rb +25 -0
  29. data/lib/packs/private/interactive_cli/use_cases/validate.rb +25 -0
  30. data/lib/packs/private/interactive_cli/use_cases/visualize.rb +44 -0
  31. data/lib/packs/private/interactive_cli.rb +52 -0
  32. data/lib/packs/private/pack_relationship_analyzer.rb +135 -0
  33. data/lib/packs/private/packwerk_wrapper/offenses_aggregator_formatter.rb +44 -0
  34. data/lib/packs/private/packwerk_wrapper.rb +70 -0
  35. data/lib/packs/private.rb +606 -4
  36. data/lib/packs/rubocop_post_processor.rb +30 -0
  37. data/lib/packs/user_event_logger.rb +199 -0
  38. data/lib/packs.rb +233 -53
  39. metadata +225 -14
  40. data/lib/packs/pack.rb +0 -48
  41. data/lib/packs/private/configuration.rb +0 -36
  42. data/lib/packs/rspec/fixture_helper.rb +0 -33
  43. data/lib/packs/rspec/support.rb +0 -21
@@ -0,0 +1,199 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module UserEventLogger
5
+ extend T::Sig
6
+ extend T::Helpers
7
+
8
+ abstract!
9
+
10
+ sig { params(pack_name: String).returns(String) }
11
+ def before_create_pack(pack_name)
12
+ <<~MSG
13
+ You are creating a pack, which is great. Check out #{documentation_link} for more info!
14
+ MSG
15
+ end
16
+
17
+ sig { params(pack_name: String).returns(String) }
18
+ def after_create_pack(pack_name)
19
+ <<~MSG
20
+ Your next steps might be:
21
+
22
+ 1) Move files into your pack with `bin/packs move #{pack_name} path/to/file.rb`
23
+
24
+ 2) Run `bin/packwerk update-todo` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
25
+
26
+ 3) Expose public API in #{pack_name}/app/public. Try `bin/packs make_public #{pack_name}/path/to/file.rb`
27
+
28
+ 4) Update your readme at #{pack_name}/README.md
29
+ MSG
30
+ end
31
+
32
+ sig { params(pack_name: String).returns(String) }
33
+ def before_move_to_pack(pack_name)
34
+ <<~MSG
35
+ You are moving a file to a pack, which is great. Check out #{documentation_link} for more info!
36
+ MSG
37
+ end
38
+
39
+ sig { params(pack_name: String).returns(String) }
40
+ def after_move_to_pack(pack_name)
41
+ <<~MSG
42
+ Your next steps might be:
43
+
44
+ 1) Run `bin/packwerk update-todo` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
45
+
46
+ 2) Touch base with each team who owns files involved in this move
47
+
48
+ 3) Expose public API in #{pack_name}/app/public. Try `bin/packs make_public #{pack_name}/path/to/file.rb`
49
+
50
+ 4) Update your readme at #{pack_name}/README.md
51
+ MSG
52
+ end
53
+
54
+ sig { returns(String) }
55
+ def before_make_public
56
+ <<~MSG
57
+ You are moving some files into public API. See #{documentation_link} for other utilities!
58
+ MSG
59
+ end
60
+
61
+ sig { returns(String) }
62
+ def after_make_public
63
+ <<~MSG
64
+ Your next steps might be:
65
+
66
+ 1) Run `bin/packwerk update-todo` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
67
+
68
+ 2) Work to migrate clients of private API to your new public API
69
+
70
+ 3) Update your README at packs/your_package_name/README.md
71
+ MSG
72
+ end
73
+
74
+ sig { params(pack_name: String).returns(String) }
75
+ def before_add_dependency(pack_name)
76
+ <<~MSG
77
+ You are adding a dependency. See #{documentation_link} for other utilities!
78
+ MSG
79
+ end
80
+
81
+ sig { params(pack_name: String).returns(String) }
82
+ def after_add_dependency(pack_name)
83
+ <<~MSG
84
+ Your next steps might be:
85
+
86
+ 1) Run `bin/packwerk update-todo` to update the violations.
87
+ MSG
88
+ end
89
+
90
+ sig { params(pack_name: String).returns(String) }
91
+ def before_move_to_parent(pack_name)
92
+ <<~MSG
93
+ You are moving one pack to be a child of a different pack. Check out #{documentation_link} for more info!
94
+ MSG
95
+ end
96
+
97
+ sig { params(pack_name: String).returns(String) }
98
+ def after_move_to_parent(pack_name)
99
+ <<~MSG
100
+ Your next steps might be:
101
+
102
+ 1) Delete the old pack when things look good: `rm -rf #{pack_name}`
103
+
104
+ 2) Run `bin/packwerk update-todo` to update the violations. Make sure to run `spring stop` first.
105
+ MSG
106
+ end
107
+
108
+ sig { params(pack_name: String).returns(String) }
109
+ def on_create_public_directory_todo(pack_name)
110
+ <<~MSG
111
+ This directory holds your public API!
112
+
113
+ Any classes, constants, or modules that you want other packs to use and you intend to support should go in here.
114
+ Anything that is considered private should go in other folders.
115
+
116
+ If another pack uses classes, constants, or modules that are not in your public folder, it will be considered a "privacy violation" by packwerk.
117
+ You can prevent other packs from using private API by using packwerk.
118
+
119
+ Want to find how your private API is being used today?
120
+ Try running: `bin/packs list_top_privacy_violations #{pack_name}`
121
+
122
+ Want to move something into this folder?
123
+ Try running: `bin/packs make_public #{pack_name}/path/to/file.rb`
124
+
125
+ One more thing -- feel free to delete this file and replace it with a README.md describing your package in the main package directory.
126
+
127
+ See #{documentation_link} for more info!
128
+ MSG
129
+ end
130
+
131
+ sig { params(pack_name: String).returns(String) }
132
+ def on_create_readme_todo(pack_name)
133
+ <<~MSG
134
+ Welcome to `#{pack_name}`!
135
+
136
+ If you're the author, please consider replacing this file with a README.md, which may contain:
137
+ - What your pack is and does
138
+ - How you expect people to use your pack
139
+ - Example usage of your pack's public API (which lives in `#{pack_name}/app/public`)
140
+ - Limitations, risks, and important considerations of usage
141
+ - How to get in touch with eng and other stakeholders for questions or issues pertaining to this pack (note: it is recommended to add ownership in `#{pack_name}/package.yml` under the `owner` metadata key)
142
+ - What SLAs/SLOs (service level agreements/objectives), if any, your package provides
143
+ - When in doubt, keep it simple
144
+ - Anything else you may want to include!
145
+
146
+ README.md files are under version control and should change as your public API changes.#{' '}
147
+
148
+ See #{documentation_link} for more info!
149
+ MSG
150
+ end
151
+
152
+ sig { params(pack_name: T.nilable(String), limit: Integer).returns(String) }
153
+ def before_list_top_dependency_violations(pack_name, limit)
154
+ if pack_name.nil?
155
+ <<~PACK_CONTENT
156
+ You are listing top #{limit} dependency violations for all packs. See #{documentation_link} for other utilities!
157
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_dependency_violations #{pack_name} -l 1000`
158
+
159
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
160
+ Anything not in pack_name/app/public is considered private API.
161
+ PACK_CONTENT
162
+ else
163
+ <<~PACK_CONTENT
164
+ You are listing top #{limit} dependency violations for #{pack_name}. See #{documentation_link} for other utilities!
165
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_dependency_violations #{pack_name} -l 1000`
166
+
167
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
168
+ Anything not in #{pack_name}/app/public is considered private API.
169
+ PACK_CONTENT
170
+ end
171
+ end
172
+
173
+ sig { params(pack_name: T.nilable(String), limit: Integer).returns(String) }
174
+ def before_list_top_privacy_violations(pack_name, limit)
175
+ if pack_name.nil?
176
+ <<~PACK_CONTENT
177
+ You are listing top #{limit} privacy violations for all packs. See #{documentation_link} for other utilities!
178
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_privacy_violations #{pack_name} -l 1000`
179
+
180
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
181
+ Anything not in pack_name/app/public is considered private API.
182
+ PACK_CONTENT
183
+ else
184
+ <<~PACK_CONTENT
185
+ You are listing top #{limit} privacy violations for #{pack_name}. See #{documentation_link} for other utilities!
186
+ Pass in a limit to display more or less, e.g. `bin/packs list_top_privacy_violations #{pack_name} -l 1000`
187
+
188
+ This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
189
+ Anything not in #{pack_name}/app/public is considered private API.
190
+ PACK_CONTENT
191
+ end
192
+ end
193
+
194
+ sig { returns(String) }
195
+ def documentation_link
196
+ 'https://github.com/rubyatscale/packs#readme'
197
+ end
198
+ end
199
+ end
data/lib/packs.rb CHANGED
@@ -1,80 +1,260 @@
1
1
  # typed: strict
2
2
 
3
- require 'yaml'
4
- require 'pathname'
5
- require 'sorbet-runtime'
6
- require 'packs/pack'
3
+ # Ruby internal requires
4
+ require 'fileutils'
5
+
6
+ # External gem requires
7
+ require 'colorized_string'
8
+
9
+ # Internal gem requires
10
+ require 'packs-specification'
11
+ require 'parse_packwerk'
12
+ require 'code_teams'
13
+ require 'code_ownership'
14
+ require 'rubocop-packs'
15
+
16
+ # Private implementation requires
7
17
  require 'packs/private'
18
+ require 'packs/per_file_processor_interface'
19
+ require 'packs/rubocop_post_processor'
20
+ require 'packs/code_ownership_post_processor'
21
+ require 'packs/logging'
22
+ require 'packs/configuration'
23
+ require 'packs/cli'
8
24
 
9
25
  module Packs
10
- PACKAGE_FILE = T.let('package.yml'.freeze, String)
26
+ extend T::Sig
27
+
28
+ PERMITTED_PACK_LOCATIONS = T.let(%w[
29
+ gems
30
+ components
31
+ packs
32
+ ], T::Array[String])
33
+
34
+ sig { void }
35
+ def self.start_interactive_mode!
36
+ Private::InteractiveCli.start!
37
+ end
11
38
 
12
- class << self
13
- extend T::Sig
39
+ sig do
40
+ params(
41
+ pack_name: String,
42
+ enforce_privacy: T::Boolean,
43
+ enforce_dependencies: T.nilable(T::Boolean),
44
+ team: T.nilable(CodeTeams::Team)
45
+ ).void
46
+ end
47
+ def self.create_pack!(
48
+ pack_name:,
49
+ enforce_privacy: true,
50
+ enforce_dependencies: nil,
51
+ team: nil
52
+ )
53
+ Private.create_pack!(
54
+ pack_name: pack_name,
55
+ enforce_privacy: enforce_privacy,
56
+ enforce_dependencies: enforce_dependencies,
57
+ team: team
58
+ )
59
+ end
14
60
 
15
- sig { returns(T::Array[Pack]) }
16
- def all
17
- packs_by_name.values
61
+ sig do
62
+ params(
63
+ pack_name: String,
64
+ paths_relative_to_root: T::Array[String],
65
+ per_file_processors: T::Array[PerFileProcessorInterface]
66
+ ).void
67
+ end
68
+ def self.move_to_pack!(
69
+ pack_name:,
70
+ paths_relative_to_root: [],
71
+ per_file_processors: []
72
+ )
73
+ Logging.section('👋 Hi!') do
74
+ intro = Packs.config.user_event_logger.before_move_to_pack(pack_name)
75
+ Logging.print_bold_green(intro)
18
76
  end
19
77
 
20
- sig { params(name: String).returns(T.nilable(Pack)) }
21
- def find(name)
22
- packs_by_name[name]
78
+ Private.move_to_pack!(
79
+ pack_name: pack_name,
80
+ paths_relative_to_root: paths_relative_to_root,
81
+ per_file_processors: per_file_processors
82
+ )
83
+
84
+ Logging.section('Next steps') do
85
+ next_steps = Packs.config.user_event_logger.after_move_to_pack(pack_name)
86
+ Logging.print_bold_green(next_steps)
23
87
  end
88
+ end
24
89
 
25
- sig { params(file_path: T.any(Pathname, String)).returns(T.nilable(Pack)) }
26
- def for_file(file_path)
27
- path_string = file_path.to_s
28
- @for_file = T.let(@for_file, T.nilable(T::Hash[String, T.nilable(Pack)]))
29
- @for_file ||= {}
30
- @for_file[path_string] ||= all.find { |package| path_string.start_with?("#{package.name}/") || path_string == package.name }
90
+ sig do
91
+ params(
92
+ paths_relative_to_root: T::Array[String],
93
+ per_file_processors: T::Array[PerFileProcessorInterface]
94
+ ).void
95
+ end
96
+ def self.make_public!(
97
+ paths_relative_to_root: [],
98
+ per_file_processors: []
99
+ )
100
+ Logging.section('Making files public') do
101
+ intro = Packs.config.user_event_logger.before_make_public
102
+ Logging.print_bold_green(intro)
31
103
  end
32
104
 
33
- sig { void }
34
- def bust_cache!
35
- @packs_by_name = nil
36
- @config = nil
37
- @for_file = nil
105
+ Private.make_public!(
106
+ paths_relative_to_root: paths_relative_to_root,
107
+ per_file_processors: per_file_processors
108
+ )
109
+
110
+ Logging.section('Next steps') do
111
+ next_steps = Packs.config.user_event_logger.after_make_public
112
+ Logging.print_bold_green(next_steps)
38
113
  end
114
+ end
39
115
 
40
- sig { returns(Private::Configuration) }
41
- def config
42
- @config = T.let(@config, T.nilable(Private::Configuration))
43
- @config ||= Private::Configuration.fetch
116
+ sig do
117
+ params(
118
+ pack_name: String,
119
+ dependency_name: String
120
+ ).void
121
+ end
122
+ def self.add_dependency!(
123
+ pack_name:,
124
+ dependency_name:
125
+ )
126
+ Logging.section('Adding a dependency') do
127
+ intro = Packs.config.user_event_logger.before_add_dependency(pack_name)
128
+ Logging.print_bold_green(intro)
44
129
  end
45
130
 
46
- sig { params(blk: T.proc.params(arg0: Private::Configuration).void).void }
47
- def configure(&blk)
48
- # If packs.yml is being used, then ignore direct configuration.
49
- # This is only a stop-gap to permit Stimpack users to more easily migrate
50
- # to packs.yml
51
- return if Private::Configuration::CONFIGURATION_PATHNAME.exist?
131
+ Private.add_dependency!(
132
+ pack_name: pack_name,
133
+ dependency_name: dependency_name
134
+ )
52
135
 
53
- yield(config)
136
+ Logging.section('Next steps') do
137
+ next_steps = Packs.config.user_event_logger.after_add_dependency(pack_name)
138
+ Logging.print_bold_green(next_steps)
54
139
  end
140
+ end
55
141
 
56
- private
142
+ sig do
143
+ params(
144
+ pack_name: String,
145
+ parent_name: String,
146
+ per_file_processors: T::Array[PerFileProcessorInterface]
147
+ ).void
148
+ end
149
+ def self.move_to_parent!(
150
+ pack_name:,
151
+ parent_name:,
152
+ per_file_processors: []
153
+ )
154
+ Logging.section('👋 Hi!') do
155
+ intro = Packs.config.user_event_logger.before_move_to_parent(pack_name)
156
+ Logging.print_bold_green(intro)
157
+ end
57
158
 
58
- sig { returns(T::Hash[String, Pack]) }
59
- def packs_by_name
60
- @packs_by_name = T.let(@packs_by_name, T.nilable(T::Hash[String, Pack]))
61
- @packs_by_name ||= begin
62
- all_packs = package_glob_patterns.map do |path|
63
- Pack.from(path)
64
- end
159
+ Private.move_to_parent!(
160
+ pack_name: pack_name,
161
+ parent_name: parent_name,
162
+ per_file_processors: per_file_processors
163
+ )
65
164
 
66
- # We want to match more specific paths first so for_file works correctly.
67
- sorted_packages = all_packs.sort_by { |package| -package.name.length }
68
- sorted_packages.to_h { |p| [p.name, p] }
69
- end
165
+ Logging.section('Next steps') do
166
+ next_steps = Packs.config.user_event_logger.after_move_to_parent(pack_name)
167
+
168
+ Logging.print_bold_green(next_steps)
70
169
  end
170
+ end
71
171
 
72
- sig { returns(T::Array[Pathname]) }
73
- def package_glob_patterns
74
- absolute_root = Private.root
75
- config.pack_paths.flat_map do |pack_path|
76
- Pathname.glob(absolute_root.join(pack_path).join(PACKAGE_FILE))
77
- end
172
+ sig do
173
+ params(
174
+ pack_name: T.nilable(String),
175
+ limit: Integer
176
+ ).void
177
+ end
178
+ def self.list_top_privacy_violations(
179
+ pack_name:,
180
+ limit:
181
+ )
182
+ Private::PackRelationshipAnalyzer.list_top_privacy_violations(
183
+ pack_name,
184
+ limit
185
+ )
186
+ end
187
+
188
+ sig do
189
+ params(
190
+ pack_name: T.nilable(String),
191
+ limit: Integer
192
+ ).void
193
+ end
194
+ def self.list_top_dependency_violations(
195
+ pack_name:,
196
+ limit:
197
+ )
198
+ Private::PackRelationshipAnalyzer.list_top_dependency_violations(
199
+ pack_name,
200
+ limit
201
+ )
202
+ end
203
+
204
+ sig do
205
+ params(
206
+ file: String,
207
+ find: Pathname,
208
+ replace_with: Pathname
209
+ ).void
210
+ end
211
+ def self.replace_in_file(file:, find:, replace_with:)
212
+ Private.replace_in_file(
213
+ file: file,
214
+ find: find,
215
+ replace_with: replace_with
216
+ )
217
+ end
218
+
219
+ sig { void }
220
+ def self.bust_cache!
221
+ Private.bust_cache!
222
+ Specification.bust_cache!
223
+ end
224
+
225
+ #
226
+ # execute_command is like `run` except it does not `exit`
227
+ #
228
+ sig { params(argv: T.untyped, formatter: T.nilable(Packwerk::OffensesFormatter)).void }
229
+ def self.execute(argv, formatter = nil)
230
+ Private::PackwerkWrapper.with_safe_exit_if_no_files_found do
231
+ Private::PackwerkWrapper.packwerk_cli(formatter).execute_command(argv)
78
232
  end
79
233
  end
234
+
235
+ sig { params(files: T::Array[String]).returns(T::Array[Packwerk::ReferenceOffense]) }
236
+ def self.get_offenses_for_files(files)
237
+ formatter = Private::PackwerkWrapper::OffensesAggregatorFormatter.new
238
+ Private::PackwerkWrapper.packwerk_cli_execute_safely(['check', *files], formatter)
239
+ formatter.aggregated_offenses.compact
240
+ end
241
+
242
+ sig { params(files: T::Array[String]).returns(T::Array[Packwerk::ReferenceOffense]) }
243
+ def self.get_offenses_for_files_by_package(files)
244
+ packages = Private::PackwerkWrapper.package_names_for_files(files)
245
+ argv = ['check', '--packages', packages.join(',')]
246
+ formatter = Private::PackwerkWrapper::OffensesAggregatorFormatter.new
247
+ Private::PackwerkWrapper.packwerk_cli_execute_safely(argv, formatter)
248
+ formatter.aggregated_offenses.compact
249
+ end
250
+
251
+ sig { void }
252
+ def self.lint_package_todo_yml_files!
253
+ Private.lint_package_todo_yml_files!
254
+ end
255
+
256
+ sig { params(packs: T::Array[Packs::Pack]).void }
257
+ def self.lint_package_yml_files!(packs)
258
+ Private.lint_package_yml_files!(packs)
259
+ end
80
260
  end