gemstar 1.0.4 → 1.1.1

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.
@@ -2,31 +2,57 @@ require "time"
2
2
 
3
3
  module Gemstar
4
4
  class Project
5
+ REVISION_HISTORY_LIMIT = 100
6
+
5
7
  attr_reader :directory
6
8
  attr_reader :gemfile_path
7
9
  attr_reader :lockfile_path
10
+ attr_reader :importmap_path
11
+ attr_reader :package_json_path
12
+ attr_reader :package_lock_path
8
13
  attr_reader :name
9
14
 
10
15
  def self.from_cli_argument(input)
11
16
  expanded_input = File.expand_path(input)
12
- gemfile_path = if File.directory?(expanded_input)
13
- File.join(expanded_input, "Gemfile")
17
+ if File.directory?(expanded_input)
18
+ directory = expanded_input
14
19
  else
15
- expanded_input
20
+ basename = File.basename(expanded_input)
21
+ directory =
22
+ case basename
23
+ when "Gemfile", "package.json", "package-lock.json"
24
+ File.dirname(expanded_input)
25
+ when "importmap.rb"
26
+ File.dirname(File.dirname(expanded_input))
27
+ else
28
+ nil
29
+ end
16
30
  end
17
31
 
18
- raise ArgumentError, "No Gemfile found for #{input}" unless File.file?(gemfile_path)
19
- raise ArgumentError, "#{gemfile_path} is not a Gemfile" unless File.basename(gemfile_path) == "Gemfile"
32
+ raise ArgumentError, "No supported project files found for #{input}" unless directory
33
+ raise ArgumentError, "No supported project files found for #{input}" unless supported_project_directory?(directory)
34
+
35
+ new(directory: directory)
36
+ end
20
37
 
21
- new(gemfile_path)
38
+ def self.supported_project_directory?(directory)
39
+ File.file?(File.join(directory, "Gemfile")) ||
40
+ File.file?(File.join(directory, "config", "importmap.rb")) ||
41
+ File.file?(File.join(directory, "package.json")) ||
42
+ File.file?(File.join(directory, "package-lock.json"))
22
43
  end
23
44
 
24
- def initialize(gemfile_path)
25
- @gemfile_path = File.expand_path(gemfile_path)
26
- @directory = File.dirname(@gemfile_path)
45
+ def initialize(directory:)
46
+ @directory = File.expand_path(directory)
47
+ @gemfile_path = File.join(@directory, "Gemfile")
27
48
  @lockfile_path = File.join(@directory, "Gemfile.lock")
49
+ @importmap_path = File.join(@directory, "config", "importmap.rb")
50
+ @package_json_path = File.join(@directory, "package.json")
51
+ @package_lock_path = File.join(@directory, "package-lock.json")
28
52
  @name = File.basename(@directory)
29
53
  @lockfile_cache = {}
54
+ @importmap_cache = {}
55
+ @package_lock_cache = {}
30
56
  @gem_states_cache = {}
31
57
  @gem_added_on_cache = {}
32
58
  @history_cache = {}
@@ -50,11 +76,39 @@ module Gemstar
50
76
  @current_lockfile ||= Gemstar::LockFile.new(path: lockfile_path)
51
77
  end
52
78
 
53
- def revision_history(limit: 20)
79
+ def gemfile?
80
+ File.file?(gemfile_path)
81
+ end
82
+
83
+ def importmap?
84
+ File.file?(importmap_path)
85
+ end
86
+
87
+ def current_importmap
88
+ return nil unless importmap?
89
+
90
+ @current_importmap ||= Gemstar::ImportmapFile.new(path: importmap_path, vendor_reader: importmap_vendor_reader("worktree"))
91
+ end
92
+
93
+ def package_lock?
94
+ File.file?(package_lock_path)
95
+ end
96
+
97
+ def package_json?
98
+ File.file?(package_json_path)
99
+ end
100
+
101
+ def current_package_lock
102
+ return nil unless package_lock?
103
+
104
+ @current_package_lock ||= Gemstar::PackageLockFile.new(path: package_lock_path)
105
+ end
106
+
107
+ def revision_history(limit: REVISION_HISTORY_LIMIT)
54
108
  history_for_paths(tracked_git_paths, limit: limit)
55
109
  end
56
110
 
57
- def lockfile_revision_history(limit: 20)
111
+ def lockfile_revision_history(limit: REVISION_HISTORY_LIMIT)
58
112
  return [] unless lockfile?
59
113
 
60
114
  relative_path = git_repo.relative_path(lockfile_path)
@@ -63,7 +117,9 @@ module Gemstar
63
117
  history_for_paths([relative_path], limit: limit)
64
118
  end
65
119
 
66
- def gemfile_revision_history(limit: 20)
120
+ def gemfile_revision_history(limit: REVISION_HISTORY_LIMIT)
121
+ return [] unless gemfile?
122
+
67
123
  relative_path = git_repo.relative_path(gemfile_path)
68
124
  return [] if relative_path.nil?
69
125
 
@@ -71,12 +127,12 @@ module Gemstar
71
127
  end
72
128
 
73
129
  def default_from_revision_id
74
- default_changed_lockfile_revision_id ||
130
+ default_changed_revision_id ||
75
131
  gemfile_revision_history(limit: 1).first&.dig(:id) ||
76
132
  "worktree"
77
133
  end
78
134
 
79
- def revision_options(limit: 20)
135
+ def revision_options(limit: REVISION_HISTORY_LIMIT)
80
136
  [{ id: "worktree", label: "Worktree", description: "Current Gemfile.lock in the working tree" }] +
81
137
  revision_history(limit: limit).map do |revision|
82
138
  {
@@ -87,6 +143,26 @@ module Gemstar
87
143
  end
88
144
  end
89
145
 
146
+ def package_scopes
147
+ scopes = []
148
+ scopes << :gems if gemfile? || lockfile?
149
+ scopes << :js if importmap? || package_lock? || package_json?
150
+ scopes
151
+ end
152
+
153
+ def package_scope_options
154
+ package_scopes.map do |scope|
155
+ {
156
+ id: package_scope_id(scope),
157
+ label: package_scope_label(scope)
158
+ }
159
+ end
160
+ end
161
+
162
+ def package_collection_label
163
+ package_scopes == [:gems] ? "Gems" : "Packages"
164
+ end
165
+
90
166
  def lockfile_for_revision(revision_id)
91
167
  cache_key = revision_id || "worktree"
92
168
  return @lockfile_cache[cache_key] if @lockfile_cache.key?(cache_key)
@@ -102,6 +178,36 @@ module Gemstar
102
178
  @lockfile_cache[cache_key] = Gemstar::LockFile.new(content: content)
103
179
  end
104
180
 
181
+ def importmap_for_revision(revision_id)
182
+ cache_key = revision_id || "worktree"
183
+ return @importmap_cache[cache_key] if @importmap_cache.key?(cache_key)
184
+ return @importmap_cache[cache_key] = current_importmap if revision_id.nil? || revision_id == "worktree"
185
+ return nil unless importmap?
186
+
187
+ relative_importmap_path = git_repo.relative_path(importmap_path)
188
+ return nil if relative_importmap_path.nil?
189
+
190
+ content = git_repo.try_git_command(["show", "#{revision_id}:#{relative_importmap_path}"])
191
+ return nil if content.nil? || content.empty?
192
+
193
+ @importmap_cache[cache_key] = Gemstar::ImportmapFile.new(content: content, vendor_reader: importmap_vendor_reader(revision_id))
194
+ end
195
+
196
+ def package_lock_for_revision(revision_id)
197
+ cache_key = revision_id || "worktree"
198
+ return @package_lock_cache[cache_key] if @package_lock_cache.key?(cache_key)
199
+ return @package_lock_cache[cache_key] = current_package_lock if revision_id.nil? || revision_id == "worktree"
200
+ return nil unless package_lock?
201
+
202
+ relative_package_lock_path = git_repo.relative_path(package_lock_path)
203
+ return nil if relative_package_lock_path.nil?
204
+
205
+ content = git_repo.try_git_command(["show", "#{revision_id}:#{relative_package_lock_path}"])
206
+ return nil if content.nil? || content.empty?
207
+
208
+ @package_lock_cache[cache_key] = Gemstar::PackageLockFile.new(content: content)
209
+ end
210
+
105
211
  def gem_states(from_revision_id: default_from_revision_id, to_revision_id: "worktree")
106
212
  cache_key = [from_revision_id, to_revision_id]
107
213
  return @gem_states_cache[cache_key] if @gem_states_cache.key?(cache_key)
@@ -111,7 +217,7 @@ module Gemstar
111
217
  from_specs = from_lockfile&.specs || {}
112
218
  to_specs = to_lockfile&.specs || {}
113
219
 
114
- @gem_states_cache[cache_key] = (from_specs.keys | to_specs.keys).map do |gem_name|
220
+ gem_states = (from_specs.keys | to_specs.keys).map do |gem_name|
115
221
  old_version = from_specs[gem_name]
116
222
  new_version = to_specs[gem_name]
117
223
  effective_lockfile = new_version ? to_lockfile : from_lockfile
@@ -119,6 +225,8 @@ module Gemstar
119
225
 
120
226
  {
121
227
  name: gem_name,
228
+ package_scope: "gems",
229
+ package_type_label: "Gem",
122
230
  old_version: old_version,
123
231
  new_version: new_version,
124
232
  status: gem_status(old_version, new_version),
@@ -128,26 +236,99 @@ module Gemstar
128
236
  bundle_origins: bundle_origins,
129
237
  bundle_origin_labels: bundle_origin_labels(bundle_origins)
130
238
  }
131
- end.sort_by { |gem| gem[:name] }
239
+ end
240
+
241
+ from_importmap = importmap_for_revision(from_revision_id)
242
+ to_importmap = importmap_for_revision(to_revision_id)
243
+ from_js_specs = from_importmap&.specs || {}
244
+ to_js_specs = to_importmap&.specs || {}
245
+ js_states = (from_js_specs.keys | to_js_specs.keys).map do |package_name|
246
+ old_target = from_js_specs[package_name]
247
+ new_target = to_js_specs[package_name]
248
+ old_source = from_importmap&.source_for(package_name) || {}
249
+ new_source = to_importmap&.source_for(package_name) || {}
250
+ old_source = enrich_importmap_source(old_source, from_lockfile)
251
+ new_source = enrich_importmap_source(new_source, to_lockfile)
252
+ effective_source = new_target ? new_source : old_source
253
+ old_package_version = js_package_version(old_source)
254
+ new_package_version = js_package_version(new_source)
255
+ comparison_old = old_package_version || old_target
256
+ comparison_new = new_package_version || new_target
257
+
258
+ {
259
+ name: package_name,
260
+ package_scope: "js",
261
+ package_type_label: "JS",
262
+ package_source_file: :importmap,
263
+ old_version: old_package_version,
264
+ new_version: new_package_version,
265
+ raw_old_version: old_target,
266
+ raw_new_version: new_target,
267
+ status: gem_status(comparison_old, comparison_new),
268
+ version_label: js_version_label(old_target, new_target, old_source, new_source),
269
+ platform: nil,
270
+ source: effective_source,
271
+ bundle_origins: [],
272
+ bundle_origin_labels: []
273
+ }
274
+ end
275
+
276
+ from_package_lock = package_lock_for_revision(from_revision_id)
277
+ to_package_lock = package_lock_for_revision(to_revision_id)
278
+ from_npm_specs = from_package_lock&.specs || {}
279
+ to_npm_specs = to_package_lock&.specs || {}
280
+ npm_states = (from_npm_specs.keys | to_npm_specs.keys).map do |package_name|
281
+ old_version = from_npm_specs[package_name]
282
+ new_version = to_npm_specs[package_name]
283
+ effective_package_lock = new_version ? to_package_lock : from_package_lock
284
+
285
+ {
286
+ name: package_name,
287
+ package_scope: "js",
288
+ package_type_label: "JS",
289
+ package_source_file: :package_lock,
290
+ old_version: old_version,
291
+ new_version: new_version,
292
+ status: gem_status(old_version, new_version),
293
+ version_label: version_label(old_version, new_version),
294
+ platform: nil,
295
+ source: effective_package_lock&.source_for(package_name),
296
+ bundle_origins: [],
297
+ bundle_origin_labels: []
298
+ }
299
+ end
300
+
301
+ @gem_states_cache[cache_key] = (gem_states + js_states + npm_states).sort_by { |gem| [gem[:name], gem[:package_scope], gem[:package_source_file].to_s] }
132
302
  end
133
303
 
134
- def gem_added_on(gem_name, revision_id: "worktree")
135
- cache_key = [gem_name, revision_id]
304
+ def package_added_on(package_name, package_scope:, revision_id: "worktree", source_file: nil)
305
+ cache_key = [package_name, package_scope, source_file, revision_id]
136
306
  return @gem_added_on_cache[cache_key] if @gem_added_on_cache.key?(cache_key)
137
- return nil unless lockfile?
138
307
 
139
- target_lockfile = lockfile_for_revision(revision_id)
140
- return @gem_added_on_cache[cache_key] = nil unless target_lockfile&.specs&.key?(gem_name)
308
+ tracked_file, reader =
309
+ if source_file == :importmap
310
+ [importmap_path, method(:importmap_for_revision)]
311
+ elsif source_file == :package_lock
312
+ [package_lock_path, method(:package_lock_for_revision)]
313
+ elsif package_scope == "js"
314
+ [importmap_path, method(:importmap_for_revision)]
315
+ else
316
+ [lockfile_path, method(:lockfile_for_revision)]
317
+ end
318
+ return @gem_added_on_cache[cache_key] = nil unless File.file?(tracked_file)
141
319
 
142
- relative_path = git_repo.relative_path(lockfile_path)
320
+ target_snapshot = reader.call(revision_id)
321
+ return @gem_added_on_cache[cache_key] = nil unless target_snapshot&.specs&.key?(package_name)
322
+
323
+ relative_path = git_repo.relative_path(tracked_file)
143
324
  return @gem_added_on_cache[cache_key] = nil if relative_path.nil?
144
325
 
145
326
  first_seen_revision = history_for_paths([relative_path], limit: nil, reverse: true).find do |revision|
146
- lockfile = lockfile_for_revision(revision[:id])
147
- lockfile&.specs&.key?(gem_name)
327
+ snapshot = reader.call(revision[:id])
328
+ snapshot&.specs&.key?(package_name)
148
329
  end
149
330
 
150
- return @gem_added_on_cache[cache_key] = worktree_added_on_info if first_seen_revision.nil? && revision_id == "worktree"
331
+ return @gem_added_on_cache[cache_key] = worktree_added_on_info(tracked_file) if first_seen_revision.nil? && revision_id == "worktree"
151
332
  return @gem_added_on_cache[cache_key] = nil unless first_seen_revision
152
333
 
153
334
  @gem_added_on_cache[cache_key] = {
@@ -161,18 +342,22 @@ module Gemstar
161
342
 
162
343
  private
163
344
 
164
- def default_changed_lockfile_revision_id
165
- return nil unless lockfile?
166
-
345
+ def default_changed_revision_id
167
346
  current_specs = current_lockfile&.specs || {}
347
+ current_importmap_specs = current_importmap&.specs || {}
348
+ current_package_lock_specs = current_package_lock&.specs || {}
168
349
 
169
- lockfile_revision_history(limit: 20).find do |revision|
350
+ revision_history(limit: REVISION_HISTORY_LIMIT).find do |revision|
170
351
  revision_lockfile = lockfile_for_revision(revision[:id])
171
- revision_lockfile && revision_lockfile.specs != current_specs
352
+ revision_importmap = importmap_for_revision(revision[:id])
353
+ revision_package_lock = package_lock_for_revision(revision[:id])
354
+ (revision_lockfile && revision_lockfile.specs != current_specs) ||
355
+ (revision_importmap && revision_importmap.specs != current_importmap_specs) ||
356
+ (revision_package_lock && revision_package_lock.specs != current_package_lock_specs)
172
357
  end&.dig(:id)
173
358
  end
174
359
 
175
- def history_for_paths(paths, limit: 20, reverse: false)
360
+ def history_for_paths(paths, limit: REVISION_HISTORY_LIMIT, reverse: false)
176
361
  return [] if git_root.nil? || git_root.empty?
177
362
  return [] if paths.empty?
178
363
 
@@ -199,13 +384,23 @@ module Gemstar
199
384
  end
200
385
 
201
386
  def tracked_git_paths
202
- [gemfile_path, lockfile_path].filter_map do |path|
387
+ [gemfile_path, lockfile_path, importmap_path, package_json_path, package_lock_path, *importmap_vendor_paths].filter_map do |path|
203
388
  next unless File.file?(path)
204
389
 
205
390
  git_repo.relative_path(path)
206
391
  end.uniq
207
392
  end
208
393
 
394
+ def importmap_vendor_paths
395
+ return [] unless current_importmap
396
+
397
+ current_importmap.specs.values.filter_map do |target|
398
+ next unless target.to_s.end_with?(".js", ".mjs")
399
+
400
+ File.join(directory, "vendor", "javascript", target.to_s)
401
+ end
402
+ end
403
+
209
404
  def gem_status(old_version, new_version)
210
405
  return :added if old_version.nil? && !new_version.nil?
211
406
  return :removed if !old_version.nil? && new_version.nil?
@@ -226,6 +421,77 @@ module Gemstar
226
421
  "#{old_version} → #{new_version}"
227
422
  end
228
423
 
424
+ def importmap_version_label(old_target, new_target)
425
+ old_label = importmap_target_label(old_target)
426
+ new_label = importmap_target_label(new_target)
427
+ return "new → #{new_label}" if old_target.nil? && !new_target.nil?
428
+ return "#{old_label} → removed" if !old_target.nil? && new_target.nil?
429
+ return new_label.to_s if old_target == new_target
430
+
431
+ "#{old_label} → #{new_label}"
432
+ end
433
+
434
+ def js_version_label(old_target, new_target, old_source, new_source)
435
+ old_label = js_version_label_part(old_target, old_source)
436
+ new_label = js_version_label_part(new_target, new_source)
437
+ return "new → #{new_label}" if old_target.nil? && !new_target.nil?
438
+ return "#{old_label} → removed" if !old_target.nil? && new_target.nil?
439
+ return new_label.to_s if old_label == new_label && old_target == new_target
440
+ return "#{new_label} (source changed)" if old_label == new_label
441
+
442
+ "#{old_label} → #{new_label}"
443
+ end
444
+
445
+ def js_version_label_part(target, source)
446
+ version = js_package_version(source)
447
+ return version if version && !version.empty?
448
+
449
+ requirement = source&.dig(:package_requirement)
450
+ return "range #{requirement}" if requirement && !requirement.empty?
451
+
452
+ importmap_target_label(target)
453
+ end
454
+
455
+ def js_package_version(source)
456
+ source && source[:package_version].to_s.empty? ? nil : source&.dig(:package_version)
457
+ end
458
+
459
+ def enrich_importmap_source(source, lockfile)
460
+ source = (source || {}).dup
461
+ provider_gem = source[:provider_gem]
462
+ return source if provider_gem.to_s.empty?
463
+
464
+ provider_version = lockfile&.specs&.[](provider_gem)
465
+ source[:provider_version] = provider_version unless provider_version.to_s.empty?
466
+ source[:package_version] ||= provider_version unless provider_version.to_s.empty?
467
+ source
468
+ end
469
+
470
+ def importmap_target_label(target)
471
+ return "" if target.nil?
472
+
473
+ version = target.to_s[/@(\d+(?:\.\d+)*[\w.-]*)/, 1]
474
+ return version if version
475
+
476
+ target.to_s.sub(%r{\Ahttps?://}, "").slice(0, 36)
477
+ end
478
+
479
+ def importmap_vendor_reader(revision_id)
480
+ lambda do |target|
481
+ next nil unless target.to_s.end_with?(".js", ".mjs")
482
+
483
+ vendor_path = File.join(directory, "vendor", "javascript", target.to_s)
484
+ if revision_id.nil? || revision_id == "worktree"
485
+ File.file?(vendor_path) ? File.read(vendor_path) : nil
486
+ else
487
+ relative_path = git_repo.relative_path(vendor_path)
488
+ next nil if relative_path.nil?
489
+
490
+ git_repo.try_git_command(["show", "#{revision_id}:#{relative_path}"])
491
+ end
492
+ end
493
+ end
494
+
229
495
  def compare_versions(left, right)
230
496
  Gem::Version.new(left.to_s.gsub(/-[\w\-]+$/, "")) <=> Gem::Version.new(right.to_s.gsub(/-[\w\-]+$/, ""))
231
497
  rescue ArgumentError
@@ -241,12 +507,12 @@ module Gemstar
241
507
  end.compact.uniq
242
508
  end
243
509
 
244
- def worktree_added_on_info
245
- return nil unless File.file?(lockfile_path)
510
+ def worktree_added_on_info(path)
511
+ return nil unless File.file?(path)
246
512
 
247
513
  {
248
514
  project_name: name,
249
- date: File.mtime(lockfile_path).strftime("%Y-%m-%d"),
515
+ date: File.mtime(path).strftime("%Y-%m-%d"),
250
516
  revision: "Worktree",
251
517
  revision_url: nil,
252
518
  worktree: true
@@ -259,5 +525,23 @@ module Gemstar
259
525
 
260
526
  "#{repo_url}/commit/#{full_sha}"
261
527
  end
528
+
529
+ def package_scope_id(scope)
530
+ case scope
531
+ when :gems then "gems"
532
+ when :js then "js"
533
+ when :python then "python"
534
+ else scope.to_s
535
+ end
536
+ end
537
+
538
+ def package_scope_label(scope)
539
+ case scope
540
+ when :gems then "Gems"
541
+ when :js then "JS"
542
+ when :python then "Python"
543
+ else scope.to_s.capitalize
544
+ end
545
+ end
262
546
  end
263
547
  end
@@ -1,23 +1,40 @@
1
1
  require "open-uri"
2
2
  require "uri"
3
3
  require "json"
4
+ require "date"
5
+ require "time"
6
+ require_relative "remote_repository"
4
7
 
5
8
  module Gemstar
6
9
  class RubyGemsMetadata
10
+ RUBY_GEMS_METADATA_PATH = File.expand_path("data/ruby_gems_metadata.json", __dir__)
11
+
7
12
  def initialize(gem_name)
8
13
  @gem_name = gem_name
9
14
  end
10
15
 
11
16
  attr_reader :gem_name
12
17
 
18
+ def self.package_metadata
19
+ @package_metadata ||= begin
20
+ JSON.parse(File.read(RUBY_GEMS_METADATA_PATH))
21
+ rescue Errno::ENOENT, JSON::ParserError
22
+ {}
23
+ end
24
+ end
25
+
26
+ def cache_key
27
+ "rubygems-#{gem_name}"
28
+ end
29
+
13
30
  def meta(cache_only: false, force_refresh: false)
14
31
  return @meta if !cache_only && defined?(@meta)
15
32
 
16
33
  json = if cache_only
17
- Cache.peek("rubygems-#{gem_name}")
34
+ Cache.peek(cache_key)
18
35
  else
19
36
  url = "https://rubygems.org/api/v1/gems/#{URI.encode_www_form_component(gem_name)}.json"
20
- Cache.fetch("rubygems-#{gem_name}", force: force_refresh) do
37
+ Cache.fetch(cache_key, force: force_refresh) do
21
38
  URI.open(url).read
22
39
  end
23
40
  end
@@ -71,5 +88,99 @@ module Gemstar
71
88
  repo
72
89
  end
73
90
 
91
+ def changelog_sections(versions: nil, cache_only: false, force_refresh: false)
92
+ Gemstar::ChangeLog.new(self).sections(cache_only: cache_only, force_refresh: force_refresh)
93
+ end
94
+
95
+ def registry_release_dates(cache_only: false, force_refresh: false)
96
+ cache_key = "rubygems-versions-#{gem_name}"
97
+ json = if cache_only
98
+ Cache.peek(cache_key)
99
+ else
100
+ url = "https://rubygems.org/api/v1/versions/#{URI.encode_www_form_component(gem_name)}.json"
101
+ Cache.fetch(cache_key, force: force_refresh) do
102
+ URI.open(url, read_timeout: 8).read
103
+ end
104
+ end
105
+
106
+ Array(JSON.parse(json)).each_with_object({}) do |version, dates|
107
+ number = version["number"].to_s
108
+ created_at = version["created_at"].to_s
109
+ next if number.empty? || created_at.empty?
110
+
111
+ dates[number] = format_registry_release_date(created_at)
112
+ end.compact
113
+ rescue JSON::ParserError
114
+ {}
115
+ end
116
+
117
+ def warm_cache(versions: nil)
118
+ meta
119
+ repo_uri
120
+ changelog_sections(versions: versions)
121
+ end
122
+
123
+ def discover_github_tag_sections?
124
+ false
125
+ end
126
+
127
+ def github_tag_candidates(version)
128
+ raw = version.to_s
129
+ [raw, (raw.start_with?("v") ? raw : "v#{raw}")].uniq
130
+ end
131
+
132
+ def github_tag_matches?(tag_name)
133
+ true
134
+ end
135
+
136
+ def changelog_source(repo_uri:, cache_only: false, force_refresh: false)
137
+ override = package_metadata.dig("changelog")
138
+ if override
139
+ override_paths = Array(override["paths"]).compact
140
+ override_branches = Array(override["branches"]).compact
141
+ override_branches = [""] if override_branches.empty? && override["raw_base"]
142
+ return {
143
+ base: expand_metadata_template(override["raw_base"] || github_raw_base(repo_uri)),
144
+ paths: override_paths.empty? ? Gemstar::ChangeLog::DEFAULT_CHANGELOG_PATHS : override_paths,
145
+ branches: override_branches.empty? ? RemoteRepository.new(github_raw_base(repo_uri)).find_main_branch(cache_only: cache_only, force_refresh: force_refresh) : override_branches
146
+ }
147
+ end
148
+
149
+ base = github_raw_base(repo_uri)
150
+ {
151
+ base: base,
152
+ paths: Gemstar::ChangeLog::DEFAULT_CHANGELOG_PATHS,
153
+ branches: RemoteRepository.new(base).find_main_branch(cache_only: cache_only, force_refresh: force_refresh)
154
+ }
155
+ end
156
+
157
+ def package_metadata
158
+ self.class.package_metadata.find do |pattern, _metadata|
159
+ File.fnmatch?(pattern, gem_name)
160
+ end&.last || {}
161
+ end
162
+
163
+ private
164
+
165
+ def github_raw_base(repo_uri)
166
+ repo_uri.sub("https://github.com", "https://raw.githubusercontent.com").chomp("/")
167
+ end
168
+
169
+ def expand_metadata_template(value)
170
+ value.to_s.gsub("{gem_name}", gem_name)
171
+ end
172
+
173
+ def format_registry_release_date(datetime)
174
+ if datetime.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
175
+ date = Date.strptime(datetime.to_s, "%Y-%m-%d")
176
+ return date.strftime("%b #{date.day}, %Y")
177
+ end
178
+
179
+ time = Time.parse(datetime.to_s).utc
180
+ time.strftime("%b #{time.day}, %Y")
181
+ rescue ArgumentError
182
+ nil
183
+ end
184
+
74
185
  end
75
186
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Gemstar # :nodoc:
4
- VERSION = "1.0.4"
4
+ VERSION = "1.1.1"
5
5
  def self.debug?
6
6
  return @debug if defined?(@debug)
7
7
  @debug = ENV["GEMSTAR_DEBUG"] == "true"