packs 0.0.5 → 0.0.22

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -12
  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 -43
  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/use_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