use_packs 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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