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 +4 -4
- data/CHANGELOG.md +25 -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 +123 -33
- 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_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 +77 -2
- data/lib/gemstar/version.rb +1 -1
- data/lib/gemstar/web/app.rb +377 -67
- data/lib/gemstar/web/templates/app.css +35 -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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bd80f7bf53281fd8f5b80138f9f564ff8e393ffaec44276168dc020765c6ae7a
|
|
4
|
+
data.tar.gz: 3d6b9c2f9a2e0955d6ace8c34100c05390cd9579aa8c8993361799450e36c625
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
[](https://github.com/FDj/gemstar/actions)
|
|
4
4
|
|
|
5
5
|
# Gemstar
|
|
6
|
-
|
|
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
|
|
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
|
|
53
|
+
This will generate an html diff report with changelog entries for each updated package:
|
|
48
54
|
|
|
49
55
|

|
|
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
data/lib/gemstar/cache_warmer.rb
CHANGED
|
@@ -21,47 +21,60 @@ module Gemstar
|
|
|
21
21
|
@completed_count = 0
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def enqueue_many(
|
|
25
|
-
|
|
24
|
+
def enqueue_many(package_states)
|
|
25
|
+
states = normalize_package_states(package_states)
|
|
26
26
|
|
|
27
27
|
@mutex.synchronize do
|
|
28
|
-
|
|
29
|
-
|
|
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 <<
|
|
32
|
-
@queued <<
|
|
32
|
+
@queue << package_state
|
|
33
|
+
@queued << key
|
|
33
34
|
end
|
|
34
|
-
@total +=
|
|
35
|
+
@total += states.count
|
|
35
36
|
start_workers_unlocked unless @started
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
log "Background cache refresh queued for #{
|
|
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(
|
|
44
|
+
def prioritize(package_name)
|
|
44
45
|
@mutex.synchronize do
|
|
45
|
-
|
|
46
|
+
existing = @queue.find do |item|
|
|
47
|
+
item[:name] == package_name || item.dig(:source, :package_name) == package_name
|
|
48
|
+
end
|
|
46
49
|
|
|
47
|
-
if
|
|
48
|
-
|
|
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
|
-
|
|
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 #{
|
|
70
|
+
log "Prioritized #{package_name}"
|
|
59
71
|
@condition.broadcast
|
|
60
72
|
end
|
|
61
73
|
|
|
62
|
-
def pending?(
|
|
74
|
+
def pending?(package_name)
|
|
63
75
|
@mutex.synchronize do
|
|
64
|
-
@
|
|
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
|
-
|
|
96
|
+
package_state = @mutex.synchronize do
|
|
84
97
|
while @queue.empty?
|
|
85
98
|
@condition.wait(@mutex)
|
|
86
99
|
end
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@
|
|
91
|
-
|
|
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
|
-
|
|
108
|
+
warm_cache_for_package(package_state)
|
|
95
109
|
|
|
96
110
|
current = @mutex.synchronize do
|
|
97
|
-
|
|
98
|
-
@
|
|
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(
|
|
117
|
+
log_progress(package_label(package_state), current)
|
|
103
118
|
end
|
|
104
119
|
end
|
|
105
120
|
|
|
106
|
-
def
|
|
107
|
-
metadata =
|
|
108
|
-
metadata
|
|
109
|
-
|
|
110
|
-
|
|
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 #{
|
|
127
|
+
log "Cache refresh failed for #{package_label(package_state)}: #{e.class}: #{e.message}"
|
|
113
128
|
end
|
|
114
129
|
|
|
115
|
-
def log_progress(
|
|
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}: #{
|
|
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
|
data/lib/gemstar/change_log.rb
CHANGED
|
@@ -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
|
-
|
|
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))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
28
42
|
|
|
29
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("/")
|
|
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
|
-
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
|
-
|
|
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(/\
|
|
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
|
|
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
|