gemstar 1.0.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bea143c3f9b89aca665d0b74402cb7f16ec65b6994139a187f406c3de800eca
4
- data.tar.gz: f9bc3eeba8f76decc7dbbe215e377a3130e1ffbbaa5d22f405604974ab99741a
3
+ metadata.gz: bd80f7bf53281fd8f5b80138f9f564ff8e393ffaec44276168dc020765c6ae7a
4
+ data.tar.gz: 3d6b9c2f9a2e0955d6ace8c34100c05390cd9579aa8c8993361799450e36c625
5
5
  SHA512:
6
- metadata.gz: b857d1cc340eab567266177d927710b13d9f569d07bea5d59332a679c3f57e827c26d9ce6d8154968cd624ac1e1b41723778d9bbc3c6ed6fede5af147fdffbba
7
- data.tar.gz: d2da645e58549938abf27c2c10a53c1eca1b2a8d842ef7f41ce85298e0a47e4b4d8a0bf152843ad080c0cf2b112f6882a5628a93cd617f1933e727d2e2992d39
6
+ metadata.gz: 79d87285b961fdd88c6e898b2016d2bc4cc65b508a5f2c7480f865f0144b12ca5a53be3ff7833d48ed1a66cf4851134a6dbe41fdfc44d807f720e0c63fe52a05
7
+ data.tar.gz: 4803b9f217fef1aae5b671fdc5a356229697aba7d5cec4b2f1a05588d63a9ca88c38f876ed4aa1d2579451d6196097818a3d9aa866f8071004c45a9966c66a95
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.1
4
+
5
+ - Server: Add **JavaScript package support** to browse change logs for your project's packages. This currently
6
+ supports packages in `importmap.rb` and `package-lock.json`.
7
+ - Server: Change launch behavior to more reliably start cache warmer at launch.
8
+ - Server: Add `--open` option to open the server root in a local browser at launch.
9
+ - Server: Defer initial detail rendering so the page becomes interactive sooner on startup.
10
+ - Server: Persist the Details disclosure state between gem views.
11
+ - Server: Continue background warming with cached parsed release sections, not just raw fetched pages.
12
+ - Server: Improve GitHub release discovery with direct tag-page fallback and paginated GitHub tags support.
13
+ - Server: Prefer real changelog file entries over GitHub-derived placeholders when both exist.
14
+ - Server: Short gem description now rendered as markdown (for minitest gem).
15
+ - Server: Improve section parsing for simplecov and similar gems.
16
+ - Server: Increase limit for number of recent commits to show in menu to 100.
17
+ - Server: Add page icon.
18
+ - Server: Unless a port has been specified, server will now attempt binding from ports 2112 up.
19
+ - Diff: Add **JavaScript package support.** Like with Server, this supports `importmap.rb` and `package-lock.json`.
20
+ - Diff: Add `--ecosystem` parameter to choose js/gems/all.
21
+ - Diff: Add `--project` directive to point at the project root to diff.
22
+ - Diff: Add `--since` parameter to limit diff to commits since a specific date, e.g. `--since "3 weeks ago"`.
23
+ - Diff: Add "file://" to generated report output to fix click-to-open in certain terminal apps.
24
+ - Diff: Add list of considered git commits to diff report.
25
+ - Special cases for specific packages now moved into json configuration files.
26
+ - `gemstar` is now a shortcut for `gemstar server --open`.
27
+
3
28
  ## 1.0.4
4
29
 
5
30
  - Server: Improve layout, detail panel state management, and initial gem selection
@@ -35,7 +60,6 @@
35
60
  - Removed Railtie from this gem.
36
61
  - Improve how git root dir is determined.
37
62
 
38
-
39
63
  ## 0.0.2
40
64
 
41
65
  - Diff: Fix regex warnings shown in terminal.
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![JRuby Build](https://github.com/FDj/gemstar/workflows/JRuby%20Build/badge.svg)](https://github.com/FDj/gemstar/actions)
4
4
 
5
5
  # Gemstar
6
- A very preliminary gem to help you keep track of your gems.
6
+ Helps you keep track of your gems and JavaScript packages.
7
7
 
8
8
  ## Installation
9
9
 
@@ -34,17 +34,23 @@ gemstar server
34
34
 
35
35
  By default, the server listens to http://127.0.0.1:2112/
36
36
 
37
+ To open the root page in your browser after startup:
38
+
39
+ ```shell
40
+ gemstar server --open
41
+ ```
42
+
37
43
 
38
44
  ### gemstar diff
39
45
 
40
- Run this after you've updated your gems.
46
+ Run this after you've updated your dependencies.
41
47
 
42
48
  ```shell
43
49
  # in your project directory, after bundle update:
44
50
  gemstar diff
45
51
  ```
46
52
 
47
- This will generate an html diff report with changelog entries for each gem that was updated:
53
+ This will generate an html diff report with changelog entries for each updated package:
48
54
 
49
55
  ![Gemstar diff command output](docs/diff.png)
50
56
 
@@ -54,12 +60,31 @@ You can also specify from and to hashes or tags to generate a diff report for a
54
60
  gemstar diff --from 8e3aa96b7027834cdbabc0d8cbd5f9455165e930 --to HEAD
55
61
  ```
56
62
 
63
+ To use a time range instead of choosing the starting commit yourself:
64
+
65
+ ```shell
66
+ gemstar diff --project ~/Code/my-app --since "3 weeks"
67
+ ```
68
+
57
69
  To examine a specific Gemfile.lock, pass it like this:
58
70
 
59
71
  ```shell
60
72
  gemstar diff --lockfile=~/MyProject/Gemfile.lock
61
73
  ```
62
74
 
75
+ To diff a project from anywhere, pass the project directory or a supported project file. In project mode, gemstar includes Ruby gems plus JS packages from `importmap.rb` and `package-lock.json` when present:
76
+
77
+ ```shell
78
+ gemstar diff --project ~/Code/my-app
79
+ ```
80
+
81
+ To filter a project diff down to one ecosystem:
82
+
83
+ ```shell
84
+ gemstar diff --project ~/Code/my-app --ecosystem js
85
+ gemstar diff --project ~/Code/my-app --ecosystem gems
86
+ ```
87
+
63
88
  To write markdown instead of html:
64
89
 
65
90
  ```shell
data/bin/gemstar CHANGED
@@ -4,4 +4,8 @@
4
4
  $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
5
  require_relative "../lib/gemstar"
6
6
 
7
- Gemstar::CLI.start(ARGV)
7
+ if ARGV.empty?
8
+ Gemstar::Commands::Server.new(open: true).run
9
+ else
10
+ Gemstar::CLI.start(ARGV)
11
+ end
@@ -21,47 +21,60 @@ module Gemstar
21
21
  @completed_count = 0
22
22
  end
23
23
 
24
- def enqueue_many(gem_names)
25
- names = gem_names.uniq
24
+ def enqueue_many(package_states)
25
+ states = normalize_package_states(package_states)
26
26
 
27
27
  @mutex.synchronize do
28
- names.each do |gem_name|
29
- next if @completed.include?(gem_name) || @queued.include?(gem_name) || @in_progress.include?(gem_name)
28
+ states.each do |package_state|
29
+ key = package_key(package_state)
30
+ next if @completed.include?(key) || @queued.include?(key) || @in_progress.include?(key)
30
31
 
31
- @queue << gem_name
32
- @queued << gem_name
32
+ @queue << package_state
33
+ @queued << key
33
34
  end
34
- @total += names.count
35
+ @total += states.count
35
36
  start_workers_unlocked unless @started
36
37
  end
37
38
 
38
- log "Background cache refresh queued for #{names.count} gems."
39
+ log "Background cache refresh queued for #{states.count} packages."
39
40
  @condition.broadcast
40
41
  self
41
42
  end
42
43
 
43
- def prioritize(gem_name)
44
+ def prioritize(package_name)
44
45
  @mutex.synchronize do
45
- return if @completed.include?(gem_name) || @in_progress.include?(gem_name)
46
+ existing = @queue.find do |item|
47
+ item[:name] == package_name || item.dig(:source, :package_name) == package_name
48
+ end
46
49
 
47
- if @queued.include?(gem_name)
48
- @queue.delete(gem_name)
50
+ if existing
51
+ key = package_key(existing)
52
+ return if @completed.include?(key) || @in_progress.include?(key)
53
+ @queue.delete(existing)
54
+ @queue.unshift(existing)
49
55
  else
50
- @queued << gem_name
56
+ synthetic = {
57
+ name: package_name,
58
+ package_scope: "gems",
59
+ source: {}
60
+ }
61
+ key = package_key(synthetic)
62
+ return if @completed.include?(key) || @in_progress.include?(key)
63
+ @queued << key
64
+ @queue.unshift(synthetic)
51
65
  @total += 1
52
66
  end
53
-
54
- @queue.unshift(gem_name)
55
67
  start_workers_unlocked unless @started
56
68
  end
57
69
 
58
- log "Prioritized #{gem_name}"
70
+ log "Prioritized #{package_name}"
59
71
  @condition.broadcast
60
72
  end
61
73
 
62
- def pending?(gem_name)
74
+ def pending?(package_name)
63
75
  @mutex.synchronize do
64
- @queued.include?(gem_name) || @in_progress.include?(gem_name)
76
+ @queue.any? { |item| item[:name] == package_name || item.dig(:source, :package_name) == package_name } ||
77
+ @in_progress.any? { |key| key.end_with?(":#{package_name}") }
65
78
  end
66
79
  end
67
80
 
@@ -80,47 +93,92 @@ module Gemstar
80
93
  Thread.current.name = "gemstar-cache-worker" if Thread.current.respond_to?(:name=)
81
94
 
82
95
  loop do
83
- gem_name = @mutex.synchronize do
96
+ package_state = @mutex.synchronize do
84
97
  while @queue.empty?
85
98
  @condition.wait(@mutex)
86
99
  end
87
100
 
88
- next_gem = @queue.shift
89
- @queued.delete(next_gem)
90
- @in_progress << next_gem
91
- next_gem
101
+ next_package = @queue.shift
102
+ key = package_key(next_package)
103
+ @queued.delete(key)
104
+ @in_progress << key
105
+ next_package
92
106
  end
93
107
 
94
- warm_cache_for_gem(gem_name)
108
+ warm_cache_for_package(package_state)
95
109
 
96
110
  current = @mutex.synchronize do
97
- @in_progress.delete(gem_name)
98
- @completed << gem_name
111
+ key = package_key(package_state)
112
+ @in_progress.delete(key)
113
+ @completed << key
99
114
  @completed_count += 1
100
115
  end
101
116
 
102
- log_progress(gem_name, current)
117
+ log_progress(package_label(package_state), current)
103
118
  end
104
119
  end
105
120
 
106
- def warm_cache_for_gem(gem_name)
107
- metadata = Gemstar::RubyGemsMetadata.new(gem_name)
108
- metadata.meta
109
- metadata.repo_uri
110
- Gemstar::ChangeLog.new(metadata).sections
121
+ def warm_cache_for_package(package_state)
122
+ metadata = metadata_adapter_for(package_state)
123
+ return unless metadata
124
+
125
+ metadata.warm_cache(versions: package_versions(package_state))
111
126
  rescue StandardError => e
112
- log "Cache refresh failed for #{gem_name}: #{e.class}: #{e.message}"
127
+ log "Cache refresh failed for #{package_label(package_state)}: #{e.class}: #{e.message}"
113
128
  end
114
129
 
115
- def log_progress(gem_name, current)
130
+ def log_progress(package_name, current)
116
131
  return unless @debug
117
132
  return unless current <= 5 || (current % 25).zero?
118
133
 
119
- log "Background cache refresh #{current}/#{@total}: #{gem_name}"
134
+ log "Background cache refresh #{current}/#{@total}: #{package_name}"
120
135
  end
121
136
 
122
137
  def log(message)
123
138
  @io.puts(message)
124
139
  end
140
+
141
+ def normalize_package_states(package_states)
142
+ Array(package_states).filter_map do |package_state|
143
+ next unless package_state.is_a?(Hash)
144
+
145
+ package_state
146
+ end.uniq { |package_state| package_key(package_state) }
147
+ end
148
+
149
+ def package_key(package_state)
150
+ scope = package_state[:package_scope].to_s
151
+ source_type = package_state[:package_source_file].to_s
152
+ package_name = package_state.dig(:source, :package_name) || package_state[:name]
153
+ "#{scope}:#{source_type}:#{package_name}"
154
+ end
155
+
156
+ def package_label(package_state)
157
+ package_state.dig(:source, :package_name) || package_state[:name]
158
+ end
159
+
160
+ def package_versions(package_state)
161
+ [
162
+ package_state[:old_version],
163
+ package_state[:new_version],
164
+ package_state[:raw_old_version],
165
+ package_state[:raw_new_version],
166
+ package_state.dig(:source, :package_version)
167
+ ].compact
168
+ end
169
+
170
+ def metadata_adapter_for(package_state)
171
+ if package_state[:package_scope] == "js"
172
+ provider_gem = package_state.dig(:source, :provider_gem)
173
+ return Gemstar::RubyGemsMetadata.new(provider_gem) unless provider_gem.to_s.empty?
174
+
175
+ package_name = package_state.dig(:source, :package_name) || package_state[:name]
176
+ return nil if package_name.to_s.empty?
177
+
178
+ return Gemstar::NpmMetadata.new(package_name)
179
+ end
180
+
181
+ Gemstar::RubyGemsMetadata.new(package_state[:name])
182
+ end
125
183
  end
126
184
  end
@@ -1,9 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
  require "cgi"
3
+ require "json"
3
4
 
4
5
  module Gemstar
5
6
  class ChangeLog
6
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
7
15
 
8
16
  def initialize(metadata)
9
17
  @metadata = metadata
@@ -20,23 +28,77 @@ module Gemstar
20
28
  end
21
29
 
22
30
  def sections(cache_only: false, force_refresh: false)
23
- return @sections if !cache_only && defined?(@sections)
31
+ return @sections if !cache_only && defined?(@sections) && !force_refresh
24
32
 
25
- result = begin
26
- changelog_sections = parse_changelog_sections(cache_only: cache_only, force_refresh: force_refresh) || {}
27
- github_sections = parse_github_release_sections(cache_only: cache_only, force_refresh: force_refresh) || {}
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))
40
+ end
41
+ end
28
42
 
29
- s = merge_section_sources(changelog_sections, github_sections)
43
+ result = if serialized
44
+ decode_sections(serialized)
45
+ elsif cache_only
46
+ nil
47
+ else
48
+ compute_sections(force_refresh: force_refresh)
49
+ end
30
50
 
31
- pp @@candidates_found if Gemstar.debug? && !cache_only
51
+ @sections = result unless cache_only
52
+ result
53
+ end
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
32
62
 
33
- s
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
34
80
  end
35
81
 
36
- @sections = result unless cache_only
37
82
  result
38
83
  end
39
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
+
40
102
  def merge_section_sources(changelog_sections, github_sections)
41
103
  return github_sections if changelog_sections.nil? || changelog_sections.empty?
42
104
  return changelog_sections if github_sections.nil? || github_sections.empty?
@@ -64,6 +126,17 @@ module Gemstar
64
126
 
65
127
  private
66
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
+
67
140
  # Extract a version token from a heading line, preferring explicit version forms
68
141
  # and avoiding returning a date string when both are present.
69
142
  def extract_version_from_heading(line)
@@ -88,30 +161,11 @@ module Gemstar
88
161
  repo_uri = @metadata.repo_uri(cache_only: cache_only, force_refresh: force_refresh)
89
162
  return [] if repo_uri.nil? || repo_uri.empty?
90
163
 
91
- if repo_uri =~ %r{https://github\.com/aws/aws-sdk-ruby}
92
- base = "https://raw.githubusercontent.com/aws/aws-sdk-ruby/refs/heads/version-3/gems/#{@metadata.gem_name}"
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("/")
164
+ changelog_source = metadata_changelog_source(repo_uri, cache_only: cache_only, force_refresh: force_refresh)
165
+ return [] unless changelog_source
100
166
 
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)
110
-
111
- branches = aws_style ? [""] : remote_repository.find_main_branch(cache_only: cache_only, force_refresh: force_refresh)
112
-
113
- candidates += paths.product(branches).map do |file, branch|
114
- 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("/")
115
169
  end
116
170
 
117
171
  # Add the gem's changelog_uri last as it's usually not the most parsable:
@@ -125,6 +179,19 @@ module Gemstar
125
179
  candidates
126
180
  end
127
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
+
128
195
  def fetch_changelog_content(cache_only: false, force_refresh: false)
129
196
  content = nil
130
197
 
@@ -269,6 +336,7 @@ module Gemstar
269
336
  heading = sec.at_css('h2.sr-only')
270
337
  next unless heading
271
338
  text = heading.text.to_s.strip
339
+ next unless github_tag_matches_metadata?(text)
272
340
  next unless text[/v?(\d[\w.\-]+)/i]
273
341
  version = $1
274
342
 
@@ -292,6 +360,7 @@ module Gemstar
292
360
  link = container.at_xpath('ancestor::*[self::section or self::div][.//a[contains(@href, "/releases/tag/")]][1]//a[contains(@href, "/releases/tag/")]')
293
361
  text = link&.text.to_s
294
362
  text = File.basename(URI(link["href"]).path) if (text.nil? || text.empty?) && link
363
+ next unless github_tag_matches_metadata?(text)
295
364
  next unless text && text[/v?(\d[\w.\-]+)/i]
296
365
  version = $1
297
366
 
@@ -316,6 +385,13 @@ module Gemstar
316
385
  force_refresh: force_refresh
317
386
  )
318
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)
319
395
  end
320
396
 
321
397
  if Gemstar.debug?
@@ -436,6 +512,7 @@ module Gemstar
436
512
  href.delete_prefix(tree_prefix)
437
513
  end
438
514
  next if tag_name.to_s.empty?
515
+ next unless github_tag_matches_metadata?(tag_name)
439
516
 
440
517
  version = normalize_github_tag_version(tag_name)
441
518
  next if version.to_s.empty?
@@ -490,21 +567,34 @@ module Gemstar
490
567
 
491
568
  def github_release_tag_urls(repo_url, version)
492
569
  github_tag_candidates(version).map do |tag|
493
- "#{repo_url}/releases/tag/#{tag}"
570
+ encoded_tag = URI.encode_www_form_component(tag)
571
+ "#{repo_url}/releases/tag/#{encoded_tag}"
494
572
  end.uniq
495
573
  end
496
574
 
497
575
  def github_tag_candidates(version)
576
+ return @metadata.github_tag_candidates(version) if @metadata.respond_to?(:github_tag_candidates)
577
+
498
578
  raw = version.to_s
499
579
  [raw, (raw.start_with?("v") ? raw : "v#{raw}")].uniq
500
580
  end
501
581
 
502
582
  def normalize_github_tag_version(tag_name)
503
583
  decoded = URI.decode_www_form_component(tag_name.to_s.split("?").first.to_s)
504
- match = decoded.match(/\Av?(\d[\w.\-]*)\z/i)
584
+ match = decoded.match(/\A(?:.+@)?v?(\d[\w.\-]*)\z/i)
505
585
  match && match[1]
506
586
  end
507
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
+
508
598
  def prefer_github_releases_first?(cache_only:, force_refresh:)
509
599
  meta = @metadata.meta(cache_only: cache_only, force_refresh: force_refresh)
510
600
  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, desc: "Port"
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