stackharbinger 0.3.0 → 1.0.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/README.md +143 -35
- data/docs/index.html +70 -23
- data/lib/harbinger/analyzers/database_detector.rb +13 -3
- data/lib/harbinger/analyzers/docker_compose_detector.rb +121 -0
- data/lib/harbinger/analyzers/go_detector.rb +86 -0
- data/lib/harbinger/analyzers/mongo_detector.rb +104 -0
- data/lib/harbinger/analyzers/mysql_detector.rb +8 -1
- data/lib/harbinger/analyzers/node_detector.rb +109 -0
- data/lib/harbinger/analyzers/postgres_detector.rb +6 -0
- data/lib/harbinger/analyzers/python_detector.rb +110 -0
- data/lib/harbinger/analyzers/rails_analyzer.rb +5 -1
- data/lib/harbinger/analyzers/redis_detector.rb +98 -0
- data/lib/harbinger/analyzers/ruby_detector.rb +9 -1
- data/lib/harbinger/analyzers/rust_detector.rb +116 -0
- data/lib/harbinger/cli.rb +453 -149
- data/lib/harbinger/eol_fetcher.rb +19 -10
- data/lib/harbinger/exporters/base_exporter.rb +100 -0
- data/lib/harbinger/exporters/csv_exporter.rb +36 -0
- data/lib/harbinger/exporters/json_exporter.rb +21 -0
- data/lib/harbinger/version.rb +1 -1
- metadata +11 -1
|
@@ -18,16 +18,14 @@ module Harbinger
|
|
|
18
18
|
cache_file = cache_file_path(product)
|
|
19
19
|
|
|
20
20
|
# Return fresh cache if available
|
|
21
|
-
if cache_fresh?(cache_file)
|
|
22
|
-
return read_cache(cache_file)
|
|
23
|
-
end
|
|
21
|
+
return read_cache(cache_file) if cache_fresh?(cache_file)
|
|
24
22
|
|
|
25
23
|
# Try to fetch from API
|
|
26
24
|
begin
|
|
27
25
|
data = fetch_from_api(product)
|
|
28
26
|
write_cache(cache_file, data)
|
|
29
27
|
data
|
|
30
|
-
rescue StandardError
|
|
28
|
+
rescue StandardError
|
|
31
29
|
# Fall back to stale cache if API fails
|
|
32
30
|
read_cache(cache_file) if File.exist?(cache_file)
|
|
33
31
|
end
|
|
@@ -39,16 +37,27 @@ module Harbinger
|
|
|
39
37
|
|
|
40
38
|
# Extract major.minor from version (e.g., "3.2.1" -> "3.2")
|
|
41
39
|
version_parts = version.split(".")
|
|
42
|
-
major_minor = "#{version_parts[0]}.#{version_parts[1]}"
|
|
43
40
|
major = version_parts[0]
|
|
41
|
+
major_minor = version_parts[1] ? "#{major}.#{version_parts[1]}" : nil
|
|
44
42
|
|
|
45
|
-
# Try major.minor first (e.g., "8.0" for MySQL, "3.2" for Ruby)
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
# Try exact major.minor first (e.g., "8.0" for MySQL, "3.2" for Ruby)
|
|
44
|
+
if major_minor
|
|
45
|
+
entry = data.find { |item| item["cycle"] == major_minor }
|
|
46
|
+
return entry["eol"] if entry
|
|
47
|
+
end
|
|
48
48
|
|
|
49
|
-
#
|
|
49
|
+
# Try major only (e.g., "16" for PostgreSQL)
|
|
50
50
|
entry = data.find { |item| item["cycle"] == major }
|
|
51
|
-
|
|
51
|
+
return entry["eol"] if entry
|
|
52
|
+
|
|
53
|
+
# For major-only versions, find the latest minor version in that major series
|
|
54
|
+
# (e.g., version "7" should match "7.4" which is the latest 7.x)
|
|
55
|
+
matching_entries = data.select { |item| item["cycle"].to_s.start_with?("#{major}.") }
|
|
56
|
+
return nil if matching_entries.empty?
|
|
57
|
+
|
|
58
|
+
# Sort by cycle version and get the latest (highest minor version)
|
|
59
|
+
latest = matching_entries.max_by { |item| item["cycle"].to_s.split(".").map(&:to_i) }
|
|
60
|
+
latest ? latest["eol"] : nil
|
|
52
61
|
end
|
|
53
62
|
|
|
54
63
|
private
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "harbinger/eol_fetcher"
|
|
5
|
+
|
|
6
|
+
module Harbinger
|
|
7
|
+
module Exporters
|
|
8
|
+
# Base class for exporters that transform project data into various formats
|
|
9
|
+
class BaseExporter
|
|
10
|
+
COMPONENTS = %w[ruby rails postgres mysql redis mongo python nodejs rust].freeze
|
|
11
|
+
PRODUCT_NAMES = {
|
|
12
|
+
"ruby" => "ruby",
|
|
13
|
+
"rails" => "rails",
|
|
14
|
+
"postgres" => "postgresql",
|
|
15
|
+
"mysql" => "mysql",
|
|
16
|
+
"redis" => "redis",
|
|
17
|
+
"mongo" => "mongodb",
|
|
18
|
+
"python" => "python",
|
|
19
|
+
"nodejs" => "nodejs",
|
|
20
|
+
"rust" => "rust"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
def initialize(projects, fetcher: nil)
|
|
24
|
+
@projects = projects
|
|
25
|
+
@fetcher = fetcher || EolFetcher.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def export
|
|
29
|
+
raise NotImplementedError, "Subclasses must implement #export"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
def build_export_data
|
|
35
|
+
@projects.filter_map do |name, data|
|
|
36
|
+
components = build_components(data)
|
|
37
|
+
next if components.empty?
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
name: name,
|
|
41
|
+
path: data["path"],
|
|
42
|
+
components: components,
|
|
43
|
+
overall_status: determine_overall_status(components)
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def build_components(data)
|
|
51
|
+
COMPONENTS.filter_map do |component|
|
|
52
|
+
version = data[component]
|
|
53
|
+
next if version.nil? || version.empty?
|
|
54
|
+
next if version.include?("gem")
|
|
55
|
+
|
|
56
|
+
product = PRODUCT_NAMES[component]
|
|
57
|
+
eol_date = @fetcher.eol_date_for(product, version)
|
|
58
|
+
days = eol_date ? days_until(eol_date) : nil
|
|
59
|
+
status = days ? calculate_status(days) : "unknown"
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
name: component,
|
|
63
|
+
version: version,
|
|
64
|
+
eol_date: eol_date,
|
|
65
|
+
days_remaining: days,
|
|
66
|
+
status: status
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def calculate_status(days)
|
|
72
|
+
if days.negative?
|
|
73
|
+
"eol"
|
|
74
|
+
elsif days < 180
|
|
75
|
+
"warning"
|
|
76
|
+
else
|
|
77
|
+
"safe"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def determine_overall_status(components)
|
|
82
|
+
statuses = components.map { |c| c[:status] }
|
|
83
|
+
return "unknown" if statuses.empty? || statuses.all? { |s| s == "unknown" }
|
|
84
|
+
|
|
85
|
+
if statuses.include?("eol")
|
|
86
|
+
"eol"
|
|
87
|
+
elsif statuses.include?("warning")
|
|
88
|
+
"warning"
|
|
89
|
+
else
|
|
90
|
+
"safe"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def days_until(date_string)
|
|
95
|
+
eol_date = Date.parse(date_string)
|
|
96
|
+
(eol_date - Date.today).to_i
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
require_relative "base_exporter"
|
|
5
|
+
|
|
6
|
+
module Harbinger
|
|
7
|
+
module Exporters
|
|
8
|
+
# Exports project EOL data to CSV format
|
|
9
|
+
class CsvExporter < BaseExporter
|
|
10
|
+
HEADERS = %w[project path component version eol_date days_remaining status overall_status].freeze
|
|
11
|
+
|
|
12
|
+
def export
|
|
13
|
+
projects = build_export_data
|
|
14
|
+
|
|
15
|
+
CSV.generate do |csv|
|
|
16
|
+
csv << HEADERS
|
|
17
|
+
|
|
18
|
+
projects.each do |project|
|
|
19
|
+
project[:components].each do |component|
|
|
20
|
+
csv << [
|
|
21
|
+
project[:name],
|
|
22
|
+
project[:path],
|
|
23
|
+
component[:name],
|
|
24
|
+
component[:version],
|
|
25
|
+
component[:eol_date],
|
|
26
|
+
component[:days_remaining],
|
|
27
|
+
component[:status],
|
|
28
|
+
project[:overall_status]
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "base_exporter"
|
|
5
|
+
|
|
6
|
+
module Harbinger
|
|
7
|
+
module Exporters
|
|
8
|
+
# Exports project EOL data to JSON format
|
|
9
|
+
class JsonExporter < BaseExporter
|
|
10
|
+
def export
|
|
11
|
+
projects = build_export_data
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
generated_at: Time.now.iso8601,
|
|
15
|
+
project_count: projects.size,
|
|
16
|
+
projects: projects
|
|
17
|
+
}.to_json
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/harbinger/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stackharbinger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rich Dabrowski
|
|
@@ -96,13 +96,23 @@ files:
|
|
|
96
96
|
- exe/harbinger
|
|
97
97
|
- lib/harbinger.rb
|
|
98
98
|
- lib/harbinger/analyzers/database_detector.rb
|
|
99
|
+
- lib/harbinger/analyzers/docker_compose_detector.rb
|
|
100
|
+
- lib/harbinger/analyzers/go_detector.rb
|
|
101
|
+
- lib/harbinger/analyzers/mongo_detector.rb
|
|
99
102
|
- lib/harbinger/analyzers/mysql_detector.rb
|
|
103
|
+
- lib/harbinger/analyzers/node_detector.rb
|
|
100
104
|
- lib/harbinger/analyzers/postgres_detector.rb
|
|
105
|
+
- lib/harbinger/analyzers/python_detector.rb
|
|
101
106
|
- lib/harbinger/analyzers/rails_analyzer.rb
|
|
107
|
+
- lib/harbinger/analyzers/redis_detector.rb
|
|
102
108
|
- lib/harbinger/analyzers/ruby_detector.rb
|
|
109
|
+
- lib/harbinger/analyzers/rust_detector.rb
|
|
103
110
|
- lib/harbinger/cli.rb
|
|
104
111
|
- lib/harbinger/config_manager.rb
|
|
105
112
|
- lib/harbinger/eol_fetcher.rb
|
|
113
|
+
- lib/harbinger/exporters/base_exporter.rb
|
|
114
|
+
- lib/harbinger/exporters/csv_exporter.rb
|
|
115
|
+
- lib/harbinger/exporters/json_exporter.rb
|
|
106
116
|
- lib/harbinger/version.rb
|
|
107
117
|
- sig/harbinger.rbs
|
|
108
118
|
homepage: https://stackharbinger.com
|