use_packs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/bin/packs +10 -0
  4. data/bin/rubocop +29 -0
  5. data/bin/tapioca +29 -0
  6. data/lib/use_packs/cli.rb +127 -0
  7. data/lib/use_packs/code_ownership_post_processor.rb +58 -0
  8. data/lib/use_packs/configuration.rb +61 -0
  9. data/lib/use_packs/default_user_event_logger.rb +7 -0
  10. data/lib/use_packs/logging.rb +37 -0
  11. data/lib/use_packs/per_file_processor_interface.rb +18 -0
  12. data/lib/use_packs/private/file_move_operation.rb +80 -0
  13. data/lib/use_packs/private/interactive_cli/pack_selector.rb +34 -0
  14. data/lib/use_packs/private/interactive_cli/team_selector.rb +35 -0
  15. data/lib/use_packs/private/interactive_cli/use_cases/add_dependency.rb +30 -0
  16. data/lib/use_packs/private/interactive_cli/use_cases/check.rb +25 -0
  17. data/lib/use_packs/private/interactive_cli/use_cases/create.rb +27 -0
  18. data/lib/use_packs/private/interactive_cli/use_cases/get_info.rb +74 -0
  19. data/lib/use_packs/private/interactive_cli/use_cases/interface.rb +34 -0
  20. data/lib/use_packs/private/interactive_cli/use_cases/lint_package_yml.rb +26 -0
  21. data/lib/use_packs/private/interactive_cli/use_cases/make_public.rb +34 -0
  22. data/lib/use_packs/private/interactive_cli/use_cases/move.rb +36 -0
  23. data/lib/use_packs/private/interactive_cli/use_cases/nest.rb +31 -0
  24. data/lib/use_packs/private/interactive_cli/use_cases/query.rb +51 -0
  25. data/lib/use_packs/private/interactive_cli/use_cases/regenerate_rubocop_todo.rb +26 -0
  26. data/lib/use_packs/private/interactive_cli/use_cases/rename.rb +34 -0
  27. data/lib/use_packs/private/interactive_cli/use_cases/update_deprecations.rb +25 -0
  28. data/lib/use_packs/private/interactive_cli/use_cases/validate.rb +25 -0
  29. data/lib/use_packs/private/interactive_cli/use_cases/visualize.rb +44 -0
  30. data/lib/use_packs/private/interactive_cli.rb +52 -0
  31. data/lib/use_packs/private/pack_relationship_analyzer.rb +135 -0
  32. data/lib/use_packs/private/packwerk_wrapper/offenses_aggregator_formatter.rb +34 -0
  33. data/lib/use_packs/private/packwerk_wrapper.rb +71 -0
  34. data/lib/use_packs/private.rb +453 -0
  35. data/lib/use_packs/rubocop_post_processor.rb +67 -0
  36. data/lib/use_packs/spring_command.rb +28 -0
  37. data/lib/use_packs/user_event_logger.rb +259 -0
  38. data/lib/use_packs.rb +298 -0
  39. metadata +351 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 88d0834366bd3d1591029828eab623a5781f0ed44a3b8c0b35ab92f131236377
4
+ data.tar.gz: 8f3dd5181a41f8bd3ff2441fc58c73bac8ec712137a5967b7447d62b9014d653
5
+ SHA512:
6
+ metadata.gz: 3cb7be9b2014cc3c065d700e19472821acda6b9a41fe73cdf695e52d2d96ee1552aabaf2e123c8f4d4b21c1b469cb26073e8eaa650874d6ded5fbdb63c3a401e
7
+ data.tar.gz: 6a6a15f8f010a10988eae52796bbbf55326c7a814bc1d0ec3a550edd860cc038fb7eb917860e7629cae1d6b2462901296b5bb163857b3cf955a7c93b6d9ffe21
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # UsePacks
2
+
3
+ UsePacks 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/packs --help` or just `bin/packs` to enter interactive mode.
8
+
9
+ ### Pack Creation
10
+ `bin/packs create packs/your_pack_name_here`
11
+
12
+ ### Moving files to packs
13
+ `bin/packs move packs/your_pack_name_here 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/packs make_public 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/packs list_top_privacy_violations 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/packs list_top_dependency_violations 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/packs add_dependency packs/my_pack 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
+ ### Setting up Spring
47
+
48
+ [Spring](https://github.com/rails/spring) is a preloader for Rails. Although `use_packs` itself does not use `Rails`, this can help speed up running commands like `bin/packs` by caching the bundle.
49
+ Firstly, spring needs to know about the `bin/packs` command when spring is loading. To do that, add `require 'use_packs/spring_command'` to `config/spring.rb` in your application.
50
+ Secondly, to enable Spring, first run `bin/spring binstub packs` which will "springify" the generated binstub.
51
+
52
+ ### Releasing
53
+ Releases happen automatically through github actions once a version update is committed to `main`.
54
+
55
+ ## Discussions, Issues, Questions, and More
56
+ To keep things organized, here are some recommended homes:
57
+
58
+ ### Issues:
59
+ https://github.com/Gusto/use_packs/issues
60
+
61
+ ### Questions:
62
+ https://github.com/Gusto/use_packs/discussions/categories/q-a
63
+
64
+ ### General discussions:
65
+ https://github.com/Gusto/use_packs/discussions/categories/general
66
+
67
+ ### Ideas, new features, requests for change:
68
+ https://github.com/Gusto/use_packs/discussions/categories/ideas
69
+
70
+ ### Showcasing your work:
71
+ https://github.com/Gusto/use_packs/discussions/categories/show-and-tell
data/bin/packs ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # typed: strict
3
+
4
+ require_relative '../lib/use_packs'
5
+
6
+ if ARGV.empty?
7
+ UsePacks.start_interactive_mode!
8
+ else
9
+ UsePacks::CLI.start(ARGV)
10
+ end
data/bin/rubocop ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rubocop' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('rubocop', 'rubocop')
data/bin/tapioca ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path('bundle', __dir__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require 'rubygems'
27
+ require 'bundler/setup'
28
+
29
+ load Gem.bin_path('tapioca', 'tapioca')
@@ -0,0 +1,127 @@
1
+ # typed: strict
2
+
3
+ require 'thor'
4
+
5
+ module UsePacks
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
+ UsePacks.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/packs 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
+ UsePacks.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
+ UsePacks.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
+ UsePacks.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
+ UsePacks.make_public!(
56
+ paths_relative_to_root: paths,
57
+ per_file_processors: [UsePacks::RubocopPostProcessor.new, UsePacks::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
+ UsePacks.move_to_pack!(
65
+ pack_name: pack_name,
66
+ paths_relative_to_root: paths,
67
+ per_file_processors: [UsePacks::RubocopPostProcessor.new, UsePacks::CodeOwnershipPostProcessor.new]
68
+ )
69
+ end
70
+
71
+ desc 'move_to_parent packs/parent_pack packs/child_pack', 'Pass in a parent pack and another pack to be made as a child to the parent pack!'
72
+ sig { params(parent_name: String, pack_name: String).void }
73
+ def move_to_parent(parent_name, pack_name)
74
+ UsePacks.move_to_parent!(
75
+ parent_name: parent_name,
76
+ pack_name: pack_name,
77
+ per_file_processors: [UsePacks::RubocopPostProcessor.new, UsePacks::CodeOwnershipPostProcessor.new]
78
+ )
79
+ end
80
+
81
+ desc 'lint_deprecated_references_yml_files', 'Ensures `deprecated_references.yml` files are up to date'
82
+ sig { void }
83
+ def lint_deprecated_references_yml_files
84
+ UsePacks.lint_deprecated_references_yml_files!
85
+ end
86
+
87
+ desc 'lint_package_yml_files [ packs/my_pack packs/my_other_pack ]', 'Lint `package.yml` files'
88
+ sig { params(pack_names: String).void }
89
+ def lint_package_yml_files(*pack_names)
90
+ UsePacks.lint_package_yml_files!(parse_pack_names(pack_names))
91
+ end
92
+
93
+ desc 'validate', 'Run bin/packwerk validate (detects cycles)'
94
+ sig { void }
95
+ def validate
96
+ system('bin/packwerk validate')
97
+ end
98
+
99
+ desc 'check [ packs/my_pack ]', 'Run bin/packwerk check'
100
+ sig { params(paths: String).void }
101
+ def check(*paths)
102
+ system("bin/packwerk check #{paths.join(' ')}")
103
+ end
104
+
105
+ desc 'update [ packs/my_pack ]', 'Run bin/packwerk update-deprecations'
106
+ sig { params(paths: String).void }
107
+ def update(*paths)
108
+ system("bin/packwerk update-deprecations #{paths.join(' ')}")
109
+ end
110
+
111
+ desc 'regenerate_rubocop_todo [ packs/my_pack packs/my_other_pack ]', "Regenerate packs/*/#{RuboCop::Packs::PACK_LEVEL_RUBOCOP_TODO_YML} for one or more packs"
112
+ sig { params(pack_names: String).void }
113
+ def regenerate_rubocop_todo(*pack_names)
114
+ RuboCop::Packs.regenerate_todo(packs: parse_pack_names(pack_names))
115
+ end
116
+
117
+ private
118
+
119
+ # This is used by thor to know that these private methods are not intended to be CLI commands
120
+ no_commands do
121
+ sig { params(pack_names: T::Array[String]).returns(T::Array[ParsePackwerk::Package]) }
122
+ def parse_pack_names(pack_names)
123
+ pack_names.empty? ? ParsePackwerk.all : pack_names.map { |p| ParsePackwerk.find(p.gsub(%r{/$}, '')) }.compact
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,58 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
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
+ UsePacks.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 { params(file_move_operations: T::Array[Private::FileMoveOperation]).void }
44
+ def after_move_files!(file_move_operations)
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,61 @@
1
+ # typed: strict
2
+
3
+ require 'use_packs/user_event_logger'
4
+ require 'use_packs/default_user_event_logger'
5
+
6
+ module UsePacks
7
+ class Configuration
8
+ extend T::Sig
9
+
10
+ sig { params(enforce_dependencies: T::Boolean).void }
11
+ attr_writer :enforce_dependencies
12
+
13
+ sig { returns(UserEventLogger) }
14
+ attr_accessor :user_event_logger
15
+
16
+ OnDeprecatedReferencesLintFailure = T.type_alias do
17
+ T.proc.params(output: String).void
18
+ end
19
+
20
+ sig { returns(OnDeprecatedReferencesLintFailure) }
21
+ attr_accessor :on_deprecated_references_lint_failure
22
+
23
+ sig { void }
24
+ def initialize
25
+ @enforce_dependencies = T.let(default_enforce_dependencies, T::Boolean)
26
+ @user_event_logger = T.let(DefaultUserEventLogger.new, UserEventLogger)
27
+ @on_deprecated_references_lint_failure = T.let(->(output) {}, OnDeprecatedReferencesLintFailure)
28
+ end
29
+
30
+ sig { returns(T::Boolean) }
31
+ def enforce_dependencies
32
+ @enforce_dependencies
33
+ end
34
+
35
+ sig { void }
36
+ def bust_cache!
37
+ @enforce_dependencies = default_enforce_dependencies
38
+ end
39
+
40
+ sig { returns(T::Boolean) }
41
+ def default_enforce_dependencies
42
+ true
43
+ end
44
+ end
45
+
46
+ class << self
47
+ extend T::Sig
48
+
49
+ sig { returns(Configuration) }
50
+ def config
51
+ Private.load_client_configuration
52
+ @config = T.let(@config, T.nilable(Configuration))
53
+ @config ||= Configuration.new
54
+ end
55
+
56
+ sig { params(blk: T.proc.params(arg0: Configuration).void).void }
57
+ def configure(&blk)
58
+ yield(config)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ class DefaultUserEventLogger
5
+ include UserEventLogger
6
+ end
7
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+
3
+ require 'colorized_string'
4
+
5
+ module UsePacks
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
+ out ColorizedString.new(title).green.bold
13
+ out "\n"
14
+ yield
15
+ end
16
+
17
+ sig { params(text: String).void }
18
+ def self.print_bold_green(text)
19
+ out ColorizedString.new(text).green.bold
20
+ end
21
+
22
+ sig { params(text: String).void }
23
+ def self.print(text)
24
+ out text
25
+ end
26
+
27
+ sig { void }
28
+ def self.print_divider
29
+ out '=' * 100
30
+ end
31
+
32
+ sig { params(str: String).void }
33
+ def self.out(str)
34
+ puts str
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
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); end
12
+
13
+ sig { params(file_move_operations: T::Array[Private::FileMoveOperation]).void }
14
+ def after_move_files!(file_move_operations)
15
+ nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,80 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
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 { returns(ParsePackwerk::Package) }
13
+ def origin_pack
14
+ ParsePackwerk.package_from_path(origin_pathname)
15
+ end
16
+
17
+ sig { params(origin_pathname: Pathname, new_package_root: Pathname).returns(Pathname) }
18
+ def self.destination_pathname_for_package_move(origin_pathname, new_package_root)
19
+ origin_pack = ParsePackwerk.package_from_path(origin_pathname)
20
+
21
+ if origin_pack.name == ParsePackwerk::ROOT_PACKAGE_NAME
22
+ new_package_root.join(origin_pathname).cleanpath
23
+ else
24
+ Pathname.new(origin_pathname.to_s.gsub(origin_pack.name, new_package_root.to_s)).cleanpath
25
+ end
26
+ end
27
+
28
+ sig { params(origin_pathname: Pathname).returns(Pathname) }
29
+ def self.destination_pathname_for_new_public_api(origin_pathname)
30
+ origin_pack = ParsePackwerk.package_from_path(origin_pathname)
31
+ if origin_pack.name == ParsePackwerk::ROOT_PACKAGE_NAME
32
+ filepath_without_pack_name = origin_pathname.to_s
33
+ else
34
+ filepath_without_pack_name = origin_pathname.to_s.gsub("#{origin_pack.name}/", '')
35
+ end
36
+
37
+ # We join the pack name with the rest of the path...
38
+ path_parts = filepath_without_pack_name.split('/')
39
+ Pathname.new(origin_pack.name).join(
40
+ # ... keeping the "app" or "spec"
41
+ T.must(path_parts[0]),
42
+ # ... substituting "controllers," "services," etc. with "public"
43
+ 'public',
44
+ # ... then the rest is the same
45
+ T.must(path_parts[2..]).join('/')
46
+ # and we take the cleanpath so `./app/...` becomes `app/...`
47
+ ).cleanpath
48
+ end
49
+
50
+ sig { returns(FileMoveOperation) }
51
+ def spec_file_move_operation
52
+ # This could probably be implemented by some "strategy pattern" where different extension types are handled by different helpers
53
+ # Such a thing could also include, for example, when moving a controller, moving its ERB view too.
54
+ if origin_pathname.extname == '.rake'
55
+ new_origin_pathname = origin_pathname.sub('/lib/', '/spec/lib/').sub(%r{^lib/}, 'spec/lib/').sub('.rake', '_spec.rb')
56
+ new_destination_pathname = destination_pathname.sub('/lib/', '/spec/lib/').sub(%r{^lib/}, 'spec/lib/').sub('.rake', '_spec.rb')
57
+ else
58
+ new_origin_pathname = origin_pathname.sub('/app/', '/spec/').sub(%r{^app/}, 'spec/').sub('.rb', '_spec.rb')
59
+ new_destination_pathname = destination_pathname.sub('/app/', '/spec/').sub(%r{^app/}, 'spec/').sub('.rb', '_spec.rb')
60
+ end
61
+ FileMoveOperation.new(
62
+ origin_pathname: new_origin_pathname,
63
+ destination_pathname: new_destination_pathname,
64
+ destination_pack: destination_pack
65
+ )
66
+ end
67
+
68
+ private
69
+
70
+ sig { params(path: Pathname).returns(FileMoveOperation) }
71
+ def relative_to(path)
72
+ FileMoveOperation.new(
73
+ origin_pathname: origin_pathname.relative_path_from(path),
74
+ destination_pathname: destination_pathname.relative_path_from(path),
75
+ destination_pack: destination_pack
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ class PackSelector
7
+ extend T::Sig
8
+
9
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(ParsePackwerk::Package) }
10
+ def self.single_pack_select(prompt, question_text: 'Please select a pack')
11
+ packs = ParsePackwerk.all.to_h { |t| [t.name, t] }
12
+ prompt.select(
13
+ question_text,
14
+ packs,
15
+ filter: true,
16
+ per_page: 10,
17
+ show_help: :always
18
+ )
19
+ end
20
+
21
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(T::Array[ParsePackwerk::Package]) }
22
+ def self.single_or_all_pack_multi_select(prompt, question_text: 'Please select one or more packs')
23
+ prompt.multi_select(
24
+ question_text,
25
+ ParsePackwerk.all.to_h { |t| [t.name, t] },
26
+ filter: true,
27
+ per_page: 10,
28
+ show_help: :always
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ class TeamSelector
7
+ extend T::Sig
8
+
9
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(CodeTeams::Team) }
10
+ def self.single_select(prompt, question_text: 'Please select a team owner')
11
+ teams = CodeTeams.all.sort_by(&:name).to_h { |t| [t.name, t] }
12
+ prompt.select(
13
+ question_text,
14
+ teams,
15
+ filter: true,
16
+ per_page: 10,
17
+ show_help: :always
18
+ )
19
+ end
20
+
21
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(T::Array[CodeTeams::Team]) }
22
+ def self.multi_select(prompt, question_text: 'Please select team owners')
23
+ teams = CodeTeams.all.to_h { |t| [t.name, t] }
24
+ prompt.multi_select(
25
+ question_text,
26
+ teams,
27
+ filter: true,
28
+ per_page: 10,
29
+ show_help: :always
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class AddDependency
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.params(prompt: TTY::Prompt).void }
13
+ def perform!(prompt)
14
+ dependent_pack = PackSelector.single_pack_select(prompt, question_text: 'Please select the pack you are adding a dependency to.')
15
+ dependency_pack = PackSelector.single_pack_select(prompt, question_text: "Please select the pack that #{dependent_pack.name} should depend on.")
16
+ UsePacks.add_dependency!(
17
+ pack_name: dependent_pack.name,
18
+ dependency_name: dependency_pack.name
19
+ )
20
+ end
21
+
22
+ sig { override.returns(String) }
23
+ def user_facing_name
24
+ 'Add a dependency'
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module UsePacks
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Check
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Run bin/packwerk check'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ system('bin/packwerk check')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end