bundler-alive 0.1.4 → 0.1.7
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 +4 -4
- data/.bundler-alive.default.yml +70 -0
- data/.rspec +0 -1
- data/.yardopts +1 -0
- data/README.md +50 -12
- data/gemspec.yml +3 -3
- data/lib/bundler/alive/cli.rb +17 -7
- data/lib/bundler/alive/client/gems_api_client.rb +53 -11
- data/lib/bundler/alive/client/{git_hub_api.rb → github_api.rb} +57 -56
- data/lib/bundler/alive/client/github_graphql.rb +82 -0
- data/lib/bundler/alive/client/gitlab_api.rb +121 -0
- data/lib/bundler/alive/client/source_code_client.rb +2 -1
- data/lib/bundler/alive/doctor.rb +24 -47
- data/lib/bundler/alive/report.rb +0 -10
- data/lib/bundler/alive/reportable.rb +24 -9
- data/lib/bundler/alive/source_code_repository.rb +3 -0
- data/lib/bundler/alive/source_code_repository_url.rb +4 -1
- data/lib/bundler/alive/status.rb +1 -2
- data/lib/bundler/alive/status_collection.rb +9 -27
- data/lib/bundler/alive/status_result.rb +9 -4
- data/lib/bundler/alive/version.rb +1 -1
- data/lib/bundler/alive.rb +9 -2
- metadata +10 -7
- data/lib/bundler/alive/announcer.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fb0738fac56612f49d0fcc2dadf41f00caa5c34b8e49923185326f3cc6ab744
|
4
|
+
data.tar.gz: e89352dbbdec31b5e82e4f42135113d1c464441bcc0d4443709e24437fffb87f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6692e5114833643c5038b41a8bbff0282837b4e70c05449f76cc810e499751a3f7f8585f5360f31b503376662e6a91f6617a335e59686a9841af65a2917b0d2f
|
7
|
+
data.tar.gz: 393f54894aabd833bd0249a54cfab165bbcacb6a9d10cae9ab446b6e81a92c9de83f5e0fc25d38327999b7c2e06fa902a730bbec5eb04b37a476af0067b882c4
|
@@ -0,0 +1,70 @@
|
|
1
|
+
---
|
2
|
+
gems:
|
3
|
+
coffee-script-source:
|
4
|
+
url: https://github.com/jashkenas/coffeescript/
|
5
|
+
erubis:
|
6
|
+
url: https://github.com/kwatch/erubis
|
7
|
+
ffi-compiler:
|
8
|
+
url: https://github.com/ffi/ffi
|
9
|
+
foundation-rails:
|
10
|
+
url: https://github.com/foundation/foundation-rails
|
11
|
+
guard-compat:
|
12
|
+
url: https://github.com/guard/guard-compat
|
13
|
+
spoon:
|
14
|
+
url: https://github.com/headius/spoon
|
15
|
+
shellany:
|
16
|
+
url: https://github.com/guard/shellany
|
17
|
+
vegas:
|
18
|
+
url: https://github.com/quirkey/vegas
|
19
|
+
zipruby:
|
20
|
+
url: https://github.com/fjg/zipruby
|
21
|
+
gli:
|
22
|
+
url: https://github.com/davetron5000/gli
|
23
|
+
zen_to_i:
|
24
|
+
url: https://github.com/yoshitsugu/zen_to_i
|
25
|
+
trailblazer-option:
|
26
|
+
url: https://github.com/trailblazer/trailblazer-option
|
27
|
+
fog-sakuracloud:
|
28
|
+
url: https://github.com/fog/fog-sakuracloud
|
29
|
+
ovirt-engine-sdk:
|
30
|
+
url: https://github.com/oVirt/ovirt-engine-sdk-ruby
|
31
|
+
declarative_policy:
|
32
|
+
url: https://gitlab.com/gitlab-org/ruby/gems/declarative-policy
|
33
|
+
spamcheck:
|
34
|
+
url: https://gitlab.com/gitlab-com/gl-security/engineering-and-research/automation-team/spam/spamcheck
|
35
|
+
tanuki_emoji:
|
36
|
+
url: https://gitlab.com/gitlab-org/ruby/gems/tanuki_emoji
|
37
|
+
cbor:
|
38
|
+
url: https://github.com/cabo/cbor-ruby
|
39
|
+
expression_parser:
|
40
|
+
url: https://github.com/nricciar/expression_parser
|
41
|
+
extended-markdown-filter:
|
42
|
+
url: https://github.com/gjtorikian/extended-markdown-filter
|
43
|
+
gettext:
|
44
|
+
url: https://github.com/ruby-gettext/gettext
|
45
|
+
png_quantizator:
|
46
|
+
url: https://github.com/rogercampos/png_quantizator
|
47
|
+
redis-actionpack:
|
48
|
+
url: https://github.com/redis-store/redis-actionpack
|
49
|
+
redis-rack:
|
50
|
+
url: https://github.com/redis-store/redis-rack
|
51
|
+
sixarm_ruby_unaccent:
|
52
|
+
url: https://github.com/SixArm/sixarm_ruby_unaccent
|
53
|
+
thrift:
|
54
|
+
url: https://github.com/szechyjs/thrift-gem
|
55
|
+
vmstat:
|
56
|
+
url: https://github.com/threez/ruby-vmstat
|
57
|
+
mongoid-rspec:
|
58
|
+
url: https://github.com/mongoid/mongoid-rspec
|
59
|
+
rack-test:
|
60
|
+
url: https://github.com/rack/rack-test
|
61
|
+
jmespath:
|
62
|
+
url: https://github.com/jmespath/jmespath.rb
|
63
|
+
slack-notifier:
|
64
|
+
url: https://github.com/slack-notifier/slack-notifier
|
65
|
+
ipaddress:
|
66
|
+
url: https://github.com/ipaddress-gem/ipaddress
|
67
|
+
fog-powerdns:
|
68
|
+
url: https://github.com/fog/fog-powerdns
|
69
|
+
html_tokenizer:
|
70
|
+
url: https://github.com/EiNSTeiN-/html_tokenizer
|
data/.rspec
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "bundler-alive Documentation" --protected
|
data/README.md
CHANGED
@@ -5,14 +5,14 @@
|
|
5
5
|
[](https://codeclimate.com/github/kyoshidajp/bundler-alive/maintainability)
|
6
6
|
[](https://codeclimate.com/github/kyoshidajp/bundler-alive/test_coverage)
|
7
7
|
|
8
|
-
`
|
8
|
+
`bundler-alive` checks if gems in a RubyGem's `Gemfile.lock` are active.
|
9
9
|
|
10
|
-
Currently
|
10
|
+
Currently, GitHub.com and GitLab.com are supported as a source code repository. If the source code repository is archived, then reports as not alive.
|
11
11
|
|
12
12
|
## Installation
|
13
13
|
|
14
14
|
```
|
15
|
-
$ gem install
|
15
|
+
$ gem install bundler-alive
|
16
16
|
```
|
17
17
|
|
18
18
|
## Usage
|
@@ -23,13 +23,15 @@ $ bundle-alive
|
|
23
23
|
..W....
|
24
24
|
Get all source code repository URLs of gems are done!
|
25
25
|
.....
|
26
|
-
Name: journey
|
27
|
-
URL: http://github.com/rails/journey
|
28
|
-
Status: false
|
29
26
|
|
30
|
-
|
27
|
+
Errors:
|
28
|
+
[bundle-alive] Not found in RubyGems.org.
|
31
29
|
|
32
|
-
|
30
|
+
Archived gems:
|
31
|
+
Name: journey
|
32
|
+
URL: http://github.com/rails/journey
|
33
|
+
|
34
|
+
Total: 6 (Archived: 1, Alive: 4, Unknown: 1)
|
33
35
|
Not alive gems are found!
|
34
36
|
```
|
35
37
|
|
@@ -39,15 +41,51 @@ Default `Gemfile.lock` location is in your current directory. You can specify it
|
|
39
41
|
$ bundle-alive -G /path/to/Gemfile.lock
|
40
42
|
```
|
41
43
|
|
42
|
-
|
44
|
+
## Access Token
|
45
|
+
|
46
|
+
You MUST set environment variables to access source code repository services.
|
47
|
+
|
48
|
+
| Repository service | ENV variable |
|
49
|
+
| ------- |---- |
|
50
|
+
| GitHub | [`BUNDLER_ALIVE_GITHUB_TOKEN`](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) |
|
51
|
+
| GitLab | [`BUNDLER_ALIVE_GITLAB_TOKEN`](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) |
|
52
|
+
|
53
|
+
## Ignore gems
|
54
|
+
|
55
|
+
You can ignore certain gems.
|
56
|
+
|
57
|
+
```
|
58
|
+
$ bundle-alive -i journey rubocop-junit-formatter
|
59
|
+
```
|
60
|
+
|
61
|
+
## Following redirect on RubyGems.org
|
62
|
+
|
63
|
+
The URL for some gems in RubyGems.org may have changed. You can follow the URL (default: doesn't follow).
|
43
64
|
|
44
65
|
```
|
45
|
-
|
66
|
+
$ bundle-alive --follow_redirect
|
46
67
|
```
|
47
68
|
|
48
|
-
|
69
|
+
## Specifying repository URL
|
70
|
+
|
71
|
+
In some cases, some gems cannot find the URL of their source code repositories. For this case, you can specify a mapping between the gem and its URL.
|
72
|
+
|
73
|
+
Put `.bundler-alive.yml` in your current directory. The following code is the sample.
|
74
|
+
|
75
|
+
```yaml
|
76
|
+
---
|
77
|
+
gems:
|
78
|
+
coffee-script-source:
|
79
|
+
url: https://github.com/jashkenas/coffeescript/
|
80
|
+
```
|
81
|
+
|
82
|
+
You can also specify the file path.
|
83
|
+
|
84
|
+
```
|
85
|
+
$ bundle-alive -c /path/to/.bundler-alive.yml
|
86
|
+
```
|
49
87
|
|
50
|
-
|
88
|
+
[.bundler-alive.default.yml](https://github.com/kyoshidajp/bundler-alive/blob/main/.bundler-alive.default.yml) may also be helpful. Considering that having these mappings obtained automatically in the future.
|
51
89
|
|
52
90
|
## Contributing
|
53
91
|
|
data/gemspec.yml
CHANGED
@@ -8,7 +8,7 @@ homepage: https://github.com/kyoshidajp/bundler-alive
|
|
8
8
|
metadata:
|
9
9
|
homepage_uri: https://github.com/kyoshidajp/bundler-alive
|
10
10
|
source_code_uri: https://github.com/kyoshidajp/bundler-alive
|
11
|
-
changelog_uri: https://github.com/kyoshidajp/bundler-alive
|
11
|
+
changelog_uri: https://github.com/kyoshidajp/bundler-alive/commits/main
|
12
12
|
|
13
13
|
required_ruby_version: ">= 2.6.0"
|
14
14
|
bindir: bin
|
@@ -16,10 +16,10 @@ require_paths: lib
|
|
16
16
|
|
17
17
|
dependencies:
|
18
18
|
faraday:
|
19
|
-
|
19
|
+
graphql-client:
|
20
20
|
rake:
|
21
21
|
thor:
|
22
|
-
|
22
|
+
gitlab:
|
23
23
|
|
24
24
|
development_dependencies:
|
25
25
|
rspec:
|
data/lib/bundler/alive/cli.rb
CHANGED
@@ -16,15 +16,15 @@ module Bundler
|
|
16
16
|
map "--version" => :version
|
17
17
|
|
18
18
|
desc "check [DIR]", "Checks the Gemfile.lock"
|
19
|
+
method_option :ignore, type: :array, aliases: "-i", default: []
|
20
|
+
method_option :follow_redirect, type: :boolean, aliases: "-r"
|
19
21
|
method_option :gemfile_lock, type: :string, aliases: "-G",
|
20
22
|
default: "Gemfile.lock"
|
21
|
-
method_option :
|
22
|
-
default: "result.toml"
|
23
|
+
method_option :config, type: :string, aliases: "-c", default: ".bundler-alive.yml"
|
23
24
|
|
24
25
|
def check(_dir = Dir.pwd)
|
25
26
|
extend Reportable
|
26
27
|
report = check_by_doctor
|
27
|
-
report.save_as_file(options[:result])
|
28
28
|
print_report(report)
|
29
29
|
|
30
30
|
exit_status = report.result.all_alive? ? 0 : 1
|
@@ -39,13 +39,23 @@ module Bundler
|
|
39
39
|
private
|
40
40
|
|
41
41
|
def check_by_doctor
|
42
|
-
doctor =
|
43
|
-
|
44
|
-
|
42
|
+
doctor = initialize_doctor
|
43
|
+
|
44
|
+
begin
|
45
|
+
doctor.diagnose
|
46
|
+
rescue Bundler::Alive::Client::GitlabApi::AccessTokenNotFoundError => e
|
47
|
+
say "\n#{e.message}", :yellow
|
45
48
|
exit 1
|
46
49
|
end
|
50
|
+
end
|
47
51
|
|
48
|
-
|
52
|
+
def initialize_doctor
|
53
|
+
Doctor.new(lock_file: options[:gemfile_lock],
|
54
|
+
config_file: options[:config],
|
55
|
+
ignore_gems: options[:ignore],
|
56
|
+
follow_redirect: options[:follow_redirect])
|
57
|
+
rescue Bundler::GemfileLockNotFound
|
58
|
+
exit 1
|
49
59
|
end
|
50
60
|
end
|
51
61
|
end
|
@@ -18,8 +18,21 @@ module Bundler
|
|
18
18
|
class NotFound < StandardError
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
#
|
22
|
+
# A new instance of `GemApiClient`
|
23
|
+
#
|
24
|
+
# @param [String] :config_path
|
25
|
+
# @param [Boolean] :follow_redirect
|
26
|
+
# Follow redirect URL in gems
|
27
|
+
#
|
28
|
+
# @return [GemApiClient]
|
29
|
+
#
|
30
|
+
def initialize(config_path: nil, follow_redirect: false)
|
22
31
|
@error_messages = []
|
32
|
+
@config_gems = get_config_gems(config_path)
|
33
|
+
@follow_redirect = follow_redirect
|
34
|
+
|
35
|
+
freeze
|
23
36
|
end
|
24
37
|
|
25
38
|
#
|
@@ -29,8 +42,8 @@ module Bundler
|
|
29
42
|
#
|
30
43
|
# @return [Client::GemsApiResponse]
|
31
44
|
#
|
32
|
-
def gems_api_response(gem_names
|
33
|
-
urls = service_with_urls(gem_names
|
45
|
+
def gems_api_response(gem_names)
|
46
|
+
urls = service_with_urls(gem_names)
|
34
47
|
$stdout.puts <<~MESSAGE
|
35
48
|
|
36
49
|
Get all source code repository URLs of gems are done!
|
@@ -43,7 +56,7 @@ module Bundler
|
|
43
56
|
|
44
57
|
private
|
45
58
|
|
46
|
-
attr_accessor :error_messages
|
59
|
+
attr_accessor :error_messages, :config_gems, :follow_redirect
|
47
60
|
|
48
61
|
def api_url(gem_name)
|
49
62
|
"https://rubygems.org/api/v1/gems/#{gem_name}.json"
|
@@ -55,8 +68,15 @@ module Bundler
|
|
55
68
|
end
|
56
69
|
end
|
57
70
|
|
58
|
-
def
|
59
|
-
|
71
|
+
def get_config_gems(path)
|
72
|
+
return {} if path.nil? || !File.exist?(path)
|
73
|
+
|
74
|
+
config = YAML.load_file(path)
|
75
|
+
config["gems"]
|
76
|
+
end
|
77
|
+
|
78
|
+
def service_with_urls(gem_names)
|
79
|
+
urls = get_repository_urls(gem_names)
|
60
80
|
urls.each_with_object({}) do |url, hash|
|
61
81
|
service_name = url.service_name
|
62
82
|
hash[service_name] = Array(hash[service_name]) << url
|
@@ -71,29 +91,51 @@ module Bundler
|
|
71
91
|
# @return [SourceCodeRepositoryUrl]
|
72
92
|
#
|
73
93
|
def get_repository_url(gem_name)
|
94
|
+
url_from_config = get_repository_url_from_config(gem_name)
|
95
|
+
return url_from_config unless url_from_config.nil?
|
96
|
+
|
74
97
|
url = api_url(gem_name)
|
75
98
|
response = connection.get(url)
|
76
99
|
|
77
|
-
raise NotFound, "
|
100
|
+
raise NotFound, "[#{gem_name}] Not found in RubyGems.org." unless response.success?
|
78
101
|
|
79
102
|
body = JSON.parse(response.body)
|
80
103
|
raw_url = source_code_url(body: body, gem_name: gem_name)
|
81
104
|
SourceCodeRepositoryUrl.new(raw_url, gem_name)
|
82
105
|
end
|
83
106
|
|
107
|
+
def get_repository_url_from_config(gem_name)
|
108
|
+
return nil if config_gems.nil?
|
109
|
+
return nil unless config_gems.key?(gem_name)
|
110
|
+
|
111
|
+
gem = config_gems[gem_name]
|
112
|
+
SourceCodeRepositoryUrl.new(gem["url"], gem_name)
|
113
|
+
end
|
114
|
+
|
84
115
|
def source_code_url(body:, gem_name:)
|
85
116
|
url = body["source_code_uri"]
|
86
|
-
return url if SourceCodeRepositoryUrl.support_url?(url)
|
117
|
+
return url_via_redirect(url) if SourceCodeRepositoryUrl.support_url?(url)
|
87
118
|
|
88
119
|
url = body["homepage_uri"]
|
89
|
-
return url if SourceCodeRepositoryUrl.support_url?(url)
|
120
|
+
return url_via_redirect(url) if SourceCodeRepositoryUrl.support_url?(url)
|
121
|
+
|
122
|
+
message = "[#{gem_name}] Source code repository is not found in RubyGems.org,"\
|
123
|
+
" or not supported. URL: https://rubygems.org/gems/#{gem_name}"
|
124
|
+
raise NotFound, message
|
125
|
+
end
|
126
|
+
|
127
|
+
def url_via_redirect(url)
|
128
|
+
return url unless follow_redirect
|
129
|
+
|
130
|
+
response = connection.head(url)
|
131
|
+
return response.headers["location"] if response.status == 301
|
90
132
|
|
91
|
-
|
133
|
+
url
|
92
134
|
end
|
93
135
|
|
94
136
|
def get_repository_urls(gem_names)
|
95
137
|
result = gem_names.map do |gem_name|
|
96
|
-
|
138
|
+
$stdout.write "."
|
97
139
|
get_repository_url(gem_name)
|
98
140
|
rescue StandardError => e
|
99
141
|
$stdout.write "W"
|
@@ -1,51 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "octokit"
|
4
3
|
require "json"
|
5
4
|
|
6
5
|
module Bundler
|
7
6
|
module Alive
|
8
7
|
module Client
|
9
8
|
#
|
10
|
-
# API Client for GitHub API
|
9
|
+
# API Client for GitHub GraphQL API
|
11
10
|
#
|
12
|
-
# @see https://docs.github.com/en/
|
11
|
+
# @see https://docs.github.com/en/graphql
|
13
12
|
#
|
14
|
-
module
|
13
|
+
module GithubApi
|
15
14
|
# Environment variable name of GitHub Access Token
|
16
15
|
ACCESS_TOKEN_ENV_NAME = "BUNDLER_ALIVE_GITHUB_TOKEN"
|
17
16
|
|
18
17
|
# Separator of query condition
|
19
18
|
QUERY_CONDITION_SEPARATOR = " "
|
19
|
+
private_constant :QUERY_CONDITION_SEPARATOR
|
20
20
|
|
21
|
-
# Number of attempts to request after too many requests
|
22
|
-
RETRIES_ON_TOO_MANY_REQUESTS = 3
|
23
|
-
|
24
|
-
# Interval second when retrying request
|
25
|
-
RETRY_INTERVAL_SEC_ON_TOO_MANY_REQUESTS = 120
|
26
|
-
|
27
|
-
#
|
28
21
|
# Max number of conditional operator at once
|
29
|
-
|
30
|
-
# @see https://docs.github.com/en/rest/search#limitations-on-query-length
|
31
|
-
QUERY_MAX_OPERATORS_AT_ONCE = 6
|
32
|
-
|
22
|
+
QUERY_MAX_OPERATORS_AT_ONCE = 50
|
33
23
|
private_constant :QUERY_MAX_OPERATORS_AT_ONCE
|
34
24
|
|
35
25
|
def self.extended(base)
|
36
26
|
base.instance_eval do
|
37
27
|
@rate_limit_exceeded = false
|
38
28
|
@retries_on_too_many_requests = 0
|
29
|
+
@name_with_archived = {}
|
39
30
|
end
|
40
31
|
end
|
41
32
|
|
42
33
|
#
|
43
|
-
# Creates a
|
34
|
+
# Creates a GraphQL client
|
44
35
|
#
|
45
|
-
# @return [
|
36
|
+
# @return [GraphQL::Client]
|
46
37
|
#
|
47
38
|
def create_client
|
48
|
-
|
39
|
+
require_relative "github_graphql"
|
40
|
+
extend GithubGraphql
|
41
|
+
|
42
|
+
GithubGraphql::CLIENT
|
49
43
|
end
|
50
44
|
|
51
45
|
#
|
@@ -55,15 +49,12 @@ module Bundler
|
|
55
49
|
#
|
56
50
|
# @return [StatusResult]
|
57
51
|
#
|
58
|
-
# rubocop:disable Metrics/MethodLength
|
59
52
|
def query(urls:)
|
60
53
|
collection = StatusCollection.new
|
61
|
-
name_with_archived = get_name_with_statuses(urls)
|
54
|
+
@name_with_archived = get_name_with_statuses(urls)
|
62
55
|
urls.each do |url|
|
63
|
-
yield if block_given?
|
64
|
-
|
65
56
|
gem_name = url.gem_name
|
66
|
-
alive =
|
57
|
+
alive = alive?(gem_name)
|
67
58
|
status = Status.new(name: gem_name, repository_url: url, alive: alive, checked_at: Time.now)
|
68
59
|
collection = collection.add(gem_name, status)
|
69
60
|
end
|
@@ -71,10 +62,16 @@ module Bundler
|
|
71
62
|
StatusResult.new(collection: collection, error_messages: @error_messages,
|
72
63
|
rate_limit_exceeded: @rate_limit_exceeded)
|
73
64
|
end
|
74
|
-
# rubocop:enable Metrics/MethodLength
|
75
65
|
|
76
66
|
private
|
77
67
|
|
68
|
+
def alive?(gem_name)
|
69
|
+
return false unless @name_with_archived.key?(gem_name)
|
70
|
+
|
71
|
+
value = @name_with_archived[gem_name]
|
72
|
+
value == Status::ALIVE_UNKNOWN ? Status::ALIVE_UNKNOWN : !value
|
73
|
+
end
|
74
|
+
|
78
75
|
#
|
79
76
|
# Search status of repositories
|
80
77
|
#
|
@@ -85,34 +82,53 @@ module Bundler
|
|
85
82
|
#
|
86
83
|
# rubocop:disable Metrics/MethodLength
|
87
84
|
def get_name_with_statuses(urls)
|
88
|
-
raise ArgumentError unless urls.instance_of?(Array)
|
89
|
-
|
90
85
|
name_with_status = {}
|
91
86
|
urls.each_slice(QUERY_MAX_OPERATORS_AT_ONCE) do |sliced_urls|
|
87
|
+
$stdout.print "." * sliced_urls.size
|
88
|
+
|
92
89
|
q = search_query(sliced_urls)
|
93
90
|
repositories = search_repositories_with_retry(q)
|
94
91
|
next if repositories.nil?
|
95
92
|
|
96
|
-
|
97
|
-
|
98
|
-
|
93
|
+
sliced_urls.each do |url|
|
94
|
+
repository = find_repository_from_repositories(url: url,
|
95
|
+
repositories: repositories)
|
96
|
+
alive_status = if repository.nil?
|
97
|
+
Status::ALIVE_UNKNOWN
|
98
|
+
else
|
99
|
+
repository["isArchived"]
|
100
|
+
end
|
101
|
+
name_with_status[url.gem_name] = alive_status
|
99
102
|
end
|
100
103
|
end
|
101
104
|
name_with_status
|
102
105
|
end
|
103
106
|
# rubocop:enable Metrics/MethodLength
|
104
107
|
|
108
|
+
# @param [SourceCodeRepositoryUrl] :url
|
109
|
+
# @param [Array<Sawyer::Resource>] :repositories
|
110
|
+
#
|
111
|
+
# @return [Sawyer::Resource|nil]
|
112
|
+
def find_repository_from_repositories(url:, repositories:)
|
113
|
+
repositories.find do |repository|
|
114
|
+
# e.g.) tod's URL is https://github.com/JackC/tod
|
115
|
+
# but, the `nameWithOwner` is `jacks/tod`
|
116
|
+
slug(url.url).downcase == repository["nameWithOwner"].downcase
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
105
120
|
#
|
106
|
-
# Search query of repositories
|
121
|
+
# Search query of repositories (includes forked)
|
107
122
|
#
|
108
123
|
# @param [Array<RepositoryUrl>] urls
|
109
124
|
#
|
110
125
|
# @return [String]
|
111
126
|
#
|
112
127
|
def search_query(urls)
|
113
|
-
urls.map do |url|
|
128
|
+
repository_query = urls.map do |url|
|
114
129
|
"repo:#{slug(url.url)}"
|
115
130
|
end.join(QUERY_CONDITION_SEPARATOR)
|
131
|
+
"#{repository_query} fork:true"
|
116
132
|
end
|
117
133
|
|
118
134
|
#
|
@@ -120,18 +136,12 @@ module Bundler
|
|
120
136
|
#
|
121
137
|
# @param [String] query
|
122
138
|
#
|
123
|
-
# @raise [Octokit::TooManyRequests]
|
124
|
-
# when too many requested to GitHub.com
|
125
|
-
# @raise [SourceCodeClient::SearchRepositoryError]
|
126
|
-
# when Error without `Octokit::TooManyRequests`
|
127
|
-
#
|
128
139
|
# @return [Array<Sawyer::Resource>|nil]
|
129
140
|
#
|
130
141
|
def search_repositories(query)
|
131
|
-
result = @client.
|
132
|
-
|
133
|
-
|
134
|
-
raise e
|
142
|
+
result = @client.query(GithubGraphql::Query,
|
143
|
+
variables: { var_query: query, var_first: QUERY_MAX_OPERATORS_AT_ONCE })
|
144
|
+
result.data.search.nodes.map(&:to_h)
|
135
145
|
rescue StandardError => e
|
136
146
|
@error_messages << e.message
|
137
147
|
[]
|
@@ -139,21 +149,6 @@ module Bundler
|
|
139
149
|
|
140
150
|
def search_repositories_with_retry(query)
|
141
151
|
search_repositories(query)
|
142
|
-
rescue Octokit::TooManyRequests
|
143
|
-
if @retries_on_too_many_requests < RETRIES_ON_TOO_MANY_REQUESTS
|
144
|
-
@retries_on_too_many_requests += 1
|
145
|
-
sleep_with_message
|
146
|
-
retry
|
147
|
-
end
|
148
|
-
|
149
|
-
@rate_limit_exceeded = true
|
150
|
-
[]
|
151
|
-
end
|
152
|
-
|
153
|
-
def sleep_with_message
|
154
|
-
puts "Too many requested. Sleep #{RETRY_INTERVAL_SEC_ON_TOO_MANY_REQUESTS} sec."
|
155
|
-
sleep RETRY_INTERVAL_SEC_ON_TOO_MANY_REQUESTS
|
156
|
-
puts "Retry request (#{@retries_on_too_many_requests}/#{RETRIES_ON_TOO_MANY_REQUESTS})"
|
157
152
|
end
|
158
153
|
|
159
154
|
#
|
@@ -164,7 +159,13 @@ module Bundler
|
|
164
159
|
# @return [String]
|
165
160
|
#
|
166
161
|
def slug(repository_url)
|
167
|
-
|
162
|
+
# from https://github.com/octokit/octokit.rb/blob/v4.22.0/lib/octokit/repository.rb#L12-L17
|
163
|
+
github_slug = URI.parse(repository_url).path[1..]
|
164
|
+
.gsub(%r{^repos/}, "")
|
165
|
+
.split("/", 3)[0..1]
|
166
|
+
.join("/")
|
167
|
+
|
168
|
+
github_slug.gsub(/\.git/, "")
|
168
169
|
end
|
169
170
|
end
|
170
171
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphql/client"
|
4
|
+
require "graphql/client/http"
|
5
|
+
|
6
|
+
module Bundler
|
7
|
+
module Alive
|
8
|
+
module Client
|
9
|
+
# GitHub GraphQL Module
|
10
|
+
module GithubGraphql
|
11
|
+
#
|
12
|
+
# Access token isn't set error
|
13
|
+
#
|
14
|
+
# @see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
|
15
|
+
#
|
16
|
+
class AccessTokenNotFoundError < StandardError
|
17
|
+
def initialize(_message = nil)
|
18
|
+
message = "Environment variable #{ACCESS_TOKEN_ENV_NAME} is not set."\
|
19
|
+
" Need to set GitHub Personal Access Token to be authenticated at GitHub GraphQL API."\
|
20
|
+
" See: https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#the-graphql-endpoint"
|
21
|
+
super(message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Environment variable name of GitHub Access Token
|
26
|
+
ACCESS_TOKEN_ENV_NAME = "BUNDLER_ALIVE_GITHUB_TOKEN"
|
27
|
+
|
28
|
+
# GraphQL API endpoint
|
29
|
+
# @see https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#the-graphql-endpoint
|
30
|
+
ENDPOINT = "https://api.github.com/graphql"
|
31
|
+
private_constant :ENDPOINT
|
32
|
+
|
33
|
+
QUERY = <<~QUERY
|
34
|
+
query($var_query: String!, $var_first: Int!) {
|
35
|
+
search(
|
36
|
+
query: $var_query
|
37
|
+
type: REPOSITORY
|
38
|
+
first: $var_first
|
39
|
+
) {
|
40
|
+
repositoryCount
|
41
|
+
nodes {
|
42
|
+
... on Repository {
|
43
|
+
isArchived
|
44
|
+
nameWithOwner
|
45
|
+
isMirror
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
QUERY
|
51
|
+
private_constant :QUERY
|
52
|
+
|
53
|
+
HTTP = GraphQL::Client::HTTP.new(ENDPOINT) do
|
54
|
+
def headers(_context)
|
55
|
+
token = ENV.fetch(ACCESS_TOKEN_ENV_NAME, nil)
|
56
|
+
raise AccessTokenNotFoundError if token.nil?
|
57
|
+
|
58
|
+
{
|
59
|
+
"Authorization" => "Bearer #{token}"
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
private_constant :HTTP
|
64
|
+
|
65
|
+
if File.exist?(SCHEMA_PATH)
|
66
|
+
SCHEMA = GraphQL::Client.load_schema(SCHEMA_PATH)
|
67
|
+
else
|
68
|
+
SCHEMA = GraphQL::Client.load_schema(HTTP)
|
69
|
+
Dir.mkdir(USER_PATH) unless Dir.exist?(USER_PATH)
|
70
|
+
GraphQL::Client.dump_schema(SCHEMA, SCHEMA_PATH)
|
71
|
+
end
|
72
|
+
private_constant :SCHEMA
|
73
|
+
|
74
|
+
# GraphQL client
|
75
|
+
CLIENT = GraphQL::Client.new(schema: SCHEMA, execute: HTTP)
|
76
|
+
|
77
|
+
# query
|
78
|
+
Query = CLIENT.parse(QUERY)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gitlab"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Bundler
|
7
|
+
module Alive
|
8
|
+
module Client
|
9
|
+
#
|
10
|
+
# API Client for GitLab API
|
11
|
+
#
|
12
|
+
# @see https://docs.gitlab.com/ee/api/projects.html#get-single-project
|
13
|
+
#
|
14
|
+
module GitlabApi
|
15
|
+
#
|
16
|
+
# Access token isn't set error
|
17
|
+
#
|
18
|
+
# @see https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
|
19
|
+
#
|
20
|
+
class AccessTokenNotFoundError < StandardError
|
21
|
+
def initialize(_message = nil)
|
22
|
+
message = "Environment variable #{ACCESS_TOKEN_ENV_NAME} is not set."\
|
23
|
+
" Need to set GitLab Personal Access Token to be authenticated at gitlab.com API."\
|
24
|
+
" See: https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html"
|
25
|
+
super(message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Environment variable name of GitLab Access Token
|
30
|
+
#
|
31
|
+
# @see https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
|
32
|
+
#
|
33
|
+
ACCESS_TOKEN_ENV_NAME = "BUNDLER_ALIVE_GITLAB_TOKEN"
|
34
|
+
|
35
|
+
# Endpoint of GitLab API
|
36
|
+
ENDPOINT = "https://gitlab.com/api/v4"
|
37
|
+
|
38
|
+
def self.extended(base)
|
39
|
+
base.instance_eval do
|
40
|
+
@rate_limit_exceeded = false
|
41
|
+
@retries_on_too_many_requests = 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Creates a GitLab client
|
47
|
+
#
|
48
|
+
# @return [Gitlab::Client]
|
49
|
+
#
|
50
|
+
def create_client
|
51
|
+
access_token = ENV.fetch(ACCESS_TOKEN_ENV_NAME, nil)
|
52
|
+
raise AccessTokenNotFoundError if access_token.nil?
|
53
|
+
|
54
|
+
Gitlab.client(
|
55
|
+
endpoint: ENDPOINT,
|
56
|
+
private_token: access_token
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Query repository statuses
|
62
|
+
#
|
63
|
+
# @param [Array<RepositoryUrl>] :urls
|
64
|
+
#
|
65
|
+
# @return [StatusResult]
|
66
|
+
#
|
67
|
+
def query(urls:)
|
68
|
+
collection = StatusCollection.new
|
69
|
+
name_with_archived = get_name_with_statuses(urls)
|
70
|
+
urls.each do |url|
|
71
|
+
gem_name = url.gem_name
|
72
|
+
alive = name_with_archived.key?(gem_name) && !name_with_archived[gem_name]
|
73
|
+
status = Status.new(name: gem_name, repository_url: url, alive: alive, checked_at: Time.now)
|
74
|
+
collection = collection.add(gem_name, status)
|
75
|
+
end
|
76
|
+
|
77
|
+
StatusResult.new(collection: collection, error_messages: @error_messages,
|
78
|
+
rate_limit_exceeded: @rate_limit_exceeded)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
#
|
84
|
+
# Search status of repositories
|
85
|
+
#
|
86
|
+
# @param [Array<RepositoryUrl>] urls
|
87
|
+
#
|
88
|
+
# @return [Hash<String, Boolean>]
|
89
|
+
# gem name with archived or not
|
90
|
+
#
|
91
|
+
def get_name_with_statuses(urls)
|
92
|
+
name_with_status = {}
|
93
|
+
urls.each do |url|
|
94
|
+
$stdout.write "."
|
95
|
+
project = search_repositories_with_retry(url)
|
96
|
+
next if project.nil? || project.empty?
|
97
|
+
|
98
|
+
name = url.gem_name
|
99
|
+
name_with_status[name] = project["archived"]
|
100
|
+
end
|
101
|
+
name_with_status
|
102
|
+
end
|
103
|
+
|
104
|
+
def project_path_from_url(url)
|
105
|
+
uri = URI.parse(url.url)
|
106
|
+
uri.path.split("/")[1..].join("/")
|
107
|
+
end
|
108
|
+
|
109
|
+
def search_repositories_with_retry(url)
|
110
|
+
project_path = project_path_from_url(url)
|
111
|
+
|
112
|
+
# Must be converted to hash due to warnings
|
113
|
+
@client.project(project_path).to_h
|
114
|
+
rescue StandardError => e
|
115
|
+
@error_messages << e.message
|
116
|
+
[]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -16,7 +16,8 @@ module Bundler
|
|
16
16
|
end
|
17
17
|
|
18
18
|
SERVICE_WITH_STRATEGIES = {
|
19
|
-
SourceCodeRepository::Service::GITHUB =>
|
19
|
+
SourceCodeRepository::Service::GITHUB => GithubApi,
|
20
|
+
SourceCodeRepository::Service::GITLAB => GitlabApi
|
20
21
|
}.freeze
|
21
22
|
|
22
23
|
private_constant :SERVICE_WITH_STRATEGIES
|
data/lib/bundler/alive/doctor.rb
CHANGED
@@ -1,33 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler"
|
4
|
-
require "octokit"
|
5
|
-
require "toml-rb"
|
6
4
|
|
7
5
|
module Bundler
|
8
6
|
module Alive
|
9
7
|
#
|
10
|
-
# Diagnoses a `Gemfile.lock`
|
8
|
+
# Diagnoses a `Gemfile.lock`
|
11
9
|
#
|
12
10
|
class Doctor
|
13
11
|
#
|
14
12
|
# A new instance of Doctor
|
15
13
|
#
|
16
|
-
# @param [String] lock_file lock file of gem
|
17
|
-
# @param [String]
|
14
|
+
# @param [String] :lock_file # lock file of gem
|
15
|
+
# @param [String] :config_file # config file
|
16
|
+
# @param [Array<String>] :ignore_gems ignore gems
|
17
|
+
# @param [Boolean] :follow_redirect Follow redirect URL in gems
|
18
18
|
#
|
19
|
-
def initialize(lock_file
|
19
|
+
def initialize(lock_file:, config_file:, ignore_gems:, follow_redirect: false)
|
20
20
|
@lock_file = lock_file
|
21
|
-
@
|
22
|
-
@
|
21
|
+
@gem_client = Client::GemsApiClient.new(config_path: config_file, follow_redirect: follow_redirect)
|
22
|
+
@ignore_gems = ignore_gems
|
23
23
|
@result = nil
|
24
24
|
@rate_limit_exceeded = false
|
25
|
-
@announcer = Announcer.new
|
26
25
|
@error_messages = []
|
27
26
|
end
|
28
27
|
|
29
28
|
#
|
30
|
-
# Diagnoses gems in lock
|
29
|
+
# Diagnoses gems in Gemfile.lock
|
31
30
|
#
|
32
31
|
# @raise [Client::SourceCodeClient::RateLimitExceededError]
|
33
32
|
# When exceeded access rate limit
|
@@ -38,41 +37,27 @@ module Bundler
|
|
38
37
|
# @return [Report]
|
39
38
|
#
|
40
39
|
def diagnose
|
41
|
-
|
40
|
+
message = "#{collection_from_gemfile.total_size + ignore_gems.size} gems are in Gemfile.lock"
|
41
|
+
message = "#{message} (#{ignore_gems.size} gems are ignored)" if ignore_gems.size.positive?
|
42
|
+
$stdout.puts message
|
43
|
+
|
42
44
|
result = _diagnose
|
43
45
|
Report.new(result)
|
44
46
|
end
|
45
47
|
|
46
48
|
private
|
47
49
|
|
48
|
-
attr_reader :lock_file, :
|
50
|
+
attr_reader :lock_file, :gem_client, :ignore_gems,
|
49
51
|
:result, :error_messages, :rate_limit_exceeded
|
50
52
|
|
51
|
-
#
|
52
|
-
# @return [Array<String>]
|
53
|
-
#
|
54
|
-
def no_need_to_get_gems
|
55
|
-
return [] unless File.exist?(result_file)
|
56
|
-
|
57
|
-
toml_hash = TomlRB.load_file(result_file)
|
58
|
-
toml_hash.each_with_object([]) do |(gem_name, v), array|
|
59
|
-
alive = v["alive"]
|
60
|
-
array << gem_name unless alive
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
53
|
def diagnose_by_service(service, urls)
|
65
54
|
client = Client::SourceCodeClient.new(service_name: service)
|
66
|
-
client.query(urls: urls)
|
67
|
-
announcer.announce
|
68
|
-
end
|
55
|
+
client.query(urls: urls)
|
69
56
|
end
|
70
57
|
|
58
|
+
# @return [StatusResult]
|
71
59
|
def result_by_search(collection)
|
72
|
-
gems_api_response = gem_client.gems_api_response(collection.names)
|
73
|
-
announcer.announce
|
74
|
-
end
|
75
|
-
|
60
|
+
gems_api_response = gem_client.gems_api_response(collection.names)
|
76
61
|
service_with_urls = gems_api_response.service_with_urls
|
77
62
|
error_messages.concat(gems_api_response.error_messages)
|
78
63
|
|
@@ -83,19 +68,12 @@ module Bundler
|
|
83
68
|
result
|
84
69
|
end
|
85
70
|
|
86
|
-
|
87
|
-
collection = StatusCollection.new
|
88
|
-
base_collection.each do |name, status|
|
89
|
-
next if gem_names.include?(name)
|
90
|
-
|
91
|
-
collection = collection.add(name, status)
|
92
|
-
end
|
93
|
-
collection
|
94
|
-
end
|
95
|
-
|
71
|
+
# @return [StatusCollection]
|
96
72
|
def collection_from_gemfile
|
97
73
|
gems_from_lockfile.each_with_object(StatusCollection.new) do |gem, collection|
|
98
74
|
gem_name = gem.name
|
75
|
+
next if ignore_gems.include?(gem_name)
|
76
|
+
|
99
77
|
status = Status.new(name: gem_name,
|
100
78
|
repository_url: nil,
|
101
79
|
alive: nil,
|
@@ -104,14 +82,13 @@ module Bundler
|
|
104
82
|
end
|
105
83
|
end
|
106
84
|
|
85
|
+
# @return [StatusResult]
|
107
86
|
def _diagnose
|
108
|
-
collection =
|
87
|
+
collection = collection_from_gemfile
|
109
88
|
result = result_by_search(collection)
|
110
|
-
|
111
|
-
new_collection = collection_from_gemfile.merge(collection_from_toml_file)
|
112
|
-
.merge(result.collection)
|
113
|
-
|
89
|
+
new_collection = collection.merge(result.collection)
|
114
90
|
messages = error_messages.concat(result.error_messages)
|
91
|
+
|
115
92
|
StatusResult.new(collection: new_collection,
|
116
93
|
error_messages: messages,
|
117
94
|
rate_limit_exceeded: result.rate_limit_exceeded)
|
data/lib/bundler/alive/report.rb
CHANGED
@@ -21,11 +21,11 @@ module Bundler
|
|
21
21
|
error_messages = report.error_messages
|
22
22
|
print_error(error_messages)
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
unknown_gems = result.unknown_gems
|
25
|
+
print_archived_gems(unknown_gems, header: "Unknown gems:") if unknown_gems.size.positive?
|
26
|
+
|
27
|
+
archived_gems = result.archived_gems
|
28
|
+
print_archived_gems(archived_gems, header: "Archived gems:") if archived_gems.size.positive?
|
29
29
|
|
30
30
|
print_summary(result)
|
31
31
|
print_message(result, report.rate_limit_exceeded)
|
@@ -33,18 +33,33 @@ module Bundler
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
+
# soo messy
|
37
|
+
def print_archived_gems(gems, header:)
|
38
|
+
$stdout.puts
|
39
|
+
$stdout.puts header
|
40
|
+
gems_to_report = gems.map do |_name, gem|
|
41
|
+
gem.report.split("\n").each_with_object([]) do |line, gem_str|
|
42
|
+
gem_str << " #{line}"
|
43
|
+
end.join("\n")
|
44
|
+
end
|
45
|
+
$stdout.puts gems_to_report.join("\n\n")
|
46
|
+
end
|
47
|
+
|
36
48
|
def print_error(error_messages)
|
37
|
-
return if error_messages.
|
49
|
+
return if error_messages.empty?
|
38
50
|
|
39
51
|
$stdout.puts <<~ERROR
|
40
52
|
|
41
|
-
|
53
|
+
|
54
|
+
Errors:
|
55
|
+
#{error_messages.join("\n ")}
|
42
56
|
ERROR
|
43
57
|
end
|
44
58
|
|
45
59
|
def print_summary(result)
|
46
60
|
$stdout.puts <<~RESULT
|
47
|
-
|
61
|
+
|
62
|
+
Total: #{result.total_size} (Archived: #{result.archived_size}, Unknown: #{result.unknown_size}, Alive: #{result.alive_size})
|
48
63
|
RESULT
|
49
64
|
end
|
50
65
|
|
@@ -55,7 +70,7 @@ module Bundler
|
|
55
70
|
end
|
56
71
|
|
57
72
|
say "Too many requested! Retry later.", :yellow if rate_limit_exceeded
|
58
|
-
if result.
|
73
|
+
if result.archived_size.positive?
|
59
74
|
say "Not alive gems are found!", :red
|
60
75
|
return
|
61
76
|
end
|
@@ -6,6 +6,7 @@ module Bundler
|
|
6
6
|
class SourceCodeRepository
|
7
7
|
module Service
|
8
8
|
GITHUB = :github
|
9
|
+
GITLAB = :gitlab
|
9
10
|
end
|
10
11
|
|
11
12
|
#
|
@@ -17,6 +18,8 @@ module Bundler
|
|
17
18
|
raise ArgumentError, "Unknown url: #{url}" unless url.instance_of?(SourceCodeRepositoryUrl)
|
18
19
|
|
19
20
|
@url = url
|
21
|
+
|
22
|
+
freeze
|
20
23
|
end
|
21
24
|
|
22
25
|
private
|
@@ -7,7 +7,8 @@ module Bundler
|
|
7
7
|
# service domain with service
|
8
8
|
DOMAIN_WITH_SERVICES = {
|
9
9
|
"github.com" => SourceCodeRepository::Service::GITHUB,
|
10
|
-
"www.github.com" => SourceCodeRepository::Service::GITHUB
|
10
|
+
"www.github.com" => SourceCodeRepository::Service::GITHUB,
|
11
|
+
"gitlab.com" => SourceCodeRepository::Service::GITLAB
|
11
12
|
}.freeze
|
12
13
|
|
13
14
|
private_constant :DOMAIN_WITH_SERVICES
|
@@ -44,6 +45,8 @@ module Bundler
|
|
44
45
|
end
|
45
46
|
message = "[#{name}] is not support URL: #{decorated_url}"
|
46
47
|
super(message)
|
48
|
+
|
49
|
+
freeze
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
data/lib/bundler/alive/status.rb
CHANGED
@@ -20,7 +20,7 @@ module Bundler
|
|
20
20
|
# @param [String] :name
|
21
21
|
# @param [SourceCodeRepositoryUrl] :repository_url
|
22
22
|
# @param [Boolean] :alive
|
23
|
-
# @param [] :checked_at
|
23
|
+
# @param [Time] :checked_at
|
24
24
|
#
|
25
25
|
# @return [Status]
|
26
26
|
#
|
@@ -67,7 +67,6 @@ module Bundler
|
|
67
67
|
<<~REPORT
|
68
68
|
Name: #{name}
|
69
69
|
URL: #{decorated_repository_url}
|
70
|
-
Status: #{decorated_alive}
|
71
70
|
|
72
71
|
REPORT
|
73
72
|
end
|
@@ -10,30 +10,7 @@ module Bundler
|
|
10
10
|
delegate each: :collection
|
11
11
|
delegate values: :collection
|
12
12
|
|
13
|
-
attr_reader :alive_size, :
|
14
|
-
|
15
|
-
#
|
16
|
-
# Creates `StatusCollection` from TOML file
|
17
|
-
#
|
18
|
-
# @param [String] path
|
19
|
-
#
|
20
|
-
# @return [StatusCollection]
|
21
|
-
#
|
22
|
-
def self.new_from_toml_file(path)
|
23
|
-
return new unless File.exist?(path)
|
24
|
-
|
25
|
-
collection = new
|
26
|
-
TomlRB.load_file(path).each do |name, v|
|
27
|
-
next if v["alive"] == Status::ALIVE_UNKNOWN
|
28
|
-
|
29
|
-
url = SourceCodeRepositoryUrl.new(v["repository_url"], name)
|
30
|
-
status = Status.new(name: name, repository_url: url,
|
31
|
-
alive: v["alive"], checked_at: v["checked_at"])
|
32
|
-
collection = collection.add(name, status)
|
33
|
-
end
|
34
|
-
|
35
|
-
collection
|
36
|
-
end
|
13
|
+
attr_reader :alive_size, :archived_size, :unknown_size
|
37
14
|
|
38
15
|
#
|
39
16
|
# Generates instance of `StatusCollection`
|
@@ -48,7 +25,8 @@ module Bundler
|
|
48
25
|
|
49
26
|
@alive_size = _alive_size
|
50
27
|
@unknown_size = _unknown_size
|
51
|
-
@
|
28
|
+
@archived_size = _archived_size
|
29
|
+
|
52
30
|
freeze
|
53
31
|
end
|
54
32
|
|
@@ -107,10 +85,14 @@ module Bundler
|
|
107
85
|
collection.transform_values(&:to_h)
|
108
86
|
end
|
109
87
|
|
110
|
-
def
|
88
|
+
def archived_gems
|
111
89
|
collection.find_all { |_name, gem| !!!gem.alive }
|
112
90
|
end
|
113
91
|
|
92
|
+
def unknown_gems
|
93
|
+
collection.find_all { |_name, gem| gem.unknown? }
|
94
|
+
end
|
95
|
+
|
114
96
|
#
|
115
97
|
# All of statuses are alive nor not
|
116
98
|
#
|
@@ -137,7 +119,7 @@ module Bundler
|
|
137
119
|
statuses_values.count { |gem| !!gem.alive && !gem.unknown? }
|
138
120
|
end
|
139
121
|
|
140
|
-
def
|
122
|
+
def _archived_size
|
141
123
|
statuses_values.count { |gem| !gem.alive && !gem.unknown? }
|
142
124
|
end
|
143
125
|
|
@@ -17,10 +17,12 @@ module Bundler
|
|
17
17
|
#
|
18
18
|
# @return [StatusResult]
|
19
19
|
#
|
20
|
-
def initialize(collection:
|
20
|
+
def initialize(collection: StatusCollection.new, error_messages: [], rate_limit_exceeded: false)
|
21
21
|
@collection = collection
|
22
22
|
@error_messages = error_messages
|
23
23
|
@rate_limit_exceeded = rate_limit_exceeded
|
24
|
+
|
25
|
+
freeze
|
24
26
|
end
|
25
27
|
|
26
28
|
#
|
@@ -31,9 +33,12 @@ module Bundler
|
|
31
33
|
# @return [StatusResult]
|
32
34
|
#
|
33
35
|
def merge(result)
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
merged_collection = collection.merge(result.collection)
|
37
|
+
merged_error_messages = error_messages + result.error_messages
|
38
|
+
merged_rate_limit_exceeded = rate_limit_exceeded || result.rate_limit_exceeded
|
39
|
+
self.class.new(collection: merged_collection,
|
40
|
+
error_messages: merged_error_messages,
|
41
|
+
rate_limit_exceeded: merged_rate_limit_exceeded)
|
37
42
|
end
|
38
43
|
end
|
39
44
|
end
|
data/lib/bundler/alive.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Bundler
|
4
|
+
module Alive
|
5
|
+
USER_PATH = File.expand_path(File.join(Gem.user_home, ".local", "share", "bundler-alive"))
|
6
|
+
SCHEMA_PATH = File.join(USER_PATH, "schema.json")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
3
10
|
require_relative "alive/version"
|
4
11
|
require_relative "alive/doctor"
|
5
12
|
require_relative "alive/source_code_repository"
|
@@ -7,10 +14,10 @@ require_relative "alive/source_code_repository_url"
|
|
7
14
|
require_relative "alive/status"
|
8
15
|
require_relative "alive/status_result"
|
9
16
|
require_relative "alive/status_collection"
|
10
|
-
require_relative "alive/announcer"
|
11
17
|
require_relative "alive/report"
|
12
18
|
require_relative "alive/client/gems_api_client"
|
13
19
|
require_relative "alive/client/gems_api_response"
|
14
|
-
require_relative "alive/client/
|
20
|
+
require_relative "alive/client/github_api"
|
21
|
+
require_relative "alive/client/gitlab_api"
|
15
22
|
require_relative "alive/client/source_code_client"
|
16
23
|
require_relative "alive/reportable"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler-alive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katsuhiko YOSHIDA
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-05-
|
11
|
+
date: 2022-05-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: graphql-client
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: gitlab
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -192,8 +192,10 @@ executables:
|
|
192
192
|
extensions: []
|
193
193
|
extra_rdoc_files: []
|
194
194
|
files:
|
195
|
+
- ".bundler-alive.default.yml"
|
195
196
|
- ".rspec"
|
196
197
|
- ".rubocop.yml"
|
198
|
+
- ".yardopts"
|
197
199
|
- Gemfile
|
198
200
|
- LICENSE
|
199
201
|
- README.md
|
@@ -203,11 +205,12 @@ files:
|
|
203
205
|
- bundler-alive.gemspec
|
204
206
|
- gemspec.yml
|
205
207
|
- lib/bundler/alive.rb
|
206
|
-
- lib/bundler/alive/announcer.rb
|
207
208
|
- lib/bundler/alive/cli.rb
|
208
209
|
- lib/bundler/alive/client/gems_api_client.rb
|
209
210
|
- lib/bundler/alive/client/gems_api_response.rb
|
210
|
-
- lib/bundler/alive/client/
|
211
|
+
- lib/bundler/alive/client/github_api.rb
|
212
|
+
- lib/bundler/alive/client/github_graphql.rb
|
213
|
+
- lib/bundler/alive/client/gitlab_api.rb
|
211
214
|
- lib/bundler/alive/client/source_code_client.rb
|
212
215
|
- lib/bundler/alive/doctor.rb
|
213
216
|
- lib/bundler/alive/report.rb
|
@@ -223,7 +226,7 @@ licenses: []
|
|
223
226
|
metadata:
|
224
227
|
homepage_uri: https://github.com/kyoshidajp/bundler-alive
|
225
228
|
source_code_uri: https://github.com/kyoshidajp/bundler-alive
|
226
|
-
changelog_uri: https://github.com/kyoshidajp/bundler-alive
|
229
|
+
changelog_uri: https://github.com/kyoshidajp/bundler-alive/commits/main
|
227
230
|
rubygems_mfa_required: 'true'
|
228
231
|
post_install_message:
|
229
232
|
rdoc_options: []
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler"
|
4
|
-
|
5
|
-
module Bundler
|
6
|
-
module Alive
|
7
|
-
#
|
8
|
-
# Announces check progress
|
9
|
-
#
|
10
|
-
class Announcer
|
11
|
-
DOT = "."
|
12
|
-
|
13
|
-
private_constant :DOT
|
14
|
-
|
15
|
-
#
|
16
|
-
# A new instance of Reporter
|
17
|
-
#
|
18
|
-
def initialize
|
19
|
-
@output = $stdout
|
20
|
-
end
|
21
|
-
|
22
|
-
def announce
|
23
|
-
output.write DOT
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
attr_reader :output
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|