gem_changelog_diff 0.4.0 → 0.5.0
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 +13 -1
- data/README.md +11 -0
- data/ROADMAP.md +0 -15
- data/lib/gem_changelog_diff/cache.rb +122 -0
- data/lib/gem_changelog_diff/changelog_parser.rb +17 -5
- data/lib/gem_changelog_diff/cli.rb +42 -5
- data/lib/gem_changelog_diff/concurrent_fetcher.rb +38 -0
- data/lib/gem_changelog_diff/configuration.rb +6 -1
- data/lib/gem_changelog_diff/github_client.rb +15 -6
- data/lib/gem_changelog_diff/rubygems_client.rb +5 -1
- data/lib/gem_changelog_diff/version.rb +1 -1
- data/lib/gem_changelog_diff.rb +2 -0
- data/sig/gem_changelog_diff.rbs +59 -6
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5fff27b4446b9283fde0a59b3df642289748c7551988836dfea4260f75798d2d
|
|
4
|
+
data.tar.gz: cd72d26b57447d9fb24e70274adeadf3955637aaf2f24c3f74d8f2d92c962eec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ec391d55ff38fefd88ad0f9c18de86d7867b74357e2eeff19b0d84f160365faff36ce70ef329fac3dca75e0adf119dcb385efad884daae630e7108715b77c64
|
|
7
|
+
data.tar.gz: 78f6be459bd426c79c3ccc6c485a917fa5e09b6d122d08dc4f72566b49e13654d17183536f795f1a22481aede43bcaa9ae324ee6dbc25665179381105efd1377
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-06-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Disk cache at `~/.cache/gem_changelog_diff/` with configurable TTL (default 24h)
|
|
15
|
+
- ETag conditional requests to avoid consuming rate limit on revalidation
|
|
16
|
+
- `cache clear` subcommand
|
|
17
|
+
- `--no-cache` and `--cache-ttl` flags
|
|
18
|
+
- Concurrent fetching via thread pool (default 4, configurable via `--concurrency`)
|
|
19
|
+
- Progress indicator via `tty-spinner` when running in a terminal
|
|
20
|
+
|
|
10
21
|
## [0.4.0] - 2026-06-18
|
|
11
22
|
|
|
12
23
|
### Added
|
|
@@ -53,7 +64,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
53
64
|
- Plain text formatter for changelog output
|
|
54
65
|
- Full end-to-end pipeline: detect → lookup → fetch → format
|
|
55
66
|
|
|
56
|
-
[Unreleased]: https://github.com/eclectic-coding/gem_changelog_diff/compare/v0.
|
|
67
|
+
[Unreleased]: https://github.com/eclectic-coding/gem_changelog_diff/compare/v0.5.0...HEAD
|
|
68
|
+
[0.5.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.5.0
|
|
57
69
|
[0.4.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.4.0
|
|
58
70
|
[0.3.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.3.0
|
|
59
71
|
[0.2.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.2.0
|
data/README.md
CHANGED
|
@@ -16,6 +16,7 @@ CLI that shows you the changelog diff for each gem before you `bundle update`, p
|
|
|
16
16
|
- [Output Control](#output-control)
|
|
17
17
|
- [Detection Strategy](#detection-strategy)
|
|
18
18
|
- [Filtering](#filtering)
|
|
19
|
+
- [Caching](#caching)
|
|
19
20
|
- [Development](#development)
|
|
20
21
|
- [Contributing](#contributing)
|
|
21
22
|
- [License](#license)
|
|
@@ -87,6 +88,16 @@ gem_changelog_diff --group development # Filter by Bundler group
|
|
|
87
88
|
gem_changelog_diff --ignore rails rake # Exclude specific gems
|
|
88
89
|
```
|
|
89
90
|
|
|
91
|
+
### Caching
|
|
92
|
+
|
|
93
|
+
API responses are cached to `~/.cache/gem_changelog_diff/` with a 24-hour TTL. Subsequent runs reuse cached data and use ETag conditional requests to avoid consuming rate limit.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
gem_changelog_diff --no-cache # Bypass the cache
|
|
97
|
+
gem_changelog_diff --cache-ttl 3600 # Set TTL to 1 hour
|
|
98
|
+
gem_changelog_diff cache clear # Clear all cached data
|
|
99
|
+
```
|
|
100
|
+
|
|
90
101
|
[Back to top](#gemchangelogdiff)
|
|
91
102
|
|
|
92
103
|
## Development
|
data/ROADMAP.md
CHANGED
|
@@ -2,21 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Feature roadmap for gem_changelog_diff. Each section is auto-pruned by `bin/release` when that version ships.
|
|
4
4
|
|
|
5
|
-
## 0.5.0 -- Caching & Performance
|
|
6
|
-
|
|
7
|
-
Avoid redundant API calls. Make repeated runs fast on large dependency trees.
|
|
8
|
-
|
|
9
|
-
- Disk cache at `~/.cache/gem_changelog_diff/` with configurable TTL (default 24h)
|
|
10
|
-
- ETag conditional requests to avoid consuming rate limit on revalidation
|
|
11
|
-
- Concurrent fetching via Ruby threads (default concurrency: 4, configurable via `--concurrency`)
|
|
12
|
-
- Progress indicator via `tty-spinner`
|
|
13
|
-
- `cache clear` subcommand, `--no-cache` and `--cache-ttl` flags
|
|
14
|
-
|
|
15
|
-
**New files:** `cache.rb`, `concurrent_fetcher.rb`
|
|
16
|
-
**Dependencies:** `tty-spinner` (runtime)
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
5
|
## 0.6.0 -- Interactive Mode & Output Formats
|
|
21
6
|
|
|
22
7
|
Let users selectively browse changelogs. Support machine-readable output for CI and scripting.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "json"
|
|
6
|
+
require "net/http"
|
|
7
|
+
|
|
8
|
+
module GemChangelogDiff
|
|
9
|
+
class Cache
|
|
10
|
+
DEFAULT_DIR = File.join(Dir.home, ".cache", "gem_changelog_diff")
|
|
11
|
+
DEFAULT_TTL = 86_400
|
|
12
|
+
|
|
13
|
+
def initialize(cache_dir: DEFAULT_DIR, ttl: DEFAULT_TTL, enabled: true)
|
|
14
|
+
@cache_dir = cache_dir
|
|
15
|
+
@ttl = ttl
|
|
16
|
+
@enabled = enabled
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get(uri, headers: {})
|
|
20
|
+
return fetch_from_network(uri, headers) unless @enabled
|
|
21
|
+
|
|
22
|
+
key = cache_key(uri)
|
|
23
|
+
entry = read_entry(key)
|
|
24
|
+
|
|
25
|
+
if entry && fresh?(entry)
|
|
26
|
+
build_response(entry)
|
|
27
|
+
elsif entry && entry["etag"]
|
|
28
|
+
revalidate(uri, headers, entry, key)
|
|
29
|
+
else
|
|
30
|
+
fetch_and_store(uri, headers, key)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def clear
|
|
35
|
+
FileUtils.rm_rf(@cache_dir)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def cache_key(uri)
|
|
41
|
+
Digest::SHA256.hexdigest(uri.to_s)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def entry_path(key)
|
|
45
|
+
File.join(@cache_dir, "#{key}.json")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read_entry(key)
|
|
49
|
+
path = entry_path(key)
|
|
50
|
+
return nil unless File.exist?(path)
|
|
51
|
+
|
|
52
|
+
JSON.parse(File.read(path))
|
|
53
|
+
rescue JSON::ParserError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def write_entry(key, body:, code:, etag:)
|
|
58
|
+
FileUtils.mkdir_p(@cache_dir)
|
|
59
|
+
data = { "body" => body, "code" => code, "etag" => etag, "timestamp" => Time.now.to_i }
|
|
60
|
+
File.write(entry_path(key), JSON.generate(data))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fresh?(entry)
|
|
64
|
+
Time.now.to_i - entry["timestamp"] < @ttl
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def revalidate(uri, headers, entry, key)
|
|
68
|
+
headers = headers.merge("If-None-Match" => entry["etag"])
|
|
69
|
+
response = fetch_from_network(uri, headers)
|
|
70
|
+
|
|
71
|
+
if response.code == "304"
|
|
72
|
+
write_entry(key, body: entry["body"], code: entry["code"], etag: entry["etag"])
|
|
73
|
+
build_response(entry)
|
|
74
|
+
else
|
|
75
|
+
store_response(key, response)
|
|
76
|
+
response
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fetch_and_store(uri, headers, key)
|
|
81
|
+
response = fetch_from_network(uri, headers)
|
|
82
|
+
store_response(key, response) if response.is_a?(Net::HTTPSuccess)
|
|
83
|
+
response
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fetch_from_network(uri, headers)
|
|
87
|
+
request = Net::HTTP::Get.new(uri)
|
|
88
|
+
headers.each { |k, v| request[k] = v }
|
|
89
|
+
|
|
90
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
91
|
+
http.request(request)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def store_response(key, response)
|
|
96
|
+
write_entry(key, body: response.body, code: response.code, etag: response["ETag"])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def build_response(entry)
|
|
100
|
+
CachedResponse.new(entry["body"], entry["code"])
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class CachedResponse
|
|
105
|
+
attr_reader :body, :code
|
|
106
|
+
|
|
107
|
+
def initialize(body, code)
|
|
108
|
+
@body = body
|
|
109
|
+
@code = code
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def is_a?(klass)
|
|
113
|
+
return true if klass == Net::HTTPSuccess && @code.start_with?("2")
|
|
114
|
+
|
|
115
|
+
super
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def [](_header)
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -9,6 +9,10 @@ module GemChangelogDiff
|
|
|
9
9
|
FILENAMES = %w[CHANGELOG.md CHANGES.md History.md NEWS.md].freeze
|
|
10
10
|
VERSION_HEADING = /^##\s+\[?v?(\d+[^\]\s]+)/
|
|
11
11
|
|
|
12
|
+
def initialize(cache: nil)
|
|
13
|
+
@cache = cache
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
def entries_between(repo, current_version, newest_version)
|
|
13
17
|
content = fetch_changelog(repo)
|
|
14
18
|
return [] unless content
|
|
@@ -40,14 +44,22 @@ module GemChangelogDiff
|
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def execute_request(uri)
|
|
47
|
+
headers = request_headers
|
|
48
|
+
return @cache.get(uri, headers: headers) if @cache
|
|
49
|
+
|
|
43
50
|
request = Net::HTTP::Get.new(uri)
|
|
44
|
-
request[
|
|
45
|
-
|
|
51
|
+
headers.each { |k, v| request[k] = v }
|
|
52
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
53
|
+
end
|
|
46
54
|
|
|
55
|
+
def request_headers
|
|
56
|
+
headers = {
|
|
57
|
+
"Accept" => "application/vnd.github.v3+json",
|
|
58
|
+
"User-Agent" => "gem_changelog_diff/#{VERSION}"
|
|
59
|
+
}
|
|
47
60
|
token = GemChangelogDiff.configuration.github_token
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
61
|
+
headers["Authorization"] = "token #{token}" if token
|
|
62
|
+
headers
|
|
51
63
|
end
|
|
52
64
|
|
|
53
65
|
def parse_entries(content, current_version, newest_version)
|
|
@@ -18,6 +18,9 @@ module GemChangelogDiff
|
|
|
18
18
|
class_option :strategy, type: :string, default: "auto", desc: "Detection strategy (auto, outdated, lockfile)"
|
|
19
19
|
class_option :group, type: :string, desc: "Filter by Bundler group"
|
|
20
20
|
class_option :ignore, type: :array, desc: "Gems to skip"
|
|
21
|
+
class_option :no_cache, type: :boolean, default: false, desc: "Disable caching"
|
|
22
|
+
class_option :cache_ttl, type: :numeric, desc: "Cache TTL in seconds"
|
|
23
|
+
class_option :concurrency, type: :numeric, default: 4, desc: "Number of concurrent fetches"
|
|
21
24
|
|
|
22
25
|
desc "check [GEM...]", "Show changelog diffs for outdated gems"
|
|
23
26
|
def check(*gem_names)
|
|
@@ -30,11 +33,22 @@ module GemChangelogDiff
|
|
|
30
33
|
return
|
|
31
34
|
end
|
|
32
35
|
|
|
33
|
-
reports = build_reports(gems)
|
|
36
|
+
reports = with_spinner { build_reports(gems) }
|
|
34
37
|
formatter = Formatter.new(color: color_enabled?)
|
|
35
38
|
say formatter.format(reports)
|
|
36
39
|
end
|
|
37
40
|
|
|
41
|
+
desc "cache SUBCOMMAND", "Manage the cache"
|
|
42
|
+
def cache(subcommand = nil)
|
|
43
|
+
case subcommand
|
|
44
|
+
when "clear"
|
|
45
|
+
Cache.new.clear
|
|
46
|
+
say "Cache cleared."
|
|
47
|
+
else
|
|
48
|
+
say "Usage: gem_changelog_diff cache clear"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
38
52
|
desc "version", "Print version"
|
|
39
53
|
def version
|
|
40
54
|
say "gem_changelog_diff #{VERSION}"
|
|
@@ -49,6 +63,12 @@ module GemChangelogDiff
|
|
|
49
63
|
GemChangelogDiff.configuration.github_token = token if token
|
|
50
64
|
end
|
|
51
65
|
|
|
66
|
+
def build_cache
|
|
67
|
+
ttl = options[:cache_ttl] || GemChangelogDiff.configuration.cache_ttl
|
|
68
|
+
enabled = !options[:no_cache] && GemChangelogDiff.configuration.cache_enabled
|
|
69
|
+
Cache.new(ttl: ttl, enabled: enabled)
|
|
70
|
+
end
|
|
71
|
+
|
|
52
72
|
def detect_gems
|
|
53
73
|
case options[:strategy]
|
|
54
74
|
when "lockfile"
|
|
@@ -83,10 +103,15 @@ module GemChangelogDiff
|
|
|
83
103
|
end
|
|
84
104
|
|
|
85
105
|
def build_reports(gems)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
cache = build_cache
|
|
107
|
+
rubygems_client = RubygemsClient.new(cache: cache)
|
|
108
|
+
source_resolver = SourceResolver.new(
|
|
109
|
+
github_client: GithubClient.new(cache: cache),
|
|
110
|
+
changelog_parser: ChangelogParser.new(cache: cache)
|
|
111
|
+
)
|
|
112
|
+
fetcher = ConcurrentFetcher.new(concurrency: options[:concurrency])
|
|
113
|
+
|
|
114
|
+
fetcher.fetch_all(gems) { |gem| build_gem_report(gem, rubygems_client, source_resolver) }
|
|
90
115
|
end
|
|
91
116
|
|
|
92
117
|
def build_gem_report(gem, rubygems_client, source_resolver)
|
|
@@ -102,6 +127,18 @@ module GemChangelogDiff
|
|
|
102
127
|
{ gem: gem, releases: [], error: " #{e.message}" }
|
|
103
128
|
end
|
|
104
129
|
|
|
130
|
+
def with_spinner
|
|
131
|
+
unless options[:quiet] || !$stderr.tty?
|
|
132
|
+
require "tty-spinner"
|
|
133
|
+
spinner = TTY::Spinner.new("[:spinner] Fetching changelogs...", format: :dots, output: $stderr)
|
|
134
|
+
spinner.auto_spin
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
result = yield
|
|
138
|
+
spinner&.stop("Done!")
|
|
139
|
+
result
|
|
140
|
+
end
|
|
141
|
+
|
|
105
142
|
def color_enabled?
|
|
106
143
|
!options[:no_color]
|
|
107
144
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GemChangelogDiff
|
|
4
|
+
class ConcurrentFetcher
|
|
5
|
+
def initialize(concurrency: 4)
|
|
6
|
+
@concurrency = concurrency
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def fetch_all(items, &)
|
|
10
|
+
return items.map(&) if @concurrency <= 1
|
|
11
|
+
|
|
12
|
+
results = Array.new(items.size)
|
|
13
|
+
queue = Queue.new
|
|
14
|
+
items.each_with_index { |item, i| queue << [item, i] }
|
|
15
|
+
|
|
16
|
+
threads = spawn_workers(queue, results, &)
|
|
17
|
+
threads.each(&:join)
|
|
18
|
+
results
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def spawn_workers(queue, results, &block)
|
|
24
|
+
worker_count = [@concurrency, queue.size].min
|
|
25
|
+
worker_count.times.map do
|
|
26
|
+
Thread.new { process_queue(queue, results, &block) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def process_queue(queue, results, &block)
|
|
31
|
+
while (item, index = queue.pop(true))
|
|
32
|
+
results[index] = block.call(item)
|
|
33
|
+
end
|
|
34
|
+
rescue ThreadError
|
|
35
|
+
# Queue is empty
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -9,6 +9,10 @@ module GemChangelogDiff
|
|
|
9
9
|
TAG_VERSION_REGEX = /\Av?(\d+\..+)\z/
|
|
10
10
|
RATE_LIMIT_WARNING_THRESHOLD = 10
|
|
11
11
|
|
|
12
|
+
def initialize(cache: nil)
|
|
13
|
+
@cache = cache
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
def releases_between(repo, current_version, newest_version)
|
|
13
17
|
releases = fetch_releases(repo)
|
|
14
18
|
filter_releases(releases, current_version, newest_version)
|
|
@@ -29,17 +33,22 @@ module GemChangelogDiff
|
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def execute_request(uri)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
request["User-Agent"] = "gem_changelog_diff/#{VERSION}"
|
|
35
|
-
apply_auth(request)
|
|
36
|
+
headers = request_headers
|
|
37
|
+
return @cache.get(uri, headers: headers) if @cache
|
|
36
38
|
|
|
39
|
+
request = Net::HTTP::Get.new(uri)
|
|
40
|
+
headers.each { |k, v| request[k] = v }
|
|
37
41
|
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
def
|
|
44
|
+
def request_headers
|
|
45
|
+
headers = {
|
|
46
|
+
"Accept" => "application/vnd.github.v3+json",
|
|
47
|
+
"User-Agent" => "gem_changelog_diff/#{VERSION}"
|
|
48
|
+
}
|
|
41
49
|
token = GemChangelogDiff.configuration.github_token
|
|
42
|
-
|
|
50
|
+
headers["Authorization"] = "token #{token}" if token
|
|
51
|
+
headers
|
|
43
52
|
end
|
|
44
53
|
|
|
45
54
|
def handle_response(response)
|
|
@@ -8,6 +8,10 @@ module GemChangelogDiff
|
|
|
8
8
|
RUBYGEMS_API = "https://rubygems.org/api/v1/gems/%<name>s.json"
|
|
9
9
|
GITHUB_REPO_REGEX = %r{github\.com/([^/]+)/([^/]+)}
|
|
10
10
|
|
|
11
|
+
def initialize(cache: nil)
|
|
12
|
+
@cache = cache
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def repo_url(gem_name)
|
|
12
16
|
data = fetch_gem_data(gem_name)
|
|
13
17
|
return nil unless data
|
|
@@ -24,7 +28,7 @@ module GemChangelogDiff
|
|
|
24
28
|
|
|
25
29
|
def fetch_gem_data(gem_name)
|
|
26
30
|
uri = URI(format(RUBYGEMS_API, name: gem_name))
|
|
27
|
-
response = Net::HTTP.get_response(uri)
|
|
31
|
+
response = @cache ? @cache.get(uri) : Net::HTTP.get_response(uri)
|
|
28
32
|
return nil unless response.is_a?(Net::HTTPSuccess)
|
|
29
33
|
|
|
30
34
|
JSON.parse(response.body)
|
data/lib/gem_changelog_diff.rb
CHANGED
|
@@ -8,6 +8,7 @@ module GemChangelogDiff
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
require_relative "gem_changelog_diff/errors"
|
|
11
|
+
require_relative "gem_changelog_diff/cache"
|
|
11
12
|
require_relative "gem_changelog_diff/outdated_gem"
|
|
12
13
|
require_relative "gem_changelog_diff/detector"
|
|
13
14
|
require_relative "gem_changelog_diff/rubygems_client"
|
|
@@ -15,5 +16,6 @@ require_relative "gem_changelog_diff/lockfile_parser"
|
|
|
15
16
|
require_relative "gem_changelog_diff/github_client"
|
|
16
17
|
require_relative "gem_changelog_diff/changelog_parser"
|
|
17
18
|
require_relative "gem_changelog_diff/source_resolver"
|
|
19
|
+
require_relative "gem_changelog_diff/concurrent_fetcher"
|
|
18
20
|
require_relative "gem_changelog_diff/formatter"
|
|
19
21
|
require_relative "gem_changelog_diff/cli"
|
data/sig/gem_changelog_diff.rbs
CHANGED
|
@@ -24,6 +24,41 @@ module GemChangelogDiff
|
|
|
24
24
|
|
|
25
25
|
class Configuration
|
|
26
26
|
attr_accessor github_token: String?
|
|
27
|
+
attr_accessor cache_enabled: bool
|
|
28
|
+
attr_accessor cache_ttl: Integer
|
|
29
|
+
|
|
30
|
+
def initialize: () -> void
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Cache
|
|
34
|
+
DEFAULT_DIR: String
|
|
35
|
+
DEFAULT_TTL: Integer
|
|
36
|
+
|
|
37
|
+
def initialize: (?cache_dir: String, ?ttl: Integer, ?enabled: bool) -> void
|
|
38
|
+
def get: (URI::Generic uri, ?headers: Hash[String, String]) -> (Net::HTTPResponse | CachedResponse)
|
|
39
|
+
def clear: () -> void
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def cache_key: (URI::Generic uri) -> String
|
|
44
|
+
def entry_path: (String key) -> String
|
|
45
|
+
def read_entry: (String key) -> Hash[String, untyped]?
|
|
46
|
+
def write_entry: (String key, body: String, code: String, etag: String?) -> void
|
|
47
|
+
def fresh?: (Hash[String, untyped] entry) -> bool
|
|
48
|
+
def revalidate: (URI::Generic uri, Hash[String, String] headers, Hash[String, untyped] entry, String key) -> (Net::HTTPResponse | CachedResponse)
|
|
49
|
+
def fetch_and_store: (URI::Generic uri, Hash[String, String] headers, String key) -> Net::HTTPResponse
|
|
50
|
+
def fetch_from_network: (URI::Generic uri, Hash[String, String] headers) -> Net::HTTPResponse
|
|
51
|
+
def store_response: (String key, Net::HTTPResponse response) -> void
|
|
52
|
+
def build_response: (Hash[String, untyped] entry) -> CachedResponse
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class CachedResponse
|
|
56
|
+
attr_reader body: String
|
|
57
|
+
attr_reader code: String
|
|
58
|
+
|
|
59
|
+
def initialize: (String body, String code) -> void
|
|
60
|
+
def is_a?: (Class klass) -> bool
|
|
61
|
+
def []: (String _header) -> nil
|
|
27
62
|
end
|
|
28
63
|
|
|
29
64
|
class OutdatedGem
|
|
@@ -50,6 +85,7 @@ module GemChangelogDiff
|
|
|
50
85
|
RUBYGEMS_API: String
|
|
51
86
|
GITHUB_REPO_REGEX: Regexp
|
|
52
87
|
|
|
88
|
+
def initialize: (?cache: Cache?) -> void
|
|
53
89
|
def repo_url: (String gem_name) -> String?
|
|
54
90
|
def latest_version: (String gem_name) -> String?
|
|
55
91
|
|
|
@@ -81,13 +117,15 @@ module GemChangelogDiff
|
|
|
81
117
|
FILENAMES: Array[String]
|
|
82
118
|
VERSION_HEADING: Regexp
|
|
83
119
|
|
|
120
|
+
def initialize: (?cache: Cache?) -> void
|
|
84
121
|
def entries_between: (String repo, String current_version, String newest_version) -> Array[release_hash]
|
|
85
122
|
|
|
86
123
|
private
|
|
87
124
|
|
|
88
125
|
def fetch_changelog: (String repo) -> String?
|
|
89
126
|
def fetch_file: (String repo, String path) -> String?
|
|
90
|
-
def execute_request: (URI::Generic uri) -> Net::HTTPResponse
|
|
127
|
+
def execute_request: (URI::Generic uri) -> (Net::HTTPResponse | CachedResponse)
|
|
128
|
+
def request_headers: () -> Hash[String, String]
|
|
91
129
|
def parse_entries: (String content, String current_version, String newest_version) -> Array[release_hash]
|
|
92
130
|
def build_entry: (String version_str, String body, Gem::Version current, Gem::Version newest) -> release_hash?
|
|
93
131
|
def split_sections: (String content) -> Array[[String, String]]
|
|
@@ -104,22 +142,34 @@ module GemChangelogDiff
|
|
|
104
142
|
class GithubClient
|
|
105
143
|
RELEASES_URL: String
|
|
106
144
|
TAG_VERSION_REGEX: Regexp
|
|
145
|
+
RATE_LIMIT_WARNING_THRESHOLD: Integer
|
|
107
146
|
|
|
147
|
+
def initialize: (?cache: Cache?) -> void
|
|
108
148
|
def releases_between: (String repo, String current_version, String newest_version) -> Array[release_hash]
|
|
109
149
|
|
|
110
150
|
private
|
|
111
151
|
|
|
112
152
|
def fetch_releases: (String repo) -> Array[Hash[String, untyped]]
|
|
113
|
-
def execute_request: (URI::Generic uri) -> Net::HTTPResponse
|
|
114
|
-
def
|
|
115
|
-
def handle_response: (Net::HTTPResponse response) -> Array[Hash[String, untyped]]
|
|
116
|
-
def check_rate_limit: (Net::HTTPResponse response) -> void
|
|
153
|
+
def execute_request: (URI::Generic uri) -> (Net::HTTPResponse | CachedResponse)
|
|
154
|
+
def request_headers: () -> Hash[String, String]
|
|
155
|
+
def handle_response: (Net::HTTPResponse | CachedResponse response) -> Array[Hash[String, untyped]]
|
|
156
|
+
def check_rate_limit: (Net::HTTPResponse | CachedResponse response) -> void
|
|
117
157
|
def filter_releases: (Array[Hash[String, untyped]] releases, String current_version, String newest_version) -> Array[release_hash]
|
|
118
158
|
def build_release: (Hash[String, untyped] release, Gem::Version current, Gem::Version newest) -> release_hash?
|
|
119
159
|
def sort_releases: (Array[release_hash] releases) -> Array[release_hash]
|
|
120
160
|
def extract_version: (String? tag) -> String?
|
|
121
161
|
end
|
|
122
162
|
|
|
163
|
+
class ConcurrentFetcher
|
|
164
|
+
def initialize: (?concurrency: Integer) -> void
|
|
165
|
+
def fetch_all: [T, U] (Array[T], &) -> Array[U]
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def spawn_workers: (Queue[untyped], Array[untyped], &) -> Array[Thread]
|
|
170
|
+
def process_queue: (Queue[untyped], Array[untyped], &) -> void
|
|
171
|
+
end
|
|
172
|
+
|
|
123
173
|
type gem_report = {
|
|
124
174
|
gem: OutdatedGem,
|
|
125
175
|
releases: Array[release_hash],
|
|
@@ -149,13 +199,16 @@ module GemChangelogDiff
|
|
|
149
199
|
class CLI < Thor
|
|
150
200
|
def self.exit_on_failure?: () -> bool
|
|
151
201
|
|
|
152
|
-
def check: () -> void
|
|
202
|
+
def check: (*String gem_names) -> void
|
|
203
|
+
def cache: (?String? subcommand) -> void
|
|
153
204
|
def version: () -> void
|
|
154
205
|
|
|
155
206
|
private
|
|
156
207
|
|
|
208
|
+
def with_spinner: [T] () { () -> T } -> T
|
|
157
209
|
def color_enabled?: () -> bool
|
|
158
210
|
def configure_token: () -> void
|
|
211
|
+
def build_cache: () -> Cache
|
|
159
212
|
def detect_gems: () -> Array[OutdatedGem]
|
|
160
213
|
def detect_with_fallback: () -> Array[OutdatedGem]
|
|
161
214
|
def detect_via_lockfile: () -> Array[OutdatedGem]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gem_changelog_diff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.6'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: tty-spinner
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.9'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.9'
|
|
40
54
|
description: CLI that shows you the changelog diff for each gem before you bundle
|
|
41
55
|
update, pulled from GitHub releases.
|
|
42
56
|
email:
|
|
@@ -57,8 +71,10 @@ files:
|
|
|
57
71
|
- codecov.yml
|
|
58
72
|
- exe/gem_changelog_diff
|
|
59
73
|
- lib/gem_changelog_diff.rb
|
|
74
|
+
- lib/gem_changelog_diff/cache.rb
|
|
60
75
|
- lib/gem_changelog_diff/changelog_parser.rb
|
|
61
76
|
- lib/gem_changelog_diff/cli.rb
|
|
77
|
+
- lib/gem_changelog_diff/concurrent_fetcher.rb
|
|
62
78
|
- lib/gem_changelog_diff/configuration.rb
|
|
63
79
|
- lib/gem_changelog_diff/detector.rb
|
|
64
80
|
- lib/gem_changelog_diff/errors.rb
|