use_packwerk 0.50.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.
- checksums.yaml +7 -0
- data/README.md +62 -0
- data/bin/use_packwerk +5 -0
- data/lib/use_packwerk/cli.rb +71 -0
- data/lib/use_packwerk/code_ownership_post_processor.rb +58 -0
- data/lib/use_packwerk/configuration.rb +49 -0
- data/lib/use_packwerk/logging.rb +33 -0
- data/lib/use_packwerk/per_file_processor_interface.rb +19 -0
- data/lib/use_packwerk/private/file_move_operation.rb +77 -0
- data/lib/use_packwerk/private/pack_relationship_analyzer.rb +219 -0
- data/lib/use_packwerk/private.rb +446 -0
- data/lib/use_packwerk/rubocop_post_processor.rb +22 -0
- data/lib/use_packwerk.rb +149 -0
- metadata +257 -0
@@ -0,0 +1,446 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'colorized_string'
|
6
|
+
require 'sorbet-runtime'
|
7
|
+
|
8
|
+
require 'use_packwerk/private/file_move_operation'
|
9
|
+
require 'use_packwerk/private/pack_relationship_analyzer'
|
10
|
+
|
11
|
+
module UsePackwerk
|
12
|
+
module Private
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(pack_name: String).returns(String) }
|
16
|
+
def self.clean_pack_name(pack_name)
|
17
|
+
# The reason we do this is a lot of terminals add an extra `/` when you tab-autocomplete.
|
18
|
+
# This results in the pack not being found, but when we write the package YML it writes to the same place,
|
19
|
+
# causing a behaviorally confusing diff.
|
20
|
+
# We ignore trailing slashes as an ergonomic feature to the user.
|
21
|
+
pack_name.gsub(/\/$/, '')
|
22
|
+
end
|
23
|
+
|
24
|
+
sig do
|
25
|
+
params(
|
26
|
+
file: String,
|
27
|
+
find: Pathname,
|
28
|
+
replace_with: Pathname,
|
29
|
+
).void
|
30
|
+
end
|
31
|
+
def self.replace_in_file(file:, find:, replace_with:)
|
32
|
+
file = Pathname.new(file)
|
33
|
+
return if !file.exist?
|
34
|
+
count = 0
|
35
|
+
file.write(file.read.gsub(find.to_s) do
|
36
|
+
count += 1
|
37
|
+
replace_with.to_s
|
38
|
+
end)
|
39
|
+
Logging.print "Replaced #{count} occurrence(s) of #{find} in #{file.to_s}" if count > 0
|
40
|
+
end
|
41
|
+
|
42
|
+
sig do
|
43
|
+
params(
|
44
|
+
pack_name: String,
|
45
|
+
enforce_privacy: T::Boolean,
|
46
|
+
enforce_dependencies: T.nilable(T::Boolean)
|
47
|
+
).void
|
48
|
+
end
|
49
|
+
def self.create_pack!(pack_name:, enforce_privacy:, enforce_dependencies:)
|
50
|
+
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
|
57
|
+
Logging.print_bold_green(intro)
|
58
|
+
end
|
59
|
+
|
60
|
+
pack_name = Private.clean_pack_name(pack_name)
|
61
|
+
|
62
|
+
package = create_pack_if_not_exists!(pack_name: pack_name, enforce_privacy: enforce_privacy, enforce_dependencies: enforce_dependencies)
|
63
|
+
add_public_directory(package)
|
64
|
+
add_readme_todo(package)
|
65
|
+
|
66
|
+
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/move_to_pack -n #{pack_name} -f 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/make_public -f #{pack_name}/path/to/file.rb`
|
77
|
+
|
78
|
+
5) Update your readme at #{pack_name}/README.md
|
79
|
+
NEXT_STEPS
|
80
|
+
|
81
|
+
Logging.print_bold_green(next_steps)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
sig do
|
86
|
+
params(
|
87
|
+
pack_name: String,
|
88
|
+
paths_relative_to_root: T::Array[String],
|
89
|
+
per_file_processors: T::Array[UsePackwerk::PerFileProcessorInterface]
|
90
|
+
).void
|
91
|
+
end
|
92
|
+
def self.move_to_pack!(pack_name:, paths_relative_to_root:, per_file_processors: [])
|
93
|
+
pack_name = Private.clean_pack_name(pack_name)
|
94
|
+
package = ParsePackwerk.all.find { |package| package.name == pack_name }
|
95
|
+
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/`")
|
97
|
+
end
|
98
|
+
|
99
|
+
Logging.section('👋 Hi!') do
|
100
|
+
intro = <<~INTRO
|
101
|
+
You are moving a file to a pack, which is great. Check out #{UsePackwerk.config.documentation_link} for more info!
|
102
|
+
|
103
|
+
Please bring any questions or issues you have in your development process to #ruby-modularity or #product-infrastructure.
|
104
|
+
We'd be happy to try to help through pairing, accepting feedback, changing our process, changing our tools, and more.
|
105
|
+
INTRO
|
106
|
+
Logging.print_bold_green(intro)
|
107
|
+
end
|
108
|
+
|
109
|
+
add_public_directory(package)
|
110
|
+
add_readme_todo(package)
|
111
|
+
package_location = package.directory
|
112
|
+
|
113
|
+
if paths_relative_to_root.any?
|
114
|
+
Logging.section('File Operations') do
|
115
|
+
file_paths = paths_relative_to_root.flat_map do |path|
|
116
|
+
origin_pathname = Pathname.new(path).cleanpath
|
117
|
+
# Note -- we used to `mv` over whole directories, rather than splatting out their contents and merging individual files.
|
118
|
+
# The main advantage to moving whole directories is that it's a bit faster and a bit less verbose
|
119
|
+
# However, this ended up being tricky and caused complexity to flow down later parts of the implementation.
|
120
|
+
# Notably:
|
121
|
+
# 1) The `mv` operation doesn't merge directories, so if the destination already has the same directory, then the mv operation
|
122
|
+
# will overwrite
|
123
|
+
# 2) We could get around this possibly with `cp_r` (https://ruby-doc.org/stdlib-1.9.3/libdoc/fileutils/rdoc/FileUtils.html#method-c-cp_r),
|
124
|
+
# but we'd also have to delete the origin destination. On top of this, we still need to splat things out later on so that we can do
|
125
|
+
# per file processor operations, and that has some complexity of its own. The simplest thing here would be to simply glob everything out.
|
126
|
+
#
|
127
|
+
# For now, we sacrifice some small level of speed and conciseness in favor of simpler implementation.
|
128
|
+
# 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
|
129
|
+
#
|
130
|
+
if origin_pathname.directory?
|
131
|
+
origin_pathname.glob('**/*.{rb,rake,erb}')
|
132
|
+
else
|
133
|
+
origin_pathname
|
134
|
+
end
|
135
|
+
end
|
136
|
+
file_move_operations = file_paths.map do |origin_pathname|
|
137
|
+
FileMoveOperation.new(
|
138
|
+
origin_pathname: origin_pathname,
|
139
|
+
destination_pathname: FileMoveOperation.destination_pathname_for_package_move(origin_pathname, package_location),
|
140
|
+
destination_pack: package,
|
141
|
+
)
|
142
|
+
end
|
143
|
+
file_move_operations.each do |file_move_operation|
|
144
|
+
Private.package_filepath(file_move_operation, per_file_processors)
|
145
|
+
Private.package_filepath_spec(file_move_operation, per_file_processors)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
per_file_processors.each do |per_file_processor|
|
151
|
+
per_file_processor.print_final_message!
|
152
|
+
end
|
153
|
+
|
154
|
+
Logging.section('Next steps') do
|
155
|
+
next_steps = <<~NEXT_STEPS
|
156
|
+
Your next steps might be:
|
157
|
+
|
158
|
+
1) 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.
|
159
|
+
|
160
|
+
2) Update TODO lists for rubocop implemented protections. See #{UsePackwerk.config.documentation_link} for more info
|
161
|
+
|
162
|
+
3) Touch base with each team who owns files involved in this move
|
163
|
+
|
164
|
+
4) Expose public API in #{pack_name}/app/public. Try `bin/make_public -f #{pack_name}/path/to/file.rb`
|
165
|
+
|
166
|
+
5) Update your readme at #{pack_name}/README.md
|
167
|
+
NEXT_STEPS
|
168
|
+
|
169
|
+
Logging.print_bold_green(next_steps)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
sig do
|
174
|
+
params(
|
175
|
+
paths_relative_to_root: T::Array[String],
|
176
|
+
per_file_processors: T::Array[UsePackwerk::PerFileProcessorInterface]
|
177
|
+
).void
|
178
|
+
end
|
179
|
+
def self.make_public!(paths_relative_to_root:, per_file_processors:)
|
180
|
+
Logging.section('Making files public') do
|
181
|
+
intro = <<~INTRO
|
182
|
+
You are moving some files into public API. See #{UsePackwerk.config.documentation_link} for other utilities!
|
183
|
+
INTRO
|
184
|
+
Logging.print_bold_green(intro)
|
185
|
+
end
|
186
|
+
|
187
|
+
if paths_relative_to_root.any?
|
188
|
+
Logging.section('File Operations') do
|
189
|
+
file_paths = paths_relative_to_root.flat_map do |path|
|
190
|
+
origin_pathname = Pathname.new(path).cleanpath
|
191
|
+
if origin_pathname.directory?
|
192
|
+
origin_pathname.glob('**/*.rb').map(&:to_s)
|
193
|
+
else
|
194
|
+
path
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
file_move_operations = file_paths.map do |path|
|
200
|
+
parts = path.to_s.split('/')
|
201
|
+
first_part_of_path = T.must(parts[0])
|
202
|
+
|
203
|
+
if Pathname.new(first_part_of_path).dirname.join(ParsePackwerk::PACKAGE_YML_NAME).exist?
|
204
|
+
package_location = Pathname.new('.')
|
205
|
+
elsif PERMITTED_PACK_LOCATIONS.include?(first_part_of_path)
|
206
|
+
package_location = Pathname.new(first_part_of_path).join(T.must(parts[1]))
|
207
|
+
else
|
208
|
+
raise StandardError.new('Can only make files in the monolith or in existing packs public')
|
209
|
+
end
|
210
|
+
|
211
|
+
package = ParsePackwerk::Package.from(package_location.join(ParsePackwerk::PACKAGE_YML_NAME))
|
212
|
+
|
213
|
+
origin_pathname = Pathname.new(path).cleanpath
|
214
|
+
|
215
|
+
FileMoveOperation.new(
|
216
|
+
origin_pathname: origin_pathname,
|
217
|
+
destination_pathname: FileMoveOperation.destination_pathname_for_new_public_api(origin_pathname),
|
218
|
+
destination_pack: package,
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
file_move_operations.each do |file_move_operation|
|
223
|
+
Private.package_filepath(file_move_operation, per_file_processors)
|
224
|
+
Private.package_filepath_spec(file_move_operation, per_file_processors)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
Logging.section('Next steps') do
|
230
|
+
next_steps = <<~NEXT_STEPS
|
231
|
+
Your next steps might be:
|
232
|
+
|
233
|
+
1) 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.
|
234
|
+
|
235
|
+
2) Update TODO lists for rubocop implemented protections. See #{UsePackwerk.config.documentation_link} for more info
|
236
|
+
|
237
|
+
3) Work to migrate clients of private API to your new public API
|
238
|
+
|
239
|
+
4) Update your README at packs/your_package_name/README.md
|
240
|
+
NEXT_STEPS
|
241
|
+
|
242
|
+
Logging.print_bold_green(next_steps)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
sig do
|
247
|
+
params(
|
248
|
+
pack_name: String,
|
249
|
+
dependency_name: String
|
250
|
+
).void
|
251
|
+
end
|
252
|
+
def self.add_dependency!(pack_name:, dependency_name:)
|
253
|
+
Logging.section('Adding a dependency') do
|
254
|
+
intro = <<~INTRO
|
255
|
+
You are adding a dependency. See #{UsePackwerk.config.documentation_link} for other utilities!
|
256
|
+
INTRO
|
257
|
+
Logging.print_bold_green(intro)
|
258
|
+
end
|
259
|
+
|
260
|
+
all_packages = ParsePackwerk.all
|
261
|
+
|
262
|
+
pack_name = Private.clean_pack_name(pack_name)
|
263
|
+
package = all_packages.find { |package| package.name == pack_name }
|
264
|
+
if package.nil?
|
265
|
+
raise StandardError.new("Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`")
|
266
|
+
end
|
267
|
+
|
268
|
+
dependency_name = Private.clean_pack_name(dependency_name)
|
269
|
+
package_dependency = all_packages.find { |package| package.name == dependency_name }
|
270
|
+
if package_dependency.nil?
|
271
|
+
raise StandardError.new("Can not find package with name #{dependency_name}. Make sure the argument is of the form `packs/my_pack/`")
|
272
|
+
end
|
273
|
+
|
274
|
+
new_package = ParsePackwerk::Package.new(
|
275
|
+
name: pack_name,
|
276
|
+
dependencies: (package.dependencies + [dependency_name]).uniq.sort,
|
277
|
+
enforce_privacy: package.enforce_privacy,
|
278
|
+
enforce_dependencies: package.enforce_dependencies,
|
279
|
+
metadata: package.metadata,
|
280
|
+
)
|
281
|
+
ParsePackwerk.write_package_yml!(new_package)
|
282
|
+
|
283
|
+
Logging.section('Next steps') do
|
284
|
+
next_steps = <<~NEXT_STEPS
|
285
|
+
Your next steps might be:
|
286
|
+
|
287
|
+
1) Run `bin/packwerk validate` to ensure you haven't introduced a cyclic dependency
|
288
|
+
|
289
|
+
2) Run `bin/packwerk update-deprecations` to update the violations.
|
290
|
+
NEXT_STEPS
|
291
|
+
|
292
|
+
Logging.print_bold_green(next_steps)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
sig { params(file_move_operation: FileMoveOperation, per_file_processors: T::Array[UsePackwerk::PerFileProcessorInterface]).void }
|
297
|
+
def self.package_filepath(file_move_operation, per_file_processors)
|
298
|
+
per_file_processors.each do |per_file_processor|
|
299
|
+
if file_move_operation.origin_pathname.exist?
|
300
|
+
per_file_processor.before_move_file!(file_move_operation)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
origin = file_move_operation.origin_pathname
|
305
|
+
destination = file_move_operation.destination_pathname
|
306
|
+
idempotent_mv(origin, destination)
|
307
|
+
end
|
308
|
+
|
309
|
+
sig { params(file_move_operation: FileMoveOperation, per_file_processors: T::Array[UsePackwerk::PerFileProcessorInterface]).void }
|
310
|
+
def self.package_filepath_spec(file_move_operation, per_file_processors)
|
311
|
+
package_filepath(file_move_operation.spec_file_move_operation, per_file_processors)
|
312
|
+
end
|
313
|
+
|
314
|
+
sig { params(origin: Pathname, destination: Pathname).void }
|
315
|
+
def self.idempotent_mv(origin, destination)
|
316
|
+
if origin.exist? && destination.exist?
|
317
|
+
Logging.print ColorizedString.new("[SKIP] Not moving #{origin}, #{destination} already exists").red
|
318
|
+
elsif origin.exist? && !destination.exist?
|
319
|
+
destination.dirname.mkpath
|
320
|
+
|
321
|
+
Logging.print "Moving file #{origin} to #{destination}"
|
322
|
+
# use git mv so that git knows that it was a move
|
323
|
+
FileUtils.mv(origin, destination)
|
324
|
+
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
|
326
|
+
else
|
327
|
+
Logging.print ColorizedString.new("[SKIP] Not moving #{origin.to_s}, does not exist").red
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
sig { params(package: ParsePackwerk::Package).void }
|
332
|
+
def self.add_public_directory(package)
|
333
|
+
public_directory = package.directory.join('app/public')
|
334
|
+
|
335
|
+
if public_directory.glob('**/**.rb').none?
|
336
|
+
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/list_top_privacy_violations -n #{package.name}`
|
348
|
+
|
349
|
+
Want to move something into this folder?
|
350
|
+
Try running: `bin/make_public -f #{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
|
356
|
+
public_directory.join('TODO.md').write(todo_md)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
sig { params(package: ParsePackwerk::Package).void }
|
361
|
+
def self.add_readme_todo(package)
|
362
|
+
pack_directory = package.directory
|
363
|
+
|
364
|
+
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
|
382
|
+
pack_directory.join('README_TODO.md').write(readme_todo_md)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
sig do
|
387
|
+
params(
|
388
|
+
pack_name: String,
|
389
|
+
enforce_privacy: T::Boolean,
|
390
|
+
enforce_dependencies: T.nilable(T::Boolean)
|
391
|
+
).returns(ParsePackwerk::Package)
|
392
|
+
end
|
393
|
+
def self.create_pack_if_not_exists!(pack_name:, enforce_privacy:, enforce_dependencies:)
|
394
|
+
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`.")
|
396
|
+
end
|
397
|
+
|
398
|
+
existing_package = ParsePackwerk.all.find { |package| package.name == pack_name }
|
399
|
+
|
400
|
+
package_location = Pathname.new(pack_name)
|
401
|
+
|
402
|
+
if existing_package.nil?
|
403
|
+
should_enforce_dependenceies = enforce_dependencies.nil? ? UsePackwerk.config.enforce_dependencies : enforce_dependencies
|
404
|
+
|
405
|
+
package = ParsePackwerk::Package.new(
|
406
|
+
enforce_dependencies: should_enforce_dependenceies,
|
407
|
+
enforce_privacy: enforce_privacy,
|
408
|
+
dependencies: [],
|
409
|
+
metadata: {
|
410
|
+
'owner' => 'MyTeam'
|
411
|
+
},
|
412
|
+
name: pack_name,
|
413
|
+
)
|
414
|
+
|
415
|
+
ParsePackwerk.write_package_yml!(package)
|
416
|
+
PackageProtections.set_defaults!([package], verbose: false)
|
417
|
+
package = rewrite_package_with_original_packwerk_values(package)
|
418
|
+
|
419
|
+
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")
|
421
|
+
package.yml.write(new_contents)
|
422
|
+
existing_package = package
|
423
|
+
end
|
424
|
+
|
425
|
+
existing_package
|
426
|
+
end
|
427
|
+
|
428
|
+
sig { params(original_package: ParsePackwerk::Package).returns(ParsePackwerk::Package) }
|
429
|
+
def self.rewrite_package_with_original_packwerk_values(original_package)
|
430
|
+
package_with_protection_defaults = T.must(ParsePackwerk.all.find { |package| package.name == original_package.name })
|
431
|
+
# PackageProtections also sets `enforce_privacy` and `enforce_dependency` to be true, so we set these back down to their original values
|
432
|
+
package = ParsePackwerk::Package.new(
|
433
|
+
enforce_dependencies: original_package.enforce_dependencies,
|
434
|
+
enforce_privacy: original_package.enforce_privacy,
|
435
|
+
dependencies: original_package.dependencies,
|
436
|
+
metadata: package_with_protection_defaults.metadata,
|
437
|
+
name: original_package.name,
|
438
|
+
)
|
439
|
+
|
440
|
+
ParsePackwerk.write_package_yml!(package)
|
441
|
+
package
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
private_constant :Private
|
446
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module UsePackwerk
|
4
|
+
class RubocopPostProcessor
|
5
|
+
include PerFileProcessorInterface
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { override.params(file_move_operation: Private::FileMoveOperation).void }
|
9
|
+
def before_move_file!(file_move_operation)
|
10
|
+
relative_path_to_origin = file_move_operation.origin_pathname
|
11
|
+
relative_path_to_destination = file_move_operation.destination_pathname
|
12
|
+
|
13
|
+
rubocop_todo = Pathname.new('.rubocop_todo.yml')
|
14
|
+
return if !rubocop_todo.exist?
|
15
|
+
UsePackwerk.replace_in_file(
|
16
|
+
file: rubocop_todo.to_s,
|
17
|
+
find: relative_path_to_origin,
|
18
|
+
replace_with: relative_path_to_destination,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/use_packwerk.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
# Ruby internal requires
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
# External gem requires
|
7
|
+
require 'colorized_string'
|
8
|
+
|
9
|
+
# Internal gem requires
|
10
|
+
require 'parse_packwerk'
|
11
|
+
require 'code_teams'
|
12
|
+
require 'code_ownership'
|
13
|
+
require 'package_protections'
|
14
|
+
|
15
|
+
# Private implementation requires
|
16
|
+
require 'use_packwerk/private'
|
17
|
+
require 'use_packwerk/per_file_processor_interface'
|
18
|
+
require 'use_packwerk/rubocop_post_processor'
|
19
|
+
require 'use_packwerk/code_ownership_post_processor'
|
20
|
+
require 'use_packwerk/logging'
|
21
|
+
require 'use_packwerk/configuration'
|
22
|
+
require 'use_packwerk/cli'
|
23
|
+
|
24
|
+
module UsePackwerk
|
25
|
+
extend T::Sig
|
26
|
+
|
27
|
+
PERMITTED_PACK_LOCATIONS = T.let([
|
28
|
+
'gems',
|
29
|
+
'components',
|
30
|
+
'packs',
|
31
|
+
], T::Array[String])
|
32
|
+
|
33
|
+
sig do
|
34
|
+
params(
|
35
|
+
pack_name: String,
|
36
|
+
enforce_privacy: T::Boolean,
|
37
|
+
enforce_dependencies: T.nilable(T::Boolean)
|
38
|
+
).void
|
39
|
+
end
|
40
|
+
def self.create_pack!(
|
41
|
+
pack_name:,
|
42
|
+
enforce_privacy: true,
|
43
|
+
enforce_dependencies: nil
|
44
|
+
)
|
45
|
+
Private.create_pack!(
|
46
|
+
pack_name: pack_name,
|
47
|
+
enforce_privacy: enforce_privacy,
|
48
|
+
enforce_dependencies: enforce_dependencies,
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig do
|
53
|
+
params(
|
54
|
+
pack_name: String,
|
55
|
+
paths_relative_to_root: T::Array[String],
|
56
|
+
per_file_processors: T::Array[PerFileProcessorInterface],
|
57
|
+
).void
|
58
|
+
end
|
59
|
+
def self.move_to_pack!(
|
60
|
+
pack_name:,
|
61
|
+
paths_relative_to_root: [],
|
62
|
+
per_file_processors: []
|
63
|
+
)
|
64
|
+
Private.move_to_pack!(
|
65
|
+
pack_name: pack_name,
|
66
|
+
paths_relative_to_root: paths_relative_to_root,
|
67
|
+
per_file_processors: per_file_processors,
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
sig do
|
72
|
+
params(
|
73
|
+
paths_relative_to_root: T::Array[String],
|
74
|
+
per_file_processors: T::Array[PerFileProcessorInterface],
|
75
|
+
).void
|
76
|
+
end
|
77
|
+
def self.make_public!(
|
78
|
+
paths_relative_to_root: [],
|
79
|
+
per_file_processors: []
|
80
|
+
)
|
81
|
+
Private.make_public!(
|
82
|
+
paths_relative_to_root: paths_relative_to_root,
|
83
|
+
per_file_processors: per_file_processors
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
sig do
|
88
|
+
params(
|
89
|
+
pack_name: String,
|
90
|
+
dependency_name: String
|
91
|
+
).void
|
92
|
+
end
|
93
|
+
def self.add_dependency!(
|
94
|
+
pack_name:,
|
95
|
+
dependency_name:
|
96
|
+
)
|
97
|
+
Private.add_dependency!(
|
98
|
+
pack_name: pack_name,
|
99
|
+
dependency_name: dependency_name
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
sig do
|
104
|
+
params(
|
105
|
+
pack_name: T.nilable(String),
|
106
|
+
limit: Integer,
|
107
|
+
).void
|
108
|
+
end
|
109
|
+
def self.list_top_privacy_violations(
|
110
|
+
pack_name:,
|
111
|
+
limit:
|
112
|
+
)
|
113
|
+
Private::PackRelationshipAnalyzer.list_top_privacy_violations(
|
114
|
+
pack_name,
|
115
|
+
limit
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
sig do
|
120
|
+
params(
|
121
|
+
pack_name: T.nilable(String),
|
122
|
+
limit: Integer
|
123
|
+
).void
|
124
|
+
end
|
125
|
+
def self.list_top_dependency_violations(
|
126
|
+
pack_name:,
|
127
|
+
limit:
|
128
|
+
)
|
129
|
+
Private::PackRelationshipAnalyzer.list_top_dependency_violations(
|
130
|
+
pack_name,
|
131
|
+
limit
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
sig do
|
136
|
+
params(
|
137
|
+
file: String,
|
138
|
+
find: Pathname,
|
139
|
+
replace_with: Pathname,
|
140
|
+
).void
|
141
|
+
end
|
142
|
+
def self.replace_in_file(file:, find:, replace_with:)
|
143
|
+
Private.replace_in_file(
|
144
|
+
file: file,
|
145
|
+
find: find,
|
146
|
+
replace_with: replace_with,
|
147
|
+
)
|
148
|
+
end
|
149
|
+
end
|