use_packwerk 0.54.0 → 0.56.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.
@@ -18,25 +18,26 @@ module UsePackwerk
18
18
  # This results in the pack not being found, but when we write the package YML it writes to the same place,
19
19
  # causing a behaviorally confusing diff.
20
20
  # We ignore trailing slashes as an ergonomic feature to the user.
21
- pack_name.gsub(/\/$/, '')
21
+ pack_name.gsub(%r{/$}, '')
22
22
  end
23
23
 
24
24
  sig do
25
25
  params(
26
26
  file: String,
27
27
  find: Pathname,
28
- replace_with: Pathname,
28
+ replace_with: Pathname
29
29
  ).void
30
30
  end
31
31
  def self.replace_in_file(file:, find:, replace_with:)
32
32
  file = Pathname.new(file)
33
33
  return if !file.exist?
34
+
34
35
  count = 0
35
36
  file.write(file.read.gsub(find.to_s) do
36
37
  count += 1
37
38
  replace_with.to_s
38
39
  end)
39
- Logging.print "Replaced #{count} occurrence(s) of #{find} in #{file.to_s}" if count > 0
40
+ Logging.print "Replaced #{count} occurrence(s) of #{find} in #{file}" if count > 0
40
41
  end
41
42
 
42
43
  sig do
@@ -48,12 +49,7 @@ module UsePackwerk
48
49
  end
49
50
  def self.create_pack!(pack_name:, enforce_privacy:, enforce_dependencies:)
50
51
  Logging.section('👋 Hi!') do
51
- intro = <<~INTRO
52
- You are creating a pack, which is great. Check out #{UsePackwerk.config.documentation_link} for more info!
53
-
54
- Please bring any questions or issues you have in your development process to #ruby-modularity or #product-infrastructure.
55
- We'd be happy to try to help through pairing, accepting feedback, changing our process, changing our tools, and more.
56
- INTRO
52
+ intro = UsePackwerk.config.user_event_logger.before_create_pack(pack_name)
57
53
  Logging.print_bold_green(intro)
58
54
  end
59
55
 
@@ -64,19 +60,7 @@ module UsePackwerk
64
60
  add_readme_todo(package)
65
61
 
66
62
  Logging.section('Next steps') do
67
- next_steps = <<~NEXT_STEPS
68
- Your next steps might be:
69
-
70
- 1) Move files into your pack with `bin/use_packwerk move #{pack_name} path/to/file.rb`
71
-
72
- 2) Run `bin/packwerk update-deprecations` to update the violations. Make sure to run `spring stop` if you've added new load paths (new top-level directories) in your pack.
73
-
74
- 3) Update TODO lists for rubocop implemented protections. See #{UsePackwerk.config.documentation_link} for more info
75
-
76
- 4) Expose public API in #{pack_name}/app/public. Try `bin/use_packwerk make_public #{pack_name}/path/to/file.rb`
77
-
78
- 5) Update your readme at #{pack_name}/README.md
79
- NEXT_STEPS
63
+ next_steps = UsePackwerk.config.user_event_logger.after_create_pack(pack_name)
80
64
 
81
65
  Logging.print_bold_green(next_steps)
82
66
  end
@@ -91,9 +75,9 @@ module UsePackwerk
91
75
  end
92
76
  def self.move_to_pack!(pack_name:, paths_relative_to_root:, per_file_processors: [])
93
77
  pack_name = Private.clean_pack_name(pack_name)
94
- package = ParsePackwerk.all.find { |package| package.name == pack_name }
78
+ package = ParsePackwerk.all.find { |p| p.name == pack_name }
95
79
  if package.nil?
96
- raise StandardError.new("Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`")
80
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
97
81
  end
98
82
 
99
83
  add_public_directory(package)
@@ -118,9 +102,9 @@ module UsePackwerk
118
102
  # Later, if we choose to go back to moving whole directories at a time, it should be a refactor and all tests should still pass
119
103
  #
120
104
  if origin_pathname.directory?
121
- origin_pathname.glob('**/*.*').reject do |path|
122
- path.to_s.include?(ParsePackwerk::PACKAGE_YML_NAME) ||
123
- path.to_s.include?(ParsePackwerk::DEPRECATED_REFERENCES_YML_NAME)
105
+ origin_pathname.glob('**/*.*').reject do |origin_path|
106
+ origin_path.to_s.include?(ParsePackwerk::PACKAGE_YML_NAME) ||
107
+ origin_path.to_s.include?(ParsePackwerk::DEPRECATED_REFERENCES_YML_NAME)
124
108
  end
125
109
  else
126
110
  origin_pathname
@@ -130,7 +114,7 @@ module UsePackwerk
130
114
  FileMoveOperation.new(
131
115
  origin_pathname: origin_pathname,
132
116
  destination_pathname: FileMoveOperation.destination_pathname_for_package_move(origin_pathname, package_location),
133
- destination_pack: package,
117
+ destination_pack: package
134
118
  )
135
119
  end
136
120
  file_move_operations.each do |file_move_operation|
@@ -140,89 +124,87 @@ module UsePackwerk
140
124
  end
141
125
  end
142
126
 
143
- per_file_processors.each do |per_file_processor|
144
- per_file_processor.print_final_message!
145
- end
127
+ per_file_processors.each(&:print_final_message!)
146
128
  end
147
129
 
148
- sig do
149
- params(
150
- pack_name: String,
151
- parent_name: String,
152
- per_file_processors: T::Array[PerFileProcessorInterface],
153
- ).void
154
- end
155
- def self.move_to_parent!(
156
- pack_name:,
157
- parent_name:,
158
- per_file_processors: []
159
- )
160
- pack_name = Private.clean_pack_name(pack_name)
161
- package = ParsePackwerk.all.find { |package| package.name == pack_name }
162
- if package.nil?
163
- raise StandardError.new("Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`")
164
- end
165
-
166
- parent_name = Private.clean_pack_name(parent_name)
167
- parent_package = ParsePackwerk.all.find { |package| package.name == parent_name }
168
- if parent_package.nil?
169
- parent_package = create_pack_if_not_exists!(pack_name: parent_name, enforce_privacy: true, enforce_dependencies: true)
130
+ sig do
131
+ params(
132
+ pack_name: String,
133
+ parent_name: String,
134
+ per_file_processors: T::Array[PerFileProcessorInterface]
135
+ ).void
170
136
  end
171
-
172
- # First we create a new pack that has the exact same properties of the old one!
173
- package_last_name = package.directory.basename
174
- new_package_name = parent_package.directory.join(package_last_name).to_s
175
-
176
- new_package = ParsePackwerk::Package.new(
177
- name: new_package_name,
178
- enforce_privacy: package.enforce_dependencies,
179
- enforce_dependencies: package.enforce_dependencies,
180
- dependencies: package.dependencies,
181
- metadata: package.metadata,
182
- )
183
- ParsePackwerk.write_package_yml!(new_package)
184
- ParsePackwerk.bust_cache!
185
-
186
- # Move everything from the old pack to the new one
187
- self.move_to_pack!(
188
- pack_name: new_package_name,
189
- paths_relative_to_root: [package.directory.to_s],
190
- per_file_processors: per_file_processors,
137
+ def self.move_to_parent!(
138
+ pack_name:,
139
+ parent_name:,
140
+ per_file_processors: []
191
141
  )
142
+ pack_name = Private.clean_pack_name(pack_name)
143
+ package = ParsePackwerk.all.find { |p| p.name == pack_name }
144
+ if package.nil?
145
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
146
+ end
192
147
 
193
- # Then delete the old package.yml and deprecated_references.yml files
194
- package.yml.delete
195
- deprecated_references_file = ParsePackwerk::DeprecatedReferences.for(package).pathname
196
- deprecated_references_file.delete if deprecated_references_file.exist?
148
+ parent_name = Private.clean_pack_name(parent_name)
149
+ parent_package = ParsePackwerk.all.find { |p| p.name == parent_name }
150
+ if parent_package.nil?
151
+ parent_package = create_pack_if_not_exists!(pack_name: parent_name, enforce_privacy: true, enforce_dependencies: true)
152
+ end
197
153
 
198
- ParsePackwerk.bust_cache!
154
+ # First we create a new pack that has the exact same properties of the old one!
155
+ package_last_name = package.directory.basename
156
+ new_package_name = parent_package.directory.join(package_last_name).to_s
199
157
 
200
- ParsePackwerk.all.each do |other_package|
201
- new_dependencies = other_package.dependencies.map{|d| d == pack_name ? new_package_name : d}
202
- if other_package.name == parent_name && !new_dependencies.include?(new_package_name)
203
- new_dependencies += [new_package_name]
204
- end
158
+ new_package = ParsePackwerk::Package.new(
159
+ name: new_package_name,
160
+ enforce_privacy: package.enforce_dependencies,
161
+ enforce_dependencies: package.enforce_dependencies,
162
+ dependencies: package.dependencies,
163
+ metadata: package.metadata
164
+ )
165
+ ParsePackwerk.write_package_yml!(new_package)
166
+ ParsePackwerk.bust_cache!
205
167
 
206
- new_other_package = ParsePackwerk::Package.new(
207
- name: other_package.name,
208
- enforce_privacy: other_package.enforce_dependencies,
209
- enforce_dependencies: other_package.enforce_dependencies,
210
- dependencies: new_dependencies.uniq.sort,
211
- metadata: other_package.metadata,
168
+ # Move everything from the old pack to the new one
169
+ move_to_pack!(
170
+ pack_name: new_package_name,
171
+ paths_relative_to_root: [package.directory.to_s],
172
+ per_file_processors: per_file_processors
212
173
  )
213
174
 
214
- ParsePackwerk.write_package_yml!(new_other_package)
215
- end
175
+ # Then delete the old package.yml and deprecated_references.yml files
176
+ package.yml.delete
177
+ deprecated_references_file = ParsePackwerk::DeprecatedReferences.for(package).pathname
178
+ deprecated_references_file.delete if deprecated_references_file.exist?
216
179
 
217
- sorbet_config = Pathname.new('sorbet/config')
218
- if sorbet_config.exist?
219
- UsePackwerk.replace_in_file(
220
- file: sorbet_config.to_s,
221
- find: package.directory.join('spec'),
222
- replace_with: new_package.directory.join('spec'),
223
- )
180
+ ParsePackwerk.bust_cache!
181
+
182
+ ParsePackwerk.all.each do |other_package|
183
+ new_dependencies = other_package.dependencies.map { |d| d == pack_name ? new_package_name : d }
184
+ if other_package.name == parent_name && !new_dependencies.include?(new_package_name)
185
+ new_dependencies += [new_package_name]
186
+ end
187
+
188
+ new_other_package = ParsePackwerk::Package.new(
189
+ name: other_package.name,
190
+ enforce_privacy: other_package.enforce_privacy,
191
+ enforce_dependencies: other_package.enforce_dependencies,
192
+ dependencies: new_dependencies.uniq.sort,
193
+ metadata: other_package.metadata
194
+ )
195
+
196
+ ParsePackwerk.write_package_yml!(new_other_package)
197
+ end
198
+
199
+ sorbet_config = Pathname.new('sorbet/config')
200
+ if sorbet_config.exist?
201
+ UsePackwerk.replace_in_file(
202
+ file: sorbet_config.to_s,
203
+ find: package.directory.join('spec'),
204
+ replace_with: new_package.directory.join('spec')
205
+ )
206
+ end
224
207
  end
225
- end
226
208
 
227
209
  sig do
228
210
  params(
@@ -242,7 +224,6 @@ module UsePackwerk
242
224
  end
243
225
  end
244
226
 
245
-
246
227
  file_move_operations = file_paths.map do |path|
247
228
  package = T.must(ParsePackwerk.package_from_path(path))
248
229
  origin_pathname = Pathname.new(path).cleanpath
@@ -250,7 +231,7 @@ module UsePackwerk
250
231
  FileMoveOperation.new(
251
232
  origin_pathname: origin_pathname,
252
233
  destination_pathname: FileMoveOperation.destination_pathname_for_new_public_api(origin_pathname),
253
- destination_pack: package,
234
+ destination_pack: package
254
235
  )
255
236
  end
256
237
 
@@ -272,15 +253,15 @@ module UsePackwerk
272
253
  all_packages = ParsePackwerk.all
273
254
 
274
255
  pack_name = Private.clean_pack_name(pack_name)
275
- package = all_packages.find { |package| package.name == pack_name }
256
+ package = all_packages.find { |p| p.name == pack_name }
276
257
  if package.nil?
277
- raise StandardError.new("Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`")
258
+ raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
278
259
  end
279
260
 
280
261
  dependency_name = Private.clean_pack_name(dependency_name)
281
- package_dependency = all_packages.find { |package| package.name == dependency_name }
262
+ package_dependency = all_packages.find { |p| p.name == dependency_name }
282
263
  if package_dependency.nil?
283
- raise StandardError.new("Can not find package with name #{dependency_name}. Make sure the argument is of the form `packs/my_pack/`")
264
+ raise StandardError, "Can not find package with name #{dependency_name}. Make sure the argument is of the form `packs/my_pack/`"
284
265
  end
285
266
 
286
267
  new_package = ParsePackwerk::Package.new(
@@ -288,7 +269,7 @@ module UsePackwerk
288
269
  dependencies: (package.dependencies + [dependency_name]).uniq.sort,
289
270
  enforce_privacy: package.enforce_privacy,
290
271
  enforce_dependencies: package.enforce_dependencies,
291
- metadata: package.metadata,
272
+ metadata: package.metadata
292
273
  )
293
274
  ParsePackwerk.write_package_yml!(new_package)
294
275
  end
@@ -322,63 +303,29 @@ module UsePackwerk
322
303
  # use git mv so that git knows that it was a move
323
304
  FileUtils.mv(origin, destination)
324
305
  elsif !origin.exist? && destination.exist?
325
- Logging.print ColorizedString.new("[SKIP] Not moving #{origin.to_s}, does not exist, (#{destination.to_s} already exists)").red
306
+ Logging.print ColorizedString.new("[SKIP] Not moving #{origin}, does not exist, (#{destination} already exists)").red
326
307
  else
327
- Logging.print ColorizedString.new("[SKIP] Not moving #{origin.to_s}, does not exist").red
308
+ Logging.print ColorizedString.new("[SKIP] Not moving #{origin}, does not exist").red
328
309
  end
329
310
  end
330
311
 
331
- sig { params(package: ParsePackwerk::Package).void }
312
+ sig { params(package: ParsePackwerk::Package).void }
332
313
  def self.add_public_directory(package)
333
314
  public_directory = package.directory.join('app/public')
334
315
 
335
316
  if public_directory.glob('**/**.rb').none?
336
317
  FileUtils.mkdir_p(public_directory)
337
- todo_md = <<~TODO
338
- This directory holds your public API!
339
-
340
- Any classes, constants, or modules that you want other packs to use and you intend to support should go in here.
341
- Anything that is considered private should go in other folders.
342
-
343
- If another pack uses classes, constants, or modules that are not in your public folder, it will be considered a "privacy violation" by packwerk.
344
- You can prevent other packs from using private API by using package_protections.
345
-
346
- Want to find how your private API is being used today?
347
- Try running: `bin/use_packwerk list_top_privacy_violations #{package.name}`
348
-
349
- Want to move something into this folder?
350
- Try running: `bin/use_packwerk make_public #{package.name}/path/to/file.rb`
351
-
352
- One more thing -- feel free to delete this file and replace it with a README.md describing your package in the main package directory.
353
-
354
- See #{UsePackwerk.config.documentation_link} for more info!
355
- TODO
318
+ todo_md = UsePackwerk.config.user_event_logger.on_create_public_directory_todo(package.name)
356
319
  public_directory.join('TODO.md').write(todo_md)
357
320
  end
358
321
  end
359
322
 
360
- sig { params(package: ParsePackwerk::Package).void }
323
+ sig { params(package: ParsePackwerk::Package).void }
361
324
  def self.add_readme_todo(package)
362
325
  pack_directory = package.directory
363
326
 
364
327
  if !pack_directory.join('README.md').exist?
365
- readme_todo_md = <<~TODO
366
- Welcome to `#{package.name}`!
367
-
368
- If you're the author, please consider replacing this file with a README.md, which may contain:
369
- - What your pack is and does
370
- - How you expect people to use your pack
371
- - Example usage of your pack's public API (which lives in `#{package.name}/app/public`)
372
- - Limitations, risks, and important considerations of usage
373
- - 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 `#{package.name}/package.yml` under the `owner` metadata key)
374
- - What SLAs/SLOs (service level agreements/objectives), if any, your package provides
375
- - When in doubt, keep it simple
376
- - Anything else you may want to include!
377
-
378
- README.md files are under version control and should change as your public API changes.
379
-
380
- See #{UsePackwerk.config.documentation_link} for more info!
381
- TODO
328
+ readme_todo_md = UsePackwerk.config.user_event_logger.on_create_readme_todo(package.name)
382
329
  pack_directory.join('README_TODO.md').write(readme_todo_md)
383
330
  end
384
331
  end
@@ -392,13 +339,10 @@ module UsePackwerk
392
339
  end
393
340
  def self.create_pack_if_not_exists!(pack_name:, enforce_privacy:, enforce_dependencies:)
394
341
  if PERMITTED_PACK_LOCATIONS.none? { |permitted_location| pack_name.start_with?(permitted_location) }
395
- raise StandardError.new("UsePackwerk only supports packages in the the following directories: #{PERMITTED_PACK_LOCATIONS.inspect}. Please make sure to pass in the name of the pack including the full directory path, e.g. `packs/my_pack`.")
342
+ raise StandardError, "UsePackwerk only supports packages in the the following directories: #{PERMITTED_PACK_LOCATIONS.inspect}. Please make sure to pass in the name of the pack including the full directory path, e.g. `packs/my_pack`."
396
343
  end
397
344
 
398
- existing_package = ParsePackwerk.all.find { |package| package.name == pack_name }
399
-
400
- package_location = Pathname.new(pack_name)
401
-
345
+ existing_package = ParsePackwerk.all.find { |p| p.name == pack_name }
402
346
  if existing_package.nil?
403
347
  should_enforce_dependenceies = enforce_dependencies.nil? ? UsePackwerk.config.enforce_dependencies : enforce_dependencies
404
348
 
@@ -409,7 +353,7 @@ module UsePackwerk
409
353
  metadata: {
410
354
  'owner' => 'MyTeam'
411
355
  },
412
- name: pack_name,
356
+ name: pack_name
413
357
  )
414
358
 
415
359
  ParsePackwerk.write_package_yml!(package)
@@ -417,7 +361,7 @@ module UsePackwerk
417
361
  package = rewrite_package_with_original_packwerk_values(package)
418
362
 
419
363
  current_contents = package.yml.read
420
- new_contents = current_contents.gsub("MyTeam", "MyTeam # specify your team here, or delete this key if this package is not owned by one team")
364
+ new_contents = current_contents.gsub('MyTeam', 'MyTeam # specify your team here, or delete this key if this package is not owned by one team')
421
365
  package.yml.write(new_contents)
422
366
  existing_package = package
423
367
  end
@@ -428,19 +372,37 @@ module UsePackwerk
428
372
  sig { params(original_package: ParsePackwerk::Package).returns(ParsePackwerk::Package) }
429
373
  def self.rewrite_package_with_original_packwerk_values(original_package)
430
374
  ParsePackwerk.bust_cache!
431
- package_with_protection_defaults = T.must(ParsePackwerk.all.find { |package| package.name == original_package.name })
375
+ package_with_protection_defaults = T.must(ParsePackwerk.all.find { |p| p.name == original_package.name })
432
376
  # PackageProtections also sets `enforce_privacy` and `enforce_dependency` to be true, so we set these back down to their original values
433
377
  package = ParsePackwerk::Package.new(
434
378
  enforce_dependencies: original_package.enforce_dependencies,
435
379
  enforce_privacy: original_package.enforce_privacy,
436
380
  dependencies: original_package.dependencies,
437
381
  metadata: package_with_protection_defaults.metadata,
438
- name: original_package.name,
382
+ name: original_package.name
439
383
  )
440
384
 
441
385
  ParsePackwerk.write_package_yml!(package)
442
386
  package
443
387
  end
388
+
389
+ sig { void }
390
+ def self.load_client_configuration
391
+ @loaded_client_configuration ||= T.let(false, T.nilable(T::Boolean))
392
+ return if @loaded_client_configuration
393
+
394
+ @loaded_client_configuration = true
395
+ client_configuration = Pathname.pwd.join('config/use_packwerk.rb')
396
+ require client_configuration.to_s if client_configuration.exist?
397
+ end
398
+
399
+ sig { void }
400
+ def self.bust_cache!
401
+ UsePackwerk.config.bust_cache!
402
+ # This comes explicitly after `PackageProtections.config.bust_cache!` because
403
+ # otherwise `PackageProtections.config` will attempt to reload the client configuratoin.
404
+ @loaded_client_configuration = false
405
+ end
444
406
  end
445
407
 
446
408
  private_constant :Private
@@ -15,7 +15,7 @@ module UsePackwerk
15
15
  UsePackwerk.replace_in_file(
16
16
  file: rubocop_todo.to_s,
17
17
  find: relative_path_to_origin,
18
- replace_with: relative_path_to_destination,
18
+ replace_with: relative_path_to_destination
19
19
  )
20
20
  end
21
21
 
@@ -26,23 +26,23 @@ module UsePackwerk
26
26
  new_origin_rubocop_todo = loaded_origin_rubocop_todo.dup
27
27
 
28
28
  loaded_origin_rubocop_todo.each do |cop_name, cop_config|
29
- if cop_config['Exclude'].include?(relative_path_to_origin.to_s)
30
- new_origin_rubocop_todo[cop_name]['Exclude'] = cop_config['Exclude'] - [relative_path_to_origin.to_s]
31
- origin_rubocop_todo.write(YAML.dump(new_origin_rubocop_todo))
32
-
33
- destination_rubocop_todo = file_move_operation.destination_pack.directory.join('.rubocop_todo.yml')
34
- if destination_rubocop_todo.exist?
35
- new_destination_rubocop_todo = YAML.load_file(destination_rubocop_todo).dup
36
- else
37
- new_destination_rubocop_todo = {}
38
- end
39
-
40
- new_destination_rubocop_todo[cop_name] ||= { 'Exclude' => [] }
41
-
42
- new_destination_rubocop_todo[cop_name]['Exclude'] += [relative_path_to_destination.to_s]
43
- destination_rubocop_todo.write(YAML.dump(new_destination_rubocop_todo))
29
+ next unless cop_config['Exclude'].include?(relative_path_to_origin.to_s)
30
+
31
+ new_origin_rubocop_todo[cop_name]['Exclude'] = cop_config['Exclude'] - [relative_path_to_origin.to_s]
32
+ origin_rubocop_todo.write(YAML.dump(new_origin_rubocop_todo))
33
+
34
+ destination_rubocop_todo = file_move_operation.destination_pack.directory.join('.rubocop_todo.yml')
35
+ if destination_rubocop_todo.exist?
36
+ new_destination_rubocop_todo = YAML.load_file(destination_rubocop_todo).dup
37
+ else
38
+ new_destination_rubocop_todo = {}
44
39
  end
45
- end
40
+
41
+ new_destination_rubocop_todo[cop_name] ||= { 'Exclude' => [] }
42
+
43
+ new_destination_rubocop_todo[cop_name]['Exclude'] += [relative_path_to_destination.to_s]
44
+ destination_rubocop_todo.write(YAML.dump(new_destination_rubocop_todo))
45
+ end
46
46
  end
47
47
  end
48
48
  end