packwerk 1.0.0

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