parse_packwerk 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 00ce04288d8a8c4f46571bd181e8a3781d325116cd58e8078cdf142e9f74ffce
4
+ data.tar.gz: 23c1e53eab84fc472d116bf025c6ddb499f509da25246f47ffbdb857ec922f3f
5
+ SHA512:
6
+ metadata.gz: f24bf1e21e1800e35aa9c4fcdbf1c5e71efafd421d1ce7829461e620c30728a23dd78e099f91bd359fa69e0bca9cef71fabea4cc139598520520942afbcf9006
7
+ data.tar.gz: 6bd309198e121db0066178ec0fcddbef4b0187110a600bc716fe72d1ebbedab713767b25700d679bd219cafabd93ce6c0896cc0cf9cfa51c279c205105c30003
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # ParsePackwerk
2
+ This gem is meant to give a way to parse the various YML files that come with [`packwerk`](https://github.com/Shopify/packwerk).
3
+
4
+ # Usage
5
+ ```ruby
6
+ # Get all packages
7
+ # Note that currently, this does not respect configuration in `packwerk.yml`
8
+ packages = ParsePackwerk.all
9
+
10
+ # Get a single package with a given ame
11
+ package = ParsePackwerk.find('packs/my_pack')
12
+
13
+ # Get a structured `deprecated_references.yml` object a single package
14
+ deprecated_references = ParsePackwerk::DeprecatedReferences.for(package)
15
+
16
+ # Count violations of a particular type for a package
17
+ deprecated_references.violations.count(&:privacy?)
18
+ deprecated_references.violations.count(&:dependency?)
19
+
20
+ # Get the number of files a particular constant is violated in
21
+ deprecated_references.violations.select { |v| v.class_name == 'SomeConstant' }.sum { |v| v.files.count }
22
+ ```
23
+
24
+ # Why does this gem exist?
25
+ We generally recommend folks depend on `packwerk` rather than `parse_packwerk`. This gem is mostly a private implementation for other parts of the Big Rails modularization toolchain.
26
+
27
+ This gem exists for this toolchain for these reasons:
28
+
29
+ (A) `packwerk` is lacking public APIs for the behavior we want. It's close with `PackageSet`, but we need to also be able to parse violations.
30
+ (B) Certain critical, production runtime code-paths need to use this, and we want a simple, low-dependency, infrequently changing dependency for our production environment. One example of production usage is that `package.yml` files can store team ownership information, which is used when an error happens in production to route it to the right team.
31
+ (C) `packwerk` has heavy duty dependencies like rails and lots of others, and it adds a degree of maintenance cost and complexity that isn’t necessary when all we want to do is read YML files
32
+
33
+ Long-term, it might make sense for these reasons to extract out some of the parsing from `packwerk` into a separate gem similar to this so that we can leverage the ecosystem of tools associated with the idea of a “pack” in ways that are simple and safe for both development and production environments.
@@ -0,0 +1,52 @@
1
+ # typed: strict
2
+
3
+ module ParsePackwerk
4
+ class Configuration < T::Struct
5
+ extend T::Sig
6
+
7
+ const :exclude, T::Array[String]
8
+ const :package_paths, T::Array[String]
9
+
10
+ sig { returns(Configuration) }
11
+ def self.fetch
12
+ packwerk_yml_filename = Pathname.new(PACKWERK_YML_NAME)
13
+ if !File.exist?(packwerk_yml_filename)
14
+ raw_packwerk_config = {}
15
+ else
16
+ # when the YML file is empty or only contains comment, it gets parsed
17
+ # as the boolean `false` for some reason. this handles that case
18
+ raw_packwerk_config = YAML.load_file(packwerk_yml_filename) || {}
19
+ end
20
+
21
+ Configuration.new(
22
+ exclude: excludes(raw_packwerk_config),
23
+ package_paths: package_paths(raw_packwerk_config),
24
+ )
25
+ end
26
+
27
+ sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[String]) }
28
+ def self.excludes(config_hash)
29
+ specified_exclude = config_hash['exclude']
30
+ excludes = if specified_exclude.nil?
31
+ DEFAULT_EXCLUDE_GLOBS.dup
32
+ else
33
+ Array(specified_exclude)
34
+ end
35
+
36
+ excludes.push Bundler.bundle_path.join("**").to_s
37
+ end
38
+
39
+ sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[String]) }
40
+ def self.package_paths(config_hash)
41
+ specified_package_paths = config_hash['package_paths']
42
+ package_paths = if specified_package_paths.nil?
43
+ DEFAULT_PACKAGE_PATHS.dup
44
+ else
45
+ Array(specified_package_paths)
46
+ end
47
+
48
+ # We add the root package path always
49
+ package_paths.push '.'
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+
3
+ module ParsePackwerk
4
+ ROOT_PACKAGE_NAME = T.let('.'.freeze, String)
5
+ PACKAGE_YML_NAME = T.let('package.yml'.freeze, String)
6
+ PACKWERK_YML_NAME = T.let('packwerk.yml'.freeze, String)
7
+ DEPRECATED_REFERENCES_YML_NAME = T.let('deprecated_references.yml'.freeze, String)
8
+ ENFORCE_DEPENDENCIES = T.let('enforce_dependencies'.freeze, String)
9
+ ENFORCE_PRIVACY = T.let('enforce_privacy'.freeze, String)
10
+ METADATA = T.let('metadata'.freeze, String)
11
+ DEPENDENCIES = T.let('dependencies'.freeze, String)
12
+
13
+ # Since this metadata is unstructured YAML, it could be any type. We leave it to clients of `ParsePackwerk::Package`
14
+ # to add types based on their known usage of metadata.
15
+ MetadataYmlType = T.type_alias do
16
+ T::Hash[T.untyped, T.untyped]
17
+ end
18
+
19
+ DEFAULT_EXCLUDE_GLOBS = T.let(["{bin,node_modules,script,tmp,vendor}/**/*"], T::Array[String])
20
+ DEFAULT_PACKAGE_PATHS = T.let(['**/'], T::Array[String])
21
+ end
@@ -0,0 +1,50 @@
1
+ # typed: strict
2
+
3
+ module ParsePackwerk
4
+ class DeprecatedReferences < T::Struct
5
+ extend T::Sig
6
+
7
+ const :pathname, Pathname
8
+ const :violations, T::Array[Violation]
9
+
10
+ sig { params(package: Package).returns(DeprecatedReferences) }
11
+ def self.for(package)
12
+ deprecated_references_yml_pathname = package.directory.join(DEPRECATED_REFERENCES_YML_NAME)
13
+ DeprecatedReferences.from(deprecated_references_yml_pathname)
14
+ end
15
+
16
+ sig { params(pathname: Pathname).returns(DeprecatedReferences) }
17
+ def self.from(pathname)
18
+ if !pathname.exist?
19
+ new(
20
+ pathname: pathname.cleanpath,
21
+ violations: []
22
+ )
23
+ else
24
+ deprecated_references_loaded_yml = YAML.load_file(pathname)
25
+
26
+ all_violations = []
27
+ deprecated_references_loaded_yml&.each_key do |to_package_name|
28
+ deprecated_references_per_package = deprecated_references_loaded_yml[to_package_name]
29
+ deprecated_references_per_package.each_key do |class_name|
30
+ symbol_usage = deprecated_references_per_package[class_name]
31
+ files = symbol_usage['files']
32
+ violations = symbol_usage['violations']
33
+ if violations.include? 'dependency'
34
+ all_violations << Violation.new(type: 'dependency', to_package_name: to_package_name, class_name: class_name, files: files)
35
+ end
36
+
37
+ if violations.include? 'privacy'
38
+ all_violations << Violation.new(type: 'privacy', to_package_name: to_package_name, class_name: class_name, files: files)
39
+ end
40
+ end
41
+ end
42
+
43
+ new(
44
+ pathname: pathname.cleanpath,
45
+ violations: all_violations
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+
3
+ module ParsePackwerk
4
+ class Package < T::Struct
5
+ extend T::Sig
6
+
7
+ const :name, String
8
+ const :enforce_dependencies, T::Boolean
9
+ const :enforce_privacy, T::Boolean
10
+ const :metadata, MetadataYmlType
11
+ const :dependencies, T::Array[String]
12
+
13
+ sig { params(pathname: Pathname).returns(Package) }
14
+ def self.from(pathname)
15
+ package_loaded_yml = YAML.load_file(pathname)
16
+ package_name = pathname.dirname.cleanpath.to_s
17
+
18
+ new(
19
+ name: package_name,
20
+ enforce_dependencies: package_loaded_yml[ENFORCE_DEPENDENCIES] ? true : false,
21
+ enforce_privacy: package_loaded_yml[ENFORCE_PRIVACY] ? true : false,
22
+ metadata: package_loaded_yml[METADATA] || {},
23
+ dependencies: package_loaded_yml[DEPENDENCIES] || []
24
+ )
25
+ end
26
+
27
+ sig { returns(Pathname) }
28
+ def yml
29
+ Pathname.new(name).join(PACKAGE_YML_NAME).cleanpath
30
+ end
31
+
32
+ sig { returns(Pathname) }
33
+ def directory
34
+ Pathname.new(name).cleanpath
35
+ end
36
+
37
+ sig { returns(T::Boolean) }
38
+ def enforces_dependencies?
39
+ enforce_dependencies
40
+ end
41
+
42
+ sig { returns(T::Boolean) }
43
+ def enforces_privacy?
44
+ enforce_privacy
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+ require 'bundler'
6
+
7
+ module ParsePackwerk
8
+ class PackageSet
9
+ extend T::Sig
10
+
11
+ sig { params(package_pathspec: T::Array[String], exclude_pathspec: T::Array[String]).returns(T::Array[Package]) }
12
+ def self.from(package_pathspec:, exclude_pathspec:)
13
+ package_glob_patterns = package_pathspec.map do |pathspec|
14
+ File.join(pathspec, PACKAGE_YML_NAME)
15
+ end
16
+
17
+ # The T.unsafe is because the upstream RBI is wrong for Pathname.glob
18
+ package_paths = T.unsafe(Pathname).glob(package_glob_patterns)
19
+ .map(&:cleanpath)
20
+ .reject { |path| exclude_path?(exclude_pathspec, path) }
21
+
22
+ package_paths.uniq.map do |path|
23
+ Package.from(path)
24
+ end
25
+ end
26
+
27
+ sig { params(globs: T::Array[String], path: Pathname).returns(T::Boolean) }
28
+ def self.exclude_path?(globs, path)
29
+ globs.any? do |glob|
30
+ path.fnmatch(glob, File::FNM_EXTGLOB)
31
+ end
32
+ end
33
+
34
+ private_class_method :exclude_path?
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # typed: strict
2
+
3
+ module ParsePackwerk
4
+ class Violation < T::Struct
5
+ extend T::Sig
6
+
7
+ const :type, String
8
+ const :to_package_name, String
9
+ const :class_name, String
10
+ const :files, T::Array[String]
11
+
12
+ sig { returns(T::Boolean) }
13
+ def dependency?
14
+ type == 'dependency'
15
+ end
16
+
17
+ sig { returns(T::Boolean) }
18
+ def privacy?
19
+ type == 'privacy'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,90 @@
1
+ # typed: strict
2
+
3
+ require 'sorbet-runtime'
4
+ require 'yaml'
5
+ require 'pathname'
6
+ require 'parse_packwerk/constants'
7
+ require 'parse_packwerk/violation'
8
+ require 'parse_packwerk/deprecated_references'
9
+ require 'parse_packwerk/package'
10
+ require 'parse_packwerk/configuration'
11
+ require 'parse_packwerk/package_set'
12
+
13
+ module ParsePackwerk
14
+ class MissingConfiguration < StandardError
15
+ extend T::Sig
16
+
17
+ sig { params(packwerk_file_name: Pathname).void }
18
+ def initialize(packwerk_file_name)
19
+ super("We could not find a configuration file at #{packwerk_file_name}")
20
+ end
21
+ end
22
+
23
+ extend T::Sig
24
+
25
+ sig do
26
+ returns(T::Array[Package])
27
+ end
28
+ def self.all
29
+ PackageSet.from(package_pathspec: yml.package_paths, exclude_pathspec: yml.exclude)
30
+ end
31
+
32
+ sig { params(name: String).returns(T.nilable(Package)) }
33
+ def self.find(name)
34
+ packages_by_name[name]
35
+ end
36
+
37
+ sig { returns(ParsePackwerk::Configuration) }
38
+ def self.yml
39
+ Configuration.fetch
40
+ end
41
+
42
+ sig { params(package: ParsePackwerk::Package).void }
43
+ def self.write_package_yml!(package)
44
+ FileUtils.mkdir_p(package.directory)
45
+ File.open(package.yml, 'w') do |file|
46
+ # We do not use `YAML.dump` or `contents.to_yaml` because it seems like packwerk writes a variation of the default YAML spec.
47
+ # If you'd like to see the difference, change this to `package_yaml = YAML.dump(contents)` to and run tests to see the difference.
48
+ package_yml = <<~PACKAGEYML
49
+ enforce_dependencies: #{package.enforces_dependencies?}
50
+ enforce_privacy: #{package.enforces_privacy?}
51
+ PACKAGEYML
52
+
53
+ if package.dependencies.any?
54
+ dependencies = <<~STATEDDEPS
55
+ dependencies:
56
+ #{package.dependencies.map { |dep| " - #{dep}" }.join("\n")}
57
+ STATEDDEPS
58
+
59
+ package_yml += dependencies
60
+ end
61
+
62
+ if package.metadata.keys.any?
63
+ raw_yaml = YAML.dump(package.metadata)
64
+ stylized_yaml = raw_yaml.gsub("---\n", '')
65
+ indented_yaml = stylized_yaml.split("\n").map { |line| " #{line}" }.join("\n")
66
+
67
+ metadata = <<~METADATA
68
+ metadata:
69
+ #{indented_yaml}
70
+ METADATA
71
+
72
+ package_yml += metadata
73
+ end
74
+
75
+ file.write(package_yml)
76
+ end
77
+ end
78
+
79
+ # We memoize packages_by_name for fast lookup.
80
+ # Since Graph is an immutable value object, we can create indexes and general caching mechanisms safely.
81
+ sig { returns(T::Hash[String, Package]) }
82
+ def self.packages_by_name
83
+ @packages_by_name = T.let(@packages_by_name, T.nilable(T::Hash[String, Package]))
84
+ @packages_by_name ||= begin
85
+ all.map{|p| [p.name, p]}.to_h
86
+ end
87
+ end
88
+
89
+ private_class_method :packages_by_name
90
+ end
data/sorbet/config ADDED
@@ -0,0 +1,3 @@
1
+ --dir
2
+ .
3
+ --ignore=/vendor/bundle
@@ -0,0 +1,82 @@
1
+ # DO NOT EDIT MANUALLY
2
+ # This is an autogenerated file for types exported from the `hashdiff` gem.
3
+ # Please instead update this file by running `bin/tapioca sync`.
4
+
5
+ # typed: true
6
+
7
+ module Hashdiff
8
+ class << self
9
+ def best_diff(obj1, obj2, options = T.unsafe(nil), &block); end
10
+ def comparable?(obj1, obj2, strict = T.unsafe(nil)); end
11
+ def compare_values(obj1, obj2, options = T.unsafe(nil)); end
12
+ def count_diff(diffs); end
13
+ def count_nodes(obj); end
14
+ def custom_compare(method, key, obj1, obj2); end
15
+ def decode_property_path(path, delimiter = T.unsafe(nil)); end
16
+ def diff(obj1, obj2, options = T.unsafe(nil), &block); end
17
+ def diff_array_lcs(arraya, arrayb, options = T.unsafe(nil)); end
18
+ def lcs(arraya, arrayb, options = T.unsafe(nil)); end
19
+ def node(hash, parts); end
20
+ def patch!(obj, changes, options = T.unsafe(nil)); end
21
+ def prefix_append_array_index(prefix, array_index, opts); end
22
+ def prefix_append_key(prefix, key, opts); end
23
+ def similar?(obja, objb, options = T.unsafe(nil)); end
24
+ def unpatch!(obj, changes, options = T.unsafe(nil)); end
25
+
26
+ private
27
+
28
+ def any_hash_or_array?(obja, objb); end
29
+ end
30
+ end
31
+
32
+ class Hashdiff::CompareHashes
33
+ class << self
34
+ def call(obj1, obj2, opts = T.unsafe(nil)); end
35
+ end
36
+ end
37
+
38
+ class Hashdiff::LcsCompareArrays
39
+ class << self
40
+ def call(obj1, obj2, opts = T.unsafe(nil)); end
41
+ end
42
+ end
43
+
44
+ class Hashdiff::LinearCompareArray
45
+ def initialize(old_array, new_array, options); end
46
+
47
+ def call; end
48
+
49
+ private
50
+
51
+ def additions; end
52
+ def append_addition(item, index); end
53
+ def append_addititions_before_match(match_index); end
54
+ def append_deletion(item, index); end
55
+ def append_deletions_before_match(match_index); end
56
+ def append_differences(difference); end
57
+ def changes; end
58
+ def compare_at_index; end
59
+ def deletions; end
60
+ def differences; end
61
+ def expected_additions; end
62
+ def expected_additions=(_arg0); end
63
+ def extra_items_in_new_array?; end
64
+ def extra_items_in_old_array?; end
65
+ def index_of_match_after_additions; end
66
+ def index_of_match_after_deletions; end
67
+ def item_difference(old_item, new_item, item_index); end
68
+ def iterated_through_both_arrays?; end
69
+ def new_array; end
70
+ def new_index; end
71
+ def new_index=(_arg0); end
72
+ def old_array; end
73
+ def old_index; end
74
+ def old_index=(_arg0); end
75
+ def options; end
76
+
77
+ class << self
78
+ def call(old_array, new_array, options = T.unsafe(nil)); end
79
+ end
80
+ end
81
+
82
+ Hashdiff::VERSION = T.let(T.unsafe(nil), String)
@@ -0,0 +1,34 @@
1
+ # DO NOT EDIT MANUALLY
2
+ # This is an autogenerated file for types exported from the `rspec` gem.
3
+ # Please instead update this file by running `bin/tapioca sync`.
4
+
5
+ # typed: true
6
+
7
+ module RSpec
8
+ class << self
9
+ def clear_examples; end
10
+ def configuration; end
11
+ def configuration=(_arg0); end
12
+ def configure; end
13
+ def const_missing(name); end
14
+ def context(*args, &example_group_block); end
15
+ def current_example; end
16
+ def current_example=(example); end
17
+ def describe(*args, &example_group_block); end
18
+ def example_group(*args, &example_group_block); end
19
+ def fcontext(*args, &example_group_block); end
20
+ def fdescribe(*args, &example_group_block); end
21
+ def reset; end
22
+ def shared_context(name, *args, &block); end
23
+ def shared_examples(name, *args, &block); end
24
+ def shared_examples_for(name, *args, &block); end
25
+ def world; end
26
+ def world=(_arg0); end
27
+ def xcontext(*args, &example_group_block); end
28
+ def xdescribe(*args, &example_group_block); end
29
+ end
30
+ end
31
+
32
+ RSpec::MODULES_TO_AUTOLOAD = T.let(T.unsafe(nil), Hash)
33
+ module RSpec::Version; end
34
+ RSpec::Version::STRING = T.let(T.unsafe(nil), String)
@@ -0,0 +1,4 @@
1
+ module RSpec
2
+ module Matchers
3
+ end
4
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parse_packwerk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.0
5
+ platform: ruby
6
+ authors:
7
+ - Gusto Engineers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sorbet-runtime
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.2.16
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.16
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sorbet
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tapioca
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: hashdiff
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: awesome_print
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: A low-dependency gem for parsing and writing packwerk YML files
126
+ email:
127
+ - dev@gusto.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - README.md
133
+ - lib/parse_packwerk.rb
134
+ - lib/parse_packwerk/configuration.rb
135
+ - lib/parse_packwerk/constants.rb
136
+ - lib/parse_packwerk/deprecated_references.rb
137
+ - lib/parse_packwerk/package.rb
138
+ - lib/parse_packwerk/package_set.rb
139
+ - lib/parse_packwerk/violation.rb
140
+ - sorbet/config
141
+ - sorbet/rbi/gems/hashdiff@1.0.1.rbi
142
+ - sorbet/rbi/gems/rspec@3.10.0.rbi
143
+ - sorbet/rbi/gems/rspec_override.rbi
144
+ homepage: https://github.com/bigrails/parse_packwerk
145
+ licenses:
146
+ - MIT
147
+ metadata:
148
+ homepage_uri: https://github.com/bigrails/parse_packwerk
149
+ source_code_uri: https://github.com/bigrails/parse_packwerk
150
+ changelog_uri: https://github.com/bigrails/parse_packwerk/releases
151
+ allowed_push_host: https://rubygems.org
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '2.6'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubygems_version: 3.3.7
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: A low-dependency gem for parsing and writing packwerk YML files
171
+ test_files: []