packs 0.0.5 → 0.0.22

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 (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