packs 0.0.5 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -12
  3. data/bin/packs +10 -0
  4. data/bin/rubocop +29 -0
  5. data/bin/tapioca +29 -0
  6. data/lib/packs/cli.rb +164 -0
  7. data/lib/packs/code_ownership_post_processor.rb +58 -0
  8. data/lib/packs/configuration.rb +61 -0
  9. data/lib/packs/default_user_event_logger.rb +7 -0
  10. data/lib/packs/logging.rb +37 -0
  11. data/lib/packs/per_file_processor_interface.rb +18 -0
  12. data/lib/packs/private/file_move_operation.rb +80 -0
  13. data/lib/packs/private/interactive_cli/file_selector.rb +26 -0
  14. data/lib/packs/private/interactive_cli/pack_selector.rb +55 -0
  15. data/lib/packs/private/interactive_cli/team_selector.rb +58 -0
  16. data/lib/packs/private/interactive_cli/use_cases/add_dependency.rb +30 -0
  17. data/lib/packs/private/interactive_cli/use_cases/check.rb +25 -0
  18. data/lib/packs/private/interactive_cli/use_cases/create.rb +27 -0
  19. data/lib/packs/private/interactive_cli/use_cases/get_info.rb +37 -0
  20. data/lib/packs/private/interactive_cli/use_cases/interface.rb +34 -0
  21. data/lib/packs/private/interactive_cli/use_cases/lint_package_todo_yml_files.rb +25 -0
  22. data/lib/packs/private/interactive_cli/use_cases/lint_package_yml_files.rb +26 -0
  23. data/lib/packs/private/interactive_cli/use_cases/make_public.rb +30 -0
  24. data/lib/packs/private/interactive_cli/use_cases/move.rb +32 -0
  25. data/lib/packs/private/interactive_cli/use_cases/move_to_parent.rb +31 -0
  26. data/lib/packs/private/interactive_cli/use_cases/query.rb +51 -0
  27. data/lib/packs/private/interactive_cli/use_cases/rename.rb +25 -0
  28. data/lib/packs/private/interactive_cli/use_cases/update.rb +25 -0
  29. data/lib/packs/private/interactive_cli/use_cases/validate.rb +25 -0
  30. data/lib/packs/private/interactive_cli/use_cases/visualize.rb +44 -0
  31. data/lib/packs/private/interactive_cli.rb +52 -0
  32. data/lib/packs/private/pack_relationship_analyzer.rb +135 -0
  33. data/lib/packs/private/packwerk_wrapper/offenses_aggregator_formatter.rb +44 -0
  34. data/lib/packs/private/packwerk_wrapper.rb +70 -0
  35. data/lib/packs/private.rb +606 -4
  36. data/lib/packs/rubocop_post_processor.rb +30 -0
  37. data/lib/packs/user_event_logger.rb +199 -0
  38. data/lib/packs.rb +233 -53
  39. metadata +225 -14
  40. data/lib/packs/pack.rb +0 -43
  41. data/lib/packs/private/configuration.rb +0 -36
  42. data/lib/packs/rspec/fixture_helper.rb +0 -33
  43. data/lib/packs/rspec/support.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59f7fd3aee7635eabbe2a66c748b1083ced17b48c020fd628825ebb47239e7d9
4
- data.tar.gz: e6d8964d6286a7ba822c15d81cc48f18017997477349ba7b13a3628a634b93e2
3
+ metadata.gz: bfd963aed9035b204092449d956a985ebdd62eec89bd32d522b2a8ca7a82ad74
4
+ data.tar.gz: 7a4e8471530a3847b42f9b1bd3f8523fa920e3b2e3f6c83036279d25ff9173b2
5
5
  SHA512:
6
- metadata.gz: a566117be564c25d46e673ea53c8d8d4d6fe04948d99d919236848314e5c8bc436b7287bb1cd7ba409bcf12b910d5befb842b0c608dd6d072132c59cd0840bc6
7
- data.tar.gz: 043c964fa23f0603c8cccbc224e4129f00d86e42a40f3746b46fc550cdfd8a0f61f5c372306ea0548b5638d158629649aa3815cf5bd3c3fe1071ad211d3eadb4
6
+ metadata.gz: 23442ca91f171f85f9b2d523cc91bd34ace4186dbbb8b6f92f57ca896c473c3bcc0556c27a2e869d55285ff8c40e72df4a019af678c2f60a674af6230dae5d1a
7
+ data.tar.gz: 8754d2368251ab1ec8073191e38791dffd6c79f07c6a8eaaa49cd69d6dae0b99def340fde2c050140fdce8017612d2266d22b13b81ddafa8cbe6f67192cbeb7a
data/README.md CHANGED
@@ -1,16 +1,105 @@
1
- # packs
1
+ # Packs
2
2
 
3
- Welcome to `packs`! `packs` are a simple ruby specification for an extensible packaging system to help modularize Ruby applications.
3
+ Packs 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 [`packs-rails`](https://github.com/rubyatscale/packs-rails) to organize your packages.
4
4
 
5
- A `pack` (also called `package`) is a folder of Ruby code with a `package.yml` at the root that is intended to represent a well-modularized domain, and the rest of the [rubyatscale](https://github.com/rubyatscale) ecosystem is intended to help make the boundaries between a pack and any other more clear.
5
+ ## Usage
6
+ Make sure to run `bundle binstub use_packs` to generate `bin/packs` within your application.
6
7
 
7
- Here are some example integrations with `packs`:
8
- - [`stimpack`](https://github.com/rubyatscale/stimpack) can be used to integrate `packs` into your `rails` application
9
- - [`rubocop-packs`](https://github.com/rubyatscale/rubocop-packs) contains cops to improve boundaries around `packs`
10
- - [`packwerk`](https://github.com/Shopify/packwerk) and [`packwerk-extensions`](https://github.com/rubyatscale/packwerk-extensions) help you describe and constrain your package graph in terms of dependencies between packs and pack public API
11
- - [`code_ownership`](https://github.com/rubyatscale/code_ownership) gives your application the capability to determine the owner of a pack
12
- - [`use_packs`](https://github.com/rubyatscale/use_packs) gives a CLI, `bin/packs`, that makes it easy to create new packs, move files between packs, and more.
13
- - [`pack_stats`](https://github.com/rubyatscale/pack_stats) makes it easy to send metrics about pack adoption and modularization to your favorite metrics provider, such as DataDog (which has built-in support).
8
+ ## CLI Documentation
9
+ ## Describe available commands or one specific command
10
+ `bin/packs help [COMMAND]`
14
11
 
15
- # How is a pack different from a gem?
16
- A ruby [`gem`](https://guides.rubygems.org/what-is-a-gem/) is the Ruby community solution for packaging and distributing Ruby code. A gem is a great place to start new projects, and a great end state for code that's been extracted from an existing codebase. `packs` are intended to help gradually modularize an application that has some conceptual boundaries, but is not yet ready to be factored into gems.
12
+ ## Create pack with name packs/your_pack
13
+ `bin/packs create packs/your_pack`
14
+
15
+ ## Add packs/to_pack to packs/from_pack/package.yml list of dependencies
16
+ `bin/packs add_dependency packs/from_pack packs/to_pack`
17
+
18
+ Use this to add a dependency between packs.
19
+
20
+ When you use bin/packs add_dependency packs/from_pack packs/to_pack, this command will
21
+ modify packs/from_pack/package.yml's list of dependencies and add packs/to_pack.
22
+
23
+ This command will also sort the list and make it unique.
24
+
25
+ ## List the top dependency violations of packs/your_pack
26
+ `bin/packs list_top_dependency_violations packs/your_pack`
27
+
28
+ Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way
29
+
30
+ You can use this command to list the top dependency violations.
31
+
32
+ If no pack name is passed in, this will list out violations across all packs.
33
+
34
+ ## List the top privacy violations of packs/your_pack
35
+ `bin/packs list_top_privacy_violations packs/your_pack`
36
+
37
+ Want to create interfaces? Not sure how your pack's code is being used?
38
+
39
+ You can use this command to list the top privacy violations.
40
+
41
+ If no pack name is passed in, this will list out violations across all packs.
42
+
43
+ ## Make files or directories public API
44
+ `bin/packs make_public path/to/file.rb path/to/directory`
45
+
46
+ This moves a file or directory to public API (that is -- the `app/public` folder).
47
+
48
+ Make sure there are no spaces between the comma-separated list of paths of directories.
49
+
50
+ ## Move files or directories from one pack to another
51
+ `bin/packs move packs/destination_pack path/to/file.rb path/to/directory`
52
+
53
+ This is used for moving files into a pack (the pack must already exist).
54
+ Note this works for moving files to packs from the monolith or from other packs
55
+
56
+ Make sure there are no spaces between the comma-separated list of paths of directories.
57
+
58
+ ## Lint `package_todo.yml` files to check for formatting issues
59
+ `bin/packs lint_package_todo_yml_files`
60
+
61
+ ## Lint `package.yml` files
62
+ `bin/packs lint_package_yml_files [ packs/my_pack packs/my_other_pack ]`
63
+
64
+ ## Run bin/packwerk validate (detects cycles)
65
+ `bin/packs validate`
66
+
67
+ ## Run bin/packwerk check
68
+ `bin/packs check [ packs/my_pack ]`
69
+
70
+ ## Run bin/packwerk update-todo
71
+ `bin/packs update`
72
+
73
+ ## Get info about size and violations for packs
74
+ `bin/packs get_info [ packs/my_pack packs/my_other_pack ]`
75
+
76
+ ## Visualize packs
77
+ `bin/packs visualize [ packs/my_pack packs/my_other_pack ]`
78
+
79
+ ## Rename a pack
80
+ `bin/packs rename`
81
+
82
+ ## Set packs/child_pack as a child of packs/parent_pack
83
+ `bin/packs move_to_parent packs/child_pack packs/parent_pack `
84
+
85
+
86
+ ## Releasing
87
+ Releases happen automatically through github actions once a version update is committed to `main`.
88
+
89
+ ## Discussions, Issues, Questions, and More
90
+ To keep things organized, here are some recommended homes:
91
+
92
+ ### Issues:
93
+ https://github.com/rubyatscale/use_packs/issues
94
+
95
+ ### Questions:
96
+ https://github.com/rubyatscale/use_packs/discussions/categories/q-a
97
+
98
+ ### General discussions:
99
+ https://github.com/rubyatscale/use_packs/discussions/categories/general
100
+
101
+ ### Ideas, new features, requests for change:
102
+ https://github.com/rubyatscale/use_packs/discussions/categories/ideas
103
+
104
+ ### Showcasing your work:
105
+ https://github.com/rubyatscale/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/packs'
5
+
6
+ if ARGV.empty?
7
+ Packs.start_interactive_mode!
8
+ else
9
+ Packs::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')
data/lib/packs/cli.rb ADDED
@@ -0,0 +1,164 @@
1
+ # typed: strict
2
+
3
+ require 'thor'
4
+
5
+ module Packs
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
+ Packs.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
+ Packs.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
+ long_desc <<~LONG_DESC
34
+ Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way
35
+
36
+ You can use this command to list the top dependency violations.
37
+
38
+ If no pack name is passed in, this will list out violations across all packs.
39
+ LONG_DESC
40
+ option :limit, type: :numeric, default: 10, aliases: :l, banner: 'Specify the limit of constants to analyze'
41
+ sig { params(pack_name: String).void }
42
+ def list_top_dependency_violations(pack_name)
43
+ Packs.list_top_dependency_violations(
44
+ pack_name: pack_name,
45
+ limit: options[:limit]
46
+ )
47
+ end
48
+
49
+ desc 'list_top_privacy_violations packs/your_pack', 'List the top privacy violations of packs/your_pack'
50
+ long_desc <<~LONG_DESC
51
+ Want to create interfaces? Not sure how your pack's code is being used?
52
+
53
+ You can use this command to list the top privacy violations.
54
+
55
+ If no pack name is passed in, this will list out violations across all packs.
56
+ LONG_DESC
57
+ option :limit, type: :numeric, default: 10, aliases: :l, banner: 'Specify the limit of constants to analyze'
58
+ sig { params(pack_name: String).void }
59
+ def list_top_privacy_violations(pack_name)
60
+ Packs.list_top_privacy_violations(
61
+ pack_name: pack_name,
62
+ limit: options[:limit]
63
+ )
64
+ end
65
+
66
+ desc 'make_public path/to/file.rb path/to/directory', 'Make files or directories public API'
67
+ long_desc <<~LONG_DESC
68
+ This moves a file or directory to public API (that is -- the `app/public` folder).
69
+
70
+ Make sure there are no spaces between the comma-separated list of paths of directories.
71
+ LONG_DESC
72
+ sig { params(paths: String).void }
73
+ def make_public(*paths)
74
+ Packs.make_public!(
75
+ paths_relative_to_root: paths,
76
+ per_file_processors: [Packs::RubocopPostProcessor.new, Packs::CodeOwnershipPostProcessor.new]
77
+ )
78
+ end
79
+
80
+ desc 'move packs/destination_pack path/to/file.rb path/to/directory', 'Move files or directories from one pack to another'
81
+ long_desc <<~LONG_DESC
82
+ This is used for moving files into a pack (the pack must already exist).
83
+ Note this works for moving files to packs from the monolith or from other packs
84
+
85
+ Make sure there are no spaces between the comma-separated list of paths of directories.
86
+ LONG_DESC
87
+ sig { params(pack_name: String, paths: String).void }
88
+ def move(pack_name, *paths)
89
+ Packs.move_to_pack!(
90
+ pack_name: pack_name,
91
+ paths_relative_to_root: paths,
92
+ per_file_processors: [Packs::RubocopPostProcessor.new, Packs::CodeOwnershipPostProcessor.new]
93
+ )
94
+ end
95
+
96
+ desc 'lint_package_todo_yml_files', 'Lint `package_todo.yml` files to check for formatting issues'
97
+ sig { void }
98
+ def lint_package_todo_yml_files
99
+ Packs.lint_package_todo_yml_files!
100
+ end
101
+
102
+ desc 'lint_package_yml_files [ packs/my_pack packs/my_other_pack ]', 'Lint `package.yml` files'
103
+ sig { params(pack_names: String).void }
104
+ def lint_package_yml_files(*pack_names)
105
+ Packs.lint_package_yml_files!(parse_pack_names(pack_names))
106
+ end
107
+
108
+ desc 'validate', 'Run bin/packwerk validate (detects cycles)'
109
+ sig { void }
110
+ def validate
111
+ system('bin/packwerk validate')
112
+ end
113
+
114
+ desc 'check [ packs/my_pack ]', 'Run bin/packwerk check'
115
+ sig { params(paths: String).void }
116
+ def check(*paths)
117
+ Packs.execute(['check', *paths])
118
+ end
119
+
120
+ desc 'update', 'Run bin/packwerk update-todo'
121
+ sig { void }
122
+ def update
123
+ system('bin/packwerk update-todo')
124
+ end
125
+
126
+ desc 'get_info [ packs/my_pack packs/my_other_pack ]', 'Get info about size and violations for packs'
127
+ sig { params(pack_names: String).void }
128
+ def get_info(*pack_names)
129
+ Private.get_info(packs: parse_pack_names(pack_names))
130
+ end
131
+
132
+ desc 'visualize [ packs/my_pack packs/my_other_pack ]', 'Visualize packs'
133
+ sig { params(pack_names: String).void }
134
+ def visualize(*pack_names)
135
+ Private.visualize(packs: parse_pack_names(pack_names))
136
+ end
137
+
138
+ desc 'rename', 'Rename a pack'
139
+ sig { void }
140
+ def rename
141
+ puts Private.rename_pack
142
+ end
143
+
144
+ desc 'move_to_parent packs/child_pack packs/parent_pack ', 'Set packs/child_pack as a child of packs/parent_pack'
145
+ sig { params(child_pack_name: String, parent_pack_name: String).void }
146
+ def move_to_parent(child_pack_name, parent_pack_name)
147
+ Packs.move_to_parent!(
148
+ parent_name: parent_pack_name,
149
+ pack_name: child_pack_name,
150
+ per_file_processors: [Packs::RubocopPostProcessor.new, Packs::CodeOwnershipPostProcessor.new]
151
+ )
152
+ end
153
+
154
+ private
155
+
156
+ # This is used by thor to know that these private methods are not intended to be CLI commands
157
+ no_commands do
158
+ sig { params(pack_names: T::Array[String]).returns(T::Array[Packs::Pack]) }
159
+ def parse_pack_names(pack_names)
160
+ pack_names.empty? ? Packs.all : pack_names.map { |p| Packs.find(p.gsub(%r{/$}, '')) }.compact
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,58 @@
1
+ # typed: strict
2
+
3
+ module Packs
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
+ return if !code_owners_allow_list_file.exist?
21
+
22
+ Packs.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
+
28
+ team = CodeOwnership.for_file(relative_path_to_origin.to_s)
29
+
30
+ if team
31
+ @teams << team.name
32
+ else
33
+ @teams << 'Unknown'
34
+ end
35
+
36
+ pack = Packs.find(file_move_operation.destination_pack.name)
37
+ if pack && !CodeOwnership.for_package(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 'packs/user_event_logger'
4
+ require 'packs/default_user_event_logger'
5
+
6
+ module Packs
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
+ OnPackageTodoLintFailure = T.type_alias do
17
+ T.proc.params(output: String).void
18
+ end
19
+
20
+ sig { returns(OnPackageTodoLintFailure) }
21
+ attr_accessor :on_package_todo_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_package_todo_lint_failure = T.let(->(output) {}, OnPackageTodoLintFailure)
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 Packs
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 Packs
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 Packs
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 Packs
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,26 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ class FileSelector
7
+ extend T::Sig
8
+
9
+ sig { params(prompt: TTY::Prompt).returns(T::Array[String]) }
10
+ def self.select(prompt)
11
+ prompt.on(:keytab) do
12
+ raw_paths_relative_to_root = prompt.multiline('Please copy in a space or new line separated list of files or directories')
13
+ paths_relative_to_root = T.let([], T::Array[String])
14
+ raw_paths_relative_to_root.each do |path|
15
+ paths_relative_to_root += path.chomp.split
16
+ end
17
+
18
+ return paths_relative_to_root
19
+ end
20
+
21
+ [prompt.ask('Please input a file or directory to move (press tab to enter multiline mode)')]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end