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
@@ -0,0 +1,55 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ class PackSelector
7
+ extend T::Sig
8
+
9
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(Packs::Pack) }
10
+ def self.single_pack_select(prompt, question_text: 'Please use space to select a pack')
11
+ packs = Packs.all.to_h { |t| [t.name, t] }
12
+
13
+ pack_selection = T.let(prompt.select(
14
+ question_text,
15
+ packs,
16
+ filter: true,
17
+ per_page: 10,
18
+ show_help: :always
19
+ ), T.nilable(Packs::Pack))
20
+
21
+ while pack_selection.nil?
22
+ prompt.error(
23
+ 'No packs were selected, please select a pack using the space key before pressing enter.'
24
+ )
25
+
26
+ pack_selection = single_pack_select(prompt, question_text: question_text)
27
+ end
28
+
29
+ pack_selection
30
+ end
31
+
32
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(T::Array[Packs::Pack]) }
33
+ def self.single_or_all_pack_multi_select(prompt, question_text: 'Please use space to select one or more packs')
34
+ pack_selection = T.let(prompt.multi_select(
35
+ question_text,
36
+ Packs.all.to_h { |t| [t.name, t] },
37
+ filter: true,
38
+ per_page: 10,
39
+ show_help: :always
40
+ ), T::Array[Packs::Pack])
41
+
42
+ while pack_selection.empty?
43
+ prompt.error(
44
+ 'No packs were selected, please select one or more packs using the space key before pressing enter.'
45
+ )
46
+
47
+ pack_selection = single_or_all_pack_multi_select(prompt, question_text: question_text)
48
+ end
49
+
50
+ pack_selection
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,58 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ class TeamSelector
7
+ extend T::Sig
8
+
9
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(T.nilable(CodeTeams::Team)) }
10
+ def self.single_select(prompt, question_text: 'Please use space to select a team owner')
11
+ teams = CodeTeams.all.sort_by(&:name).to_h { |t| [t.name, t] }
12
+ return nil if teams.count == 0
13
+
14
+ team_selection = T.let(prompt.select(
15
+ question_text,
16
+ teams,
17
+ filter: true,
18
+ per_page: 10,
19
+ show_help: :always
20
+ ), T.nilable(CodeTeams::Team))
21
+
22
+ while team_selection.nil?
23
+ prompt.error(
24
+ 'No owners were selected, please select an owner using the space key before pressing enter.'
25
+ )
26
+
27
+ team_selection = single_select(prompt, question_text: question_text)
28
+ end
29
+
30
+ team_selection
31
+ end
32
+
33
+ sig { params(prompt: TTY::Prompt, question_text: String).returns(T::Array[CodeTeams::Team]) }
34
+ def self.multi_select(prompt, question_text: 'Please use space to select team owners')
35
+ teams = CodeTeams.all.to_h { |t| [t.name, t] }
36
+
37
+ team_selection = T.let(prompt.multi_select(
38
+ question_text,
39
+ teams,
40
+ filter: true,
41
+ per_page: 10,
42
+ show_help: :always
43
+ ), T::Array[CodeTeams::Team])
44
+
45
+ while team_selection.empty?
46
+ prompt.error(
47
+ 'No owners were selected, please select one or more owners using the space key before pressing enter.'
48
+ )
49
+
50
+ team_selection = multi_select(prompt, question_text: question_text)
51
+ end
52
+
53
+ team_selection
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+
3
+ module Packs
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
+ Packs.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 Packs
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
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Create
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
+ pack_name = prompt.ask('What should the name of your pack be?', value: 'packs/')
15
+ team = TeamSelector.single_select(prompt)
16
+ Packs.create_pack!(pack_name: pack_name, team: team)
17
+ end
18
+
19
+ sig { override.returns(String) }
20
+ def user_facing_name
21
+ 'Create a new pack'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class GetInfo
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Get info on one or more packs'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ team_or_pack = prompt.select('Do you want info by team or by pack?', ['By team', 'By pack'])
20
+ if team_or_pack == 'By team'
21
+ teams = TeamSelector.multi_select(prompt)
22
+ selected_packs = Packs.all.select do |p|
23
+ teams.map(&:name).include?(CodeOwnership.for_package(p)&.name)
24
+ end
25
+ else
26
+ selected_packs = PackSelector.single_or_all_pack_multi_select(prompt, question_text: 'What pack(s) would you like info on?')
27
+ end
28
+
29
+ puts "You've selected #{selected_packs.count} packs. Wow! Here's all the info."
30
+
31
+ Private.get_info(packs: selected_packs)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ module Interface
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ interface!
12
+
13
+ sig { params(base: Class).void }
14
+ def self.included(base)
15
+ @use_cases ||= T.let(@use_cases, T.nilable(T::Array[Class]))
16
+ @use_cases ||= []
17
+ @use_cases << base
18
+ end
19
+
20
+ sig { returns(T::Array[Interface]) }
21
+ def self.all
22
+ T.unsafe(@use_cases).map(&:new)
23
+ end
24
+
25
+ sig { abstract.params(prompt: TTY::Prompt).void }
26
+ def perform!(prompt); end
27
+
28
+ sig { abstract.returns(String) }
29
+ def user_facing_name; end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class LintPackageYmlTodoFiles
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
+ Private.lint_package_todo_yml_files!
15
+ end
16
+
17
+ sig { override.returns(String) }
18
+ def user_facing_name
19
+ 'Lint .package_todo.yml files'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class LintPackageYmlFiles
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
+ packs = PackSelector.single_or_all_pack_multi_select(prompt, question_text: 'Please select the packs you want to lint package.yml files for')
15
+ Packs.lint_package_yml_files!(packs)
16
+ end
17
+
18
+ sig { override.returns(String) }
19
+ def user_facing_name
20
+ 'Lint packs/*/package.yml for one or more packs'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class MakePublic
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Make files or directories public'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ paths_relative_to_root = FileSelector.select(prompt)
20
+
21
+ Packs.make_public!(
22
+ paths_relative_to_root: paths_relative_to_root,
23
+ per_file_processors: [Packs::RubocopPostProcessor.new, Packs::CodeOwnershipPostProcessor.new]
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Move
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
+ pack = PackSelector.single_pack_select(prompt, question_text: 'Please select a destination pack')
15
+ paths_relative_to_root = FileSelector.select(prompt)
16
+
17
+ Packs.move_to_pack!(
18
+ pack_name: pack.name,
19
+ paths_relative_to_root: paths_relative_to_root,
20
+ per_file_processors: [Packs::RubocopPostProcessor.new, Packs::CodeOwnershipPostProcessor.new]
21
+ )
22
+ end
23
+
24
+ sig { override.returns(String) }
25
+ def user_facing_name
26
+ 'Move files'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class MoveToParent
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
+ child_pack = PackSelector.single_pack_select(prompt, question_text: 'Please select the child pack that will be nested')
15
+ parent_pack = PackSelector.single_pack_select(prompt, question_text: 'Please select the pack that will be the parent')
16
+ Packs.move_to_parent!(
17
+ parent_name: parent_pack.name,
18
+ pack_name: child_pack.name,
19
+ per_file_processors: [Packs::RubocopPostProcessor.new, Packs::CodeOwnershipPostProcessor.new]
20
+ )
21
+ end
22
+
23
+ sig { override.returns(String) }
24
+ def user_facing_name
25
+ 'Move a child pack to be nested under a parent pack'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ #
8
+ # We have not yet pulled QueryPackwerk into open source, so we cannot include it in this CLI yet
9
+ #
10
+ class Query
11
+ extend T::Sig
12
+ extend T::Helpers
13
+ include Interface
14
+
15
+ sig { override.returns(String) }
16
+ def user_facing_name
17
+ 'Query violations about a pack'
18
+ end
19
+
20
+ sig { override.params(prompt: TTY::Prompt).void }
21
+ def perform!(prompt)
22
+ selection = prompt.select('For one pack or all packs?', ['One pack', 'All packs'])
23
+ if selection == 'All packs'
24
+ # Probably should just make `list_top_dependency_violations` take in an array of things
25
+ # Better yet we might just want to replace these functions with `QueryPackwerk`
26
+ selected_pack = nil
27
+ else
28
+ selected_pack = PackSelector.single_pack_select(prompt).name
29
+ end
30
+
31
+ limit = prompt.ask('Specify the limit of constants to analyze', default: 10, convert: :integer)
32
+
33
+ selection = prompt.select('Are you interested in dependency or privacy violations?', %w[Dependency Privacy], default: 'Privacy')
34
+
35
+ if selection == 'Dependency'
36
+ Packs.list_top_dependency_violations(
37
+ pack_name: selected_pack,
38
+ limit: limit
39
+ )
40
+ else
41
+ Packs.list_top_privacy_violations(
42
+ pack_name: selected_pack,
43
+ limit: limit
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Rename
8
+ extend T::Sig
9
+ extend T::Helpers
10
+ include Interface
11
+
12
+ sig { override.returns(String) }
13
+ def user_facing_name
14
+ 'Rename a pack'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ prompt.warn(Private.rename_pack)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Update
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 update'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ system('bin/packwerk update')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # typed: strict
2
+
3
+ module Packs
4
+ module Private
5
+ module InteractiveCli
6
+ module UseCases
7
+ class Validate
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 validate (detects cycles)'
15
+ end
16
+
17
+ sig { override.params(prompt: TTY::Prompt).void }
18
+ def perform!(prompt)
19
+ system('bin/packwerk validate')
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ # typed: strict
2
+
3
+ require 'visualize_packs'
4
+
5
+ module Packs
6
+ module Private
7
+ module InteractiveCli
8
+ module UseCases
9
+ class Visualize
10
+ extend T::Sig
11
+ extend T::Helpers
12
+ include Interface
13
+
14
+ sig { override.params(prompt: TTY::Prompt).void }
15
+ def perform!(prompt)
16
+ teams_or_packs = prompt.select('Do you want the graph nodes to be teams or packs?', %w[Teams Packs])
17
+
18
+ if teams_or_packs == 'Teams'
19
+ teams = TeamSelector.multi_select(prompt)
20
+ VisualizePacks.team_graph!(teams)
21
+ else
22
+ by_name_or_by_owner = prompt.select('Do you select packs by name or by owner?', ['By name', 'By owner'])
23
+ if by_name_or_by_owner == 'By owner'
24
+ teams = TeamSelector.multi_select(prompt)
25
+ selected_packs = Packs.all.select do |p|
26
+ teams.map(&:name).include?(CodeOwnership.for_package(p)&.name)
27
+ end
28
+ else
29
+ selected_packs = PackSelector.single_or_all_pack_multi_select(prompt)
30
+ end
31
+
32
+ Private.visualize(packs: selected_packs)
33
+ end
34
+ end
35
+
36
+ sig { override.returns(String) }
37
+ def user_facing_name
38
+ 'Visualize pack relationships'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+
3
+ # https://github.com/piotrmurach/tty-prompt
4
+ require 'tty-prompt'
5
+
6
+ require 'packs/private/interactive_cli/team_selector'
7
+ require 'packs/private/interactive_cli/pack_selector'
8
+ require 'packs/private/interactive_cli/file_selector'
9
+ require 'packs/private/interactive_cli/use_cases/interface'
10
+ require 'packs/private/interactive_cli/use_cases/create'
11
+ require 'packs/private/interactive_cli/use_cases/move'
12
+ require 'packs/private/interactive_cli/use_cases/add_dependency'
13
+ require 'packs/private/interactive_cli/use_cases/get_info'
14
+ require 'packs/private/interactive_cli/use_cases/query'
15
+ require 'packs/private/interactive_cli/use_cases/make_public'
16
+ require 'packs/private/interactive_cli/use_cases/move_to_parent'
17
+ require 'packs/private/interactive_cli/use_cases/rename'
18
+ require 'packs/private/interactive_cli/use_cases/check'
19
+ require 'packs/private/interactive_cli/use_cases/update'
20
+ require 'packs/private/interactive_cli/use_cases/validate'
21
+ require 'packs/private/interactive_cli/use_cases/lint_package_yml_files'
22
+ require 'packs/private/interactive_cli/use_cases/visualize'
23
+
24
+ module Packs
25
+ module Private
26
+ module InteractiveCli
27
+ extend T::Sig
28
+
29
+ sig { params(prompt: T.nilable(TTY::Prompt)).void }
30
+ def self.start!(prompt: nil)
31
+ prompt ||= TTY::Prompt.new(interrupt: lambda {
32
+ puts "\n\nGoodbye! I hope you have a good day."
33
+ exit 1 })
34
+ help_text = '(Press ↑/↓ arrow to move, Enter to select and letters to filter)'
35
+ choice = prompt.select('Hello! What would you like to do?',
36
+ cycle: true,
37
+ filter: true,
38
+ help: help_text,
39
+ show_help: :always,
40
+ per_page: 15) do |menu|
41
+ menu.enum '.'
42
+
43
+ UseCases::Interface.all.each do |use_case|
44
+ menu.choice use_case.user_facing_name, use_case
45
+ end
46
+ end
47
+
48
+ choice.perform!(prompt)
49
+ end
50
+ end
51
+ end
52
+ end