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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +32 -1
- data/README.md +28 -3
- data/bin/gemstar +5 -1
- data/lib/gemstar/cache_warmer.rb +93 -35
- data/lib/gemstar/change_log.rb +319 -36
- data/lib/gemstar/cli.rb +5 -1
- data/lib/gemstar/commands/diff.rb +197 -31
- data/lib/gemstar/commands/server.rb +93 -10
- data/lib/gemstar/data/importmap_package_metadata.json +22 -0
- data/lib/gemstar/data/ruby_gems_metadata.json +9 -0
- data/lib/gemstar/git_hub.rb +3 -2
- data/lib/gemstar/git_repo.rb +41 -3
- data/lib/gemstar/importmap_file.rb +193 -0
- data/lib/gemstar/npm_metadata.rb +159 -0
- data/lib/gemstar/outputs/html.rb +53 -4
- data/lib/gemstar/outputs/markdown.rb +29 -3
- data/lib/gemstar/package_lock_file.rb +101 -0
- data/lib/gemstar/project.rb +319 -35
- data/lib/gemstar/ruby_gems_metadata.rb +113 -2
- data/lib/gemstar/version.rb +1 -1
- data/lib/gemstar/web/app.rb +422 -68
- data/lib/gemstar/web/templates/app.css +48 -0
- data/lib/gemstar/web/templates/app.js.erb +51 -16
- data/lib/gemstar/web/templates/page.html.erb +2 -1
- data/lib/gemstar.rb +3 -0
- metadata +6 -1
data/lib/gemstar/change_log.rb
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require "cgi"
|
|
3
|
+
require "date"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
3
6
|
|
|
4
7
|
module Gemstar
|
|
5
8
|
class ChangeLog
|
|
6
9
|
@@candidates_found = Hash.new(0)
|
|
10
|
+
DEFAULT_CHANGELOG_PATHS = %w[
|
|
11
|
+
CHANGELOG.md releases.md CHANGES.md
|
|
12
|
+
Changelog.md changelog.md ChangeLog.md
|
|
13
|
+
Changes.md changes.md
|
|
14
|
+
HISTORY.md History.md history.md
|
|
15
|
+
History CHANGELOG.rdoc
|
|
16
|
+
].freeze
|
|
7
17
|
|
|
8
18
|
def initialize(metadata)
|
|
9
19
|
@metadata = metadata
|
|
@@ -20,28 +30,122 @@ module Gemstar
|
|
|
20
30
|
end
|
|
21
31
|
|
|
22
32
|
def sections(cache_only: false, force_refresh: false)
|
|
23
|
-
return @sections if !cache_only && defined?(@sections)
|
|
33
|
+
return @sections if !cache_only && defined?(@sections) && !force_refresh
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
metadata_key = @metadata.respond_to?(:cache_key) ? @metadata.cache_key : @metadata.gem_name
|
|
36
|
+
cache_key = "sections-v5-#{metadata_key}"
|
|
37
|
+
serialized = if cache_only
|
|
38
|
+
Cache.peek(cache_key)
|
|
39
|
+
else
|
|
40
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
41
|
+
JSON.generate(compute_sections(force_refresh: force_refresh))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
result = if serialized
|
|
46
|
+
decode_sections(serialized)
|
|
47
|
+
elsif cache_only
|
|
48
|
+
nil
|
|
49
|
+
else
|
|
50
|
+
compute_sections(force_refresh: force_refresh)
|
|
51
|
+
end
|
|
30
52
|
|
|
31
|
-
|
|
53
|
+
@sections = result unless cache_only
|
|
54
|
+
result
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def sections_for_versions(versions, cache_only: false, force_refresh: false)
|
|
58
|
+
requested_versions = normalize_requested_versions(versions)
|
|
59
|
+
return {} if requested_versions.empty?
|
|
32
60
|
|
|
33
|
-
|
|
61
|
+
cached_sections = sections(cache_only: true) || {}
|
|
62
|
+
result = cached_sections.select { |version, _| requested_versions.include?(normalize_version_key(version)) }
|
|
63
|
+
return result if cache_only
|
|
64
|
+
|
|
65
|
+
changelog_sections = parse_changelog_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
66
|
+
changelog_sections.each do |version, lines|
|
|
67
|
+
result[version] ||= lines if requested_versions.include?(normalize_version_key(version))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
repo_uri = @metadata&.repo_uri(cache_only: false, force_refresh: force_refresh)
|
|
71
|
+
if repo_uri&.include?("github.com")
|
|
72
|
+
missing_versions = requested_versions - result.keys.map { |version| normalize_version_key(version) }
|
|
73
|
+
missing_versions.each do |version|
|
|
74
|
+
specific_release = parse_specific_github_release_pages(
|
|
75
|
+
repo_uri,
|
|
76
|
+
version,
|
|
77
|
+
cache_only: false,
|
|
78
|
+
force_refresh: force_refresh
|
|
79
|
+
)
|
|
80
|
+
result.merge!(specific_release) if specific_release
|
|
81
|
+
end
|
|
34
82
|
end
|
|
35
83
|
|
|
36
|
-
@sections = result unless cache_only
|
|
37
84
|
result
|
|
38
85
|
end
|
|
39
86
|
|
|
87
|
+
def release_dates(versions: nil, cache_only: false, force_refresh: false)
|
|
88
|
+
requested_versions = normalize_requested_versions(versions)
|
|
89
|
+
metadata_key = @metadata.respond_to?(:cache_key) ? @metadata.cache_key : @metadata.gem_name
|
|
90
|
+
cache_key = "release-dates-v2-#{metadata_key}"
|
|
91
|
+
serialized = if cache_only
|
|
92
|
+
Cache.peek(cache_key)
|
|
93
|
+
else
|
|
94
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
95
|
+
JSON.generate(compute_release_dates(force_refresh: force_refresh))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
dates = if serialized
|
|
100
|
+
decode_sections(serialized) || {}
|
|
101
|
+
elsif cache_only
|
|
102
|
+
{}
|
|
103
|
+
else
|
|
104
|
+
compute_release_dates(force_refresh: force_refresh)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
return dates if requested_versions.empty?
|
|
108
|
+
|
|
109
|
+
dates.select { |version, _date| requested_versions.include?(normalize_version_key(version)) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def compute_sections(force_refresh: false)
|
|
113
|
+
changelog_sections = parse_changelog_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
114
|
+
github_sections = parse_github_release_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
115
|
+
|
|
116
|
+
sections = merge_section_sources(changelog_sections, github_sections)
|
|
117
|
+
|
|
118
|
+
pp @@candidates_found if Gemstar.debug?
|
|
119
|
+
|
|
120
|
+
sections
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def compute_release_dates(force_refresh: false)
|
|
124
|
+
registry_dates = if @metadata.respond_to?(:registry_release_dates)
|
|
125
|
+
@metadata.registry_release_dates(cache_only: false, force_refresh: force_refresh)
|
|
126
|
+
else
|
|
127
|
+
{}
|
|
128
|
+
end
|
|
129
|
+
return registry_dates unless registry_dates.nil? || registry_dates.empty?
|
|
130
|
+
|
|
131
|
+
changelog_dates = parse_changelog_release_dates(cache_only: false, force_refresh: force_refresh)
|
|
132
|
+
return changelog_dates unless changelog_dates.nil? || changelog_dates.empty?
|
|
133
|
+
|
|
134
|
+
repo_uri = @metadata&.repo_uri(cache_only: false, force_refresh: force_refresh)
|
|
135
|
+
return {} unless repo_uri&.include?("github.com")
|
|
136
|
+
|
|
137
|
+
parse_github_tag_dates(repo_uri, cache_only: false, force_refresh: force_refresh)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def decode_sections(serialized)
|
|
141
|
+
JSON.parse(serialized)
|
|
142
|
+
rescue JSON::ParserError
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
40
146
|
def merge_section_sources(changelog_sections, github_sections)
|
|
41
147
|
return github_sections if changelog_sections.nil? || changelog_sections.empty?
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
github_sections.merge(changelog_sections)
|
|
148
|
+
changelog_sections
|
|
45
149
|
end
|
|
46
150
|
|
|
47
151
|
def extract_relevant_sections(old_version, new_version)
|
|
@@ -64,6 +168,17 @@ module Gemstar
|
|
|
64
168
|
|
|
65
169
|
private
|
|
66
170
|
|
|
171
|
+
def normalize_requested_versions(versions)
|
|
172
|
+
Array(versions).filter_map { |version| normalize_version_key(version) }.uniq
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def normalize_version_key(version)
|
|
176
|
+
value = version.to_s.strip
|
|
177
|
+
return nil if value.empty?
|
|
178
|
+
|
|
179
|
+
value.sub(/\Av/i, "")
|
|
180
|
+
end
|
|
181
|
+
|
|
67
182
|
# Extract a version token from a heading line, preferring explicit version forms
|
|
68
183
|
# and avoiding returning a date string when both are present.
|
|
69
184
|
def extract_version_from_heading(line)
|
|
@@ -82,40 +197,30 @@ module Gemstar
|
|
|
82
197
|
nil
|
|
83
198
|
end
|
|
84
199
|
|
|
200
|
+
def extract_release_date_from_heading(line)
|
|
201
|
+
return nil unless line
|
|
202
|
+
|
|
203
|
+
raw_date = line.to_s[/\b(\d{4}-\d{2}-\d{2})\b/, 1]
|
|
204
|
+
format_release_date(raw_date)
|
|
205
|
+
end
|
|
206
|
+
|
|
85
207
|
def changelog_uri_candidates(cache_only: false, force_refresh: false)
|
|
86
208
|
candidates = []
|
|
87
209
|
|
|
88
210
|
repo_uri = @metadata.repo_uri(cache_only: cache_only, force_refresh: force_refresh)
|
|
89
211
|
return [] if repo_uri.nil? || repo_uri.empty?
|
|
90
212
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
aws_style = true
|
|
94
|
-
else
|
|
95
|
-
base = repo_uri.sub("https://github.com", "https://raw.githubusercontent.com")
|
|
96
|
-
aws_style = false
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
base = base.chomp("/")
|
|
100
|
-
|
|
101
|
-
paths = aws_style ? ["CHANGELOG.md"] : %w[
|
|
102
|
-
CHANGELOG.md releases.md CHANGES.md
|
|
103
|
-
Changelog.md changelog.md ChangeLog.md
|
|
104
|
-
Changes.md changes.md
|
|
105
|
-
HISTORY.md History.md history.md
|
|
106
|
-
History CHANGELOG.rdoc
|
|
107
|
-
]
|
|
108
|
-
|
|
109
|
-
remote_repository = RemoteRepository.new(base)
|
|
213
|
+
meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
|
|
214
|
+
candidates += changelog_uri_markdown_candidates(meta["changelog_uri"]) if meta
|
|
110
215
|
|
|
111
|
-
|
|
216
|
+
changelog_source = metadata_changelog_source(repo_uri, cache_only: cache_only, force_refresh: force_refresh)
|
|
217
|
+
return [] unless changelog_source
|
|
112
218
|
|
|
113
|
-
candidates += paths.product(branches).map do |file, branch|
|
|
114
|
-
|
|
219
|
+
candidates += changelog_source[:paths].product(changelog_source[:branches]).map do |file, branch|
|
|
220
|
+
[changelog_source[:base], branch, file].reject { |segment| segment.to_s.empty? }.join("/")
|
|
115
221
|
end
|
|
116
222
|
|
|
117
223
|
# Add the gem's changelog_uri last as it's usually not the most parsable:
|
|
118
|
-
meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
|
|
119
224
|
candidates += [Gemstar::GitHub::github_blob_to_raw(meta["changelog_uri"])] if meta
|
|
120
225
|
|
|
121
226
|
candidates.flatten!
|
|
@@ -125,6 +230,43 @@ module Gemstar
|
|
|
125
230
|
candidates
|
|
126
231
|
end
|
|
127
232
|
|
|
233
|
+
def changelog_uri_markdown_candidates(changelog_uri)
|
|
234
|
+
raw_uri = Gemstar::GitHub::github_blob_to_raw(changelog_uri)
|
|
235
|
+
return [] if raw_uri.to_s.empty?
|
|
236
|
+
|
|
237
|
+
candidates = []
|
|
238
|
+
candidates << raw_uri if raw_uri.match?(/\.(?:md|markdown|rdoc|txt)\z/i)
|
|
239
|
+
|
|
240
|
+
begin
|
|
241
|
+
uri = URI(raw_uri)
|
|
242
|
+
path = uri.path.to_s
|
|
243
|
+
if path.end_with?("/")
|
|
244
|
+
uri.path = "#{path.chomp("/")}.md"
|
|
245
|
+
candidates << uri.to_s
|
|
246
|
+
elsif File.extname(path).empty?
|
|
247
|
+
uri.path = "#{path}.md"
|
|
248
|
+
candidates << uri.to_s
|
|
249
|
+
end
|
|
250
|
+
rescue URI::InvalidURIError
|
|
251
|
+
nil
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
candidates
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def metadata_changelog_source(repo_uri, cache_only:, force_refresh:)
|
|
258
|
+
if @metadata.respond_to?(:changelog_source)
|
|
259
|
+
return @metadata.changelog_source(repo_uri: repo_uri, cache_only: cache_only, force_refresh: force_refresh)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
base = repo_uri.sub("https://github.com", "https://raw.githubusercontent.com").chomp("/")
|
|
263
|
+
{
|
|
264
|
+
base: base,
|
|
265
|
+
paths: DEFAULT_CHANGELOG_PATHS,
|
|
266
|
+
branches: RemoteRepository.new(base).find_main_branch(cache_only: cache_only, force_refresh: force_refresh)
|
|
267
|
+
}
|
|
268
|
+
end
|
|
269
|
+
|
|
128
270
|
def fetch_changelog_content(cache_only: false, force_refresh: false)
|
|
129
271
|
content = nil
|
|
130
272
|
|
|
@@ -220,6 +362,21 @@ module Gemstar
|
|
|
220
362
|
sections
|
|
221
363
|
end
|
|
222
364
|
|
|
365
|
+
def parse_changelog_release_dates(cache_only: false, force_refresh: false)
|
|
366
|
+
c = content(cache_only: cache_only, force_refresh: force_refresh)
|
|
367
|
+
return {} if c.nil? || c.strip.empty?
|
|
368
|
+
return {} if c.include?("<html") || c.include?("<!DOCTYPE html")
|
|
369
|
+
|
|
370
|
+
c.lines.each_with_object({}) do |line, dates|
|
|
371
|
+
line = line.gsub(/^=+/) { |m| "#" * m.length }
|
|
372
|
+
next unless VERSION_PATTERNS.any? { |re| line.match?(re) }
|
|
373
|
+
|
|
374
|
+
version = extract_version_from_heading(line)
|
|
375
|
+
date = extract_release_date_from_heading(line)
|
|
376
|
+
dates[version] ||= date if version && date
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
223
380
|
def parse_github_release_sections(cache_only: false, force_refresh: false)
|
|
224
381
|
begin
|
|
225
382
|
require "nokogiri"
|
|
@@ -269,6 +426,7 @@ module Gemstar
|
|
|
269
426
|
heading = sec.at_css('h2.sr-only')
|
|
270
427
|
next unless heading
|
|
271
428
|
text = heading.text.to_s.strip
|
|
429
|
+
next unless github_tag_matches_metadata?(text)
|
|
272
430
|
next unless text[/v?(\d[\w.\-]+)/i]
|
|
273
431
|
version = $1
|
|
274
432
|
|
|
@@ -292,6 +450,7 @@ module Gemstar
|
|
|
292
450
|
link = container.at_xpath('ancestor::*[self::section or self::div][.//a[contains(@href, "/releases/tag/")]][1]//a[contains(@href, "/releases/tag/")]')
|
|
293
451
|
text = link&.text.to_s
|
|
294
452
|
text = File.basename(URI(link["href"]).path) if (text.nil? || text.empty?) && link
|
|
453
|
+
next unless github_tag_matches_metadata?(text)
|
|
295
454
|
next unless text && text[/v?(\d[\w.\-]+)/i]
|
|
296
455
|
version = $1
|
|
297
456
|
|
|
@@ -316,6 +475,13 @@ module Gemstar
|
|
|
316
475
|
force_refresh: force_refresh
|
|
317
476
|
)
|
|
318
477
|
sections = tag_sections.merge(current_release_sections)
|
|
478
|
+
elsif discover_github_tag_sections?
|
|
479
|
+
tag_sections = parse_github_tag_sections(
|
|
480
|
+
repo_uri,
|
|
481
|
+
cache_only: cache_only,
|
|
482
|
+
force_refresh: force_refresh
|
|
483
|
+
)
|
|
484
|
+
sections = tag_sections.merge(sections)
|
|
319
485
|
end
|
|
320
486
|
|
|
321
487
|
if Gemstar.debug?
|
|
@@ -413,6 +579,40 @@ module Gemstar
|
|
|
413
579
|
sections
|
|
414
580
|
end
|
|
415
581
|
|
|
582
|
+
def parse_github_tag_dates(repo_uri, cache_only:, force_refresh:)
|
|
583
|
+
return {} unless repo_uri&.include?("github.com")
|
|
584
|
+
|
|
585
|
+
url = github_tags_url(repo_uri)
|
|
586
|
+
return {} unless url
|
|
587
|
+
|
|
588
|
+
dates = {}
|
|
589
|
+
seen_urls = {}
|
|
590
|
+
|
|
591
|
+
while url && !seen_urls[url]
|
|
592
|
+
seen_urls[url] = true
|
|
593
|
+
html = if cache_only
|
|
594
|
+
Cache.peek("tags-#{url}")
|
|
595
|
+
else
|
|
596
|
+
Cache.fetch("tags-#{url}", force: force_refresh) do
|
|
597
|
+
begin
|
|
598
|
+
URI.open(url, read_timeout: 8)&.read
|
|
599
|
+
rescue => e
|
|
600
|
+
puts "#{url}: #{e}" if Gemstar.debug?
|
|
601
|
+
nil
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
break if html.nil? || html.strip.empty?
|
|
607
|
+
|
|
608
|
+
page_dates, next_url = parse_single_github_tag_dates_page(html, repo_uri)
|
|
609
|
+
dates.merge!(page_dates) { |_version, existing, _new| existing }
|
|
610
|
+
url = next_url
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
dates
|
|
614
|
+
end
|
|
615
|
+
|
|
416
616
|
def parse_single_github_tags_page(html, repo_uri)
|
|
417
617
|
require "nokogiri"
|
|
418
618
|
|
|
@@ -436,6 +636,7 @@ module Gemstar
|
|
|
436
636
|
href.delete_prefix(tree_prefix)
|
|
437
637
|
end
|
|
438
638
|
next if tag_name.to_s.empty?
|
|
639
|
+
next unless github_tag_matches_metadata?(tag_name)
|
|
439
640
|
|
|
440
641
|
version = normalize_github_tag_version(tag_name)
|
|
441
642
|
next if version.to_s.empty?
|
|
@@ -462,6 +663,75 @@ module Gemstar
|
|
|
462
663
|
[{}, nil]
|
|
463
664
|
end
|
|
464
665
|
|
|
666
|
+
def parse_single_github_tag_dates_page(html, repo_uri)
|
|
667
|
+
require "nokogiri"
|
|
668
|
+
|
|
669
|
+
doc = begin
|
|
670
|
+
Nokogiri::HTML5(html)
|
|
671
|
+
rescue => _
|
|
672
|
+
Nokogiri::HTML(html)
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
dates = {}
|
|
676
|
+
repo_path = URI(repo_uri).path
|
|
677
|
+
release_prefix = "#{repo_path}/releases/tag/"
|
|
678
|
+
tree_prefix = "#{repo_path}/tree/"
|
|
679
|
+
|
|
680
|
+
doc.css("a[href]").each do |link|
|
|
681
|
+
href = link["href"].to_s
|
|
682
|
+
tag_name =
|
|
683
|
+
if href.start_with?(release_prefix)
|
|
684
|
+
href.delete_prefix(release_prefix)
|
|
685
|
+
elsif href.start_with?(tree_prefix)
|
|
686
|
+
href.delete_prefix(tree_prefix)
|
|
687
|
+
end
|
|
688
|
+
next if tag_name.to_s.empty?
|
|
689
|
+
next unless github_tag_matches_metadata?(tag_name)
|
|
690
|
+
|
|
691
|
+
version = normalize_github_tag_version(tag_name)
|
|
692
|
+
next if version.to_s.empty?
|
|
693
|
+
|
|
694
|
+
datetime = github_tag_datetime_for(link)
|
|
695
|
+
next if datetime.to_s.empty?
|
|
696
|
+
|
|
697
|
+
dates[version] ||= format_release_date(datetime)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
next_href =
|
|
701
|
+
doc.at_css('a[rel="next"], a.next_page')&.[]("href") ||
|
|
702
|
+
doc.css("a[href]").find do |link|
|
|
703
|
+
href = link["href"].to_s
|
|
704
|
+
text = link.text.to_s.gsub(/\s+/, " ").strip
|
|
705
|
+
href.include?("/tags?after=") && text == "Next"
|
|
706
|
+
end&.[]("href")
|
|
707
|
+
next_url = if next_href && !next_href.empty?
|
|
708
|
+
URI.join(repo_uri, next_href).to_s
|
|
709
|
+
end
|
|
710
|
+
|
|
711
|
+
[dates.compact, next_url]
|
|
712
|
+
rescue LoadError
|
|
713
|
+
[{}, nil]
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
def github_tag_datetime_for(link)
|
|
717
|
+
container = link.at_xpath('ancestor::*[self::li or self::div][.//relative-time or .//time-ago][1]')
|
|
718
|
+
time_node = container&.at_css("relative-time[datetime], time-ago[datetime]") ||
|
|
719
|
+
link.xpath('following::relative-time[@datetime] | following::time-ago[@datetime]').first
|
|
720
|
+
time_node&.[]("datetime")
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def format_release_date(datetime)
|
|
724
|
+
if datetime.to_s.match?(/\A\d{4}-\d{2}-\d{2}\z/)
|
|
725
|
+
date = Date.strptime(datetime.to_s, "%Y-%m-%d")
|
|
726
|
+
return date.strftime("%b #{date.day}, %Y")
|
|
727
|
+
end
|
|
728
|
+
|
|
729
|
+
time = Time.parse(datetime.to_s).utc
|
|
730
|
+
time.strftime("%b #{time.day}, %Y")
|
|
731
|
+
rescue ArgumentError
|
|
732
|
+
nil
|
|
733
|
+
end
|
|
734
|
+
|
|
465
735
|
def parse_single_github_release_page(html, version)
|
|
466
736
|
require "nokogiri"
|
|
467
737
|
|
|
@@ -490,21 +760,34 @@ module Gemstar
|
|
|
490
760
|
|
|
491
761
|
def github_release_tag_urls(repo_url, version)
|
|
492
762
|
github_tag_candidates(version).map do |tag|
|
|
493
|
-
|
|
763
|
+
encoded_tag = URI.encode_www_form_component(tag)
|
|
764
|
+
"#{repo_url}/releases/tag/#{encoded_tag}"
|
|
494
765
|
end.uniq
|
|
495
766
|
end
|
|
496
767
|
|
|
497
768
|
def github_tag_candidates(version)
|
|
769
|
+
return @metadata.github_tag_candidates(version) if @metadata.respond_to?(:github_tag_candidates)
|
|
770
|
+
|
|
498
771
|
raw = version.to_s
|
|
499
772
|
[raw, (raw.start_with?("v") ? raw : "v#{raw}")].uniq
|
|
500
773
|
end
|
|
501
774
|
|
|
502
775
|
def normalize_github_tag_version(tag_name)
|
|
503
776
|
decoded = URI.decode_www_form_component(tag_name.to_s.split("?").first.to_s)
|
|
504
|
-
match = decoded.match(/\
|
|
777
|
+
match = decoded.match(/\A(?:.+@)?v?(\d[\w.\-]*)\z/i)
|
|
505
778
|
match && match[1]
|
|
506
779
|
end
|
|
507
780
|
|
|
781
|
+
def github_tag_matches_metadata?(tag_name)
|
|
782
|
+
return @metadata.github_tag_matches?(tag_name) if @metadata.respond_to?(:github_tag_matches?)
|
|
783
|
+
|
|
784
|
+
true
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def discover_github_tag_sections?
|
|
788
|
+
@metadata.respond_to?(:discover_github_tag_sections?) && @metadata.discover_github_tag_sections?
|
|
789
|
+
end
|
|
790
|
+
|
|
508
791
|
def prefer_github_releases_first?(cache_only:, force_refresh:)
|
|
509
792
|
meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
|
|
510
793
|
repo_uri = @metadata.repo_uri(cache_only: cache_only, force_refresh: force_refresh)
|
data/lib/gemstar/cli.rb
CHANGED
|
@@ -13,6 +13,9 @@ module Gemstar
|
|
|
13
13
|
desc "diff", "Show changelogs for updated gems"
|
|
14
14
|
method_option :from, type: :string, desc: "Git ref or lockfile"
|
|
15
15
|
method_option :to, type: :string, desc: "Git ref or lockfile"
|
|
16
|
+
method_option :since, type: :string, desc: "Set --from to the latest commit before this relative time (for example: '3 weeks')"
|
|
17
|
+
method_option :project, type: :string, desc: "Project directory or supported project file path"
|
|
18
|
+
method_option :ecosystem, type: :string, desc: "Filter packages by ecosystem (all, gems, js)"
|
|
16
19
|
method_option :format, type: :string, desc: "Output format (html or markdown)"
|
|
17
20
|
method_option :output_file, type: :string, desc: "Output file path"
|
|
18
21
|
method_option :debug_gem_regex, type: :string, desc: "Debug matching gems", hide: true
|
|
@@ -22,9 +25,10 @@ module Gemstar
|
|
|
22
25
|
|
|
23
26
|
desc "server", "Start the interactive web server"
|
|
24
27
|
method_option :bind, type: :string, default: "127.0.0.1", desc: "Bind address"
|
|
25
|
-
method_option :port, type: :numeric, default: 2112
|
|
28
|
+
method_option :port, type: :numeric, desc: "Port (default: 2112; auto-increments when omitted)"
|
|
26
29
|
method_option :project, type: :string, repeatable: true, desc: "Project directories or Gemfile paths"
|
|
27
30
|
method_option :reload, type: :boolean, default: false, desc: "Restart automatically when files change"
|
|
31
|
+
method_option :open, type: :boolean, default: false, desc: "Open the server root in a browser after startup"
|
|
28
32
|
def server
|
|
29
33
|
Gemstar::Commands::Server.new(options).run
|
|
30
34
|
end
|