modularization_statistics 1.33.0 → 1.34.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +55 -0
- data/lib/modularization_statistics/private/source_code_file.rb +1 -1
- data/lib/modularization_statistics.rb +1 -1
- data/sorbet/rbi/gems/{code_ownership@1.23.0.rbi → code_ownership@1.28.0.rbi} +27 -26
- data/sorbet/rbi/gems/{bigrails-teams@0.1.0.rbi → code_teams@1.0.0.rbi} +25 -25
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0d44cafa4c07aed6fb4248234f60f70e6a0f9124b7ad6184f515feb22ef972a
|
4
|
+
data.tar.gz: 3a1ae5714c23a52c63c2cff8616a9f739e3bb68a44abd8e76e35b864e792c2c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 750643b2ff0d4705f69c66f049be32d070e50189173e2b44dd3975fde11ed8052c182b24729831e5e4553bc5f23c8d88f76ee5a0b70a85f5f1842c855bbd0d7e
|
7
|
+
data.tar.gz: 434a4e77901c9af6783de0db879daaaf1bb4918835f1a147a0cc5aa0ff7c4c35ba12e306e23db3a65a9b875b97c8503b6e49a2c93b2e2c4014c9c1d01845afd2
|
data/README.md
CHANGED
@@ -43,6 +43,61 @@ ModularizationStatistics.report_to_datadog!(
|
|
43
43
|
)
|
44
44
|
```
|
45
45
|
|
46
|
+
It's recommended to run this in CI on the main/development branch so each new commit has metrics emitted for it.
|
47
|
+
|
48
|
+
# Tracking Privacy and Dependency Violations Reliably
|
49
|
+
With [`packwerk`](https://github.com/Shopify/packwerk), privacy and dependency violations do not show up until a package has set `enforce_privacy` and `enforce_dependency` (respectively) to `true`. As such, when you're first starting off, you'll see no violations, and then periodic large increases as teams start using these protections. If you're interested in looking at privacy and dependency violations over time as if all packages were enforcing dependencies and privacy the whole time, we recommend setting these values to be true before running modularization statistics in your CI.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require 'modularization_statistics'
|
53
|
+
|
54
|
+
namespace(:modularization) do
|
55
|
+
desc(
|
56
|
+
'Publish modularization stats to datadog. ' \
|
57
|
+
'Example: bin/rails "modularization:upload_statistics"'
|
58
|
+
)
|
59
|
+
task(:upload_statistics, [:verbose] => :environment) do |_, args|
|
60
|
+
ignored_paths = Pathname.glob('spec/fixtures/**/**')
|
61
|
+
source_code_pathnames = Pathname.glob('{app,components,lib,packs,spec}/**/**').select(&:file?) - ignored_paths
|
62
|
+
|
63
|
+
# To correctly track violations, we rewrite all `package.yml` files with
|
64
|
+
# `enforce_dependencies` and `enforce_privacy` set to true, then update deprecations.
|
65
|
+
old_packages = ParsePackwerk.all
|
66
|
+
old_packages.each do |package|
|
67
|
+
new_package = ParsePackwerk::Package.new(
|
68
|
+
dependencies: package.dependencies,
|
69
|
+
enforce_dependencies: true,
|
70
|
+
enforce_privacy: true,
|
71
|
+
metadata: package.metadata,
|
72
|
+
name: package.name
|
73
|
+
)
|
74
|
+
ParsePackwerk.write_package_yml!(new_package)
|
75
|
+
end
|
76
|
+
|
77
|
+
Packwerk::Cli.new.execute_command(['update-deprecations'])
|
78
|
+
|
79
|
+
# Now we reset it back so that the protection values are the same as the native packwerk configuration
|
80
|
+
old_packages.each do |package|
|
81
|
+
new_package = ParsePackwerk::Package.new(
|
82
|
+
dependencies: package.dependencies,
|
83
|
+
enforce_dependencies: package.enforce_dependencies,
|
84
|
+
enforce_privacy: package.enforce_privacy,
|
85
|
+
metadata: package.metadata,
|
86
|
+
name: package.name
|
87
|
+
)
|
88
|
+
ParsePackwerk.write_package_yml!(new_package)
|
89
|
+
end
|
90
|
+
|
91
|
+
ModularizationStatistics.report_to_datadog!(
|
92
|
+
datadog_client: Dogapi::Client.new(ENV.fetch('DATADOG_API_KEY')),
|
93
|
+
app_name: Rails.application.class.module_parent_name,
|
94
|
+
source_code_pathnames: source_code_pathnames,
|
95
|
+
verbose: args[:verbose] == 'true' || false
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
46
101
|
# Using Other Observability Tools
|
47
102
|
|
48
103
|
Right now this tool sends metrics to DataDog early. However, if you want to use this with other tools, you can call `ModularizationStatistics.get_metrics(...)` to get generic metrics that you can then send to whatever observability provider you use.
|
@@ -7,7 +7,7 @@ module ModularizationStatistics
|
|
7
7
|
|
8
8
|
const :is_componentized_file, T::Boolean
|
9
9
|
const :is_packaged_file, T::Boolean
|
10
|
-
const :team_owner, T.nilable(
|
10
|
+
const :team_owner, T.nilable(CodeTeams::Team)
|
11
11
|
const :pathname, Pathname
|
12
12
|
|
13
13
|
sig { returns(T::Boolean) }
|
@@ -12,18 +12,18 @@ module CodeOwnership
|
|
12
12
|
sig do
|
13
13
|
params(
|
14
14
|
backtrace: T.nilable(T::Array[::String]),
|
15
|
-
excluded_teams: T::Array[::
|
16
|
-
).returns(T.nilable(::
|
15
|
+
excluded_teams: T::Array[::CodeTeams::Team]
|
16
|
+
).returns(T.nilable(::CodeTeams::Team))
|
17
17
|
end
|
18
18
|
def for_backtrace(backtrace, excluded_teams: T.unsafe(nil)); end
|
19
19
|
|
20
|
-
sig { params(klass: T.nilable(T.any(::Class, ::Module))).returns(T.nilable(::
|
20
|
+
sig { params(klass: T.nilable(T.any(::Class, ::Module))).returns(T.nilable(::CodeTeams::Team)) }
|
21
21
|
def for_class(klass); end
|
22
22
|
|
23
|
-
sig { params(file: ::String).returns(T.nilable(::
|
23
|
+
sig { params(file: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
24
24
|
def for_file(file); end
|
25
25
|
|
26
|
-
sig { params(package: ::ParsePackwerk::Package).returns(T.nilable(::
|
26
|
+
sig { params(package: ::ParsePackwerk::Package).returns(T.nilable(::CodeTeams::Team)) }
|
27
27
|
def for_package(package); end
|
28
28
|
|
29
29
|
sig { params(files: T::Array[::String], autocorrect: T::Boolean, stage_changes: T::Boolean).void }
|
@@ -40,6 +40,7 @@ end
|
|
40
40
|
|
41
41
|
class CodeOwnership::Cli
|
42
42
|
class << self
|
43
|
+
def for_file(argv); end
|
43
44
|
def run!(argv); end
|
44
45
|
|
45
46
|
private
|
@@ -64,7 +65,7 @@ module CodeOwnership::Private
|
|
64
65
|
sig { params(files: T::Array[::String]).returns(T::Hash[::String, T::Array[::String]]) }
|
65
66
|
def files_by_mapper(files); end
|
66
67
|
|
67
|
-
sig { params(team_name: ::String, location_of_reference: ::String).returns(::
|
68
|
+
sig { params(team_name: ::String, location_of_reference: ::String).returns(::CodeTeams::Team) }
|
68
69
|
def find_team!(team_name, location_of_reference); end
|
69
70
|
|
70
71
|
sig { returns(T::Array[::CodeOwnership::Private::OwnershipMappers::Interface]) }
|
@@ -107,19 +108,19 @@ class CodeOwnership::Private::OwnershipMappers::FileAnnotations
|
|
107
108
|
sig { override.void }
|
108
109
|
def bust_caches!; end
|
109
110
|
|
110
|
-
sig { override.returns(T::Hash[::String, T.nilable(::
|
111
|
+
sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
111
112
|
def codeowners_lines_to_owners; end
|
112
113
|
|
113
114
|
sig { override.returns(::String) }
|
114
115
|
def description; end
|
115
116
|
|
116
|
-
sig { params(filename: ::String).returns(T.nilable(::
|
117
|
+
sig { params(filename: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
117
118
|
def file_annotation_based_owner(filename); end
|
118
119
|
|
119
|
-
sig { override.params(file: ::String).returns(T.nilable(::
|
120
|
+
sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
120
121
|
def map_file_to_owner(file); end
|
121
122
|
|
122
|
-
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::
|
123
|
+
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
123
124
|
def map_files_to_owners(files); end
|
124
125
|
|
125
126
|
sig { params(filename: ::String).void }
|
@@ -134,16 +135,16 @@ module CodeOwnership::Private::OwnershipMappers::Interface
|
|
134
135
|
sig { abstract.void }
|
135
136
|
def bust_caches!; end
|
136
137
|
|
137
|
-
sig { abstract.returns(T::Hash[::String, T.nilable(::
|
138
|
+
sig { abstract.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
138
139
|
def codeowners_lines_to_owners; end
|
139
140
|
|
140
141
|
sig { abstract.returns(::String) }
|
141
142
|
def description; end
|
142
143
|
|
143
|
-
sig { abstract.params(file: ::String).returns(T.nilable(::
|
144
|
+
sig { abstract.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
144
145
|
def map_file_to_owner(file); end
|
145
146
|
|
146
|
-
sig { abstract.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::
|
147
|
+
sig { abstract.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
147
148
|
def map_files_to_owners(files); end
|
148
149
|
end
|
149
150
|
|
@@ -153,19 +154,19 @@ class CodeOwnership::Private::OwnershipMappers::JsPackageOwnership
|
|
153
154
|
sig { override.void }
|
154
155
|
def bust_caches!; end
|
155
156
|
|
156
|
-
sig { override.returns(T::Hash[::String, T.nilable(::
|
157
|
+
sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
157
158
|
def codeowners_lines_to_owners; end
|
158
159
|
|
159
160
|
sig { override.returns(::String) }
|
160
161
|
def description; end
|
161
162
|
|
162
|
-
sig { override.params(file: ::String).returns(T.nilable(::
|
163
|
+
sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
163
164
|
def map_file_to_owner(file); end
|
164
165
|
|
165
|
-
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::
|
166
|
+
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
166
167
|
def map_files_to_owners(files); end
|
167
168
|
|
168
|
-
sig { params(package: ::CodeOwnership::Private::ParseJsPackages::Package).returns(T.nilable(::
|
169
|
+
sig { params(package: ::CodeOwnership::Private::ParseJsPackages::Package).returns(T.nilable(::CodeTeams::Team)) }
|
169
170
|
def owner_for_package(package); end
|
170
171
|
|
171
172
|
private
|
@@ -180,19 +181,19 @@ class CodeOwnership::Private::OwnershipMappers::PackageOwnership
|
|
180
181
|
sig { override.void }
|
181
182
|
def bust_caches!; end
|
182
183
|
|
183
|
-
sig { override.returns(T::Hash[::String, T.nilable(::
|
184
|
+
sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
184
185
|
def codeowners_lines_to_owners; end
|
185
186
|
|
186
187
|
sig { override.returns(::String) }
|
187
188
|
def description; end
|
188
189
|
|
189
|
-
sig { override.params(file: ::String).returns(T.nilable(::
|
190
|
+
sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
190
191
|
def map_file_to_owner(file); end
|
191
192
|
|
192
|
-
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::
|
193
|
+
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
193
194
|
def map_files_to_owners(files); end
|
194
195
|
|
195
|
-
sig { params(package: ::ParsePackwerk::Package).returns(T.nilable(::
|
196
|
+
sig { params(package: ::ParsePackwerk::Package).returns(T.nilable(::CodeTeams::Team)) }
|
196
197
|
def owner_for_package(package); end
|
197
198
|
|
198
199
|
private
|
@@ -207,16 +208,16 @@ class CodeOwnership::Private::OwnershipMappers::TeamGlobs
|
|
207
208
|
sig { override.void }
|
208
209
|
def bust_caches!; end
|
209
210
|
|
210
|
-
sig { override.returns(T::Hash[::String, T.nilable(::
|
211
|
+
sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
211
212
|
def codeowners_lines_to_owners; end
|
212
213
|
|
213
214
|
sig { override.returns(::String) }
|
214
215
|
def description; end
|
215
216
|
|
216
|
-
sig { override.params(file: ::String).returns(T.nilable(::
|
217
|
+
sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
217
218
|
def map_file_to_owner(file); end
|
218
219
|
|
219
|
-
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::
|
220
|
+
sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) }
|
220
221
|
def map_files_to_owners(files); end
|
221
222
|
end
|
222
223
|
|
@@ -248,7 +249,7 @@ end
|
|
248
249
|
CodeOwnership::Private::ParseJsPackages::ROOT_PACKAGE_NAME = T.let(T.unsafe(nil), String)
|
249
250
|
module CodeOwnership::Private::TeamPlugins; end
|
250
251
|
|
251
|
-
class CodeOwnership::Private::TeamPlugins::Github < ::
|
252
|
+
class CodeOwnership::Private::TeamPlugins::Github < ::CodeTeams::Plugin
|
252
253
|
sig { returns(::CodeOwnership::Private::TeamPlugins::Github::GithubStruct) }
|
253
254
|
def github; end
|
254
255
|
end
|
@@ -267,7 +268,7 @@ class CodeOwnership::Private::TeamPlugins::Github::GithubStruct < ::Struct
|
|
267
268
|
end
|
268
269
|
end
|
269
270
|
|
270
|
-
class CodeOwnership::Private::TeamPlugins::Ownership < ::
|
271
|
+
class CodeOwnership::Private::TeamPlugins::Ownership < ::CodeTeams::Plugin
|
271
272
|
sig { returns(T::Array[::String]) }
|
272
273
|
def owned_globs; end
|
273
274
|
end
|
@@ -1,78 +1,78 @@
|
|
1
1
|
# typed: true
|
2
2
|
|
3
3
|
# DO NOT EDIT MANUALLY
|
4
|
-
# This is an autogenerated file for types exported from the `
|
5
|
-
# Please instead update this file by running `bin/tapioca gem
|
4
|
+
# This is an autogenerated file for types exported from the `code_teams` gem.
|
5
|
+
# Please instead update this file by running `bin/tapioca gem code_teams`.
|
6
6
|
|
7
|
-
module
|
7
|
+
module CodeTeams
|
8
8
|
class << self
|
9
|
-
sig { returns(T::Array[::
|
9
|
+
sig { returns(T::Array[::CodeTeams::Team]) }
|
10
10
|
def all; end
|
11
11
|
|
12
12
|
sig { void }
|
13
13
|
def bust_caches!; end
|
14
14
|
|
15
|
-
sig { params(name: ::String).returns(T.nilable(::
|
15
|
+
sig { params(name: ::String).returns(T.nilable(::CodeTeams::Team)) }
|
16
16
|
def find(name); end
|
17
17
|
|
18
|
-
sig { params(dir: ::String).returns(T::Array[::
|
18
|
+
sig { params(dir: ::String).returns(T::Array[::CodeTeams::Team]) }
|
19
19
|
def for_directory(dir); end
|
20
20
|
|
21
21
|
sig { params(string: ::String).returns(::String) }
|
22
22
|
def tag_value_for(string); end
|
23
23
|
|
24
|
-
sig { params(teams: T::Array[::
|
24
|
+
sig { params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) }
|
25
25
|
def validation_errors(teams); end
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
class
|
29
|
+
class CodeTeams::IncorrectPublicApiUsageError < ::StandardError; end
|
30
30
|
|
31
|
-
class
|
31
|
+
class CodeTeams::Plugin
|
32
32
|
abstract!
|
33
33
|
|
34
|
-
sig { params(team: ::
|
34
|
+
sig { params(team: ::CodeTeams::Team).void }
|
35
35
|
def initialize(team); end
|
36
36
|
|
37
37
|
class << self
|
38
|
-
sig { returns(T::Array[T.class_of(
|
38
|
+
sig { returns(T::Array[T.class_of(CodeTeams::Plugin)]) }
|
39
39
|
def all_plugins; end
|
40
40
|
|
41
|
-
sig { params(team: ::
|
41
|
+
sig { params(team: ::CodeTeams::Team).returns(T.attached_class) }
|
42
42
|
def for(team); end
|
43
43
|
|
44
44
|
sig { params(base: T.untyped).void }
|
45
45
|
def inherited(base); end
|
46
46
|
|
47
|
-
sig { params(team: ::
|
47
|
+
sig { params(team: ::CodeTeams::Team, key: ::String).returns(::String) }
|
48
48
|
def missing_key_error_message(team, key); end
|
49
49
|
|
50
|
-
sig { params(teams: T::Array[::
|
50
|
+
sig { params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) }
|
51
51
|
def validation_errors(teams); end
|
52
52
|
|
53
53
|
private
|
54
54
|
|
55
|
-
sig { params(team: ::
|
55
|
+
sig { params(team: ::CodeTeams::Team).returns(T.attached_class) }
|
56
56
|
def register_team(team); end
|
57
57
|
|
58
|
-
sig { returns(T::Hash[T.nilable(::String), T::Hash[::Class, ::
|
58
|
+
sig { returns(T::Hash[T.nilable(::String), T::Hash[::Class, ::CodeTeams::Plugin]]) }
|
59
59
|
def registry; end
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
module
|
63
|
+
module CodeTeams::Plugins; end
|
64
64
|
|
65
|
-
class
|
66
|
-
sig { returns(::
|
65
|
+
class CodeTeams::Plugins::Identity < ::CodeTeams::Plugin
|
66
|
+
sig { returns(::CodeTeams::Plugins::Identity::IdentityStruct) }
|
67
67
|
def identity; end
|
68
68
|
|
69
69
|
class << self
|
70
|
-
sig { override.params(teams: T::Array[::
|
70
|
+
sig { override.params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) }
|
71
71
|
def validation_errors(teams); end
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
class
|
75
|
+
class CodeTeams::Plugins::Identity::IdentityStruct < ::Struct
|
76
76
|
def name; end
|
77
77
|
def name=(_); end
|
78
78
|
|
@@ -84,7 +84,7 @@ class Teams::Plugins::Identity::IdentityStruct < ::Struct
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
-
class
|
87
|
+
class CodeTeams::Team
|
88
88
|
sig { params(config_yml: T.nilable(::String), raw_hash: T::Hash[T.untyped, T.untyped]).void }
|
89
89
|
def initialize(config_yml:, raw_hash:); end
|
90
90
|
|
@@ -109,12 +109,12 @@ class Teams::Team
|
|
109
109
|
def to_tag; end
|
110
110
|
|
111
111
|
class << self
|
112
|
-
sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(::
|
112
|
+
sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(::CodeTeams::Team) }
|
113
113
|
def from_hash(raw_hash); end
|
114
114
|
|
115
|
-
sig { params(config_yml: ::String).returns(::
|
115
|
+
sig { params(config_yml: ::String).returns(::CodeTeams::Team) }
|
116
116
|
def from_yml(config_yml); end
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
|
-
|
120
|
+
CodeTeams::UNKNOWN_TEAM_STRING = T.let(T.unsafe(nil), String)
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modularization_statistics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.34.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: code_teams
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -181,21 +181,21 @@ files:
|
|
181
181
|
- lib/modularization_statistics/tag.rb
|
182
182
|
- lib/modularization_statistics/tags.rb
|
183
183
|
- sorbet/config
|
184
|
-
- sorbet/rbi/gems/
|
185
|
-
- sorbet/rbi/gems/
|
184
|
+
- sorbet/rbi/gems/code_ownership@1.28.0.rbi
|
185
|
+
- sorbet/rbi/gems/code_teams@1.0.0.rbi
|
186
186
|
- sorbet/rbi/gems/dogapi@1.45.0.rbi
|
187
187
|
- sorbet/rbi/gems/manual.rbi
|
188
188
|
- sorbet/rbi/gems/package_protections@0.64.0.rbi
|
189
189
|
- sorbet/rbi/gems/parse_packwerk@0.10.0.rbi
|
190
190
|
- sorbet/rbi/gems/rspec@3.10.0.rbi
|
191
191
|
- sorbet/rbi/todo.rbi
|
192
|
-
homepage: https://github.com/
|
192
|
+
homepage: https://github.com/rubyatscale/modularization_statistics
|
193
193
|
licenses:
|
194
194
|
- MIT
|
195
195
|
metadata:
|
196
|
-
homepage_uri: https://github.com/
|
197
|
-
source_code_uri: https://github.com/
|
198
|
-
changelog_uri: https://github.com/
|
196
|
+
homepage_uri: https://github.com/rubyatscale/modularization_statistics
|
197
|
+
source_code_uri: https://github.com/rubyatscale/modularization_statistics
|
198
|
+
changelog_uri: https://github.com/rubyatscale/modularization_statistics/releases
|
199
199
|
allowed_push_host: https://rubygems.org
|
200
200
|
post_install_message:
|
201
201
|
rdoc_options: []
|