packwerk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  3. data/.github/probots.yml +2 -0
  4. data/.github/pull_request_template.md +27 -0
  5. data/.github/workflows/ci.yml +50 -0
  6. data/.gitignore +12 -0
  7. data/.rubocop.yml +46 -0
  8. data/.ruby-version +1 -0
  9. data/CODEOWNERS +1 -0
  10. data/CODE_OF_CONDUCT.md +76 -0
  11. data/CONTRIBUTING.md +17 -0
  12. data/Gemfile +22 -0
  13. data/Gemfile.lock +236 -0
  14. data/LICENSE.md +7 -0
  15. data/README.md +73 -0
  16. data/Rakefile +13 -0
  17. data/TROUBLESHOOT.md +67 -0
  18. data/USAGE.md +250 -0
  19. data/bin/console +15 -0
  20. data/bin/setup +8 -0
  21. data/dev.yml +32 -0
  22. data/docs/cohesion.png +0 -0
  23. data/exe/packwerk +6 -0
  24. data/lib/packwerk.rb +44 -0
  25. data/lib/packwerk/application_validator.rb +343 -0
  26. data/lib/packwerk/association_inspector.rb +44 -0
  27. data/lib/packwerk/checking_deprecated_references.rb +40 -0
  28. data/lib/packwerk/cli.rb +238 -0
  29. data/lib/packwerk/configuration.rb +82 -0
  30. data/lib/packwerk/const_node_inspector.rb +44 -0
  31. data/lib/packwerk/constant_discovery.rb +60 -0
  32. data/lib/packwerk/constant_name_inspector.rb +22 -0
  33. data/lib/packwerk/dependency_checker.rb +28 -0
  34. data/lib/packwerk/deprecated_references.rb +92 -0
  35. data/lib/packwerk/file_processor.rb +43 -0
  36. data/lib/packwerk/files_for_processing.rb +67 -0
  37. data/lib/packwerk/formatters/progress_formatter.rb +46 -0
  38. data/lib/packwerk/generators/application_validation.rb +62 -0
  39. data/lib/packwerk/generators/configuration_file.rb +69 -0
  40. data/lib/packwerk/generators/inflections_file.rb +43 -0
  41. data/lib/packwerk/generators/root_package.rb +37 -0
  42. data/lib/packwerk/generators/templates/inflections.yml +6 -0
  43. data/lib/packwerk/generators/templates/package.yml +17 -0
  44. data/lib/packwerk/generators/templates/packwerk +23 -0
  45. data/lib/packwerk/generators/templates/packwerk.yml.erb +23 -0
  46. data/lib/packwerk/generators/templates/packwerk_validator_test.rb +11 -0
  47. data/lib/packwerk/graph.rb +74 -0
  48. data/lib/packwerk/inflections/custom.rb +33 -0
  49. data/lib/packwerk/inflections/default.rb +73 -0
  50. data/lib/packwerk/inflector.rb +41 -0
  51. data/lib/packwerk/node.rb +259 -0
  52. data/lib/packwerk/node_processor.rb +49 -0
  53. data/lib/packwerk/node_visitor.rb +22 -0
  54. data/lib/packwerk/offense.rb +44 -0
  55. data/lib/packwerk/output_styles.rb +41 -0
  56. data/lib/packwerk/package.rb +56 -0
  57. data/lib/packwerk/package_set.rb +59 -0
  58. data/lib/packwerk/parsed_constant_definitions.rb +62 -0
  59. data/lib/packwerk/parsers.rb +23 -0
  60. data/lib/packwerk/parsers/erb.rb +66 -0
  61. data/lib/packwerk/parsers/factory.rb +34 -0
  62. data/lib/packwerk/parsers/ruby.rb +42 -0
  63. data/lib/packwerk/privacy_checker.rb +45 -0
  64. data/lib/packwerk/reference.rb +6 -0
  65. data/lib/packwerk/reference_extractor.rb +81 -0
  66. data/lib/packwerk/reference_lister.rb +23 -0
  67. data/lib/packwerk/run_context.rb +103 -0
  68. data/lib/packwerk/sanity_checker.rb +10 -0
  69. data/lib/packwerk/spring_command.rb +28 -0
  70. data/lib/packwerk/updating_deprecated_references.rb +51 -0
  71. data/lib/packwerk/version.rb +6 -0
  72. data/lib/packwerk/violation_type.rb +13 -0
  73. data/library.yml +6 -0
  74. data/packwerk.gemspec +58 -0
  75. data/service.yml +6 -0
  76. data/shipit.rubygems.yml +1 -0
  77. data/sorbet/config +2 -0
  78. data/sorbet/rbi/gems/actioncable@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +840 -0
  79. data/sorbet/rbi/gems/actionmailbox@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +571 -0
  80. data/sorbet/rbi/gems/actionmailer@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +568 -0
  81. data/sorbet/rbi/gems/actionpack@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +5216 -0
  82. data/sorbet/rbi/gems/actiontext@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +663 -0
  83. data/sorbet/rbi/gems/actionview@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +2504 -0
  84. data/sorbet/rbi/gems/activejob@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +635 -0
  85. data/sorbet/rbi/gems/activemodel@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +1201 -0
  86. data/sorbet/rbi/gems/activerecord@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +8011 -0
  87. data/sorbet/rbi/gems/activestorage@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +904 -0
  88. data/sorbet/rbi/gems/activesupport@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +3888 -0
  89. data/sorbet/rbi/gems/ast@2.4.1.rbi +54 -0
  90. data/sorbet/rbi/gems/better_html@1.0.15.rbi +317 -0
  91. data/sorbet/rbi/gems/builder@3.2.4.rbi +8 -0
  92. data/sorbet/rbi/gems/byebug@11.1.3.rbi +8 -0
  93. data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
  94. data/sorbet/rbi/gems/colorize@0.8.1.rbi +40 -0
  95. data/sorbet/rbi/gems/commander@4.5.2.rbi +8 -0
  96. data/sorbet/rbi/gems/concurrent-ruby@1.1.6.rbi +1966 -0
  97. data/sorbet/rbi/gems/constant_resolver@0.1.5.rbi +26 -0
  98. data/sorbet/rbi/gems/crass@1.0.6.rbi +138 -0
  99. data/sorbet/rbi/gems/erubi@1.9.0.rbi +39 -0
  100. data/sorbet/rbi/gems/globalid@0.4.2.rbi +178 -0
  101. data/sorbet/rbi/gems/highline@2.0.3.rbi +8 -0
  102. data/sorbet/rbi/gems/html_tokenizer@0.0.7.rbi +46 -0
  103. data/sorbet/rbi/gems/i18n@1.8.2.rbi +633 -0
  104. data/sorbet/rbi/gems/jaro_winkler@1.5.4.rbi +8 -0
  105. data/sorbet/rbi/gems/loofah@2.5.0.rbi +272 -0
  106. data/sorbet/rbi/gems/m@1.5.1.rbi +108 -0
  107. data/sorbet/rbi/gems/mail@2.7.1.rbi +2490 -0
  108. data/sorbet/rbi/gems/marcel@0.3.3.rbi +30 -0
  109. data/sorbet/rbi/gems/method_source@1.0.0.rbi +76 -0
  110. data/sorbet/rbi/gems/mimemagic@0.3.5.rbi +47 -0
  111. data/sorbet/rbi/gems/mini_mime@1.0.2.rbi +71 -0
  112. data/sorbet/rbi/gems/mini_portile2@2.4.0.rbi +8 -0
  113. data/sorbet/rbi/gems/minitest@5.14.0.rbi +542 -0
  114. data/sorbet/rbi/gems/mocha@1.11.2.rbi +964 -0
  115. data/sorbet/rbi/gems/nio4r@2.5.2.rbi +89 -0
  116. data/sorbet/rbi/gems/nokogiri@1.10.9.rbi +1608 -0
  117. data/sorbet/rbi/gems/parallel@1.19.1.rbi +8 -0
  118. data/sorbet/rbi/gems/parlour@4.0.1.rbi +561 -0
  119. data/sorbet/rbi/gems/parser@2.7.1.4.rbi +1632 -0
  120. data/sorbet/rbi/gems/pry@0.13.1.rbi +8 -0
  121. data/sorbet/rbi/gems/rack-test@1.1.0.rbi +335 -0
  122. data/sorbet/rbi/gems/rack@2.2.2.rbi +1730 -0
  123. data/sorbet/rbi/gems/rails-dom-testing@2.0.3.rbi +123 -0
  124. data/sorbet/rbi/gems/rails-html-sanitizer@1.3.0.rbi +213 -0
  125. data/sorbet/rbi/gems/rails@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +8 -0
  126. data/sorbet/rbi/gems/railties@6.1.0.alpha-d80c18a391e33552ae2d943e68af56946f883f65.rbi +869 -0
  127. data/sorbet/rbi/gems/rainbow@3.0.0.rbi +155 -0
  128. data/sorbet/rbi/gems/rake@13.0.1.rbi +841 -0
  129. data/sorbet/rbi/gems/rexml@3.2.4.rbi +8 -0
  130. data/sorbet/rbi/gems/rubocop-performance@1.5.2.rbi +8 -0
  131. data/sorbet/rbi/gems/rubocop-shopify@1.0.2.rbi +8 -0
  132. data/sorbet/rbi/gems/rubocop-sorbet@0.3.7.rbi +8 -0
  133. data/sorbet/rbi/gems/rubocop@0.82.0.rbi +8 -0
  134. data/sorbet/rbi/gems/ruby-progressbar@1.10.1.rbi +8 -0
  135. data/sorbet/rbi/gems/smart_properties@1.15.0.rbi +168 -0
  136. data/sorbet/rbi/gems/spoom@1.0.4.rbi +418 -0
  137. data/sorbet/rbi/gems/spring@2.1.0.rbi +160 -0
  138. data/sorbet/rbi/gems/sprockets-rails@3.2.1.rbi +431 -0
  139. data/sorbet/rbi/gems/sprockets@4.0.0.rbi +1132 -0
  140. data/sorbet/rbi/gems/tapioca@0.4.5.rbi +518 -0
  141. data/sorbet/rbi/gems/thor@1.0.1.rbi +892 -0
  142. data/sorbet/rbi/gems/tzinfo@2.0.2.rbi +547 -0
  143. data/sorbet/rbi/gems/unicode-display_width@1.7.0.rbi +8 -0
  144. data/sorbet/rbi/gems/websocket-driver@0.7.1.rbi +438 -0
  145. data/sorbet/rbi/gems/websocket-extensions@0.1.4.rbi +71 -0
  146. data/sorbet/rbi/gems/zeitwerk@2.3.0.rbi +8 -0
  147. data/sorbet/tapioca/require.rb +25 -0
  148. data/static/packwerk-check-demo.png +0 -0
  149. data/static/packwerk_check.gif +0 -0
  150. data/static/packwerk_check_violation.gif +0 -0
  151. data/static/packwerk_update.gif +0 -0
  152. data/static/packwerk_validate.gif +0 -0
  153. metadata +341 -0
@@ -0,0 +1,28 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "packwerk/violation_type"
5
+
6
+ module Packwerk
7
+ class DependencyChecker
8
+ def violation_type
9
+ ViolationType::Dependency
10
+ end
11
+
12
+ def invalid_reference?(reference, reference_lister)
13
+ return unless reference.source_package
14
+ return unless reference.source_package.enforce_dependencies?
15
+ return if reference.source_package.dependency?(reference.constant.package)
16
+ return if reference_lister.listed?(reference, violation_type: violation_type)
17
+ true
18
+ end
19
+
20
+ def message_for(reference)
21
+ "Dependency violation: #{reference.constant.name} belongs to '#{reference.constant.package}', but " \
22
+ "'#{reference.source_package}' does not specify a dependency on " \
23
+ "'#{reference.constant.package}'.\n" \
24
+ "Are we missing an abstraction?\n" \
25
+ "Is the code making the reference, and the referenced constant, in the right packages?\n"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,92 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "yaml"
6
+
7
+ require "packwerk/reference"
8
+ require "packwerk/reference_lister"
9
+ require "packwerk/violation_type"
10
+
11
+ module Packwerk
12
+ class DeprecatedReferences
13
+ extend T::Sig
14
+ include ReferenceLister
15
+
16
+ def initialize(package, filepath)
17
+ @package = package
18
+ @filepath = filepath
19
+ @new_entries = {}
20
+ end
21
+
22
+ sig do
23
+ params(reference: Packwerk::Reference, violation_type: ViolationType)
24
+ .returns(T::Boolean)
25
+ .override
26
+ end
27
+ def listed?(reference, violation_type:)
28
+ violated_constants_found = deprecated_references.dig(reference.constant.package.name, reference.constant.name)
29
+ return false unless violated_constants_found
30
+
31
+ violated_constant_in_file = violated_constants_found.fetch("files", []).include?(reference.relative_path)
32
+ return false unless violated_constant_in_file
33
+
34
+ violated_constants_found.fetch("violations", []).include?(violation_type.serialize)
35
+ end
36
+
37
+ def add_entries(reference, violation_type)
38
+ package_violations = @new_entries.fetch(reference.constant.package.name, {})
39
+ entries_for_file = package_violations[reference.constant.name] ||= {}
40
+
41
+ entries_for_file["violations"] ||= []
42
+ entries_for_file["violations"] << violation_type
43
+
44
+ entries_for_file["files"] ||= []
45
+ entries_for_file["files"] << reference.relative_path.to_s
46
+
47
+ @new_entries[reference.constant.package.name] = package_violations
48
+ end
49
+
50
+ def dump
51
+ if @new_entries.empty?
52
+ File.delete(@filepath) if File.exist?(@filepath)
53
+ else
54
+ prepare_entries_for_dump
55
+ message = <<~MESSAGE
56
+ # This file contains a list of dependencies that are not part of the long term plan for #{@package.name}.
57
+ # We should generally work to reduce this list, but not at the expense of actually getting work done.
58
+ #
59
+ # You can regenerate this file using the following command:
60
+ #
61
+ # bundle exec packwerk update #{@package.name}
62
+ MESSAGE
63
+ File.open(@filepath, "w") do |f|
64
+ f.write(message)
65
+ f.write(@new_entries.to_yaml)
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def prepare_entries_for_dump
73
+ @new_entries.each do |package_name, package_violations|
74
+ package_violations.each do |_, entries_for_file|
75
+ entries_for_file["violations"].sort!.uniq!
76
+ entries_for_file["files"].sort!.uniq!
77
+ end
78
+ @new_entries[package_name] = package_violations.sort.to_h
79
+ end
80
+
81
+ @new_entries = @new_entries.sort.to_h
82
+ end
83
+
84
+ def deprecated_references
85
+ @deprecated_references ||= if File.exist?(@filepath)
86
+ YAML.load_file(@filepath) || {}
87
+ else
88
+ {}
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,43 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "ast/node"
5
+
6
+ require "packwerk/node"
7
+ require "packwerk/offense"
8
+ require "packwerk/parsers"
9
+
10
+ module Packwerk
11
+ class FileProcessor
12
+ class UnknownFileTypeResult < Offense
13
+ def initialize(file:)
14
+ super(file: file, message: "unknown file type")
15
+ end
16
+ end
17
+
18
+ def initialize(run_context:, parser_factory: nil)
19
+ @run_context = run_context
20
+ @parser_factory = parser_factory || Packwerk::Parsers::Factory.instance
21
+ end
22
+
23
+ def call(file_path)
24
+ parser = @parser_factory.for_path(file_path)
25
+ return [UnknownFileTypeResult.new(file: file_path)] if parser.nil?
26
+
27
+ node = File.open(file_path, "r", external_encoding: Encoding::UTF_8) do |file|
28
+ parser.call(io: file, file_path: file_path)
29
+ rescue Parsers::ParseError => e
30
+ return [e.result]
31
+ end
32
+
33
+ result = []
34
+ if node
35
+ @node_processor = @run_context.node_processor_for(filename: file_path, ast_node: node)
36
+ node_visitor = Packwerk::NodeVisitor.new(node_processor: @node_processor)
37
+
38
+ node_visitor.visit(node, ancestors: [], result: result)
39
+ end
40
+ result
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ class FilesForProcessing
6
+ class << self
7
+ def fetch(paths:, configuration:)
8
+ new(paths, configuration).files
9
+ end
10
+ end
11
+
12
+ def initialize(paths, configuration)
13
+ @paths = paths
14
+ @configuration = configuration
15
+ end
16
+
17
+ def files
18
+ include_files = if custom_files.empty?
19
+ configured_included_files
20
+ else
21
+ custom_files
22
+ end
23
+
24
+ include_files - configured_excluded_files
25
+ end
26
+
27
+ private
28
+
29
+ def custom_files
30
+ @custom_files ||= @paths.flat_map do |path|
31
+ path = File.expand_path(path, @configuration.root_path)
32
+ if File.file?(path)
33
+ path
34
+ else
35
+ custom_included_files(path)
36
+ end
37
+ end
38
+ end
39
+
40
+ def custom_included_files(path)
41
+ # Note, assuming include globs are always relative paths
42
+ absolute_includes = @configuration.include.map do |glob|
43
+ File.expand_path(glob, @configuration.root_path)
44
+ end
45
+
46
+ Dir.glob([File.join(path, "**", "*")]).select do |file_path|
47
+ absolute_includes.any? do |pattern|
48
+ File.fnmatch?(pattern, file_path, File::FNM_EXTGLOB)
49
+ end
50
+ end
51
+ end
52
+
53
+ def configured_included_files
54
+ files_for_globs(@configuration.include)
55
+ end
56
+
57
+ def configured_excluded_files
58
+ files_for_globs(@configuration.exclude)
59
+ end
60
+
61
+ def files_for_globs(globs)
62
+ globs
63
+ .flat_map { |glob| Dir[File.expand_path(glob, @configuration.root_path)] }
64
+ .uniq
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,46 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "benchmark"
5
+
6
+ require "packwerk/inflector"
7
+ require "packwerk/output_styles"
8
+
9
+ module Packwerk
10
+ module Formatters
11
+ class ProgressFormatter
12
+ def initialize(out, style: OutputStyles::Plain)
13
+ @out = out
14
+ @style = style
15
+ end
16
+
17
+ def started(target_files)
18
+ files_size = target_files.size
19
+ files_string = Inflector.default.pluralize("file", files_size)
20
+ @out.puts("📦 Packwerk is inspecting #{files_size} #{files_string}")
21
+ end
22
+
23
+ def started_validation
24
+ @out.puts("📦 Packwerk is running validation...")
25
+
26
+ execution_time = Benchmark.realtime { yield }
27
+ finished(execution_time)
28
+
29
+ @out.puts("✅ Packages are valid. Use `packwerk check` to run static checks.")
30
+ end
31
+
32
+ def mark_as_inspected
33
+ @out.print(".")
34
+ end
35
+
36
+ def mark_as_failed
37
+ @out.print("#{@style.error}E#{@style.reset}")
38
+ end
39
+
40
+ def finished(execution_time)
41
+ @out.puts
42
+ @out.puts("📦 Finished in #{execution_time.round(2)} seconds")
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,62 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Generators
6
+ class ApplicationValidation
7
+ class << self
8
+ def generate(for_rails_app: false, root: ".", out: $stdout)
9
+ new(root, out: out).generate(for_rails_app: for_rails_app)
10
+ end
11
+ end
12
+
13
+ def initialize(root, out: $stdout)
14
+ @root = root
15
+ @out = out
16
+ end
17
+
18
+ def generate(for_rails_app:)
19
+ @out.puts("📦 Generating application validator...")
20
+ if for_rails_app
21
+ generate_packwerk_validate_script
22
+ else
23
+ generate_validation_test
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def generate_packwerk_validate_script
30
+ destination_file_path = File.join(@root, "bin")
31
+ FileUtils.mkdir_p(destination_file_path)
32
+
33
+ if File.exist?(File.join(destination_file_path, "packwerk"))
34
+ @out.puts("⚠️ Packwerk application validation bin script already exists.")
35
+ return true
36
+ end
37
+
38
+ source_file_path = File.expand_path("../templates/packwerk", __FILE__)
39
+ FileUtils.cp(source_file_path, destination_file_path)
40
+
41
+ @out.puts("✅ Packwerk application validation bin script generated in #{destination_file_path}")
42
+ true
43
+ end
44
+
45
+ def generate_validation_test
46
+ destination_file_path = File.join(@root, "test")
47
+ FileUtils.mkdir_p(destination_file_path)
48
+
49
+ if File.exist?(File.join(destination_file_path, "packwerk_validator_test.rb"))
50
+ @out.puts("⚠️ Packwerk application validation test already exists.")
51
+ return true
52
+ end
53
+
54
+ source_file_path = File.expand_path("../templates/packwerk_validator_test.rb", __FILE__)
55
+ FileUtils.cp(source_file_path, destination_file_path)
56
+
57
+ @out.puts("✅ Packwerk application validation test generated in #{destination_file_path}")
58
+ true
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "packwerk/configuration"
5
+ require "erb"
6
+
7
+ module Packwerk
8
+ module Generators
9
+ class ConfigurationFile
10
+ extend T::Sig
11
+
12
+ CONFIGURATION_TEMPLATE_FILE_PATH = "templates/packwerk.yml.erb"
13
+
14
+ class << self
15
+ def generate(load_paths:, root:, out:)
16
+ new(load_paths: load_paths, root: root, out: out).generate
17
+ end
18
+ end
19
+
20
+ sig { params(load_paths: T::Array[String], root: String, out: T.any(StringIO, IO)).void }
21
+ def initialize(load_paths:, root:, out: $stdout)
22
+ @load_paths = load_paths
23
+ @root = root
24
+ @out = out
25
+
26
+ set_template_variables
27
+ end
28
+
29
+ sig { returns(T::Boolean) }
30
+ def generate
31
+ @out.puts("📦 Generating Packwerk configuration file...")
32
+ default_config_path = File.join(@root, ::Packwerk::Configuration::DEFAULT_CONFIG_PATH)
33
+
34
+ if File.exist?(default_config_path)
35
+ @out.puts("⚠️ Packwerk configuration file already exists.")
36
+ return true
37
+ end
38
+
39
+ File.write(default_config_path, render)
40
+
41
+ @out.puts("✅ Packwerk configuration file generated in #{default_config_path}")
42
+ true
43
+ end
44
+
45
+ private
46
+
47
+ def set_template_variables
48
+ @load_paths_formatted = if @load_paths.empty?
49
+ "# load_paths:\n# - 'app/models'\n"
50
+ else
51
+ @load_paths.map { |path| "- #{path}\n" }.join
52
+ end
53
+
54
+ @load_paths_comment = unless @load_paths.empty?
55
+ "# These load paths were auto generated by Packwerk.\nload_paths:\n"
56
+ end
57
+ end
58
+
59
+ def render
60
+ ERB.new(template, trim_mode: "-").result(binding)
61
+ end
62
+
63
+ def template
64
+ template_file_path = File.join(__dir__, CONFIGURATION_TEMPLATE_FILE_PATH)
65
+ File.read(template_file_path)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,43 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Packwerk
5
+ module Generators
6
+ class InflectionsFile
7
+ extend T::Sig
8
+
9
+ class << self
10
+ def generate(root:, out:)
11
+ new(root, out: out).generate
12
+ end
13
+ end
14
+
15
+ def initialize(root, out: $stdout)
16
+ @root = root
17
+ @out = out
18
+ end
19
+
20
+ sig { returns(T::Boolean) }
21
+ def generate
22
+ ruby_inflection_file_exist = Dir.glob("#{@root}/**/inflections.rb").any?
23
+ yaml_inflection_file_exist = Dir.glob("#{@root}/**/inflections.yml").any?
24
+
25
+ if !ruby_inflection_file_exist || yaml_inflection_file_exist
26
+ return true
27
+ end
28
+
29
+ @out.puts("📦 Generating `inflections.yml` file...")
30
+
31
+ destination_file_path = File.join(@root, "config")
32
+ FileUtils.mkdir_p(destination_file_path)
33
+
34
+ source_file_path = File.join(__dir__, "/templates/inflections.yml")
35
+ FileUtils.cp(source_file_path, destination_file_path)
36
+
37
+ @out.puts("✅ `inflections.yml` generated in #{destination_file_path}")
38
+
39
+ true
40
+ end
41
+ end
42
+ end
43
+ end