obituary 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +94 -0
- data/Rakefile +8 -0
- data/exe/obituary +6 -0
- data/lib/obituary/archive_checker.rb +60 -0
- data/lib/obituary/cli.rb +107 -0
- data/lib/obituary/configuration.rb +38 -0
- data/lib/obituary/dependency_resolver.rb +17 -0
- data/lib/obituary/repository_finder.rb +82 -0
- data/lib/obituary/result.rb +38 -0
- data/lib/obituary/rspec/matchers.rb +33 -0
- data/lib/obituary/rspec.rb +3 -0
- data/lib/obituary/runner.rb +32 -0
- data/lib/obituary/version.rb +5 -0
- data/lib/obituary.rb +25 -0
- metadata +74 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4f4cdf9f9835202c98cc63e88ad67118e118452dcbc685377300db5f931758ba
|
|
4
|
+
data.tar.gz: 381d5992bc0e4450700095fef0a967f951adbceee8328e6f8a3d7f504d81657d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 865223eb644a0a2cd994d885b521d747d051b04c20c177898278f8cd80e920d22fe1ef55705617289468b3e03ac3b14ecde25d948855e8030ce909ac701afa12
|
|
7
|
+
data.tar.gz: 0dfe545e0847c6233217c133f563204b4bf866be060c9e04a8194ed6d6ab8c306353b58d06b8e2d9a4b2f1f6d7858e2f9ff1b01f4ea03db07c1055cc81d08c04
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Obituary
|
|
2
|
+
|
|
3
|
+
Obituary detects archived GitHub repositories for your RubyGem dependencies. It can be used as a CLI command in CI or as an RSpec matcher to keep dependency health visible.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add obituary
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install obituary
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
Set your GitHub token in the environment so the GitHub API can be queried.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export GITHUB_TOKEN=your_token_here
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Obituary reads `.obituary.yml` from the project root by default.
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
include_transitive: false
|
|
33
|
+
github_token_env: "GITHUB_TOKEN"
|
|
34
|
+
ignore:
|
|
35
|
+
- some_archived_but_ok_gem
|
|
36
|
+
overrides:
|
|
37
|
+
some_gem: "https://github.com/owner/repo"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage (CLI)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bundle exec obituary
|
|
44
|
+
bundle exec obituary --include-transitive
|
|
45
|
+
bundle exec obituary --format json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage (RSpec)
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
require "obituary/rspec"
|
|
52
|
+
|
|
53
|
+
RSpec.describe "Dependency health" do
|
|
54
|
+
it "does not depend on archived gems" do
|
|
55
|
+
expect(:bundle).not_to have_archived_gems
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## CI Example
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
name: Dependency Health Check
|
|
64
|
+
on:
|
|
65
|
+
schedule:
|
|
66
|
+
- cron: '0 9 * * 1'
|
|
67
|
+
workflow_dispatch:
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
check-archived:
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
- uses: ruby/setup-ruby@v1
|
|
75
|
+
with:
|
|
76
|
+
bundler-cache: true
|
|
77
|
+
- run: gem install obituary
|
|
78
|
+
- run: obituary check
|
|
79
|
+
env:
|
|
80
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Limitations
|
|
84
|
+
|
|
85
|
+
- Only GitHub repositories are supported.
|
|
86
|
+
- Private repositories require a token with access.
|
|
87
|
+
|
|
88
|
+
## Development
|
|
89
|
+
|
|
90
|
+
After checking out the repo, run `bundle exec rspec` to run the tests.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
The gem is available as open source under the terms of the MIT License.
|
data/Rakefile
ADDED
data/exe/obituary
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module Obituary
|
|
8
|
+
class ArchiveChecker
|
|
9
|
+
API_ENDPOINT = 'https://api.github.com/repos'
|
|
10
|
+
WARNING_THRESHOLD = 100
|
|
11
|
+
|
|
12
|
+
def initialize(github_token:)
|
|
13
|
+
@github_token = github_token
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def archived?(owner_repo)
|
|
17
|
+
url = URI.join("#{API_ENDPOINT}/", owner_repo)
|
|
18
|
+
request = Net::HTTP::Get.new(url)
|
|
19
|
+
request['Authorization'] = "Bearer #{@github_token}"
|
|
20
|
+
request['Accept'] = 'application/vnd.github.v3+json'
|
|
21
|
+
|
|
22
|
+
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
|
|
23
|
+
http.request(request)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
warn_if_rate_limit_low(response)
|
|
27
|
+
|
|
28
|
+
case response
|
|
29
|
+
when Net::HTTPSuccess
|
|
30
|
+
data = JSON.parse(response.body)
|
|
31
|
+
data.fetch('archived', nil)
|
|
32
|
+
when Net::HTTPNotFound
|
|
33
|
+
warn("Repository not found for #{owner_repo}")
|
|
34
|
+
nil
|
|
35
|
+
when Net::HTTPUnauthorized
|
|
36
|
+
raise Obituary::Error, 'GitHub token unauthorized'
|
|
37
|
+
when Net::HTTPForbidden
|
|
38
|
+
raise Obituary::Error, 'GitHub API rate limit exceeded'
|
|
39
|
+
else
|
|
40
|
+
warn("GitHub API error for #{owner_repo}: #{response.code}")
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
rescue JSON::ParserError
|
|
44
|
+
warn("Unable to parse GitHub response for #{owner_repo}")
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def warn_if_rate_limit_low(response)
|
|
51
|
+
remaining = response['X-RateLimit-Remaining']
|
|
52
|
+
return unless remaining
|
|
53
|
+
|
|
54
|
+
remaining_value = remaining.to_i
|
|
55
|
+
return if remaining_value > WARNING_THRESHOLD
|
|
56
|
+
|
|
57
|
+
warn("GitHub rate limit low: #{remaining_value} remaining")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/obituary/cli.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
|
|
6
|
+
module Obituary
|
|
7
|
+
class CLI
|
|
8
|
+
DEFAULT_CONFIG = '.obituary.yml'
|
|
9
|
+
|
|
10
|
+
def self.start(argv)
|
|
11
|
+
new.start(argv)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start(argv)
|
|
15
|
+
summary = run(argv)
|
|
16
|
+
exit(summary)
|
|
17
|
+
rescue SystemExit
|
|
18
|
+
raise
|
|
19
|
+
rescue StandardError => e
|
|
20
|
+
warn(e.message)
|
|
21
|
+
exit(2)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run(argv)
|
|
25
|
+
options = {
|
|
26
|
+
include_transitive: nil,
|
|
27
|
+
config: DEFAULT_CONFIG,
|
|
28
|
+
format: 'text',
|
|
29
|
+
no_fail: false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
parser = OptionParser.new do |opts|
|
|
33
|
+
opts.banner = 'Usage: obituary check [options]'
|
|
34
|
+
opts.on('--include-transitive', 'Include transitive dependencies') { options[:include_transitive] = true }
|
|
35
|
+
opts.on('--config PATH', 'Path to config file') { |value| options[:config] = value }
|
|
36
|
+
opts.on('--format FORMAT', 'Output format: text or json') { |value| options[:format] = value }
|
|
37
|
+
opts.on('--no-fail', 'Always return exit code 0') { options[:no_fail] = true }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
args = argv.dup
|
|
41
|
+
if args.first == 'check'
|
|
42
|
+
args.shift
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
parser.parse!(args)
|
|
46
|
+
|
|
47
|
+
config = Configuration.load(path: options[:config])
|
|
48
|
+
config.include_transitive = options[:include_transitive] unless options[:include_transitive].nil?
|
|
49
|
+
|
|
50
|
+
summary = Runner.new(config: config).run
|
|
51
|
+
|
|
52
|
+
output(summary, options[:format])
|
|
53
|
+
|
|
54
|
+
summary.passed? || options[:no_fail] ? 0 : 1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def output(summary, format)
|
|
60
|
+
case format
|
|
61
|
+
when 'json'
|
|
62
|
+
payload = {
|
|
63
|
+
archived: format_results(summary.archived_gems),
|
|
64
|
+
active: format_results(summary.active_gems),
|
|
65
|
+
unresolved: format_results(summary.unresolved_gems),
|
|
66
|
+
passed: summary.passed?
|
|
67
|
+
}
|
|
68
|
+
puts JSON.pretty_generate(payload)
|
|
69
|
+
else
|
|
70
|
+
puts "obituary - Checking #{summary.results.size} gems for archived repositories..."
|
|
71
|
+
puts ''
|
|
72
|
+
|
|
73
|
+
if summary.archived_gems.any?
|
|
74
|
+
puts "\e[31m✗ ARCHIVED:\e[0m"
|
|
75
|
+
summary.archived_gems.each do |result|
|
|
76
|
+
puts " - #{result.gem_name} (https://github.com/#{result.repository})"
|
|
77
|
+
end
|
|
78
|
+
puts ''
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if summary.unresolved_gems.any?
|
|
82
|
+
puts "\e[33m⚠ UNRESOLVED (repository not found):\e[0m"
|
|
83
|
+
summary.unresolved_gems.each do |result|
|
|
84
|
+
puts " - #{result.gem_name}"
|
|
85
|
+
end
|
|
86
|
+
puts ''
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
ok_count = summary.active_gems.count
|
|
90
|
+
archived_count = summary.archived_gems.count
|
|
91
|
+
unresolved_count = summary.unresolved_gems.count
|
|
92
|
+
puts "\e[32m✓ #{ok_count} gems OK, #{archived_count} archived, #{unresolved_count} unresolved\e[0m"
|
|
93
|
+
|
|
94
|
+
if archived_count.positive?
|
|
95
|
+
puts ''
|
|
96
|
+
puts "FAILED: #{archived_count} archived gems detected."
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def format_results(results)
|
|
102
|
+
results.map do |result|
|
|
103
|
+
{ gem: result.gem_name, repository: result.repository, archived: result.archived, error: result.error }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Obituary
|
|
6
|
+
class Configuration
|
|
7
|
+
DEFAULT_INCLUDE_TRANSITIVE = false
|
|
8
|
+
DEFAULT_GITHUB_TOKEN_ENV = 'GITHUB_TOKEN'
|
|
9
|
+
|
|
10
|
+
attr_accessor :include_transitive, :github_token_env, :ignore, :overrides
|
|
11
|
+
|
|
12
|
+
def self.load(path: '.obituary.yml')
|
|
13
|
+
return new unless File.exist?(path)
|
|
14
|
+
|
|
15
|
+
raw = YAML.safe_load_file(path, aliases: false)
|
|
16
|
+
data = (raw || {}).transform_keys(&:to_sym)
|
|
17
|
+
|
|
18
|
+
new(
|
|
19
|
+
include_transitive: data.fetch(:include_transitive, DEFAULT_INCLUDE_TRANSITIVE),
|
|
20
|
+
github_token_env: data.fetch(:github_token_env, DEFAULT_GITHUB_TOKEN_ENV),
|
|
21
|
+
ignore: Array(data[:ignore]),
|
|
22
|
+
overrides: data.fetch(:overrides, {})
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(
|
|
27
|
+
include_transitive: DEFAULT_INCLUDE_TRANSITIVE,
|
|
28
|
+
github_token_env: DEFAULT_GITHUB_TOKEN_ENV,
|
|
29
|
+
ignore: [],
|
|
30
|
+
overrides: {}
|
|
31
|
+
)
|
|
32
|
+
@include_transitive = include_transitive
|
|
33
|
+
@github_token_env = github_token_env
|
|
34
|
+
@ignore = ignore
|
|
35
|
+
@overrides = overrides
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler'
|
|
4
|
+
|
|
5
|
+
module Obituary
|
|
6
|
+
class DependencyResolver
|
|
7
|
+
def resolve(include_transitive:)
|
|
8
|
+
gems = if include_transitive
|
|
9
|
+
Bundler.definition.specs.map(&:name)
|
|
10
|
+
else
|
|
11
|
+
Bundler.definition.dependencies.map(&:name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
gems.reject { |name| name == 'bundler' }.uniq.sort
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module Obituary
|
|
8
|
+
class RepositoryFinder
|
|
9
|
+
RUBYGEMS_API = 'https://rubygems.org/api/v1/gems'
|
|
10
|
+
GITHUB_REGEX = %r{github\.com[/:]([^/]+)/([^/.]+)}
|
|
11
|
+
|
|
12
|
+
def initialize(config: Obituary.configuration)
|
|
13
|
+
@config = config
|
|
14
|
+
@cache = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find(gem_name)
|
|
18
|
+
return @cache[gem_name] if @cache.key?(gem_name)
|
|
19
|
+
|
|
20
|
+
override = @config.overrides[gem_name]
|
|
21
|
+
if override
|
|
22
|
+
@cache[gem_name] = parse_github_repo(override)
|
|
23
|
+
return @cache[gem_name]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
data = fetch_rubygems(gem_name)
|
|
27
|
+
if data
|
|
28
|
+
repo = parse_github_repo(data['source_code_uri']) ||
|
|
29
|
+
parse_github_repo(data['homepage_uri'])
|
|
30
|
+
return @cache[gem_name] = repo
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
@cache[gem_name] = parse_git_source_from_lock(gem_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def fetch_rubygems(gem_name)
|
|
39
|
+
url = URI.join("#{RUBYGEMS_API}/", "#{gem_name}.json")
|
|
40
|
+
response = Net::HTTP.get_response(url)
|
|
41
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
42
|
+
|
|
43
|
+
JSON.parse(response.body)
|
|
44
|
+
rescue JSON::ParserError, URI::InvalidURIError
|
|
45
|
+
nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_git_source_from_lock(gem_name)
|
|
49
|
+
lock_path = Bundler.default_lockfile
|
|
50
|
+
return nil unless File.exist?(lock_path)
|
|
51
|
+
|
|
52
|
+
content = File.read(lock_path)
|
|
53
|
+
content.split(/\n\n+/).each do |section|
|
|
54
|
+
next unless section.start_with?("GIT\n")
|
|
55
|
+
|
|
56
|
+
remote = section.lines.find { |line| line.strip.start_with?('remote:') }
|
|
57
|
+
next unless remote
|
|
58
|
+
|
|
59
|
+
repo = parse_github_repo(remote.split(':', 2).last.to_s.strip)
|
|
60
|
+
next unless repo
|
|
61
|
+
|
|
62
|
+
gems_section = section.split("\n\n").last
|
|
63
|
+
next unless gems_section
|
|
64
|
+
|
|
65
|
+
return repo if gems_section.lines.any? { |line| line.strip.start_with?("#{gem_name} (") }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def parse_github_repo(url)
|
|
72
|
+
return nil if url.nil? || url.strip.empty?
|
|
73
|
+
|
|
74
|
+
match = url.match(GITHUB_REGEX)
|
|
75
|
+
return nil unless match
|
|
76
|
+
|
|
77
|
+
owner = match[1]
|
|
78
|
+
repo = match[2]
|
|
79
|
+
"#{owner}/#{repo}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Obituary
|
|
4
|
+
class Result
|
|
5
|
+
attr_reader :gem_name, :repository, :archived, :error
|
|
6
|
+
|
|
7
|
+
def initialize(gem_name:, repository:, archived:, error: nil)
|
|
8
|
+
@gem_name = gem_name
|
|
9
|
+
@repository = repository
|
|
10
|
+
@archived = archived
|
|
11
|
+
@error = error
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class CheckSummary
|
|
16
|
+
attr_reader :results
|
|
17
|
+
|
|
18
|
+
def initialize(results)
|
|
19
|
+
@results = results
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def archived_gems
|
|
23
|
+
results.select { |result| result.archived == true }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def active_gems
|
|
27
|
+
results.select { |result| result.archived == false }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def unresolved_gems
|
|
31
|
+
results.select { |result| result.repository.nil? }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def passed?
|
|
35
|
+
archived_gems.empty?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec/expectations'
|
|
4
|
+
|
|
5
|
+
module Obituary
|
|
6
|
+
module RSpec
|
|
7
|
+
module Matchers
|
|
8
|
+
::RSpec::Matchers.define :have_archived_gems do |options = {}|
|
|
9
|
+
supports_block_expectations
|
|
10
|
+
match do |_|
|
|
11
|
+
config = Obituary.configuration
|
|
12
|
+
config = Configuration.new(
|
|
13
|
+
include_transitive: config.include_transitive,
|
|
14
|
+
github_token_env: config.github_token_env,
|
|
15
|
+
ignore: config.ignore,
|
|
16
|
+
overrides: config.overrides
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
config.include_transitive = options[:include_transitive] if options.key?(:include_transitive)
|
|
20
|
+
|
|
21
|
+
@summary = Runner.new(config: config).run
|
|
22
|
+
@summary.archived_gems.any?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
failure_message_when_negated do |_|
|
|
26
|
+
archived = @summary.archived_gems
|
|
27
|
+
lines = archived.map { |result| " - #{result.gem_name} (#{result.repository})" }
|
|
28
|
+
"Expected no archived gems, but found #{archived.size}:\n#{lines.join("\n")}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Obituary
|
|
4
|
+
class Runner
|
|
5
|
+
def initialize(config: Obituary.configuration)
|
|
6
|
+
@config = config
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
token = ENV.fetch(@config.github_token_env) do
|
|
11
|
+
raise Obituary::Error, "Missing GitHub token in #{@config.github_token_env}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
resolver = DependencyResolver.new
|
|
15
|
+
finder = RepositoryFinder.new(config: @config)
|
|
16
|
+
checker = ArchiveChecker.new(github_token: token)
|
|
17
|
+
|
|
18
|
+
gems = resolver.resolve(include_transitive: @config.include_transitive)
|
|
19
|
+
gems = gems.reject { |name| @config.ignore.include?(name) }
|
|
20
|
+
|
|
21
|
+
results = gems.map do |gem_name|
|
|
22
|
+
repository = finder.find(gem_name)
|
|
23
|
+
archived = repository ? checker.archived?(repository) : nil
|
|
24
|
+
Result.new(gem_name: gem_name, repository: repository, archived: archived)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
Result.new(gem_name: gem_name, repository: nil, archived: nil, error: e.message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
CheckSummary.new(results)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/obituary.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'obituary/version'
|
|
4
|
+
require_relative 'obituary/configuration'
|
|
5
|
+
require_relative 'obituary/dependency_resolver'
|
|
6
|
+
require_relative 'obituary/repository_finder'
|
|
7
|
+
require_relative 'obituary/archive_checker'
|
|
8
|
+
require_relative 'obituary/result'
|
|
9
|
+
require_relative 'obituary/runner'
|
|
10
|
+
require_relative 'obituary/cli'
|
|
11
|
+
require_relative 'obituary/rspec'
|
|
12
|
+
|
|
13
|
+
module Obituary
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def configure
|
|
18
|
+
yield(configuration)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configuration
|
|
22
|
+
@configuration ||= Configuration.load
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: obituary
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: Checks Gem dependencies against GitHub and reports archived repositories.
|
|
27
|
+
email:
|
|
28
|
+
- t.yudai92@gmail.com
|
|
29
|
+
executables:
|
|
30
|
+
- obituary
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- LICENSE.txt
|
|
35
|
+
- README.md
|
|
36
|
+
- Rakefile
|
|
37
|
+
- exe/obituary
|
|
38
|
+
- lib/obituary.rb
|
|
39
|
+
- lib/obituary/archive_checker.rb
|
|
40
|
+
- lib/obituary/cli.rb
|
|
41
|
+
- lib/obituary/configuration.rb
|
|
42
|
+
- lib/obituary/dependency_resolver.rb
|
|
43
|
+
- lib/obituary/repository_finder.rb
|
|
44
|
+
- lib/obituary/result.rb
|
|
45
|
+
- lib/obituary/rspec.rb
|
|
46
|
+
- lib/obituary/rspec/matchers.rb
|
|
47
|
+
- lib/obituary/runner.rb
|
|
48
|
+
- lib/obituary/version.rb
|
|
49
|
+
homepage: https://github.com/ydah/obituary
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata:
|
|
53
|
+
allowed_push_host: https://rubygems.org
|
|
54
|
+
homepage_uri: https://github.com/ydah/obituary
|
|
55
|
+
source_code_uri: https://github.com/ydah/obituary
|
|
56
|
+
rubygems_mfa_required: 'true'
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: 3.1.0
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
requirements: []
|
|
71
|
+
rubygems_version: 4.0.6
|
|
72
|
+
specification_version: 4
|
|
73
|
+
summary: Detect archived GitHub repositories for RubyGem dependencies.
|
|
74
|
+
test_files: []
|