package-audit 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4f817af73ba4616da54a934f0f6c4120d921e6026226c20a02c592c6b55e064
4
- data.tar.gz: 818282a8f489ba8afd7b9675b78db96c3e2b9b18731415085ff44c25eaf07a77
3
+ metadata.gz: cca016948e4ab3d4643c8e7c7b6bca064873a0b390709d6318d899ee03464c36
4
+ data.tar.gz: '037543298033670ef8cd62ceb31a021389e55b0baf634117777018933d89b6b3'
5
5
  SHA512:
6
- metadata.gz: 46267de0e991aaf0e1fdeb4f8c778cc2a59abf49f7d7a0849ae9731c9fb633ae8c958ac1865bf17aa98d50cd359e8e30d16975031f4dfd4fc1a7c2aacf6868fc
7
- data.tar.gz: 65c6acb5e9e224f6736781f1db26eee0ec7772f220c87b8e8da1f2cecc872cfb667f78336314ad4b670000f849c60051087cdc9cda1b9e69ad11d896b717c471
6
+ metadata.gz: 494ad5afd212b57e596fd599b8b57f86453c3cb5f988d4ae88a572c685a83f8ba4c39c750353e962453d98ba3336d30ecf1da729ffad4c26cfce418c049e56eb
7
+ data.tar.gz: 6088d06e5a5ccbf73d9ecd3e3fbcd130030dbc47ca861a910f56ba21850ae467eda5f7a4b0064200d18bb1d7fe461f7db561f64f471b0c747a25d6bc18ec9115
@@ -3,7 +3,16 @@ require_relative '../models/package'
3
3
  module Package
4
4
  module Audit
5
5
  module Ruby
6
- class GemMetaData
6
+ class GemMetaData # rubocop:disable Metrics/ClassLength
7
+ # API and timeout constants
8
+ RUBYGEMS_API_BASE = 'https://rubygems.org/api/v1/versions'
9
+ HTTP_READ_TIMEOUT = 10
10
+ HTTP_OPEN_TIMEOUT = 5
11
+ PLACEHOLDER_DATE_THRESHOLD = 1980
12
+ DEFAULT_DATE_FORMAT = '%Y-%m-%d'
13
+ EPOCH_TIME = Time.new(0)
14
+ INITIAL_VERSION = Gem::Version.new('0.0.0.0')
15
+
7
16
  def initialize(dir, pkgs)
8
17
  @dir = dir
9
18
  @pkgs = pkgs
@@ -18,45 +27,226 @@ module Package
18
27
 
19
28
  private
20
29
 
21
- def find_rubygems_metadata # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
30
+ def find_rubygems_metadata
31
+ # Performance-optimized approach:
32
+ # 1. Use fast local SpecFetcher for version numbers and dates
33
+ # 2. Only make HTTP API calls for gems with placeholder dates (1980-01-02)
34
+ # 3. This avoids network calls for gems with proper local date metadata
22
35
  fetcher = Gem::SpecFetcher.fetcher
36
+ gems_needing_api_lookup = []
23
37
 
24
38
  @pkgs.each do |pkg|
25
- gem_dependency = Gem::Dependency.new pkg.name, ">= #{pkg.version}"
26
- local_version_date = Time.new(0)
27
- latest_version_date = Time.new(0)
28
- local_version = Gem::Version.new(pkg.version)
29
- latest_version = Gem::Version.new('0.0.0.0')
39
+ result = process_package_metadata(pkg, fetcher)
40
+ next unless result
30
41
 
31
- remote_dependencies, = fetcher.spec_for_dependency gem_dependency
42
+ if result[:needs_api_lookup]
43
+ gems_needing_api_lookup << result[:gem_data]
44
+ else
45
+ update_package_with_local_dates(pkg, result[:metadata])
46
+ end
47
+ end
32
48
 
33
- next unless remote_dependencies.any?
49
+ # Batch API lookups only for gems with placeholder dates
50
+ process_api_lookups(gems_needing_api_lookup) unless gems_needing_api_lookup.empty?
51
+ end
34
52
 
35
- remote_dependencies.each do |remote_spec, _|
36
- latest_version = remote_spec.version if latest_version < remote_spec.version
37
- latest_version_date = remote_spec.date if latest_version_date < remote_spec.date
38
- local_version_date = remote_spec.date if local_version == remote_spec.version
39
- end
53
+ def needs_api_lookup?(date)
54
+ return true if date.nil?
55
+
56
+ date.year <= PLACEHOLDER_DATE_THRESHOLD
57
+ end
58
+
59
+ def process_package_metadata(pkg, fetcher)
60
+ gem_dependency = Gem::Dependency.new pkg.name, ">= #{pkg.version}"
61
+ remote_dependencies, = fetcher.spec_for_dependency gem_dependency
62
+ return nil unless remote_dependencies.any?
63
+
64
+ metadata = extract_local_metadata(pkg, remote_dependencies)
65
+ needs_lookup = needs_api_lookup?(metadata[:local_version_date]) ||
66
+ needs_api_lookup?(metadata[:latest_version_date])
40
67
 
41
- @gem_hash[pkg.name] = pkg
42
- pkg.update latest_version: latest_version.to_s,
43
- version_date: local_version_date.strftime('%Y-%m-%d'),
44
- latest_version_date: latest_version_date.strftime('%Y-%m-%d')
68
+ {
69
+ needs_api_lookup: needs_lookup,
70
+ metadata: metadata,
71
+ gem_data: needs_lookup ? build_gem_data(pkg, metadata) : nil
72
+ }
73
+ end
74
+
75
+ def extract_local_metadata(pkg, remote_dependencies)
76
+ metadata = initialize_metadata_defaults(pkg)
77
+
78
+ remote_dependencies.each do |remote_spec, _|
79
+ update_version_info(metadata, remote_spec)
45
80
  end
81
+
82
+ metadata
83
+ end
84
+
85
+ def initialize_metadata_defaults(pkg)
86
+ {
87
+ local_version_date: EPOCH_TIME,
88
+ latest_version_date: EPOCH_TIME,
89
+ local_version: Gem::Version.new(pkg.version),
90
+ latest_version: INITIAL_VERSION
91
+ }
92
+ end
93
+
94
+ def update_version_info(metadata, remote_spec)
95
+ metadata[:latest_version] = remote_spec.version if metadata[:latest_version] < remote_spec.version
96
+
97
+ metadata[:latest_version_date] = remote_spec.date if metadata[:latest_version_date] < remote_spec.date
98
+
99
+ return unless metadata[:local_version] == remote_spec.version
100
+
101
+ metadata[:local_version_date] = remote_spec.date
102
+ end
103
+
104
+ def build_gem_data(pkg, metadata)
105
+ {
106
+ pkg: pkg,
107
+ latest_version: metadata[:latest_version].to_s,
108
+ local_version_date: metadata[:local_version_date],
109
+ latest_version_date: metadata[:latest_version_date]
110
+ }
111
+ end
112
+
113
+ def update_package_with_local_dates(pkg, metadata)
114
+ store_package(pkg)
115
+ pkg.update(
116
+ latest_version: metadata[:latest_version].to_s,
117
+ version_date: format_time(metadata[:local_version_date]),
118
+ latest_version_date: format_time(metadata[:latest_version_date])
119
+ )
120
+ end
121
+
122
+ def store_package(pkg)
123
+ @gem_hash[pkg.name] = pkg
124
+ end
125
+
126
+ def format_time(time)
127
+ time.strftime(DEFAULT_DATE_FORMAT)
128
+ end
129
+
130
+ def process_api_lookups(gem_data_array)
131
+ gem_data_array.each { |gem_data| process_single_api_lookup(gem_data) }
132
+ end
133
+
134
+ def process_single_api_lookup(gem_data) # rubocop:disable Metrics/MethodLength
135
+ pkg = gem_data[:pkg]
136
+ version_dates = fetch_gem_version_dates(pkg.name)
137
+ final_dates = determine_final_dates(
138
+ version_dates,
139
+ pkg.version,
140
+ gem_data[:latest_version],
141
+ gem_data[:local_version_date],
142
+ gem_data[:latest_version_date]
143
+ )
144
+
145
+ store_package(pkg)
146
+ pkg.update(
147
+ latest_version: gem_data[:latest_version],
148
+ version_date: final_dates[:local],
149
+ latest_version_date: final_dates[:latest]
150
+ )
151
+ end
152
+
153
+ def determine_final_dates(version_dates, local_version, latest_version, local_date, latest_date)
154
+ return fallback_to_local_dates(local_date, latest_date) unless version_dates
155
+
156
+ {
157
+ local: format_date(resolve_date(version_dates[local_version], local_date)),
158
+ latest: format_date(resolve_date(version_dates[latest_version], latest_date))
159
+ }
46
160
  end
47
161
 
48
- def assign_groups # rubocop:disable Metrics/AbcSize
162
+ def resolve_date(api_date, fallback_date)
163
+ api_date || (needs_api_lookup?(fallback_date) ? nil : fallback_date)
164
+ end
165
+
166
+ def fallback_to_local_dates(local_date, latest_date)
167
+ {
168
+ local: local_date.strftime(DEFAULT_DATE_FORMAT),
169
+ latest: latest_date.strftime(DEFAULT_DATE_FORMAT)
170
+ }
171
+ end
172
+
173
+ def fetch_gem_version_dates(gem_name)
174
+ uri = build_api_uri(gem_name)
175
+ response = make_http_request(uri)
176
+
177
+ return nil unless success_response?(response)
178
+
179
+ parse_version_dates(response.body)
180
+ rescue StandardError => e
181
+ log_api_error(gem_name, e) if debug_mode?
182
+ nil
183
+ end
184
+
185
+ def build_api_uri(gem_name)
186
+ URI("#{RUBYGEMS_API_BASE}/#{gem_name}.json")
187
+ end
188
+
189
+ def make_http_request(uri)
190
+ http = create_http_client(uri)
191
+ http.request(Net::HTTP::Get.new(uri))
192
+ end
193
+
194
+ def success_response?(response)
195
+ response.code == '200'
196
+ end
197
+
198
+ def debug_mode?
199
+ ENV.fetch('DEBUG', nil)
200
+ end
201
+
202
+ def log_api_error(gem_name, error)
203
+ warn "Warning: Failed to fetch version dates for #{gem_name}: #{error.message}"
204
+ end
205
+
206
+ def create_http_client(uri)
207
+ Net::HTTP.new(uri.host, uri.port).tap do |http|
208
+ http.use_ssl = true
209
+ http.read_timeout = HTTP_READ_TIMEOUT
210
+ http.open_timeout = HTTP_OPEN_TIMEOUT
211
+ end
212
+ end
213
+
214
+ def parse_version_dates(response_body)
215
+ versions = JSON.parse(response_body)
216
+ versions.each_with_object({}) do |version_info, dates|
217
+ dates[version_info['number']] = version_info['created_at']
218
+ end
219
+ end
220
+
221
+ def format_date(date_string)
222
+ return 'N/A' if date_string.nil?
223
+
224
+ Time.parse(date_string).strftime(DEFAULT_DATE_FORMAT)
225
+ rescue StandardError
226
+ 'N/A'
227
+ end
228
+
229
+ def assign_groups
230
+ definition = build_bundler_definition
231
+ groups = definition.groups.uniq.sort
232
+ groups.each { |group| update_gem_groups(definition, group) }
233
+ end
234
+
235
+ def build_bundler_definition
49
236
  definition = Bundler::Definition.build Pathname("#{@dir}/Gemfile"), Pathname("#{@dir}/Gemfile.lock"), nil
50
237
  Bundler.ui.level = 'error'
51
238
  definition.resolve_remotely!
52
- groups = definition.groups.uniq.sort
53
- groups.each do |group|
54
- specs = definition.specs_for([group])
55
- specs.each do |spec|
56
- if @gem_hash.key? spec.name
57
- @gem_hash[spec.name].update(groups: (@gem_hash[spec.name].groups | [group]).map(&:to_s))
58
- end
59
- end
239
+ definition
240
+ end
241
+
242
+ def update_gem_groups(definition, group)
243
+ specs = definition.specs_for([group])
244
+ specs.each do |spec|
245
+ next unless @gem_hash.key?(spec.name)
246
+
247
+ current_groups = @gem_hash[spec.name].groups
248
+ updated_groups = (current_groups | [group]).map(&:to_s)
249
+ @gem_hash[spec.name].update(groups: updated_groups)
60
250
  end
61
251
  end
62
252
  end
@@ -1,5 +1,5 @@
1
1
  module Package
2
2
  module Audit
3
- VERSION = '0.6.1'
3
+ VERSION = '0.6.2'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: package-audit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vadim Kononov
@@ -111,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
111
  - !ruby/object:Gem::Version
112
112
  version: '0'
113
113
  requirements: []
114
- rubygems_version: 3.6.7
114
+ rubygems_version: 3.6.9
115
115
  specification_version: 4
116
116
  summary: A helper tool to find outdated, deprecated and vulnerable dependencies.
117
117
  test_files: []