parse_packwerk 0.10.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.
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: []