end_of_life 0.4.1 → 0.5.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +43 -33
- data/README.md +23 -14
- data/end_of_life.gemspec +3 -2
- data/lib/end_of_life/api.rb +17 -0
- data/lib/end_of_life/cli.rb +7 -58
- data/lib/end_of_life/helpers/silent_bundler.rb +18 -0
- data/lib/end_of_life/{terminal_helper.rb → helpers/terminal.rb} +1 -1
- data/lib/end_of_life/in_memory_file.rb +3 -0
- data/lib/end_of_life/options.rb +18 -14
- data/lib/end_of_life/parsers/gemfile.rb +31 -0
- data/lib/end_of_life/parsers/gemfile_lock.rb +14 -0
- data/lib/end_of_life/parsers/ruby_version.rb +11 -0
- data/lib/end_of_life/parsers/tool_versions.rb +23 -0
- data/lib/end_of_life/product/registry.rb +23 -0
- data/lib/end_of_life/product/release.rb +30 -0
- data/lib/end_of_life/product.rb +32 -0
- data/lib/end_of_life/report.rb +35 -0
- data/lib/end_of_life/repository/search/query.rb +31 -0
- data/lib/end_of_life/repository/search.rb +45 -0
- data/lib/end_of_life/repository.rb +17 -81
- data/lib/end_of_life/scanner.rb +41 -0
- data/lib/end_of_life/version.rb +1 -1
- data/lib/end_of_life/version_detector.rb +27 -0
- data/lib/end_of_life/version_detectors/rails.rb +26 -0
- data/lib/end_of_life/version_detectors/ruby.rb +35 -0
- data/lib/end_of_life/version_detectors.rb +17 -0
- data/lib/end_of_life.rb +10 -7
- metadata +38 -9
- data/lib/end_of_life/ruby_version/parser.rb +0 -96
- data/lib/end_of_life/ruby_version.rb +0 -69
- data/lib/end_of_life.json +0 -98
@@ -0,0 +1,32 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
class Product
|
3
|
+
def self.find(name) = EndOfLife.find_product(name)
|
4
|
+
|
5
|
+
attr_reader :name, :search_query, :version_detector
|
6
|
+
|
7
|
+
def initialize(name, search_query, version_detector)
|
8
|
+
@name = name.to_s.downcase
|
9
|
+
@search_query = search_query
|
10
|
+
@version_detector = version_detector
|
11
|
+
end
|
12
|
+
|
13
|
+
def eol_releases_at(date)
|
14
|
+
all_releases.filter { |release| release.eol_date <= date }
|
15
|
+
end
|
16
|
+
|
17
|
+
def latest_eol_release(at: Date.today)
|
18
|
+
eol_releases_at(at).max
|
19
|
+
end
|
20
|
+
|
21
|
+
def label = name.capitalize
|
22
|
+
def to_s = label
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def all_releases
|
27
|
+
@all_releases ||= API.fetch_product(name)
|
28
|
+
.dig(:result, :releases)
|
29
|
+
.map { |json| Release.new(name, json[:latest][:name], json[:eolFrom]) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "stringio"
|
2
|
+
|
3
|
+
module EndOfLife
|
4
|
+
class Report < Data.define(:product, :repositories, :max_eol_date)
|
5
|
+
include Helpers::Terminal
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
report = StringIO.new
|
9
|
+
report.puts
|
10
|
+
|
11
|
+
if repositories.empty?
|
12
|
+
report.puts "No repositories using EOL #{product}."
|
13
|
+
else
|
14
|
+
word = (repositories.size == 1) ? "repository" : "repositories"
|
15
|
+
report.puts "Found #{repositories.size} #{word} using EOL #{product} (<= #{product.latest_eol_release(at: max_eol_date)}):"
|
16
|
+
report.puts end_of_life_table(repositories)
|
17
|
+
end
|
18
|
+
|
19
|
+
report.string
|
20
|
+
end
|
21
|
+
|
22
|
+
def failure? = repositories.any?
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def end_of_life_table(repositories)
|
27
|
+
headers = ["", "Repository", "#{product} version"]
|
28
|
+
rows = repositories.map.with_index(1) do |repo, i|
|
29
|
+
[i, repo.url, repo.min_release_of(product)]
|
30
|
+
end
|
31
|
+
|
32
|
+
table(headers, rows)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
class Repository
|
3
|
+
class Search
|
4
|
+
Query = Data.define(:options) do
|
5
|
+
def to_s
|
6
|
+
query = options[:product].search_query
|
7
|
+
|
8
|
+
query += if options[:repository]
|
9
|
+
" repo:#{options[:repository]}"
|
10
|
+
elsif options[:organizations]
|
11
|
+
options[:organizations].map { |org| " org:#{org}" }.join
|
12
|
+
else
|
13
|
+
" user:#{options[:user]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
if options[:visibility]
|
17
|
+
query += " is:#{options[:visibility]}"
|
18
|
+
end
|
19
|
+
|
20
|
+
if options[:excludes]
|
21
|
+
words_to_exclude = options[:excludes].map { |word| "NOT #{word} " }.join
|
22
|
+
|
23
|
+
query += " #{words_to_exclude} in:name"
|
24
|
+
end
|
25
|
+
|
26
|
+
query
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
class Repository
|
3
|
+
class Search
|
4
|
+
include Dry::Monads[:result, :maybe]
|
5
|
+
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def result
|
13
|
+
github_client.bind do |github|
|
14
|
+
github.auto_paginate = true
|
15
|
+
options[:user] ||= github.user.login
|
16
|
+
|
17
|
+
query = Query.new(options).to_s
|
18
|
+
items = github.search_repositories(query, {sort: :updated}).items
|
19
|
+
|
20
|
+
Success(
|
21
|
+
items.filter_map do |repo|
|
22
|
+
next if repo.archived && options[:skip_archived]
|
23
|
+
|
24
|
+
Repository.new(
|
25
|
+
full_name: repo.full_name,
|
26
|
+
url: repo.html_url,
|
27
|
+
github_client: github
|
28
|
+
)
|
29
|
+
end
|
30
|
+
)
|
31
|
+
rescue => e
|
32
|
+
Failure("Unexpected error: #{e}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def github_client
|
39
|
+
Maybe(ENV["GITHUB_TOKEN"])
|
40
|
+
.fmap { |token| Octokit::Client.new(access_token: token) }
|
41
|
+
.or { Failure("Please set GITHUB_TOKEN environment variable") }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,61 +1,7 @@
|
|
1
1
|
module EndOfLife
|
2
2
|
class Repository
|
3
3
|
class << self
|
4
|
-
|
5
|
-
|
6
|
-
def fetch(options)
|
7
|
-
github_client.bind do |github|
|
8
|
-
github.auto_paginate = true
|
9
|
-
options[:user] ||= github.user.login
|
10
|
-
|
11
|
-
query = search_query_for(options)
|
12
|
-
items = github.search_repositories(query, {sort: :updated}).items
|
13
|
-
|
14
|
-
Success(
|
15
|
-
items.filter_map do |repo|
|
16
|
-
next if repo.archived && options[:skip_archived]
|
17
|
-
|
18
|
-
Repository.new(
|
19
|
-
full_name: repo.full_name,
|
20
|
-
url: repo.html_url,
|
21
|
-
github_client: github
|
22
|
-
)
|
23
|
-
end
|
24
|
-
)
|
25
|
-
rescue => e
|
26
|
-
Failure("Unexpected error: #{e}")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def github_client
|
31
|
-
Maybe(ENV["GITHUB_TOKEN"])
|
32
|
-
.fmap { |token| Octokit::Client.new(access_token: token) }
|
33
|
-
.or { Failure("Please set GITHUB_TOKEN environment variable") }
|
34
|
-
end
|
35
|
-
|
36
|
-
def search_query_for(options)
|
37
|
-
query = "language:ruby"
|
38
|
-
|
39
|
-
query += if options[:repository]
|
40
|
-
" repo:#{options[:repository]}"
|
41
|
-
elsif options[:organizations]
|
42
|
-
options[:organizations].map { |org| " org:#{org}" }.join
|
43
|
-
else
|
44
|
-
" user:#{options[:user]}"
|
45
|
-
end
|
46
|
-
|
47
|
-
if options[:visibility]
|
48
|
-
query += " is:#{options[:visibility]}"
|
49
|
-
end
|
50
|
-
|
51
|
-
if options[:excludes]
|
52
|
-
words_to_exclude = options[:excludes].map { |word| "NOT #{word} " }.join
|
53
|
-
|
54
|
-
query += " #{words_to_exclude} in:name"
|
55
|
-
end
|
56
|
-
|
57
|
-
query
|
58
|
-
end
|
4
|
+
def search(options) = Search.new(options).result
|
59
5
|
end
|
60
6
|
|
61
7
|
attr_reader :full_name, :url
|
@@ -64,39 +10,33 @@ module EndOfLife
|
|
64
10
|
@full_name = full_name
|
65
11
|
@url = url
|
66
12
|
@github_client = github_client
|
13
|
+
@product_releases = {}
|
67
14
|
end
|
68
15
|
|
69
|
-
def
|
70
|
-
|
16
|
+
def using_eol?(product, at: Date.today)
|
17
|
+
min_release_of(product)&.eol?(at: at)
|
71
18
|
end
|
72
19
|
|
73
|
-
def
|
74
|
-
return @ruby_version if defined?(@ruby_version)
|
75
|
-
|
76
|
-
@ruby_version = ruby_versions.min
|
77
|
-
end
|
20
|
+
def min_release_of(product) = releases_for(product).min
|
78
21
|
|
79
22
|
private
|
80
23
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
parse_version_file(file)
|
86
|
-
}
|
24
|
+
def releases_for(product)
|
25
|
+
@product_releases[product] ||= product.version_detector.detect_all(
|
26
|
+
fetch_files(product.version_detector.relevant_files)
|
27
|
+
)
|
87
28
|
end
|
88
29
|
|
89
|
-
|
90
|
-
".ruby-version",
|
91
|
-
"Gemfile.lock",
|
92
|
-
"Gemfile",
|
93
|
-
".tool-versions"
|
94
|
-
]
|
95
|
-
def fetch_ruby_version_files
|
30
|
+
def fetch_files(file_paths)
|
96
31
|
Sync do
|
97
|
-
|
32
|
+
file_paths
|
98
33
|
.map { |file_path| Async { fetch_file(file_path) } }
|
99
|
-
.filter_map
|
34
|
+
.filter_map { |task|
|
35
|
+
file = task.wait
|
36
|
+
next if file.nil?
|
37
|
+
|
38
|
+
InMemoryFile.new(file.path, decode_file(file))
|
39
|
+
}
|
100
40
|
end
|
101
41
|
end
|
102
42
|
|
@@ -106,10 +46,6 @@ module EndOfLife
|
|
106
46
|
nil
|
107
47
|
end
|
108
48
|
|
109
|
-
def parse_version_file(file)
|
110
|
-
RubyVersion.from_file(file_name: file.name, content: decode_file(file))
|
111
|
-
end
|
112
|
-
|
113
49
|
def decode_file(file)
|
114
50
|
return file.content if file.encoding.nil?
|
115
51
|
return Base64.decode64(file.content) if file.encoding == "base64"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
module Scanner
|
3
|
+
include Helpers::Terminal
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def scan(options)
|
7
|
+
fetch_repositories(options)
|
8
|
+
.fmap { |repositories| filter_repositories_with_eol_products(repositories, **options.slice(:product, :max_eol_date)) }
|
9
|
+
.fmap { |repositories| output_report(repositories, **options.slice(:product, :max_eol_date)) }
|
10
|
+
.or { |error| abort "\n#{error_msg(error)}" }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def fetch_repositories(options)
|
16
|
+
with_loading_spinner("Searching repositories with #{options[:product].label}...") do |spinner|
|
17
|
+
result = Repository.search(options)
|
18
|
+
spinner.error if result.failure?
|
19
|
+
|
20
|
+
result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def filter_repositories_with_eol_products(repositories, product:, max_eol_date:)
|
25
|
+
with_loading_spinner("Searching for EOL #{product} in your repositories...") do
|
26
|
+
Sync do
|
27
|
+
repositories
|
28
|
+
.map { |repo| Async { [repo, repo.using_eol?(product, at: max_eol_date)] } }.map(&:wait)
|
29
|
+
.filter_map { |repo, contains_eol| contains_eol ? repo : nil }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def output_report(repositories, product:, max_eol_date:)
|
35
|
+
report = Report.new(product, repositories, max_eol_date)
|
36
|
+
puts report
|
37
|
+
|
38
|
+
exit(1) if report.failure?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/end_of_life/version.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
module VersionDetector
|
3
|
+
def file_detectors
|
4
|
+
@file_detectors ||= {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def detects_from(file, &block)
|
8
|
+
file_detectors[file] = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def relevant_files = file_detectors.keys
|
12
|
+
|
13
|
+
def detect_all(files)
|
14
|
+
files.filter_map { |file| detect(file) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def detect(file)
|
18
|
+
return if file.read.strip.empty?
|
19
|
+
detector = file_detectors[File.basename(file.path)] or return
|
20
|
+
version = detector.call(file.read) or return
|
21
|
+
|
22
|
+
return if version.zero?
|
23
|
+
|
24
|
+
version
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
module VersionDetectors
|
3
|
+
module Rails
|
4
|
+
extend VersionDetector
|
5
|
+
|
6
|
+
detects_from "Gemfile.lock" do |file_content|
|
7
|
+
gemfile_lock = Parsers::GemfileLock.parse(file_content) or next
|
8
|
+
rails = gemfile_lock.specs.find { |it| it.name == "rails" } or next
|
9
|
+
|
10
|
+
Product::Release.new(product: "rails", version: rails.version)
|
11
|
+
end
|
12
|
+
|
13
|
+
detects_from "Gemfile" do |file_content|
|
14
|
+
gemfile = Parsers::Gemfile.parse(file_content) or next
|
15
|
+
rails_dep = gemfile.dependencies.find { |it| it.name == "rails" } or next
|
16
|
+
exact_version = rails_dep
|
17
|
+
.requirement
|
18
|
+
.requirements
|
19
|
+
.filter { |op, _| op == "=" } # standard:disable Style/HashSlice
|
20
|
+
.max_by { |_, version| version } or next
|
21
|
+
|
22
|
+
Product::Release.new(product: "rails", version: exact_version.last)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "bundler"
|
2
|
+
|
3
|
+
module EndOfLife
|
4
|
+
module VersionDetectors
|
5
|
+
module Ruby
|
6
|
+
extend VersionDetector
|
7
|
+
|
8
|
+
detects_from ".ruby-version" do |file_content|
|
9
|
+
string_version = Parsers::RubyVersion.parse(file_content)
|
10
|
+
|
11
|
+
Product::Release.ruby(string_version)
|
12
|
+
end
|
13
|
+
|
14
|
+
detects_from "Gemfile.lock" do |file_content|
|
15
|
+
gemfile_lock_version = Parsers::GemfileLock.parse(file_content).ruby_version or next
|
16
|
+
|
17
|
+
Product::Release.ruby(gemfile_lock_version.delete_prefix("ruby "))
|
18
|
+
end
|
19
|
+
|
20
|
+
detects_from "Gemfile" do |file_content|
|
21
|
+
gemfile = Parsers::Gemfile.parse(file_content)
|
22
|
+
gemfile_version = gemfile&.ruby_version&.gem_version or next
|
23
|
+
|
24
|
+
Product::Release.ruby(gemfile_version)
|
25
|
+
end
|
26
|
+
|
27
|
+
detects_from ".tool-versions" do |file_content|
|
28
|
+
tool_versions = Parsers::ToolVersions.parse(file_content)
|
29
|
+
ruby_version = tool_versions["ruby"] or next
|
30
|
+
|
31
|
+
Product::Release.ruby(ruby_version)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module EndOfLife
|
2
|
+
module VersionDetectors
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def for_product(product)
|
6
|
+
detector_class = "#{self}::#{camelize(product.to_s)}"
|
7
|
+
|
8
|
+
const_get(detector_class)
|
9
|
+
rescue NameError
|
10
|
+
raise ArgumentError, "Undefined version detector for product: #{product}"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def camelize(word) = Zeitwerk::Inflector.new.camelize(word, nil)
|
16
|
+
end
|
17
|
+
end
|
data/lib/end_of_life.rb
CHANGED
@@ -5,13 +5,16 @@ require "dry-monads"
|
|
5
5
|
require "json"
|
6
6
|
require "base64"
|
7
7
|
require "octokit"
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
require_relative "end_of_life/cli"
|
8
|
+
require "zeitwerk"
|
9
|
+
|
10
|
+
Zeitwerk::Loader.for_gem.tap { |it|
|
11
|
+
it.inflector.inflect("cli" => "CLI", "api" => "API")
|
12
|
+
}.setup
|
14
13
|
|
15
14
|
module EndOfLife
|
16
|
-
extend
|
15
|
+
extend Product::Registry
|
16
|
+
extend Helpers::Terminal
|
17
|
+
|
18
|
+
scans_for :ruby, search_query: "language:ruby"
|
19
|
+
scans_for :rails, search_query: "language:ruby"
|
17
20
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: end_of_life
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matheus Richard
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: async
|
@@ -93,8 +93,22 @@ dependencies:
|
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
95
|
version: 0.12.0
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: zeitwerk
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '2.7'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.7'
|
96
110
|
description: Searches your GitHub repositores and lists the ones using end-of-life,
|
97
|
-
i.e. unmaintained,
|
111
|
+
i.e. unmaintained, software versions.
|
98
112
|
email:
|
99
113
|
- matheusrichardt@gmail.com
|
100
114
|
executables:
|
@@ -117,15 +131,30 @@ files:
|
|
117
131
|
- end_of_life.gemspec
|
118
132
|
- exe/end_of_life
|
119
133
|
- exe/eol
|
120
|
-
- lib/end_of_life.json
|
121
134
|
- lib/end_of_life.rb
|
135
|
+
- lib/end_of_life/api.rb
|
122
136
|
- lib/end_of_life/cli.rb
|
137
|
+
- lib/end_of_life/helpers/silent_bundler.rb
|
138
|
+
- lib/end_of_life/helpers/terminal.rb
|
139
|
+
- lib/end_of_life/in_memory_file.rb
|
123
140
|
- lib/end_of_life/options.rb
|
141
|
+
- lib/end_of_life/parsers/gemfile.rb
|
142
|
+
- lib/end_of_life/parsers/gemfile_lock.rb
|
143
|
+
- lib/end_of_life/parsers/ruby_version.rb
|
144
|
+
- lib/end_of_life/parsers/tool_versions.rb
|
145
|
+
- lib/end_of_life/product.rb
|
146
|
+
- lib/end_of_life/product/registry.rb
|
147
|
+
- lib/end_of_life/product/release.rb
|
148
|
+
- lib/end_of_life/report.rb
|
124
149
|
- lib/end_of_life/repository.rb
|
125
|
-
- lib/end_of_life/
|
126
|
-
- lib/end_of_life/
|
127
|
-
- lib/end_of_life/
|
150
|
+
- lib/end_of_life/repository/search.rb
|
151
|
+
- lib/end_of_life/repository/search/query.rb
|
152
|
+
- lib/end_of_life/scanner.rb
|
128
153
|
- lib/end_of_life/version.rb
|
154
|
+
- lib/end_of_life/version_detector.rb
|
155
|
+
- lib/end_of_life/version_detectors.rb
|
156
|
+
- lib/end_of_life/version_detectors/rails.rb
|
157
|
+
- lib/end_of_life/version_detectors/ruby.rb
|
129
158
|
homepage: https://github.com/MatheusRich/end_of_life
|
130
159
|
licenses:
|
131
160
|
- MIT
|
@@ -147,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
176
|
- !ruby/object:Gem::Version
|
148
177
|
version: '0'
|
149
178
|
requirements: []
|
150
|
-
rubygems_version: 3.
|
179
|
+
rubygems_version: 3.7.1
|
151
180
|
specification_version: 4
|
152
|
-
summary: Lists repositories using end-of-life
|
181
|
+
summary: Lists repositories using end-of-life software
|
153
182
|
test_files: []
|
@@ -1,96 +0,0 @@
|
|
1
|
-
require "bundler"
|
2
|
-
require "tempfile"
|
3
|
-
|
4
|
-
module EndOfLife
|
5
|
-
class RubyVersion
|
6
|
-
module Parser
|
7
|
-
extend self
|
8
|
-
|
9
|
-
def parse_file(file_name:, content:)
|
10
|
-
return if content.strip.empty?
|
11
|
-
|
12
|
-
version = if file_name == ".ruby-version"
|
13
|
-
parse_ruby_version_file(content)
|
14
|
-
elsif file_name == "Gemfile.lock"
|
15
|
-
parse_gemfile_lock_file(content)
|
16
|
-
elsif file_name == "Gemfile"
|
17
|
-
parse_gemfile_file(content)
|
18
|
-
elsif file_name == ".tool-versions"
|
19
|
-
parse_tool_versions_file(content)
|
20
|
-
else
|
21
|
-
raise ArgumentError, "Unsupported file #{file_name}"
|
22
|
-
end
|
23
|
-
|
24
|
-
# Gem::Version is pretty forgiving and will accept empty strings
|
25
|
-
# as valid versions. This is a catch-all to ensure we don't return
|
26
|
-
# a version 0, which always takes precedence over any other version
|
27
|
-
# when comparing.
|
28
|
-
return if version&.zero?
|
29
|
-
|
30
|
-
version
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def parse_ruby_version_file(file_content)
|
36
|
-
string_version = file_content.strip.delete_prefix("ruby-")
|
37
|
-
|
38
|
-
RubyVersion.new(string_version)
|
39
|
-
end
|
40
|
-
|
41
|
-
def parse_gemfile_lock_file(file_content)
|
42
|
-
with_silent_bundler do
|
43
|
-
gemfile_lock_version = Bundler::LockfileParser.new(file_content).ruby_version
|
44
|
-
return if gemfile_lock_version.nil?
|
45
|
-
|
46
|
-
RubyVersion.new(gemfile_lock_version.delete_prefix("ruby "))
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def parse_gemfile_file(file_content)
|
51
|
-
with_temp_gemfile(file_content) do |temp_gemfile|
|
52
|
-
gemfile_version = temp_gemfile.ruby_version&.gem_version
|
53
|
-
return if gemfile_version.nil?
|
54
|
-
|
55
|
-
RubyVersion.new(gemfile_version)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def parse_tool_versions_file(file_content)
|
60
|
-
file_content
|
61
|
-
.split("\n")
|
62
|
-
.filter_map do |line|
|
63
|
-
tool, version = line.strip.split
|
64
|
-
|
65
|
-
tool == "ruby" && RubyVersion.new(version)
|
66
|
-
end
|
67
|
-
.first
|
68
|
-
end
|
69
|
-
|
70
|
-
def with_silent_bundler
|
71
|
-
previous_ui = Bundler.ui
|
72
|
-
Bundler.ui = Bundler::UI::Silent.new
|
73
|
-
|
74
|
-
yield
|
75
|
-
ensure
|
76
|
-
Bundler.ui = previous_ui
|
77
|
-
end
|
78
|
-
|
79
|
-
def with_temp_gemfile(contents)
|
80
|
-
# Bundler requires a file to parse, so we need to create a temporary file
|
81
|
-
Tempfile.create("tempGemfile") do |tempfile|
|
82
|
-
tempfile.write(contents)
|
83
|
-
tempfile.rewind
|
84
|
-
gemfile = with_silent_bundler do
|
85
|
-
# This is security problem, since it runs the code inside the file
|
86
|
-
Bundler::Definition.build(tempfile.path, nil, {})
|
87
|
-
end
|
88
|
-
|
89
|
-
yield(gemfile)
|
90
|
-
rescue Bundler::BundlerError
|
91
|
-
nil
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|