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.
@@ -17,6 +17,7 @@ module StillActive
17
17
  add_token_options(opts)
18
18
  add_parallelism_options(opts)
19
19
  add_range_options(opts)
20
+ add_exit_options(opts)
20
21
  add_emoji_options(opts)
21
22
  end
22
23
  end
@@ -48,14 +49,18 @@ module StillActive
48
49
  end
49
50
 
50
51
  def add_output_options(opts)
51
- opts.on("--markdown", "Prints output in markdown format") { StillActive.config { |config| config.output_format = :markdown } }
52
- opts.on("--json", "Prints output in JSON format") { StillActive.config { |config| config.output_format = :json } }
52
+ opts.on("--terminal", "Coloured terminal output (default in TTY)") { StillActive.config { |config| config.output_format = :terminal } }
53
+ opts.on("--markdown", "Markdown table output") { StillActive.config { |config| config.output_format = :markdown } }
54
+ opts.on("--json", "JSON output (default when piped)") { StillActive.config { |config| config.output_format = :json } }
53
55
  end
54
56
 
55
57
  def add_token_options(opts)
56
58
  opts.on("--github-oauth-token=TOKEN", String, "GitHub OAuth token to make API calls") do |value|
57
59
  StillActive.config { |config| config.github_oauth_token = value }
58
60
  end
61
+ opts.on("--gitlab-token=TOKEN", String, "GitLab personal access token for API calls") do |value|
62
+ StillActive.config { |config| config.gitlab_token = value }
63
+ end
59
64
  end
60
65
 
61
66
  def add_parallelism_options(opts)
@@ -66,21 +71,30 @@ module StillActive
66
71
 
67
72
  def add_range_options(opts)
68
73
  opts.on(
69
- "--no-warning-range-end=YEARS",
74
+ "--safe-range-end=YEARS",
70
75
  Integer,
71
- "maximum number of years since last activity until which you do not want to be warned about ",
76
+ "maximum years since last activity considered safe (no warning)",
72
77
  ) do |value|
73
78
  StillActive.config { |config| config.no_warning_range_end = value }
74
79
  end
75
80
  opts.on(
76
81
  "--warning-range-end=YEARS",
77
82
  Integer,
78
- "maximum number of years since last activity that you want to be warned about",
83
+ "maximum years since last activity that triggers a warning (beyond this is critical)",
79
84
  ) do |value|
80
85
  StillActive.config { |config| config.warning_range_end = value }
81
86
  end
82
87
  end
83
88
 
89
+ def add_exit_options(opts)
90
+ opts.on("--fail-if-critical", "Exit 1 if any gem has critical activity warning") do
91
+ StillActive.config { |config| config.fail_if_critical = true }
92
+ end
93
+ opts.on("--fail-if-warning", "Exit 1 if any gem has warning or critical activity warning") do
94
+ StillActive.config { |config| config.fail_if_warning = true }
95
+ end
96
+ end
97
+
84
98
  def add_emoji_options(opts)
85
99
  opts.on("--critical-warning-emoji=EMOJI") { |value| StillActive.config { |config| config.critical_warning_emoji = value } }
86
100
  opts.on("--futurist-emoji=EMOJI") { |value| StillActive.config { |config| config.futurist_emoji = value } }
@@ -2,28 +2,21 @@
2
2
 
3
3
  module StillActive
4
4
  module Repository
5
- GITHUB_REGEX = %r{(http(?:s)?://(?:www\.)?(github)\.com/((?:\w|_|-)+)/((?:\w|_|-)+))}i
6
- GITLAB_REGEX = %r{(http(?:s)?://(?:www\.)?(gitlab)\.com/((?:\w|_|-)+)/((?:\w|_|-)+))}i
7
-
8
- HASH_KEYS = [:url, :source, :owner, :name]
9
- private_constant :HASH_KEYS
5
+ REPO_REGEX = %r{(https?://(?:www\.)?(github|gitlab)\.com/([\w.\-]+)/([\w.\-]+))}i
10
6
 
11
7
  extend self
12
8
 
13
9
  def valid?(url:)
14
- [GITHUB_REGEX, GITLAB_REGEX].any? do |regex|
15
- !url.scan(regex)&.first.nil?
16
- end
10
+ return false if url.nil?
11
+
12
+ url.match?(REPO_REGEX)
17
13
  end
18
14
 
19
15
  def url_with_owner_and_name(url:)
20
- [GITHUB_REGEX, GITLAB_REGEX].each do |regex|
21
- values = url&.scan(regex)&.first
22
- next if values.nil? || values.empty?
16
+ match = url&.match(REPO_REGEX)
17
+ return { source: :unhandled, owner: nil, name: nil } unless match
23
18
 
24
- return HASH_KEYS.zip(values).to_h
25
- end
26
- { source: :unhandled, owner: nil, name: nil }
19
+ { url: match[1], source: match[2].to_sym, owner: match[3], name: match[4] }
27
20
  end
28
21
  end
29
22
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StillActive
4
- VERSION = "0.5.0"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "deps_dev_client"
4
+ require_relative "gitlab_client"
3
5
  require_relative "repository"
4
6
  require_relative "../helpers/version_helper"
5
7
  require "async"
6
8
  require "async/barrier"
7
9
  require "async/semaphore"
8
10
  require "gems"
9
- require "github_api"
10
11
 
11
12
  module StillActive
12
13
  module Workflow
13
14
  extend self
14
- include VersionHelper
15
15
 
16
16
  def call
17
17
  task = Async do
@@ -20,7 +20,7 @@ module StillActive
20
20
  result_object = {}
21
21
  StillActive.config.gems.each_with_object(result_object) do |gem, hash|
22
22
  semaphore.async do
23
- gem_info(gem_name: gem[:name], result_object: hash, gem_version: gem.dig(:version))
23
+ gem_info(gem_name: gem[:name], result_object: hash, gem_version: gem[:version])
24
24
  end
25
25
  end
26
26
  barrier.wait
@@ -37,13 +37,17 @@ module StillActive
37
37
 
38
38
  vs = versions(gem_name: gem_name)
39
39
  repo_info = repository_info(gem_name: gem_name, versions: vs)
40
- last_commit_date = last_commit_date(
40
+ commit_date = last_commit_date(
41
41
  source: repo_info[:source],
42
42
  repository_owner: repo_info[:owner],
43
43
  repository_name: repo_info[:name],
44
44
  )
45
45
  last_release = VersionHelper.find_version(versions: vs, pre_release: false)
46
46
  last_pre_release = VersionHelper.find_version(versions: vs, pre_release: true)
47
+ deps_dev = fetch_deps_dev_info(
48
+ gem_name: gem_name,
49
+ version: VersionHelper.gem_version(version_hash: last_release),
50
+ )
47
51
  result_object[gem_name].merge!({
48
52
  latest_version: VersionHelper.gem_version(version_hash: last_release),
49
53
  latest_version_release_date: VersionHelper.release_date(version_hash: last_release),
@@ -52,7 +56,8 @@ module StillActive
52
56
  latest_pre_release_version_release_date: VersionHelper.release_date(version_hash: last_pre_release),
53
57
 
54
58
  repository_url: repo_info[:url],
55
- last_commit_date: last_commit_date,
59
+ last_commit_date: commit_date,
60
+ **deps_dev,
56
61
  })
57
62
 
58
63
  unless vs.empty?
@@ -62,8 +67,7 @@ module StillActive
62
67
  if gem_version
63
68
  version_used = VersionHelper.find_version(versions: vs, version_string: gem_version)
64
69
  result_object[gem_name].merge!({
65
- up_to_date:
66
- VersionHelper.up_to_date?(
70
+ up_to_date: VersionHelper.up_to_date(
67
71
  version_used: version_used,
68
72
  latest_version: last_release,
69
73
  latest_pre_release_version: last_pre_release,
@@ -73,7 +77,16 @@ module StillActive
73
77
  })
74
78
  end
75
79
  rescue StandardError => e
76
- puts "error occured for #{gem_name}: #{e.class}\n\t#{e.message}"
80
+ $stderr.puts "error occurred for #{gem_name}: #{e.class}\n\t#{e.message}"
81
+ end
82
+
83
+ def fetch_deps_dev_info(gem_name:, version:)
84
+ info = DepsDevClient.version_info(gem_name: gem_name, version: version)
85
+ scorecard = DepsDevClient.project_scorecard(project_id: info&.dig(:project_id))
86
+ {
87
+ scorecard_score: scorecard&.dig(:score),
88
+ vulnerability_count: info&.dig(:advisory_keys)&.length,
89
+ }
77
90
  end
78
91
 
79
92
  # makes network request
@@ -96,8 +109,8 @@ module StillActive
96
109
  return [] if info.nil?
97
110
 
98
111
  [
99
- info&.metadata&.dig("source_code_uri"),
100
- info&.homepage,
112
+ info.metadata&.dig("source_code_uri"),
113
+ info.homepage,
101
114
  ].compact.uniq
102
115
  end
103
116
 
@@ -119,22 +132,17 @@ module StillActive
119
132
  []
120
133
  end
121
134
 
122
- def last_commit(source:, repository_owner:, repository_name:)
123
- case source.to_sym
124
- when :github
125
- StillActive.config.github_client.repos.commits.all(repository_owner, repository_name, per_page: 1)&.first
126
- # when :gitlab
127
- # Gitlab.commits(name, per_page: 1)
128
- end
129
- end
130
-
131
135
  def last_commit_date(source:, repository_owner:, repository_name:)
132
- commit = last_commit(source: source, repository_owner: repository_owner, repository_name: repository_name)
133
- case source.to_sym
136
+ case source
134
137
  when :github
135
- commit&.dig("commit", "author", "date").then { |date| Time.parse(date) unless date.nil? }
136
- # when :gitlab
137
- # commit
138
+ commit = StillActive.config.github_client.commits("#{repository_owner}/#{repository_name}", per_page: 1)&.first
139
+ date = commit&.commit&.author&.date
140
+ case date
141
+ when Time then date
142
+ when String then Time.parse(date)
143
+ end
144
+ when :gitlab
145
+ GitlabClient.last_commit_date(owner: repository_owner, name: repository_name)
138
146
  end
139
147
  end
140
148
  end
data/still_active.gemspec CHANGED
@@ -8,20 +8,21 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Sean Floyd"]
9
9
  spec.email = ["contact@seanfloyd.dev"]
10
10
 
11
- spec.summary = "Check if gems are under active development."
12
- spec.description = "Obtain last release, pre-release, and last commit date to determine if a gem is still under active development."
11
+ spec.summary = "Check if your Ruby dependencies are still actively maintained."
12
+ spec.description = "Analyses your Gemfile dependencies for staleness: latest releases, " \
13
+ "last commit dates (GitHub and GitLab), OpenSSF Scorecard scores, " \
14
+ "and known vulnerabilities via deps.dev. " \
15
+ "Outputs coloured terminal tables, markdown, or JSON with CI gating support."
13
16
  spec.homepage = "https://github.com/SeanLF/still_active"
14
17
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 2.7.0"
16
-
17
- # spec.metadata["allowed_push_host"] = "TODO: Set to 'https://mygemserver.com'"
18
+ spec.required_ruby_version = ">= 3.2.0"
18
19
 
19
20
  spec.metadata["homepage_uri"] = spec.homepage
20
21
  spec.metadata["source_code_uri"] = spec.homepage
21
- spec.metadata["changelog_uri"] = "#{spec.metadata["source_code_uri"]}/blob/main/CHANGELOG.md"
22
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
23
+ spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
24
+ spec.metadata["rubygems_mfa_required"] = "true"
22
25
 
23
- # Specify which files should be added to the gem when it is released.
24
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
26
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
27
  %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
28
  end
@@ -29,9 +30,6 @@ Gem::Specification.new do |spec|
29
30
  spec.executables = spec.files.grep(%r{\Abin/still_active}) { |f| File.basename(f) }
30
31
  spec.require_paths = ["lib"]
31
32
 
32
- # For more information and examples about making a new gem, checkout our
33
- # guide at: https://bundler.io/guides/creating_gem.html
34
- spec.add_development_dependency("dead_end")
35
33
  spec.add_development_dependency("debug")
36
34
  spec.add_development_dependency("faker")
37
35
  spec.add_development_dependency("rubocop")
@@ -39,13 +37,9 @@ Gem::Specification.new do |spec|
39
37
  spec.add_development_dependency("rubocop-rspec")
40
38
  spec.add_development_dependency("rubocop-shopify")
41
39
 
42
- spec.add_runtime_dependency("activesupport")
43
40
  spec.add_runtime_dependency("async")
44
- # spec.add_runtime_dependency("cli-ui")
45
- spec.add_runtime_dependency("async-http")
46
41
  spec.add_runtime_dependency("bundler", ">= 2.0")
42
+ spec.add_runtime_dependency("faraday-retry")
47
43
  spec.add_runtime_dependency("gems")
48
- spec.add_runtime_dependency("github_api")
49
- # spec.add_runtime_dependency("gitlab")
50
- # spec.add_runtime_dependency("tty-progressbar")
44
+ spec.add_runtime_dependency("octokit")
51
45
  end
metadata CHANGED
@@ -1,29 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: still_active
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Floyd
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-05-21 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: dead_end
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
12
  - !ruby/object:Gem::Dependency
28
13
  name: debug
29
14
  requirement: !ruby/object:Gem::Requirement
@@ -109,7 +94,7 @@ dependencies:
109
94
  - !ruby/object:Gem::Version
110
95
  version: '0'
111
96
  - !ruby/object:Gem::Dependency
112
- name: activesupport
97
+ name: async
113
98
  requirement: !ruby/object:Gem::Requirement
114
99
  requirements:
115
100
  - - ">="
@@ -123,21 +108,21 @@ dependencies:
123
108
  - !ruby/object:Gem::Version
124
109
  version: '0'
125
110
  - !ruby/object:Gem::Dependency
126
- name: async
111
+ name: bundler
127
112
  requirement: !ruby/object:Gem::Requirement
128
113
  requirements:
129
114
  - - ">="
130
115
  - !ruby/object:Gem::Version
131
- version: '0'
116
+ version: '2.0'
132
117
  type: :runtime
133
118
  prerelease: false
134
119
  version_requirements: !ruby/object:Gem::Requirement
135
120
  requirements:
136
121
  - - ">="
137
122
  - !ruby/object:Gem::Version
138
- version: '0'
123
+ version: '2.0'
139
124
  - !ruby/object:Gem::Dependency
140
- name: async-http
125
+ name: faraday-retry
141
126
  requirement: !ruby/object:Gem::Requirement
142
127
  requirements:
143
128
  - - ">="
@@ -150,20 +135,6 @@ dependencies:
150
135
  - - ">="
151
136
  - !ruby/object:Gem::Version
152
137
  version: '0'
153
- - !ruby/object:Gem::Dependency
154
- name: bundler
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - ">="
158
- - !ruby/object:Gem::Version
159
- version: '2.0'
160
- type: :runtime
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - ">="
165
- - !ruby/object:Gem::Version
166
- version: '2.0'
167
138
  - !ruby/object:Gem::Dependency
168
139
  name: gems
169
140
  requirement: !ruby/object:Gem::Requirement
@@ -179,7 +150,7 @@ dependencies:
179
150
  - !ruby/object:Gem::Version
180
151
  version: '0'
181
152
  - !ruby/object:Gem::Dependency
182
- name: github_api
153
+ name: octokit
183
154
  requirement: !ruby/object:Gem::Requirement
184
155
  requirements:
185
156
  - - ">="
@@ -192,8 +163,10 @@ dependencies:
192
163
  - - ">="
193
164
  - !ruby/object:Gem::Version
194
165
  version: '0'
195
- description: Obtain last release, pre-release, and last commit date to determine if
196
- a gem is still under active development.
166
+ description: 'Analyses your Gemfile dependencies for staleness: latest releases, last
167
+ commit dates (GitHub and GitLab), OpenSSF Scorecard scores, and known vulnerabilities
168
+ via deps.dev. Outputs coloured terminal tables, markdown, or JSON with CI gating
169
+ support.'
197
170
  email:
198
171
  - contact@seanfloyd.dev
199
172
  executables:
@@ -201,7 +174,9 @@ executables:
201
174
  extensions: []
202
175
  extra_rdoc_files: []
203
176
  files:
177
+ - ".github/dependabot.yml"
204
178
  - ".github/workflows/codeql-analysis.yml"
179
+ - ".github/workflows/publish.yml"
205
180
  - ".github/workflows/rspec.yml"
206
181
  - ".github/workflows/rubocop-analysis.yml"
207
182
  - ".gitignore"
@@ -216,15 +191,25 @@ files:
216
191
  - bin/console
217
192
  - bin/setup
218
193
  - bin/still_active
194
+ - fixtures/debug_versions.json
195
+ - fixtures/still_active_version.json
196
+ - fixtures/vcr_cassettes/deps_dev_project.yml
197
+ - fixtures/vcr_cassettes/deps_dev_version.yml
219
198
  - fixtures/vcr_cassettes/gems.yml
199
+ - lib/helpers/activity_helper.rb
200
+ - lib/helpers/ansi_helper.rb
220
201
  - lib/helpers/bundler_helper.rb
221
202
  - lib/helpers/emoji_helper.rb
203
+ - lib/helpers/http_helper.rb
222
204
  - lib/helpers/markdown_helper.rb
205
+ - lib/helpers/terminal_helper.rb
223
206
  - lib/helpers/version_helper.rb
224
207
  - lib/still_active.rb
225
208
  - lib/still_active/cli.rb
226
209
  - lib/still_active/config.rb
227
- - lib/still_active/gemfile.rb
210
+ - lib/still_active/core_ext.rb
211
+ - lib/still_active/deps_dev_client.rb
212
+ - lib/still_active/gitlab_client.rb
228
213
  - lib/still_active/options.rb
229
214
  - lib/still_active/repository.rb
230
215
  - lib/still_active/version.rb
@@ -237,7 +222,8 @@ metadata:
237
222
  homepage_uri: https://github.com/SeanLF/still_active
238
223
  source_code_uri: https://github.com/SeanLF/still_active
239
224
  changelog_uri: https://github.com/SeanLF/still_active/blob/main/CHANGELOG.md
240
- post_install_message:
225
+ bug_tracker_uri: https://github.com/SeanLF/still_active/issues
226
+ rubygems_mfa_required: 'true'
241
227
  rdoc_options: []
242
228
  require_paths:
243
229
  - lib
@@ -245,15 +231,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
245
231
  requirements:
246
232
  - - ">="
247
233
  - !ruby/object:Gem::Version
248
- version: 2.7.0
234
+ version: 3.2.0
249
235
  required_rubygems_version: !ruby/object:Gem::Requirement
250
236
  requirements:
251
237
  - - ">="
252
238
  - !ruby/object:Gem::Version
253
239
  version: '0'
254
240
  requirements: []
255
- rubygems_version: 3.4.10
256
- signing_key:
241
+ rubygems_version: 4.0.3
257
242
  specification_version: 4
258
- summary: Check if gems are under active development.
243
+ summary: Check if your Ruby dependencies are still actively maintained.
259
244
  test_files: []
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler"
4
-
5
- module StillActive
6
- module Gemfile
7
- extend self
8
-
9
- def dependencies(gemfile_path:)
10
- Bundler::SharedHelpers.set_env("BUNDLE_GEMFILE", File.expand_path(gemfile_path))
11
- Bundler.definition.dependencies.map(&:name)
12
- end
13
- end
14
- end