still_active 0.5.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.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../still_active/core_ext"
4
+
5
+ module StillActive
6
+ module ActivityHelper
7
+ extend self
8
+
9
+ using StillActive::CoreExt
10
+
11
+ # Returns :ok, :stale, :critical, or :unknown
12
+ def activity_level(gem_data)
13
+ most_recent = [
14
+ gem_data[:last_commit_date],
15
+ gem_data[:latest_version_release_date],
16
+ gem_data[:latest_pre_release_version_release_date],
17
+ ].compact.max
18
+
19
+ return :unknown if most_recent.nil?
20
+
21
+ config = StillActive.config
22
+ if most_recent >= config.no_warning_range_end.years.ago
23
+ :ok
24
+ elsif most_recent >= config.warning_range_end.years.ago
25
+ :stale
26
+ else
27
+ :critical
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StillActive
4
+ module AnsiHelper
5
+ extend self
6
+
7
+ RESET = "\e[0m"
8
+ ANSI_PATTERN = /\e\[[0-9;]*m/
9
+
10
+ def green(text) = "\e[32m#{text}#{RESET}"
11
+ def yellow(text) = "\e[33m#{text}#{RESET}"
12
+ def red(text) = "\e[31m#{text}#{RESET}"
13
+ def dim(text) = "\e[2m#{text}#{RESET}"
14
+ def bold(text) = "\e[1m#{text}#{RESET}"
15
+
16
+ def visible_length(text)
17
+ text.gsub(ANSI_PATTERN, "").length
18
+ end
19
+
20
+ def pad(text, width)
21
+ padding = width - visible_length(text)
22
+ padding > 0 ? "#{text}#{" " * padding}" : text
23
+ end
24
+ end
25
+ end
@@ -1,26 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/isolated_execution_state"
4
- require "active_support/core_ext/integer/time"
3
+ require_relative "activity_helper"
5
4
 
6
5
  module StillActive
7
6
  module EmojiHelper
8
7
  extend self
9
- def inactive_gem_emoji(result_hash)
10
- most_recent_activity = [
11
- result_hash.dig(:last_commit_date),
12
- result_hash.dig(:latest_version_release_date),
13
- result_hash.dig(:latest_pre_release_version_release_date),
14
- ].compact.sort.last
15
- return StillActive.config.unsure_emoji if most_recent_activity.nil?
16
8
 
17
- case most_recent_activity
18
- when (StillActive.config.no_warning_range_end.years.ago)..(Time.now)
19
- ""
20
- when (StillActive.config.warning_range_end.years.ago)..(StillActive.config.no_warning_range_end.years.ago)
21
- StillActive.config.warning_emoji
22
- else
23
- StillActive.config.critical_warning_emoji
9
+ def inactive_gem_emoji(result_hash)
10
+ case ActivityHelper.activity_level(result_hash)
11
+ when :ok then ""
12
+ when :stale then StillActive.config.warning_emoji
13
+ when :critical then StillActive.config.critical_warning_emoji
14
+ when :unknown then StillActive.config.unsure_emoji
24
15
  end
25
16
  end
26
17
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module StillActive
7
+ module HttpHelper
8
+ extend self
9
+
10
+ def get_json(base_uri, path, headers: {}, params: {})
11
+ uri = base_uri.dup
12
+ uri.path = path
13
+ uri.query = URI.encode_www_form(params) unless params.empty?
14
+
15
+ http = Net::HTTP.new(uri.host, uri.port)
16
+ http.use_ssl = true
17
+ http.open_timeout = 10
18
+ http.read_timeout = 10
19
+
20
+ request = Net::HTTP::Get.new(uri)
21
+ headers.each { |key, value| request[key] = value }
22
+
23
+ response = http.request(request)
24
+ return unless response.is_a?(Net::HTTPSuccess)
25
+
26
+ JSON.parse(response.body)
27
+ rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, Errno::ECONNREFUSED, JSON::ParserError
28
+ nil
29
+ end
30
+ end
31
+ end
@@ -3,78 +3,89 @@
3
3
  module StillActive
4
4
  module MarkdownHelper
5
5
  extend self
6
+
6
7
  def markdown_table_header_line
7
- "| gem activity old? | up to date? | name | version used | release date | latest version | release date | latest pre-release version | release date | last commit date |\n" \
8
- "| ----------------- | ----------- | ---- | ------------ | ------------ | -------------- | ------------ | --------------------------- | ------------ | ---------------- |"
8
+ "| activity | up to date? | OpenSSF | vulns | name | version used | latest version | latest pre-release | last commit |\n" \
9
+ "| -------- | ----------- | ------- | ----- | ---- | ------------ | -------------- | ------------------ | ----------- |"
9
10
  end
10
11
 
11
12
  def markdown_table_body_line(gem_name:, data:)
12
13
  repository_url = data[:repository_url]
13
14
  ruby_gems_url = data[:ruby_gems_url]
14
15
 
15
- version_used = data.dig(:version_used)
16
- version_used_url =
17
- if version_used && ruby_gems_url
18
- "#{ruby_gems_url}/versions/#{version_used}"
19
- end
20
- version_used_release_date = data.dig(:version_used_release_date)
21
-
22
- latest_version = data[:latest_version]
23
- latest_version_url =
24
- if latest_version && ruby_gems_url
25
- "#{ruby_gems_url}/versions/#{latest_version}"
26
- end
27
- latest_version_release_date = data.dig(:latest_version_release_date)
28
-
29
- latest_version_prerelease = data.dig(:latest_pre_release_version)
30
- latest_version_prerelease_url =
31
- if latest_version_prerelease && ruby_gems_url
32
- "#{ruby_gems_url}/versions/#{latest_version_prerelease}"
33
- end
34
- latest_version_prerelease_date = data.dig(:latest_pre_release_version_release_date)
35
-
36
- last_commit_date = data.dig(:last_commit_date)
37
- last_commit_url = repository_url
38
-
39
- inactive_repository_emoji = data.dig(:last_activity_warning_emoj)
40
- using_latest_version_emoji = data.dig(:up_to_date_emoji)
16
+ inactive_repository_emoji = data[:last_activity_warning_emoji]
17
+ using_latest_version_emoji = data[:up_to_date_emoji]
41
18
 
42
19
  formatted_name = markdown_url(text: gem_name, url: repository_url)
43
20
 
44
- formatted_version_used = markdown_url(text: version_used, url: version_used_url)
45
- formatted_version_used_date = year_month(version_used_release_date)
21
+ formatted_version_used = version_with_date(
22
+ text: data[:version_used],
23
+ url: version_url(ruby_gems_url, data[:version_used]),
24
+ date: data[:version_used_release_date],
25
+ )
46
26
 
47
- formatted_latest_release_version = markdown_url(text: latest_version, url: latest_version_url)
48
- formatted_latest_release_date = year_month(latest_version_release_date)
27
+ formatted_latest_version = version_with_date(
28
+ text: data[:latest_version],
29
+ url: version_url(ruby_gems_url, data[:latest_version]),
30
+ date: data[:latest_version_release_date],
31
+ )
49
32
 
50
- formatted_latest_pre_release_version = markdown_url(
51
- text: latest_version_prerelease,
52
- url: latest_version_prerelease_url,
33
+ formatted_latest_pre_release = version_with_date(
34
+ text: data[:latest_pre_release_version],
35
+ url: version_url(ruby_gems_url, data[:latest_pre_release_version]),
36
+ date: data[:latest_pre_release_version_release_date],
53
37
  )
54
- formatted_latest_pre_release_date = year_month(latest_version_prerelease_date)
55
-
56
- formatted_last_commit_date = markdown_url(text: year_month(last_commit_date), url: last_commit_url)
57
-
58
- formatted_markdown_table_line =
59
- [
60
- inactive_repository_emoji || StillActive.config.unsure_emoji,
61
- using_latest_version_emoji || StillActive.config.unsure_emoji,
62
- formatted_name,
63
- formatted_version_used,
64
- formatted_version_used_date || StillActive.config.unsure_emoji,
65
- formatted_latest_release_version || StillActive.config.unsure_emoji,
66
- formatted_latest_release_date || StillActive.config.unsure_emoji,
67
- formatted_latest_pre_release_version || StillActive.config.unsure_emoji,
68
- formatted_latest_pre_release_date || StillActive.config.unsure_emoji,
69
- formatted_last_commit_date || StillActive.config.unsure_emoji,
70
- ]
71
- .join(" | ")
72
-
73
- "| #{formatted_markdown_table_line} |"
38
+
39
+ formatted_last_commit = markdown_url(text: year_month(data[:last_commit_date]), url: repository_url)
40
+
41
+ unsure = StillActive.config.unsure_emoji
42
+
43
+ cells = [
44
+ inactive_repository_emoji || unsure,
45
+ using_latest_version_emoji || unsure,
46
+ format_scorecard(data[:scorecard_score]),
47
+ format_vulns(data[:vulnerability_count]),
48
+ formatted_name,
49
+ formatted_version_used || unsure,
50
+ formatted_latest_version || unsure,
51
+ formatted_latest_pre_release || unsure,
52
+ formatted_last_commit || unsure,
53
+ ]
54
+
55
+ "| #{cells.join(" | ")} |"
74
56
  end
75
57
 
76
58
  private
77
59
 
60
+ def version_with_date(text:, url:, date:)
61
+ version_part = markdown_url(text: text, url: url)
62
+ return if version_part.nil?
63
+
64
+ date_part = year_month(date)
65
+ return version_part unless date_part
66
+
67
+ "#{version_part} (#{date_part})"
68
+ end
69
+
70
+ def version_url(ruby_gems_url, version)
71
+ return if ruby_gems_url.nil? || version.nil?
72
+
73
+ "#{ruby_gems_url}/versions/#{version}"
74
+ end
75
+
76
+ def format_scorecard(score)
77
+ return StillActive.config.unsure_emoji if score.nil?
78
+
79
+ "#{score}/10"
80
+ end
81
+
82
+ def format_vulns(count)
83
+ return StillActive.config.unsure_emoji if count.nil?
84
+ return StillActive.config.success_emoji if count.zero?
85
+
86
+ count.to_s
87
+ end
88
+
78
89
  def markdown_url(text:, url:)
79
90
  return text if url.nil?
80
91
 
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "activity_helper"
4
+ require_relative "ansi_helper"
5
+ require_relative "version_helper"
6
+
7
+ module StillActive
8
+ module TerminalHelper
9
+ extend self
10
+
11
+ HEADERS = ["Name", "Version", "Activity", "OpenSSF", "Vulns"].freeze
12
+
13
+ def render(result)
14
+ rows = result.keys.sort.map { |name| build_row(name, result[name]) }
15
+ widths = column_widths(rows)
16
+
17
+ lines = []
18
+ lines << header_line(widths)
19
+ lines << separator_line(widths)
20
+ rows.each { |row| lines << row_line(row, widths) }
21
+ lines << ""
22
+ lines << summary_line(result)
23
+ lines.join("\n")
24
+ end
25
+
26
+ private
27
+
28
+ def build_row(name, data)
29
+ [
30
+ name,
31
+ format_version(data),
32
+ format_activity(data),
33
+ format_scorecard(data[:scorecard_score]),
34
+ format_vulns(data[:vulnerability_count]),
35
+ ]
36
+ end
37
+
38
+ def format_version(data)
39
+ used = data[:version_used]
40
+ latest = data[:latest_version]
41
+ return AnsiHelper.dim("-") if used.nil? && latest.nil?
42
+
43
+ if VersionHelper.up_to_date(version_used: used, latest_version: latest)
44
+ AnsiHelper.green("#{used} (latest)")
45
+ elsif latest
46
+ AnsiHelper.yellow("#{used} → #{latest}")
47
+ else
48
+ used.to_s
49
+ end
50
+ end
51
+
52
+ def format_activity(data)
53
+ case ActivityHelper.activity_level(data)
54
+ when :ok then AnsiHelper.green("ok")
55
+ when :stale then AnsiHelper.yellow("stale")
56
+ when :critical then AnsiHelper.red("critical")
57
+ when :unknown then AnsiHelper.dim("-")
58
+ end
59
+ end
60
+
61
+ def format_scorecard(score)
62
+ score.nil? ? AnsiHelper.dim("-") : "#{score}/10"
63
+ end
64
+
65
+ def format_vulns(count)
66
+ return AnsiHelper.dim("-") if count.nil?
67
+ return AnsiHelper.green("0") if count.zero?
68
+
69
+ AnsiHelper.red(count.to_s)
70
+ end
71
+
72
+ def column_widths(rows)
73
+ return HEADERS.map { |h| h.length + 2 } if rows.empty?
74
+
75
+ HEADERS.zip(rows.transpose).map do |header, cells|
76
+ widths = cells.map { AnsiHelper.visible_length(_1) }
77
+ [header.length, *widths].max + 2
78
+ end
79
+ end
80
+
81
+ def header_line(widths)
82
+ HEADERS.zip(widths)
83
+ .map { |h, w| AnsiHelper.pad(AnsiHelper.bold(h), w) }
84
+ .join
85
+ end
86
+
87
+ def separator_line(widths)
88
+ AnsiHelper.dim(widths.map { |w| "─" * w }.join)
89
+ end
90
+
91
+ def row_line(row, widths)
92
+ row.zip(widths)
93
+ .map { |cell, w| AnsiHelper.pad(cell, w) }
94
+ .join
95
+ end
96
+
97
+ def summary_line(result)
98
+ total = result.size
99
+ level_counts = result.each_value.map { |d| ActivityHelper.activity_level(d) }.tally
100
+ version_counts = result.each_value
101
+ .map { |d| VersionHelper.up_to_date(version_used: d[:version_used], latest_version: d[:latest_version]) }
102
+ .tally
103
+
104
+ up_to_date = version_counts.fetch(true, 0)
105
+ outdated = version_counts.fetch(false, 0)
106
+ active = level_counts.fetch(:ok, 0)
107
+ stale = level_counts.fetch(:stale, 0) + level_counts.fetch(:critical, 0)
108
+ vulns = result.each_value.sum { |d| d[:vulnerability_count] || 0 }
109
+
110
+ parts = [
111
+ "#{total} gems: #{up_to_date} up to date, #{outdated} outdated",
112
+ "#{active} active, #{stale} stale",
113
+ "#{vulns} vulnerabilities",
114
+ ]
115
+ parts.join(" · ")
116
+ end
117
+ end
118
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time"
4
+
3
5
  module StillActive
4
6
  module VersionHelper
5
7
  extend self
@@ -14,27 +16,16 @@ module StillActive
14
16
  end
15
17
  end
16
18
 
17
- def up_to_date?(version_used:, latest_version: nil, latest_pre_release_version: nil)
18
- return nil if latest_version.nil? && latest_pre_release_version.nil?
19
+ def up_to_date(version_used:, latest_version: nil, latest_pre_release_version: nil)
20
+ return if latest_version.nil? && latest_pre_release_version.nil?
19
21
 
20
- version_used = if version_used.is_a?(String)
21
- version_used
22
- else
23
- gem_version(version_hash: version_used)
24
- end
22
+ used = to_gem_version(version_used)
23
+ return if used.nil?
25
24
 
26
- latest_version = if latest_version.is_a?(String)
27
- latest_version
28
- else
29
- gem_version(version_hash: latest_version)
30
- end
31
- latest_pre_release_version = if latest_pre_release_version.is_a?(String)
32
- latest_pre_release_version
33
- else
34
- gem_version(version_hash: latest_pre_release_version)
35
- end
36
-
37
- [latest_version, latest_pre_release_version].include?(version_used)
25
+ [latest_version, latest_pre_release_version]
26
+ .compact
27
+ .filter_map { |v| to_gem_version(v) }
28
+ .any? { |v| used >= v }
38
29
  end
39
30
 
40
31
  def gem_version(version_hash:)
@@ -46,5 +37,16 @@ module StillActive
46
37
 
47
38
  Time.parse(release_date) unless release_date.nil?
48
39
  end
40
+
41
+ private
42
+
43
+ def normalize_version(version)
44
+ version.is_a?(String) ? version : gem_version(version_hash: version)
45
+ end
46
+
47
+ def to_gem_version(version)
48
+ str = normalize_version(version)
49
+ Gem::Version.new(str) if str
50
+ end
49
51
  end
50
52
  end
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "options"
4
+ require_relative "../helpers/activity_helper"
4
5
  require_relative "../helpers/bundler_helper"
5
6
  require_relative "../helpers/emoji_helper"
6
7
  require_relative "../helpers/markdown_helper"
8
+ require_relative "../helpers/terminal_helper"
7
9
  require_relative "../helpers/version_helper"
8
10
  require_relative "workflow"
9
- # require "cli/ui"
10
11
 
11
12
  module StillActive
12
13
  class CLI
13
- include VersionHelper
14
14
  def run(args)
15
15
  options = Options.new.parse!(args)
16
16
  if options[:provided_gems]
@@ -20,27 +20,54 @@ module StillActive
20
20
  end
21
21
 
22
22
  result = Workflow.call
23
- case StillActive.config.output_format
23
+
24
+ case resolve_format
24
25
  when :json
25
26
  puts result.to_json
27
+ when :terminal
28
+ puts TerminalHelper.render(result)
26
29
  when :markdown
27
- puts MarkdownHelper.markdown_table_header_line
28
- result.keys.sort.each do |name|
29
- result[name].merge!({
30
- last_activity_warning_emoj: EmojiHelper.inactive_gem_emoji(result[name]),
31
- up_to_date_emoji: EmojiHelper.using_latest_emoji(
32
- using_last_release: VersionHelper.up_to_date?(
33
- version_used: result[name].dig(:version_used), latest_version: result[name].dig(:latest_version),
34
- ),
35
- using_last_pre_release: VersionHelper.up_to_date?(
36
- version_used: result[name].dig(:version_used), latest_version: result[name].dig(:latest_pre_release_version),
37
- ),
38
- ),
39
- })
40
-
41
- puts MarkdownHelper.markdown_table_body_line(gem_name: name, data: result[name])
42
- end
30
+ render_markdown(result)
31
+ end
32
+
33
+ check_exit_status(result)
34
+ end
35
+
36
+ private
37
+
38
+ def resolve_format
39
+ format = StillActive.config.output_format
40
+ return format unless format == :auto
41
+
42
+ $stdout.tty? ? :terminal : :json
43
+ end
44
+
45
+ def render_markdown(result)
46
+ puts MarkdownHelper.markdown_table_header_line
47
+ result.keys.sort.each do |name|
48
+ gem_data = result[name]
49
+ gem_data[:last_activity_warning_emoji] = EmojiHelper.inactive_gem_emoji(gem_data)
50
+ gem_data[:up_to_date_emoji] = EmojiHelper.using_latest_emoji(
51
+ using_last_release: VersionHelper.up_to_date(
52
+ version_used: gem_data[:version_used], latest_version: gem_data[:latest_version],
53
+ ),
54
+ using_last_pre_release: VersionHelper.up_to_date(
55
+ version_used: gem_data[:version_used], latest_pre_release_version: gem_data[:latest_pre_release_version],
56
+ ),
57
+ )
58
+
59
+ puts MarkdownHelper.markdown_table_body_line(gem_name: name, data: gem_data)
43
60
  end
44
61
  end
62
+
63
+ def check_exit_status(result)
64
+ config = StillActive.config
65
+ return unless config.fail_if_critical || config.fail_if_warning
66
+
67
+ levels = result.each_value.map { |gem_data| ActivityHelper.activity_level(gem_data) }
68
+
69
+ exit(1) if config.fail_if_warning && levels.intersect?([:stale, :critical])
70
+ exit(1) if config.fail_if_critical && levels.include?(:critical)
71
+ end
45
72
  end
46
73
  end
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler"
4
+ require "octokit"
4
5
 
5
6
  module StillActive
6
7
  class Config
7
8
  attr_accessor :critical_warning_emoji,
9
+ :fail_if_critical,
10
+ :fail_if_warning,
8
11
  :futurist_emoji,
9
12
  :gemfile_path,
10
13
  :gems,
11
14
  :github_oauth_token,
15
+ :gitlab_token,
12
16
  :output_format,
13
17
  :parallelism,
14
18
  :no_warning_range_end,
@@ -18,12 +22,16 @@ module StillActive
18
22
  :warning_range_end
19
23
 
20
24
  def initialize
25
+ @fail_if_critical = false
26
+ @fail_if_warning = false
21
27
  @gemfile_path = Bundler.default_gemfile.to_s
22
28
  @gems = []
29
+ @github_oauth_token = ENV["GITHUB_TOKEN"]
30
+ @gitlab_token = ENV["GITLAB_TOKEN"]
23
31
 
24
32
  @parallelism = 10
25
33
 
26
- @output_format = :markdown
34
+ @output_format = :auto
27
35
 
28
36
  @critical_warning_emoji = "🚩"
29
37
  @futurist_emoji = "🔮"
@@ -37,7 +45,7 @@ module StillActive
37
45
 
38
46
  def github_client
39
47
  @github_client ||=
40
- Github.new(oauth_token: github_oauth_token)
48
+ Octokit::Client.new(access_token: github_oauth_token)
41
49
  end
42
50
  end
43
51
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StillActive
4
+ module CoreExt
5
+ SECONDS_PER_YEAR = 31_556_952 # Gregorian average (365.2425 days)
6
+
7
+ refine Numeric do
8
+ def days = self * 86_400
9
+ def years = self * SECONDS_PER_YEAR
10
+ def ago = Time.now - self
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../helpers/http_helper"
4
+
5
+ module StillActive
6
+ module DepsDevClient
7
+ extend self
8
+
9
+ BASE_URI = URI("https://api.deps.dev/")
10
+
11
+ def version_info(gem_name:, version:)
12
+ return if gem_name.nil? || version.nil?
13
+
14
+ path = "/v3alpha/systems/rubygems/packages/#{encode(gem_name)}/versions/#{encode(version)}"
15
+ body = HttpHelper.get_json(BASE_URI, path)
16
+ return if body.nil?
17
+
18
+ {
19
+ advisory_keys: body.dig("advisoryKeys")&.map { |a| a["id"] } || [],
20
+ project_id: extract_project_id(body),
21
+ }
22
+ end
23
+
24
+ def project_scorecard(project_id:)
25
+ return if project_id.nil?
26
+
27
+ path = "/v3alpha/projects/#{encode(project_id)}"
28
+ body = HttpHelper.get_json(BASE_URI, path)
29
+ return if body.nil?
30
+
31
+ scorecard = body["scorecard"]
32
+ return if scorecard.nil?
33
+
34
+ {
35
+ score: scorecard["overallScore"],
36
+ date: scorecard["date"],
37
+ }
38
+ end
39
+
40
+ private
41
+
42
+ # Extracts "host/owner/repo" from the SOURCE_REPO link URL.
43
+ # URLs may have trailing slashes or extra path segments (e.g. /tree/v1.0).
44
+ def extract_project_id(body)
45
+ url = body.dig("links")&.find { |l| l["label"] == "SOURCE_REPO" }&.dig("url")
46
+ return if url.nil?
47
+
48
+ path = url.delete_prefix("https://").delete_prefix("http://")
49
+ segments = path.split("/")
50
+ segments[0..2].join("/") if segments.length >= 3
51
+ end
52
+
53
+ def encode(value)
54
+ URI.encode_www_form_component(value)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+ require_relative "../helpers/http_helper"
5
+
6
+ module StillActive
7
+ module GitlabClient
8
+ extend self
9
+
10
+ BASE_URI = URI("https://gitlab.com/")
11
+
12
+ def last_commit_date(owner:, name:)
13
+ return if owner.nil? || name.nil?
14
+
15
+ project_id = URI.encode_www_form_component("#{owner}/#{name}")
16
+ headers = {}
17
+ token = StillActive.config.gitlab_token
18
+ headers["PRIVATE-TOKEN"] = token if token
19
+
20
+ path = "/api/v4/projects/#{project_id}/repository/commits"
21
+ body = HttpHelper.get_json(BASE_URI, path, headers: headers, params: { per_page: 1 })
22
+ return if body.nil? || body.empty?
23
+
24
+ date = body.first["committed_date"]
25
+ Time.parse(date) if date
26
+ rescue ArgumentError
27
+ nil
28
+ end
29
+ end
30
+ end