usedby 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES.md +21 -0
- data/LICENSE.txt +23 -0
- data/README.md +99 -0
- data/bin/usedby +5 -0
- data/lib/usedby.rb +4 -0
- data/lib/usedby/cli.rb +274 -0
- data/lib/usedby/version.rb +5 -0
- data/lib/usedby/version_ranges_intersection.rb +51 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 51866979c0e578e1c2c4bda5c7f61e79cd9b6bb0a5366eaaf2c66f474b00c6ea
|
4
|
+
data.tar.gz: 54e3aa2e5a415b570ead51e7c004b5a37683f76360c8d084874f0b05812281c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: da372934168ed4cf0fb96e648c7735631c0068652516fa08142a8666f20be2f57f9a0b35861aec240b6a2b2ee29cc35b5387b3fa9c4a9f152ac2375beadb7b04
|
7
|
+
data.tar.gz: 5ad1483db112e0b5cbf819f095de455dae8f90105c9a93fc85fcaf4d2c5bec296f9a9291519274ed03c54eee80e9dc878575961b638bb4a01288fe012442d8d7
|
data/CHANGES.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## 0.3.0 (2018/07/20)
|
4
|
+
|
5
|
+
**Added**
|
6
|
+
|
7
|
+
* Don't include archived repositories.
|
8
|
+
|
9
|
+
|
10
|
+
## 0.2.0 (2018/07/03)
|
11
|
+
|
12
|
+
**Added**
|
13
|
+
|
14
|
+
* Require `GITHUB_ORGANIZATION` to be provided on the command line.
|
15
|
+
|
16
|
+
## 0.1.0 (2018/07/03)
|
17
|
+
|
18
|
+
**Added**
|
19
|
+
|
20
|
+
* Provide initial version of the `organization_gem_dependencies` command line
|
21
|
+
tool.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2018, AppFolio, Inc.
|
2
|
+
Copyright (c) 2018, Bryce Boe
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
list of conditions and the following disclaimer.
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
15
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
16
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
17
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
18
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
19
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
20
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
21
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
22
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
23
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# usedby
|
2
|
+
|
3
|
+
Figure out where your gems are actually being used!
|
4
|
+
Similar to GitHub's "Used By" feature, but for private repos.
|
5
|
+
|
6
|
+
This gem installs a command line utility `usedby`, that
|
7
|
+
outputs a json file with a reverse dependency tree.
|
8
|
+
|
9
|
+
This acts more or less like GitHub's "Used By" feature.
|
10
|
+
It currently uses GitHub's code search API which has a few limitations:
|
11
|
+
https://help.github.com/en/github/searching-for-information-on-github/searching-code
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
```sh
|
16
|
+
gem install usedby
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
```sh
|
22
|
+
usedby GITHUB_ORGANIZATION [--direct] [--gems GEM1,GEM2,GEM3]
|
23
|
+
```
|
24
|
+
|
25
|
+
You will be securely prompted for a [GitHub Personal Access Token](https://github.com/settings/tokens).
|
26
|
+
|
27
|
+
For example, running `usedby rails --direct --gems railties,rake` produces output
|
28
|
+
like the following:
|
29
|
+
|
30
|
+
```json
|
31
|
+
{
|
32
|
+
"railties": {
|
33
|
+
"4.0.0.beta": [
|
34
|
+
"routing_concerns/Gemfile.lock"
|
35
|
+
],
|
36
|
+
"4.0.0": [
|
37
|
+
"prototype-rails/Gemfile.lock"
|
38
|
+
],
|
39
|
+
"4.2.1": [
|
40
|
+
"rails-perftest/Gemfile.lock"
|
41
|
+
],
|
42
|
+
"4.2.10": [
|
43
|
+
"rails-docs-server/test/fixtures/releases/v4.2.10/Gemfile.lock"
|
44
|
+
],
|
45
|
+
"5.1.1": [
|
46
|
+
"actioncable-examples/Gemfile.lock"
|
47
|
+
],
|
48
|
+
"5.2.1": [
|
49
|
+
"rails_fast_attributes/Gemfile.lock"
|
50
|
+
],
|
51
|
+
"5.2.2": [
|
52
|
+
"globalid/Gemfile.lock"
|
53
|
+
],
|
54
|
+
"6.0.1": [
|
55
|
+
"webpacker/Gemfile.lock"
|
56
|
+
],
|
57
|
+
"6.0.2.1": [
|
58
|
+
"rails-contributors/Gemfile.lock"
|
59
|
+
],
|
60
|
+
"6.1.0.alpha": [
|
61
|
+
"rails/Gemfile.lock"
|
62
|
+
]
|
63
|
+
},
|
64
|
+
"rake": {
|
65
|
+
"0.9.2.2": [
|
66
|
+
"commands/Gemfile.lock",
|
67
|
+
"etagger/Gemfile.lock",
|
68
|
+
"routing_concerns/Gemfile.lock"
|
69
|
+
],
|
70
|
+
"10.1.0": [
|
71
|
+
"prototype-rails/Gemfile.lock"
|
72
|
+
],
|
73
|
+
"10.4.2": [
|
74
|
+
"jquery-ujs/Gemfile.lock",
|
75
|
+
"rails-perftest/Gemfile.lock"
|
76
|
+
],
|
77
|
+
"10.5.0": [
|
78
|
+
"rails_fast_attributes/Gemfile.lock",
|
79
|
+
"record_tag_helper/Gemfile.lock"
|
80
|
+
],
|
81
|
+
"12.0.0": [
|
82
|
+
"actioncable-examples/Gemfile.lock",
|
83
|
+
"rails-docs-server/test/fixtures/releases/v4.2.10/Gemfile.lock",
|
84
|
+
"rails-dom-testing/Gemfile.lock"
|
85
|
+
],
|
86
|
+
"12.3.2": [
|
87
|
+
"globalid/Gemfile.lock"
|
88
|
+
],
|
89
|
+
"13.0.0": [
|
90
|
+
"webpacker/Gemfile.lock"
|
91
|
+
],
|
92
|
+
"13.0.1": [
|
93
|
+
"rails-contributors/Gemfile.lock",
|
94
|
+
"rails/Gemfile.lock"
|
95
|
+
]
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
```
|
data/bin/usedby
ADDED
data/lib/usedby.rb
ADDED
data/lib/usedby/cli.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'io/console'
|
5
|
+
require 'json'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
require 'octokit'
|
9
|
+
|
10
|
+
require 'usedby/version_ranges_intersection'
|
11
|
+
|
12
|
+
module Usedby
|
13
|
+
# Define the command line interface.
|
14
|
+
class Cli
|
15
|
+
using VersionRangesIntersection
|
16
|
+
|
17
|
+
GEMFILE_LOCK_SEARCH_TERM = 'org:%s filename:Gemfile.lock'
|
18
|
+
GEMSPEC_SEARCH_TERM = 'org:%s extension:gemspec'
|
19
|
+
USAGE = <<~USAGE
|
20
|
+
Usage: usedby [options] GITHUB_ORGANIZATION
|
21
|
+
USAGE
|
22
|
+
|
23
|
+
|
24
|
+
def run
|
25
|
+
parse_options
|
26
|
+
if ARGV.size != 1
|
27
|
+
STDERR.puts USAGE
|
28
|
+
return 1
|
29
|
+
end
|
30
|
+
github_organization = ARGV[0]
|
31
|
+
|
32
|
+
access_token = ENV['GITHUB_ACCESS_TOKEN'] || \
|
33
|
+
STDIN.getpass('GitHub Personal Access Token: ')
|
34
|
+
github = Octokit::Client.new(access_token: access_token)
|
35
|
+
|
36
|
+
gems = {}
|
37
|
+
|
38
|
+
remote_search(github, github_organization, GEMFILE_LOCK_SEARCH_TERM) do |gemfile_lock|
|
39
|
+
content = remote_file(github, gemfile_lock)
|
40
|
+
next unless content
|
41
|
+
content = Bundler::LockfileParser.new(content)
|
42
|
+
merge!(gems, process_gemfile(content, "#{gemfile_lock.repository.name}/#{gemfile_lock.path}"))
|
43
|
+
end
|
44
|
+
|
45
|
+
remote_search(github, github_organization, GEMSPEC_SEARCH_TERM) do |gemspec|
|
46
|
+
content = remote_file(github, gemspec)
|
47
|
+
next unless content
|
48
|
+
merge!(gems, process_gemspec(content, "#{gemspec.repository.name}/#{gemspec.path}"))
|
49
|
+
end
|
50
|
+
|
51
|
+
output gems
|
52
|
+
|
53
|
+
0
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def archived_repositories(github, organization)
|
59
|
+
github.organization_repositories(organization)
|
60
|
+
last_response = github.last_response
|
61
|
+
|
62
|
+
repositories = []
|
63
|
+
last_response.data.each do |repository|
|
64
|
+
repositories << repository.name if repository.archived
|
65
|
+
end
|
66
|
+
until last_response.rels[:next].nil?
|
67
|
+
sleep_time = 0
|
68
|
+
begin
|
69
|
+
last_response = last_response.rels[:next].get
|
70
|
+
rescue StandardError
|
71
|
+
sleep_time += 1
|
72
|
+
STDERR.puts "Sleeping #{sleep_time} seconds"
|
73
|
+
sleep(sleep_time)
|
74
|
+
retry
|
75
|
+
end
|
76
|
+
last_response.data.each do |repository|
|
77
|
+
repositories << repository.name if repository.archived
|
78
|
+
end
|
79
|
+
end
|
80
|
+
repositories
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_ignore_paths(ignored_paths, file)
|
84
|
+
File.open(file).each do |line|
|
85
|
+
cleaned = line.strip
|
86
|
+
ignored_paths << cleaned if cleaned != ''
|
87
|
+
end
|
88
|
+
rescue Errno::ENOENT, Errno::EISDIR
|
89
|
+
STDERR.puts "No such file #{file}"
|
90
|
+
exit 1
|
91
|
+
end
|
92
|
+
|
93
|
+
def filtered?(gemfile_path)
|
94
|
+
@options[:ignore_paths].each do |ignore_path|
|
95
|
+
return true if gemfile_path.start_with?(ignore_path)
|
96
|
+
end
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def remote_search(github, organization, search_term)
|
101
|
+
archived = archived_repositories(github, organization)
|
102
|
+
github.search_code(search_term % organization, per_page: 1000)
|
103
|
+
last_response = github.last_response
|
104
|
+
|
105
|
+
matches = []
|
106
|
+
last_response.data.items.each do |match|
|
107
|
+
matches << match unless archived.include? match.repository.name
|
108
|
+
end
|
109
|
+
until last_response.rels[:next].nil?
|
110
|
+
sleep_time = 0
|
111
|
+
begin
|
112
|
+
last_response = last_response.rels[:next].get
|
113
|
+
rescue StandardError
|
114
|
+
sleep_time += 1
|
115
|
+
STDERR.puts "Sleeping #{sleep_time} seconds"
|
116
|
+
sleep(sleep_time)
|
117
|
+
retry
|
118
|
+
end
|
119
|
+
last_response.data.items.each do |match|
|
120
|
+
matches << match unless archived.include? match.repository.name
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
matches.sort_by(&:html_url).each do |match|
|
125
|
+
yield match
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def remote_file(github, file)
|
130
|
+
github_path = "#{file.repository.name}/#{file.path}"
|
131
|
+
if filtered?(github_path)
|
132
|
+
STDERR.puts "Skipping #{github_path}"
|
133
|
+
return
|
134
|
+
end
|
135
|
+
STDERR.puts "Processing #{github_path}"
|
136
|
+
sleep_time = 0
|
137
|
+
begin
|
138
|
+
content = Base64.decode64(github.get(file.url).content)
|
139
|
+
rescue StandardError
|
140
|
+
sleep_time += 1
|
141
|
+
STDERR.puts "Sleeping #{sleep_time} seconds"
|
142
|
+
sleep(sleep_time)
|
143
|
+
retry
|
144
|
+
end
|
145
|
+
content
|
146
|
+
end
|
147
|
+
|
148
|
+
def merge!(base, additions)
|
149
|
+
additions.each do |gem, versions|
|
150
|
+
if base.include? gem
|
151
|
+
base_versions = base[gem]
|
152
|
+
versions.each do |version, projects|
|
153
|
+
if base_versions.include? version
|
154
|
+
base_versions[version].concat(projects)
|
155
|
+
else
|
156
|
+
base_versions[version] = projects
|
157
|
+
end
|
158
|
+
end
|
159
|
+
else
|
160
|
+
base[gem] = versions
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def output(gems)
|
166
|
+
sorted_gems = {}
|
167
|
+
gems.sort.each do |gem, versions|
|
168
|
+
sorted_gems[gem] = {}
|
169
|
+
versions.sort.each do |version, projects|
|
170
|
+
sorted_gems[gem][version_ranges_to_s(version)] = projects.sort
|
171
|
+
end
|
172
|
+
end
|
173
|
+
puts JSON.pretty_generate(sorted_gems)
|
174
|
+
end
|
175
|
+
|
176
|
+
def parse_options
|
177
|
+
@options = { direct: false, ignore_paths: [] }
|
178
|
+
OptionParser.new do |config|
|
179
|
+
config.banner = USAGE
|
180
|
+
config.on('-d', '--direct',
|
181
|
+
'Consider only direct dependencies.') do |direct|
|
182
|
+
@options[:direct] = direct
|
183
|
+
end
|
184
|
+
config.on('-i', '--ignore-file [FILEPATH]',
|
185
|
+
'Ignore projects included in file.') do |ignore_file|
|
186
|
+
|
187
|
+
build_ignore_paths(@options[:ignore_paths], ignore_file)
|
188
|
+
end
|
189
|
+
config.on('-g', '--gems [GEM1,GEM2,GEM3]',
|
190
|
+
'Consider only given gems.') do |gems|
|
191
|
+
|
192
|
+
@options[:gems] = gems.split(',')
|
193
|
+
end
|
194
|
+
config.version = Usedby::VERSION
|
195
|
+
end.parse!
|
196
|
+
end
|
197
|
+
|
198
|
+
def process_gemfile(gemfile, project)
|
199
|
+
dependencies = gemfile.dependencies.map { |dependency, _, _| dependency }
|
200
|
+
gems = {}
|
201
|
+
|
202
|
+
gemfile.specs.each do |spec|
|
203
|
+
next if @options[:direct] && !dependencies.include?(spec.name)
|
204
|
+
next if @options[:gems] && !@options[:gems].include?(spec.name)
|
205
|
+
spec_version_ranges = Bundler::VersionRanges.for(Gem::Requirement.new(spec.version))
|
206
|
+
gems[spec.name] = {}
|
207
|
+
gems[spec.name][spec_version_ranges] = [project]
|
208
|
+
end
|
209
|
+
gems
|
210
|
+
end
|
211
|
+
|
212
|
+
# Process dependencies in gemspec according to:
|
213
|
+
# https://guides.rubygems.org/specification-reference/
|
214
|
+
# Sample supported formats:
|
215
|
+
# s.add_dependency(%q<rspec>.freeze, ["~> 3.2"])
|
216
|
+
# spec.add_runtime_dependency "multi_json", "~>1.12", ">=1.12.0"
|
217
|
+
# Sample unsupported formats:
|
218
|
+
# s.add_development_dependency "rake", "~> 10.5" if on_less_than_1_9_3?
|
219
|
+
# s.add_dependency 'sunspot', Sunspot::VERSION
|
220
|
+
def process_gemspec(content, project)
|
221
|
+
gems = {}
|
222
|
+
dummy_spec = Gem::Specification.new
|
223
|
+
content.each_line do |line|
|
224
|
+
if line =~ /^\s*(\w+)\.add_(development_dependency|runtime_dependency|dependency)\b/
|
225
|
+
spec_name = $1
|
226
|
+
begin
|
227
|
+
eval line.sub(spec_name, 'dummy_spec')
|
228
|
+
rescue => e
|
229
|
+
$stderr.puts e
|
230
|
+
next
|
231
|
+
end
|
232
|
+
dep = dummy_spec.dependencies.last
|
233
|
+
gem_name = dep.name
|
234
|
+
next if @options[:gems] && !@options[:gems].include?(gem_name)
|
235
|
+
gem_version_ranges = Bundler::VersionRanges.for(dep.requirement)
|
236
|
+
gems[gem_name] = {}
|
237
|
+
gems[gem_name][gem_version_ranges] = [project]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
gems
|
241
|
+
end
|
242
|
+
|
243
|
+
# Uses mathematical notation for ranges
|
244
|
+
# The unbounded range is [0, ∞)
|
245
|
+
def version_ranges_to_s(gem_version_ranges)
|
246
|
+
if !gem_version_ranges.kind_of?(Array) || gem_version_ranges.size != 2
|
247
|
+
$stderr.puts "Unknown format for version ranges: #{gem_version_ranges}"
|
248
|
+
return ""
|
249
|
+
end
|
250
|
+
|
251
|
+
# base case: a specific version
|
252
|
+
if gem_version_ranges[0].size == 1
|
253
|
+
range = gem_version_ranges[0][0]
|
254
|
+
if range.left.version == range.right.version
|
255
|
+
return range.left.version.to_s
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
gem_version_ranges[0] = Bundler::VersionRanges::ReqR.reduce(gem_version_ranges[0])
|
260
|
+
|
261
|
+
arr = []
|
262
|
+
gem_version_ranges[0].each do |reqr|
|
263
|
+
range_begin = reqr.left.inclusive ? "[" : "("
|
264
|
+
range_end = reqr.right.inclusive ? "]" : ")"
|
265
|
+
arr << "#{range_begin}#{reqr.left.version.to_s}, #{reqr.right.version.to_s}#{range_end}"
|
266
|
+
end
|
267
|
+
gem_version_ranges[1].each do |neq|
|
268
|
+
# special case: exclude specific version
|
269
|
+
arr << "!= #{neq.version.to_s}"
|
270
|
+
end
|
271
|
+
arr.join(", ")
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module VersionRangesIntersection
|
4
|
+
refine Bundler::VersionRanges::ReqR do
|
5
|
+
# Checks whether two ranges overlap
|
6
|
+
def intersect?(other)
|
7
|
+
case self <=> other
|
8
|
+
when -1
|
9
|
+
return (right <=> other.left) == 1
|
10
|
+
when 1
|
11
|
+
return (other.right <=> left) == 1
|
12
|
+
end
|
13
|
+
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a new range which is the intersection, or else nil
|
18
|
+
def intersection(other)
|
19
|
+
return nil unless intersect?(other)
|
20
|
+
|
21
|
+
inter = clone
|
22
|
+
case inter <=> other
|
23
|
+
when -1
|
24
|
+
inter.left = other.left
|
25
|
+
when 1
|
26
|
+
inter.right = other.right
|
27
|
+
end
|
28
|
+
|
29
|
+
inter
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Class methods need to be refined on the singleton_class
|
34
|
+
refine Bundler::VersionRanges::ReqR.singleton_class do
|
35
|
+
def reduce(reqrs = [])
|
36
|
+
reduced_reqrs = reqrs.clone
|
37
|
+
i = 0
|
38
|
+
while i < reduced_reqrs.size - 1
|
39
|
+
intersecting_range = reduced_reqrs[i].intersection(reduced_reqrs[i + 1])
|
40
|
+
if intersecting_range
|
41
|
+
reduced_reqrs[i + 1] = intersecting_range
|
42
|
+
reduced_reqrs.delete_at(i)
|
43
|
+
else
|
44
|
+
i += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
reduced_reqrs
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: usedby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- AppFolio, Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-02-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: octokit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
description: 'usedby is a command line tool to discover all dependents of ruby gems
|
56
|
+
across a github organization.
|
57
|
+
|
58
|
+
'
|
59
|
+
email: opensource@appfolio.com
|
60
|
+
executables:
|
61
|
+
- usedby
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- CHANGES.md
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- bin/usedby
|
69
|
+
- lib/usedby.rb
|
70
|
+
- lib/usedby/cli.rb
|
71
|
+
- lib/usedby/version.rb
|
72
|
+
- lib/usedby/version_ranges_intersection.rb
|
73
|
+
homepage: https://github.com/appfolio/usedby
|
74
|
+
licenses:
|
75
|
+
- BSD-2-Clause
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.1.2
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Discover all dependents of ruby gems across a github organization.
|
96
|
+
test_files: []
|