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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f4f9377346178fe79c699c9493993c0fb270170dcb200e1e443df752b6d7d32c
|
4
|
+
data.tar.gz: e9bf3060d53092534f2193fe93d68188cf750b2bbb824d60dc2c6ec6205c51f5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a4ef1cc30ac3db3613a1cb5d294a9bdd5afa2bdf1c70cbfb47db4b592c98d01130d5a3722ac3ebad206c0d36a906016148304cbe84f6fa193186bd4d734a213
|
7
|
+
data.tar.gz: 0430dfe13a6d016ea79b499ad0c28a53fdfa02dbd211c3777a32aae6f8b96b7bc6694c4e03d84add447505080a6d428faac4fffa6d3d861049ba09ed5edc6113
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# UsePackwerk
|
2
|
+
|
3
|
+
UsePackwerk is a gem that helps in creating and maintaining packs. It exists to help perform some basic operations needed for pack setup and configuration. It provides a basic ruby file packager utility for [`packwerk`](https://github.com/Shopify/packwerk/). It assumes you are using [`stimpack`](https://github.com/rubyatscale/stimpack) to organize your packages.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
### General Help
|
7
|
+
`bin/use_packwerk --help`
|
8
|
+
|
9
|
+
### Pack Creation
|
10
|
+
`bin/create_pack -n packs/your_pack_name_here`
|
11
|
+
|
12
|
+
### Moving files to packs
|
13
|
+
`bin/move_to_pack -n packs/your_pack_name_here -f path/to/file.rb,path/to/directory`
|
14
|
+
This is used for moving files into a pack (the pack must already exist).
|
15
|
+
Note this works for moving files to packs from the monolith or from other packs
|
16
|
+
|
17
|
+
Make sure there are no spaces between the comma-separated list of paths of directories.
|
18
|
+
|
19
|
+
### Moving a file to public API
|
20
|
+
`bin/make_public -f path/to/file.rb,path/to/directory`
|
21
|
+
This moves a file or directory to public API (that is -- the `app/public` folder).
|
22
|
+
|
23
|
+
Make sure there are no spaces between the comma-separated list of paths of directories.
|
24
|
+
|
25
|
+
### Listing top privacy violations
|
26
|
+
`bin/list_top_privacy_violations -n packs/my_pack`
|
27
|
+
Want to create interfaces? Not sure how your pack's code is being used?
|
28
|
+
|
29
|
+
You can use this command to list the top privacy violations.
|
30
|
+
|
31
|
+
If no pack name is passed in, this will list out violations across all packs.
|
32
|
+
|
33
|
+
### Listing top dependency violations
|
34
|
+
`bin/list_top_dependency_violations -n packs/my_pack`
|
35
|
+
Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way
|
36
|
+
|
37
|
+
You can use this command to list the top dependency violations.
|
38
|
+
|
39
|
+
If no pack name is passed in, this will list out violations across all packs.
|
40
|
+
|
41
|
+
### Adding a dependency
|
42
|
+
`bin/add_pack_dependency -n packs/my_pack -d packs/dependency_pack_name`
|
43
|
+
|
44
|
+
This can be used to quickly modify a `package.yml` file and add a dependency. It also cleans up the list of dependencies to sort the list and remove redundant entries.
|
45
|
+
|
46
|
+
## Discussions, Issues, Questions, and More
|
47
|
+
To keep things organized, here are some recommended homes:
|
48
|
+
|
49
|
+
### Issues:
|
50
|
+
https://github.com/Gusto/use_packwerk/issues
|
51
|
+
|
52
|
+
### Questions:
|
53
|
+
https://github.com/Gusto/use_packwerk/discussions/categories/q-a
|
54
|
+
|
55
|
+
### General discussions:
|
56
|
+
https://github.com/Gusto/use_packwerk/discussions/categories/general
|
57
|
+
|
58
|
+
### Ideas, new features, requests for change:
|
59
|
+
https://github.com/Gusto/use_packwerk/discussions/categories/ideas
|
60
|
+
|
61
|
+
### Showcasing your work:
|
62
|
+
https://github.com/Gusto/use_packwerk/discussions/categories/show-and-tell
|
data/bin/use_packwerk
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module UsePackwerk
|
6
|
+
class CLI < Thor
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
desc "create packs/your_pack", "Create pack with name packs/your_pack"
|
10
|
+
sig { params(pack_name: String).void }
|
11
|
+
def create(pack_name)
|
12
|
+
UsePackwerk.create_pack!(pack_name: pack_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "add_dependency packs/from_pack packs/to_pack", "Add packs/to_pack to packs/from_pack/package.yml list of dependencies"
|
16
|
+
long_desc <<~LONG_DESC
|
17
|
+
Use this to add a dependency between packs.
|
18
|
+
|
19
|
+
When you use bin/use_packwerk add_dependency packs/from_pack packs/to_pack, this command will
|
20
|
+
modify packs/from_pack/package.yml's list of dependencies and add packs/to_pack.
|
21
|
+
|
22
|
+
This command will also sort the list and make it unique.
|
23
|
+
LONG_DESC
|
24
|
+
sig { params(from_pack: String, to_pack: String).void }
|
25
|
+
def add_dependency(from_pack, to_pack)
|
26
|
+
UsePackwerk.add_dependency!(
|
27
|
+
pack_name: from_pack,
|
28
|
+
dependency_name: to_pack
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "list_top_dependency_violations packs/your_pack", "List the top dependency violations of packs/your_pack"
|
33
|
+
option :limit, type: :numeric, default: 10, aliases: :l, banner: 'Specify the limit of constants to analyze'
|
34
|
+
sig { params(pack_name: String).void }
|
35
|
+
def list_top_dependency_violations(pack_name)
|
36
|
+
UsePackwerk.list_top_dependency_violations(
|
37
|
+
pack_name: pack_name,
|
38
|
+
limit: options[:limit]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "list_top_privacy_violations packs/your_pack", "List the top privacy violations of packs/your_pack"
|
43
|
+
option :limit, type: :numeric, default: 10, aliases: :l, banner: 'Specify the limit of constants to analyze'
|
44
|
+
sig { params(pack_name: String).void }
|
45
|
+
def list_top_privacy_violations(pack_name)
|
46
|
+
UsePackwerk.list_top_privacy_violations(
|
47
|
+
pack_name: pack_name,
|
48
|
+
limit: options[:limit]
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "make_public path/to/file.rb path/to/directory", "Pass in a space-separated list of file or directory paths to make public"
|
53
|
+
sig { params(paths: String).void }
|
54
|
+
def make_public(*paths)
|
55
|
+
UsePackwerk.make_public!(
|
56
|
+
paths_relative_to_root: paths,
|
57
|
+
per_file_processors: [UsePackwerk::RubocopPostProcessor.new, UsePackwerk::CodeOwnershipPostProcessor.new],
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "move packs/destination_pack path/to/file.rb path/to/directory", "Pass in a destination pack and a space-separated list of file or directory paths to move to the destination pack"
|
62
|
+
sig { params(pack_name: String, paths: String).void }
|
63
|
+
def move(pack_name, *paths)
|
64
|
+
UsePackwerk.move_to_pack!(
|
65
|
+
pack_name: pack_name,
|
66
|
+
paths_relative_to_root: paths,
|
67
|
+
per_file_processors: [UsePackwerk::RubocopPostProcessor.new, UsePackwerk::CodeOwnershipPostProcessor.new],
|
68
|
+
)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module UsePackwerk
|
4
|
+
class CodeOwnershipPostProcessor
|
5
|
+
include PerFileProcessorInterface
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { void }
|
9
|
+
def initialize
|
10
|
+
@teams = T.let([], T::Array[String])
|
11
|
+
@did_move_files = T.let(false, T::Boolean)
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { override.params(file_move_operation: Private::FileMoveOperation).void }
|
15
|
+
def before_move_file!(file_move_operation)
|
16
|
+
relative_path_to_origin = file_move_operation.origin_pathname
|
17
|
+
relative_path_to_destination = file_move_operation.destination_pathname
|
18
|
+
|
19
|
+
code_owners_allow_list_file = Pathname.new('config/code_ownership.yml')
|
20
|
+
|
21
|
+
if code_owners_allow_list_file.exist?
|
22
|
+
UsePackwerk.replace_in_file(
|
23
|
+
file: code_owners_allow_list_file.to_s,
|
24
|
+
find: relative_path_to_origin,
|
25
|
+
replace_with: relative_path_to_destination,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
team = CodeOwnership.for_file(relative_path_to_origin.to_s)
|
30
|
+
|
31
|
+
if team
|
32
|
+
@teams << team.name
|
33
|
+
else
|
34
|
+
@teams << 'Unknown'
|
35
|
+
end
|
36
|
+
|
37
|
+
if !CodeOwnership.for_package(file_move_operation.destination_pack).nil?
|
38
|
+
CodeOwnership.remove_file_annotation!(relative_path_to_origin.to_s)
|
39
|
+
@did_move_files = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { void }
|
44
|
+
def print_final_message!
|
45
|
+
if @teams.any?
|
46
|
+
Logging.section('Code Ownership') do
|
47
|
+
Logging.print('This section contains info about the current ownership distribution of the moved files.')
|
48
|
+
@teams.group_by { |team| team }.sort_by { |team, instances| -instances.count }.each do |team, instances|
|
49
|
+
Logging.print " #{team} - #{instances.count} files"
|
50
|
+
end
|
51
|
+
if @did_move_files
|
52
|
+
Logging.print "Since the destination package has package-based ownership, file-annotations were removed from moved files."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module UsePackwerk
|
4
|
+
class Configuration
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig { params(enforce_dependencies: T::Boolean).void }
|
8
|
+
attr_writer :enforce_dependencies
|
9
|
+
|
10
|
+
sig { params(documentation_link: String).void }
|
11
|
+
attr_writer :documentation_link
|
12
|
+
|
13
|
+
sig { void }
|
14
|
+
def initialize
|
15
|
+
@enforce_dependencies = T.let(@enforce_dependencies, T.nilable(T::Boolean))
|
16
|
+
@documentation_link = T.let(documentation_link, T.nilable(String) )
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T::Boolean) }
|
20
|
+
def enforce_dependencies
|
21
|
+
if !@enforce_dependencies.nil?
|
22
|
+
@enforce_dependencies
|
23
|
+
else
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Configure a link to show up for users who are looking for more info
|
29
|
+
sig { returns(String) }
|
30
|
+
def documentation_link
|
31
|
+
"https://go/packwerk"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
extend T::Sig
|
37
|
+
|
38
|
+
sig { returns(Configuration) }
|
39
|
+
def config
|
40
|
+
@config = T.let(@config, T.nilable(Configuration))
|
41
|
+
@config ||= Configuration.new
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { params(blk: T.proc.params(arg0: Configuration).void).void }
|
45
|
+
def configure(&blk) # rubocop:disable Lint/UnusedMethodArgument
|
46
|
+
yield(config)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'colorized_string'
|
4
|
+
|
5
|
+
module UsePackwerk
|
6
|
+
module Logging
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
sig { params(title: String, block: T.proc.void).void }
|
10
|
+
def self.section(title, &block)
|
11
|
+
print_divider
|
12
|
+
puts ColorizedString.new("#{title}").green.bold
|
13
|
+
puts "\n"
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { params(text: String).void }
|
18
|
+
def self.print_bold_green(text)
|
19
|
+
puts ColorizedString.new(text).green.bold
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(text: String).void }
|
23
|
+
def self.print(text)
|
24
|
+
puts text
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { void }
|
28
|
+
def self.print_divider
|
29
|
+
puts '=' * 100
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module UsePackwerk
|
4
|
+
module PerFileProcessorInterface
|
5
|
+
extend T::Sig
|
6
|
+
extend T::Helpers
|
7
|
+
|
8
|
+
abstract!
|
9
|
+
|
10
|
+
sig { abstract.params(file_move_operation: Private::FileMoveOperation).void }
|
11
|
+
def before_move_file!(file_move_operation)
|
12
|
+
end
|
13
|
+
|
14
|
+
sig { void }
|
15
|
+
def print_final_message!
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module UsePackwerk
|
4
|
+
module Private
|
5
|
+
class FileMoveOperation < T::Struct
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
const :origin_pathname, Pathname
|
9
|
+
const :destination_pathname, Pathname
|
10
|
+
const :destination_pack, ParsePackwerk::Package
|
11
|
+
|
12
|
+
sig { params(origin_pathname: Pathname, new_package_root: Pathname).returns(Pathname) }
|
13
|
+
def self.destination_pathname_for_package_move(origin_pathname, new_package_root)
|
14
|
+
parts = origin_pathname.to_s.split('/')
|
15
|
+
toplevel_directory = parts[0]
|
16
|
+
|
17
|
+
case toplevel_directory.to_s
|
18
|
+
# This allows us to move files from monolith to packs
|
19
|
+
when 'app', 'spec', 'lib'
|
20
|
+
new_package_root.join(origin_pathname).cleanpath
|
21
|
+
# This allows us to move files from packs to packs
|
22
|
+
when *PERMITTED_PACK_LOCATIONS # parts looks like ['packs', 'organisms', 'app', 'services', 'bird_like', 'eagle.rb']
|
23
|
+
new_package_root.join(T.must(parts[2..]).join('/')).cleanpath
|
24
|
+
else
|
25
|
+
raise StandardError.new("Don't know how to find destination path for #{origin_pathname.inspect}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { params(origin_pathname: Pathname).returns(Pathname) }
|
30
|
+
def self.destination_pathname_for_new_public_api(origin_pathname)
|
31
|
+
parts = origin_pathname.to_s.split('/')
|
32
|
+
toplevel_directory = Pathname.new(parts[0])
|
33
|
+
|
34
|
+
case toplevel_directory.to_s
|
35
|
+
# This allows us to make API in the monolith public
|
36
|
+
when 'app', 'spec'
|
37
|
+
toplevel_directory.join('public').join(T.must(parts[2..]).join('/')).cleanpath
|
38
|
+
# This allows us to make API in existing packs public
|
39
|
+
when *PERMITTED_PACK_LOCATIONS # parts looks like ['packs', 'organisms', 'app', 'services', 'bird_like', 'eagle.rb']
|
40
|
+
pack_name = Pathname.new(parts[1])
|
41
|
+
toplevel_directory.join(pack_name).join('app/public').join(T.must(parts[4..]).join('/')).cleanpath
|
42
|
+
else
|
43
|
+
raise StandardError.new("Don't know how to find destination path for #{origin_pathname.inspect}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { returns(FileMoveOperation) }
|
48
|
+
def spec_file_move_operation
|
49
|
+
# This could probably be implemented by some "strategy pattern" where different extension types are handled by different helpers
|
50
|
+
# Such a thing could also include, for example, when moving a controller, moving its ERB view too.
|
51
|
+
if origin_pathname.extname == '.rake'
|
52
|
+
new_origin_pathname = origin_pathname.sub('/lib/', '/spec/lib/').sub(/^lib\//, 'spec/lib/').sub('.rake', '_spec.rb')
|
53
|
+
new_destination_pathname = destination_pathname.sub('/lib/', '/spec/lib/').sub(/^lib\//, 'spec/lib/').sub('.rake', '_spec.rb')
|
54
|
+
else
|
55
|
+
new_origin_pathname = origin_pathname.sub('/app/', '/spec/').sub(/^app\//, 'spec/').sub('.rb', '_spec.rb')
|
56
|
+
new_destination_pathname = destination_pathname.sub('/app/', '/spec/').sub(/^app\//, 'spec/').sub('.rb', '_spec.rb')
|
57
|
+
end
|
58
|
+
FileMoveOperation.new(
|
59
|
+
origin_pathname: new_origin_pathname,
|
60
|
+
destination_pathname: new_destination_pathname,
|
61
|
+
destination_pack: destination_pack,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
sig { params(path: Pathname).returns(FileMoveOperation) }
|
68
|
+
def relative_to(path)
|
69
|
+
FileMoveOperation.new(
|
70
|
+
origin_pathname: origin_pathname.relative_path_from(path),
|
71
|
+
destination_pathname: destination_pathname.relative_path_from(path),
|
72
|
+
destination_pack: destination_pack,
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module UsePackwerk
|
4
|
+
module Private
|
5
|
+
module PackRelationshipAnalyzer
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig do
|
9
|
+
params(
|
10
|
+
pack_name: T.nilable(String),
|
11
|
+
limit: Integer,
|
12
|
+
).void
|
13
|
+
end
|
14
|
+
def self.list_top_privacy_violations(pack_name, limit)
|
15
|
+
all_packages = ParsePackwerk.all
|
16
|
+
if !pack_name.nil?
|
17
|
+
pack_name = Private.clean_pack_name(pack_name)
|
18
|
+
package = all_packages.find { |package| package.name == pack_name }
|
19
|
+
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/`")
|
21
|
+
end
|
22
|
+
|
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/list_top_privacy_violations -n #{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
|
+
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/list_top_privacy_violations -n #{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
|
+
end
|
43
|
+
|
44
|
+
violations_by_count = {}
|
45
|
+
total_pack_violation_count = 0
|
46
|
+
|
47
|
+
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/make_public -f #{pack_name}/path/to/file.rb` to make your class, constant, or module public API.
|
76
|
+
INTRO
|
77
|
+
Logging.print_bold_green(intro)
|
78
|
+
end
|
79
|
+
|
80
|
+
# TODO: This is a copy of the implementation below. We may want to refactor out this implementation detail before making changes that apply to both.
|
81
|
+
all_packages.each do |client_package|
|
82
|
+
PackageProtections::ProtectedPackage.from(client_package).violations.select(&:privacy?).each do |violation|
|
83
|
+
next unless to_package_names.include?(violation.to_package_name)
|
84
|
+
if pack_name.nil?
|
85
|
+
violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
|
86
|
+
else
|
87
|
+
violated_symbol = "#{violation.class_name}"
|
88
|
+
end
|
89
|
+
violations_by_count[violated_symbol] ||= {}
|
90
|
+
violations_by_count[violated_symbol][:total_count] ||= 0
|
91
|
+
violations_by_count[violated_symbol][:by_package] ||= {}
|
92
|
+
violations_by_count[violated_symbol][:by_package][client_package.name] ||= 0
|
93
|
+
violations_by_count[violated_symbol][:total_count] += violation.files.count
|
94
|
+
violations_by_count[violated_symbol][:by_package][client_package.name] += violation.files.count
|
95
|
+
total_pack_violation_count += violation.files.count
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
Logging.print("Total Count: #{total_pack_violation_count}")
|
100
|
+
|
101
|
+
sorted_violations = violations_by_count.sort_by { |violated_symbol, count_info| [-count_info[:total_count], violated_symbol] }
|
102
|
+
sorted_violations.first(limit).each do |violated_symbol, count_info|
|
103
|
+
percentage_of_total = (count_info[:total_count] * 100.0 / total_pack_violation_count).round(2)
|
104
|
+
Logging.print(violated_symbol)
|
105
|
+
Logging.print(" - Total Count: #{count_info[:total_count]} (#{percentage_of_total}% of total)")
|
106
|
+
|
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|
|
109
|
+
Logging.print(" - #{client_package_name}: #{count}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
sig do
|
115
|
+
params(
|
116
|
+
pack_name: T.nilable(String),
|
117
|
+
limit: Integer
|
118
|
+
).void
|
119
|
+
end
|
120
|
+
def self.list_top_dependency_violations(pack_name, limit)
|
121
|
+
all_packages = ParsePackwerk.all
|
122
|
+
|
123
|
+
if !pack_name.nil?
|
124
|
+
pack_name = Private.clean_pack_name(pack_name)
|
125
|
+
package = all_packages.find { |package| package.name == pack_name }
|
126
|
+
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/`")
|
128
|
+
end
|
129
|
+
|
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/list_top_dependency_violations -n #{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
|
+
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. `bin/list_top_dependency_violations -n #{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
|
+
end
|
150
|
+
|
151
|
+
violations_by_count = {}
|
152
|
+
total_pack_violation_count = 0
|
153
|
+
|
154
|
+
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
|
182
|
+
Logging.print_bold_green(intro)
|
183
|
+
end
|
184
|
+
|
185
|
+
# 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
|
+
all_packages.each do |client_package|
|
187
|
+
PackageProtections::ProtectedPackage.from(client_package).violations.select(&:dependency?).each do |violation|
|
188
|
+
next unless to_package_names.include?(violation.to_package_name)
|
189
|
+
if pack_name.nil?
|
190
|
+
violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
|
191
|
+
else
|
192
|
+
violated_symbol = "#{violation.class_name}"
|
193
|
+
end
|
194
|
+
violations_by_count[violated_symbol] ||= {}
|
195
|
+
violations_by_count[violated_symbol][:total_count] ||= 0
|
196
|
+
violations_by_count[violated_symbol][:by_package] ||= {}
|
197
|
+
violations_by_count[violated_symbol][:by_package][client_package.name] ||= 0
|
198
|
+
violations_by_count[violated_symbol][:total_count] += violation.files.count
|
199
|
+
violations_by_count[violated_symbol][:by_package][client_package.name] += violation.files.count
|
200
|
+
total_pack_violation_count += violation.files.count
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
Logging.print("Total Count: #{total_pack_violation_count}")
|
205
|
+
|
206
|
+
sorted_violations = violations_by_count.sort_by { |violated_symbol, count_info| [-count_info[:total_count], violated_symbol] }
|
207
|
+
sorted_violations.first(limit).each do |violated_symbol, count_info|
|
208
|
+
percentage_of_total = (count_info[:total_count] * 100.0 / total_pack_violation_count).round(2)
|
209
|
+
Logging.print(violated_symbol)
|
210
|
+
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|
|
213
|
+
Logging.print(" - #{client_package_name}: #{count}")
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|