gemstar 1.0.2 → 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 +39 -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 +337 -31
- data/lib/gemstar/cli.rb +5 -1
- data/lib/gemstar/commands/diff.rb +197 -31
- data/lib/gemstar/commands/server.rb +94 -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_repo.rb +41 -3
- data/lib/gemstar/importmap_file.rb +193 -0
- data/lib/gemstar/lock_file.rb +91 -9
- 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 +325 -37
- data/lib/gemstar/ruby_gems_metadata.rb +77 -2
- data/lib/gemstar/version.rb +1 -2
- data/lib/gemstar/web/app.rb +418 -70
- data/lib/gemstar/web/templates/app.css +40 -5
- data/lib/gemstar/web/templates/app.js.erb +119 -15
- data/lib/gemstar/web/templates/page.html.erb +2 -1
- data/lib/gemstar.rb +3 -0
- metadata +7 -2
data/lib/gemstar/change_log.rb
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require "cgi"
|
|
3
|
+
require "json"
|
|
2
4
|
|
|
3
5
|
module Gemstar
|
|
4
6
|
class ChangeLog
|
|
5
7
|
@@candidates_found = Hash.new(0)
|
|
8
|
+
DEFAULT_CHANGELOG_PATHS = %w[
|
|
9
|
+
CHANGELOG.md releases.md CHANGES.md
|
|
10
|
+
Changelog.md changelog.md ChangeLog.md
|
|
11
|
+
Changes.md changes.md
|
|
12
|
+
HISTORY.md History.md history.md
|
|
13
|
+
History CHANGELOG.rdoc
|
|
14
|
+
].freeze
|
|
6
15
|
|
|
7
16
|
def initialize(metadata)
|
|
8
17
|
@metadata = metadata
|
|
@@ -19,23 +28,84 @@ module Gemstar
|
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
def sections(cache_only: false, force_refresh: false)
|
|
22
|
-
return @sections if !cache_only && defined?(@sections)
|
|
31
|
+
return @sections if !cache_only && defined?(@sections) && !force_refresh
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
metadata_key = @metadata.respond_to?(:cache_key) ? @metadata.cache_key : @metadata.gem_name
|
|
34
|
+
cache_key = "sections-v4-#{metadata_key}"
|
|
35
|
+
serialized = if cache_only
|
|
36
|
+
Cache.peek(cache_key)
|
|
37
|
+
else
|
|
38
|
+
Cache.fetch(cache_key, force: force_refresh) do
|
|
39
|
+
JSON.generate(compute_sections(force_refresh: force_refresh))
|
|
28
40
|
end
|
|
41
|
+
end
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
result = if serialized
|
|
44
|
+
decode_sections(serialized)
|
|
45
|
+
elsif cache_only
|
|
46
|
+
nil
|
|
47
|
+
else
|
|
48
|
+
compute_sections(force_refresh: force_refresh)
|
|
33
49
|
end
|
|
34
50
|
|
|
35
51
|
@sections = result unless cache_only
|
|
36
52
|
result
|
|
37
53
|
end
|
|
38
54
|
|
|
55
|
+
def sections_for_versions(versions, cache_only: false, force_refresh: false)
|
|
56
|
+
requested_versions = normalize_requested_versions(versions)
|
|
57
|
+
return {} if requested_versions.empty?
|
|
58
|
+
|
|
59
|
+
cached_sections = sections(cache_only: true) || {}
|
|
60
|
+
result = cached_sections.select { |version, _| requested_versions.include?(normalize_version_key(version)) }
|
|
61
|
+
return result if cache_only
|
|
62
|
+
|
|
63
|
+
changelog_sections = parse_changelog_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
64
|
+
changelog_sections.each do |version, lines|
|
|
65
|
+
result[version] ||= lines if requested_versions.include?(normalize_version_key(version))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
repo_uri = @metadata&.repo_uri(cache_only: false, force_refresh: force_refresh)
|
|
69
|
+
if repo_uri&.include?("github.com")
|
|
70
|
+
missing_versions = requested_versions - result.keys.map { |version| normalize_version_key(version) }
|
|
71
|
+
missing_versions.each do |version|
|
|
72
|
+
specific_release = parse_specific_github_release_pages(
|
|
73
|
+
repo_uri,
|
|
74
|
+
version,
|
|
75
|
+
cache_only: false,
|
|
76
|
+
force_refresh: force_refresh
|
|
77
|
+
)
|
|
78
|
+
result.merge!(specific_release) if specific_release
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
result
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def compute_sections(force_refresh: false)
|
|
86
|
+
changelog_sections = parse_changelog_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
87
|
+
github_sections = parse_github_release_sections(cache_only: false, force_refresh: force_refresh) || {}
|
|
88
|
+
|
|
89
|
+
sections = merge_section_sources(changelog_sections, github_sections)
|
|
90
|
+
|
|
91
|
+
pp @@candidates_found if Gemstar.debug?
|
|
92
|
+
|
|
93
|
+
sections
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def decode_sections(serialized)
|
|
97
|
+
JSON.parse(serialized)
|
|
98
|
+
rescue JSON::ParserError
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def merge_section_sources(changelog_sections, github_sections)
|
|
103
|
+
return github_sections if changelog_sections.nil? || changelog_sections.empty?
|
|
104
|
+
return changelog_sections if github_sections.nil? || github_sections.empty?
|
|
105
|
+
|
|
106
|
+
github_sections.merge(changelog_sections)
|
|
107
|
+
end
|
|
108
|
+
|
|
39
109
|
def extract_relevant_sections(old_version, new_version)
|
|
40
110
|
from = Gem::Version.new(old_version.gsub(/-[\w\-]+$/, "")) rescue nil if old_version
|
|
41
111
|
from ||= Gem::Version.new("0.0.0")
|
|
@@ -56,6 +126,17 @@ module Gemstar
|
|
|
56
126
|
|
|
57
127
|
private
|
|
58
128
|
|
|
129
|
+
def normalize_requested_versions(versions)
|
|
130
|
+
Array(versions).filter_map { |version| normalize_version_key(version) }.uniq
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def normalize_version_key(version)
|
|
134
|
+
value = version.to_s.strip
|
|
135
|
+
return nil if value.empty?
|
|
136
|
+
|
|
137
|
+
value.sub(/\Av/i, "")
|
|
138
|
+
end
|
|
139
|
+
|
|
59
140
|
# Extract a version token from a heading line, preferring explicit version forms
|
|
60
141
|
# and avoiding returning a date string when both are present.
|
|
61
142
|
def extract_version_from_heading(line)
|
|
@@ -80,30 +161,11 @@ module Gemstar
|
|
|
80
161
|
repo_uri = @metadata.repo_uri(cache_only: cache_only, force_refresh: force_refresh)
|
|
81
162
|
return [] if repo_uri.nil? || repo_uri.empty?
|
|
82
163
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
aws_style = true
|
|
86
|
-
else
|
|
87
|
-
base = repo_uri.sub("https://github.com", "https://raw.githubusercontent.com")
|
|
88
|
-
aws_style = false
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
base = base.chomp("/")
|
|
92
|
-
|
|
93
|
-
paths = aws_style ? ["CHANGELOG.md"] : %w[
|
|
94
|
-
CHANGELOG.md releases.md CHANGES.md
|
|
95
|
-
Changelog.md changelog.md ChangeLog.md
|
|
96
|
-
Changes.md changes.md
|
|
97
|
-
HISTORY.md History.md history.md
|
|
98
|
-
History CHANGELOG.rdoc
|
|
99
|
-
]
|
|
164
|
+
changelog_source = metadata_changelog_source(repo_uri, cache_only: cache_only, force_refresh: force_refresh)
|
|
165
|
+
return [] unless changelog_source
|
|
100
166
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
branches = aws_style ? [""] : remote_repository.find_main_branch(cache_only: cache_only, force_refresh: force_refresh)
|
|
104
|
-
|
|
105
|
-
candidates += paths.product(branches).map do |file, branch|
|
|
106
|
-
uri = aws_style ? "#{base}/#{file}" : "#{base}/#{branch}/#{file}"
|
|
167
|
+
candidates += changelog_source[:paths].product(changelog_source[:branches]).map do |file, branch|
|
|
168
|
+
[changelog_source[:base], branch, file].reject { |segment| segment.to_s.empty? }.join("/")
|
|
107
169
|
end
|
|
108
170
|
|
|
109
171
|
# Add the gem's changelog_uri last as it's usually not the most parsable:
|
|
@@ -117,6 +179,19 @@ module Gemstar
|
|
|
117
179
|
candidates
|
|
118
180
|
end
|
|
119
181
|
|
|
182
|
+
def metadata_changelog_source(repo_uri, cache_only:, force_refresh:)
|
|
183
|
+
if @metadata.respond_to?(:changelog_source)
|
|
184
|
+
return @metadata.changelog_source(repo_uri: repo_uri, cache_only: cache_only, force_refresh: force_refresh)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
base = repo_uri.sub("https://github.com", "https://raw.githubusercontent.com").chomp("/")
|
|
188
|
+
{
|
|
189
|
+
base: base,
|
|
190
|
+
paths: DEFAULT_CHANGELOG_PATHS,
|
|
191
|
+
branches: RemoteRepository.new(base).find_main_branch(cache_only: cache_only, force_refresh: force_refresh)
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
|
|
120
195
|
def fetch_changelog_content(cache_only: false, force_refresh: false)
|
|
121
196
|
content = nil
|
|
122
197
|
|
|
@@ -261,6 +336,7 @@ module Gemstar
|
|
|
261
336
|
heading = sec.at_css('h2.sr-only')
|
|
262
337
|
next unless heading
|
|
263
338
|
text = heading.text.to_s.strip
|
|
339
|
+
next unless github_tag_matches_metadata?(text)
|
|
264
340
|
next unless text[/v?(\d[\w.\-]+)/i]
|
|
265
341
|
version = $1
|
|
266
342
|
|
|
@@ -284,6 +360,7 @@ module Gemstar
|
|
|
284
360
|
link = container.at_xpath('ancestor::*[self::section or self::div][.//a[contains(@href, "/releases/tag/")]][1]//a[contains(@href, "/releases/tag/")]')
|
|
285
361
|
text = link&.text.to_s
|
|
286
362
|
text = File.basename(URI(link["href"]).path) if (text.nil? || text.empty?) && link
|
|
363
|
+
next unless github_tag_matches_metadata?(text)
|
|
287
364
|
next unless text && text[/v?(\d[\w.\-]+)/i]
|
|
288
365
|
version = $1
|
|
289
366
|
|
|
@@ -294,6 +371,29 @@ module Gemstar
|
|
|
294
371
|
end
|
|
295
372
|
end
|
|
296
373
|
|
|
374
|
+
if sections.empty?
|
|
375
|
+
current_version = @metadata&.meta(cache_only: cache_only, force_refresh: force_refresh)&.dig("version")
|
|
376
|
+
current_release_sections = parse_specific_github_release_pages(
|
|
377
|
+
repo_uri,
|
|
378
|
+
current_version,
|
|
379
|
+
cache_only: cache_only,
|
|
380
|
+
force_refresh: force_refresh
|
|
381
|
+
)
|
|
382
|
+
tag_sections = parse_github_tag_sections(
|
|
383
|
+
repo_uri,
|
|
384
|
+
cache_only: cache_only,
|
|
385
|
+
force_refresh: force_refresh
|
|
386
|
+
)
|
|
387
|
+
sections = tag_sections.merge(current_release_sections)
|
|
388
|
+
elsif discover_github_tag_sections?
|
|
389
|
+
tag_sections = parse_github_tag_sections(
|
|
390
|
+
repo_uri,
|
|
391
|
+
cache_only: cache_only,
|
|
392
|
+
force_refresh: force_refresh
|
|
393
|
+
)
|
|
394
|
+
sections = tag_sections.merge(sections)
|
|
395
|
+
end
|
|
396
|
+
|
|
297
397
|
if Gemstar.debug?
|
|
298
398
|
puts "parse_github_release_sections #{@metadata.gem_name}:"
|
|
299
399
|
pp sections.keys
|
|
@@ -308,5 +408,211 @@ module Gemstar
|
|
|
308
408
|
return nil if repo.empty?
|
|
309
409
|
"#{repo}/releases"
|
|
310
410
|
end
|
|
411
|
+
|
|
412
|
+
def github_tags_url(repo_uri = @metadata&.repo_uri)
|
|
413
|
+
return nil unless repo_uri
|
|
414
|
+
repo = repo_uri.chomp("/")
|
|
415
|
+
return nil if repo.empty?
|
|
416
|
+
"#{repo}/tags"
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def parse_specific_github_release_pages(repo_uri, version, cache_only:, force_refresh:)
|
|
420
|
+
return {} unless repo_uri&.include?("github.com")
|
|
421
|
+
return {} if version.to_s.empty?
|
|
422
|
+
|
|
423
|
+
github_release_tag_urls(repo_uri, version).each do |url|
|
|
424
|
+
html = if cache_only
|
|
425
|
+
Cache.peek("releases-#{url}")
|
|
426
|
+
else
|
|
427
|
+
Cache.fetch("releases-#{url}", force: force_refresh) do
|
|
428
|
+
begin
|
|
429
|
+
URI.open(url, read_timeout: 8)&.read
|
|
430
|
+
rescue => e
|
|
431
|
+
puts "#{url}: #{e}" if Gemstar.debug?
|
|
432
|
+
nil
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
next if html.nil? || html.strip.empty?
|
|
438
|
+
|
|
439
|
+
section = parse_single_github_release_page(html, version)
|
|
440
|
+
return { version => section } if section
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
{}
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def parse_github_tag_sections(repo_uri, cache_only:, force_refresh:)
|
|
447
|
+
return {} unless repo_uri&.include?("github.com")
|
|
448
|
+
|
|
449
|
+
url = github_tags_url(repo_uri)
|
|
450
|
+
return {} unless url
|
|
451
|
+
|
|
452
|
+
sections = {}
|
|
453
|
+
seen_urls = {}
|
|
454
|
+
|
|
455
|
+
while url && !seen_urls[url]
|
|
456
|
+
seen_urls[url] = true
|
|
457
|
+
html = if cache_only
|
|
458
|
+
Cache.peek("tags-#{url}")
|
|
459
|
+
else
|
|
460
|
+
Cache.fetch("tags-#{url}", force: force_refresh) do
|
|
461
|
+
begin
|
|
462
|
+
URI.open(url, read_timeout: 8)&.read
|
|
463
|
+
rescue => e
|
|
464
|
+
puts "#{url}: #{e}" if Gemstar.debug?
|
|
465
|
+
nil
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
break if html.nil? || html.strip.empty?
|
|
471
|
+
|
|
472
|
+
page_sections, next_url = parse_single_github_tags_page(html, repo_uri)
|
|
473
|
+
sections.merge!(page_sections) { |_version, existing, _new| existing }
|
|
474
|
+
url = next_url
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
sections.keys.each do |version|
|
|
478
|
+
specific_release = parse_specific_github_release_pages(
|
|
479
|
+
repo_uri,
|
|
480
|
+
version,
|
|
481
|
+
cache_only: cache_only,
|
|
482
|
+
force_refresh: force_refresh
|
|
483
|
+
)
|
|
484
|
+
next if specific_release.nil? || specific_release.empty?
|
|
485
|
+
|
|
486
|
+
sections[version] = specific_release[version] if specific_release[version]
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
sections
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def parse_single_github_tags_page(html, repo_uri)
|
|
493
|
+
require "nokogiri"
|
|
494
|
+
|
|
495
|
+
doc = begin
|
|
496
|
+
Nokogiri::HTML5(html)
|
|
497
|
+
rescue => _
|
|
498
|
+
Nokogiri::HTML(html)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
sections = {}
|
|
502
|
+
repo_path = URI(repo_uri).path
|
|
503
|
+
release_prefix = "#{repo_path}/releases/tag/"
|
|
504
|
+
tree_prefix = "#{repo_path}/tree/"
|
|
505
|
+
|
|
506
|
+
doc.css("a[href]").each do |link|
|
|
507
|
+
href = link["href"].to_s
|
|
508
|
+
tag_name =
|
|
509
|
+
if href.start_with?(release_prefix)
|
|
510
|
+
href.delete_prefix(release_prefix)
|
|
511
|
+
elsif href.start_with?(tree_prefix)
|
|
512
|
+
href.delete_prefix(tree_prefix)
|
|
513
|
+
end
|
|
514
|
+
next if tag_name.to_s.empty?
|
|
515
|
+
next unless github_tag_matches_metadata?(tag_name)
|
|
516
|
+
|
|
517
|
+
version = normalize_github_tag_version(tag_name)
|
|
518
|
+
next if version.to_s.empty?
|
|
519
|
+
|
|
520
|
+
sections[version] ||= [
|
|
521
|
+
"## #{version}\n",
|
|
522
|
+
"<p>No release information available. Check the release page for more information.</p>"
|
|
523
|
+
]
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
next_href =
|
|
527
|
+
doc.at_css('a[rel="next"], a.next_page')&.[]("href") ||
|
|
528
|
+
doc.css("a[href]").find do |link|
|
|
529
|
+
href = link["href"].to_s
|
|
530
|
+
text = link.text.to_s.gsub(/\s+/, " ").strip
|
|
531
|
+
href.include?("/tags?after=") && text == "Next"
|
|
532
|
+
end&.[]("href")
|
|
533
|
+
next_url = if next_href && !next_href.empty?
|
|
534
|
+
URI.join(repo_uri, next_href).to_s
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
[sections, next_url]
|
|
538
|
+
rescue LoadError
|
|
539
|
+
[{}, nil]
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def parse_single_github_release_page(html, version)
|
|
543
|
+
require "nokogiri"
|
|
544
|
+
|
|
545
|
+
doc = begin
|
|
546
|
+
Nokogiri::HTML5(html)
|
|
547
|
+
rescue => _
|
|
548
|
+
Nokogiri::HTML(html)
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
body = doc.at_css('[data-test-selector="body-content"] .markdown-body') ||
|
|
552
|
+
doc.at_css('[data-test-selector="body-content"]') ||
|
|
553
|
+
doc.at_css('.markdown-body')
|
|
554
|
+
if body
|
|
555
|
+
html_chunk = body.inner_html.to_s.strip
|
|
556
|
+
return ["## #{version}\n", html_chunk] unless html_chunk.empty?
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
title = doc.at_css("title")&.text.to_s.strip
|
|
560
|
+
synthetic_title = normalize_github_release_title(title, version)
|
|
561
|
+
return nil if synthetic_title.nil? || synthetic_title.empty?
|
|
562
|
+
|
|
563
|
+
["## #{version}\n", "<p>#{CGI.escapeHTML(synthetic_title)}</p>"]
|
|
564
|
+
rescue LoadError
|
|
565
|
+
nil
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def github_release_tag_urls(repo_url, version)
|
|
569
|
+
github_tag_candidates(version).map do |tag|
|
|
570
|
+
encoded_tag = URI.encode_www_form_component(tag)
|
|
571
|
+
"#{repo_url}/releases/tag/#{encoded_tag}"
|
|
572
|
+
end.uniq
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def github_tag_candidates(version)
|
|
576
|
+
return @metadata.github_tag_candidates(version) if @metadata.respond_to?(:github_tag_candidates)
|
|
577
|
+
|
|
578
|
+
raw = version.to_s
|
|
579
|
+
[raw, (raw.start_with?("v") ? raw : "v#{raw}")].uniq
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def normalize_github_tag_version(tag_name)
|
|
583
|
+
decoded = URI.decode_www_form_component(tag_name.to_s.split("?").first.to_s)
|
|
584
|
+
match = decoded.match(/\A(?:.+@)?v?(\d[\w.\-]*)\z/i)
|
|
585
|
+
match && match[1]
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def github_tag_matches_metadata?(tag_name)
|
|
589
|
+
return @metadata.github_tag_matches?(tag_name) if @metadata.respond_to?(:github_tag_matches?)
|
|
590
|
+
|
|
591
|
+
true
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def discover_github_tag_sections?
|
|
595
|
+
@metadata.respond_to?(:discover_github_tag_sections?) && @metadata.discover_github_tag_sections?
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def prefer_github_releases_first?(cache_only:, force_refresh:)
|
|
599
|
+
meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
|
|
600
|
+
repo_uri = @metadata.repo_uri(cache_only: cache_only, force_refresh: force_refresh)
|
|
601
|
+
|
|
602
|
+
repo_uri.to_s.include?("github.com") && meta["changelog_uri"].to_s.empty?
|
|
603
|
+
rescue StandardError
|
|
604
|
+
false
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def normalize_github_release_title(title, version)
|
|
608
|
+
return nil if title.to_s.empty?
|
|
609
|
+
|
|
610
|
+
cleaned = title.sub(/\s*·\s*[^·]+\s*·\s*GitHub\z/, "")
|
|
611
|
+
cleaned = cleaned.sub(/\ARelease\s+/, "")
|
|
612
|
+
cleaned = cleaned.strip
|
|
613
|
+
return nil if cleaned.empty? || cleaned == version.to_s
|
|
614
|
+
|
|
615
|
+
cleaned
|
|
616
|
+
end
|
|
311
617
|
end
|
|
312
618
|
end
|
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
|