gem_changelog_diff 0.9.0 → 1.0.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/.yardopts +6 -0
- data/CHANGELOG.md +14 -1
- data/CONTRIBUTING.md +57 -0
- data/README.md +22 -2
- data/ROADMAP.md +0 -13
- data/SECURITY.md +33 -0
- data/lib/gem_changelog_diff/cache.rb +10 -0
- data/lib/gem_changelog_diff/changelog_parser.rb +7 -0
- data/lib/gem_changelog_diff/cli.rb +15 -0
- data/lib/gem_changelog_diff/concurrent_fetcher.rb +6 -0
- data/lib/gem_changelog_diff/config_loader.rb +3 -0
- data/lib/gem_changelog_diff/configuration.rb +20 -0
- data/lib/gem_changelog_diff/detector.rb +4 -0
- data/lib/gem_changelog_diff/errors.rb +7 -0
- data/lib/gem_changelog_diff/exit_code.rb +1 -0
- data/lib/gem_changelog_diff/formatter.rb +1 -0
- data/lib/gem_changelog_diff/formatters/base.rb +10 -0
- data/lib/gem_changelog_diff/formatters/json.rb +1 -0
- data/lib/gem_changelog_diff/formatters/markdown.rb +1 -0
- data/lib/gem_changelog_diff/formatters/text.rb +1 -0
- data/lib/gem_changelog_diff/github_client.rb +6 -0
- data/lib/gem_changelog_diff/interactive.rb +3 -0
- data/lib/gem_changelog_diff/lockfile_parser.rb +5 -0
- data/lib/gem_changelog_diff/outdated_gem.rb +7 -0
- data/lib/gem_changelog_diff/rubygems_client.rb +7 -0
- data/lib/gem_changelog_diff/source_resolver.rb +6 -0
- data/lib/gem_changelog_diff/tag_matcher.rb +4 -0
- data/lib/gem_changelog_diff/uri_resolver.rb +5 -0
- data/lib/gem_changelog_diff/version.rb +1 -1
- data/lib/gem_changelog_diff.rb +2 -0
- data/sig/gem_changelog_diff.rbs +1 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d30e61b6a53988f812beaba85ab06f90dfbe3968a2b9c6fe036813c81e233ad8
|
|
4
|
+
data.tar.gz: e163dd6a353a7abf62a2fc02a841bf19854e8f5a427f194572e169a5f63384b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5083b02d3caf6f4fdcc1f6695ff4f1189789069b44c34fb70ee8246ab5e909024ef2d342805077d0032cd298f7d5a57ec827961c1935063ab9b78015dc8e5dec
|
|
7
|
+
data.tar.gz: d5f330c0b92aeb969281928b976672a1d9c0cf9a886f1df8e2f37a84be03635426ce86e40709f22adaf7a59e0f2e0c4b3d18790decdf48275d99838ef0d813e6
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.0] - 2026-06-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- YARD documentation on all public classes and methods
|
|
15
|
+
- `CONTRIBUTING.md` with development setup, architecture overview, and PR requirements
|
|
16
|
+
- `SECURITY.md` with vulnerability reporting instructions
|
|
17
|
+
- API stability guarantee: semver contract begins at 1.0.0
|
|
18
|
+
- `.yardopts` configuration and `yard` development dependency
|
|
19
|
+
- `documentation_uri` in gemspec metadata
|
|
20
|
+
- `gh auth token` as automatic token source for developers with GitHub CLI installed
|
|
21
|
+
|
|
10
22
|
## [0.9.0] - 2026-06-18
|
|
11
23
|
|
|
12
24
|
### Added
|
|
@@ -131,7 +143,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
131
143
|
- Plain text formatter for changelog output
|
|
132
144
|
- Full end-to-end pipeline: detect → lookup → fetch → format
|
|
133
145
|
|
|
134
|
-
[Unreleased]: https://github.com/eclectic-coding/gem_changelog_diff/compare/
|
|
146
|
+
[Unreleased]: https://github.com/eclectic-coding/gem_changelog_diff/compare/v1.0.0...HEAD
|
|
147
|
+
[1.0.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v1.0.0
|
|
135
148
|
[0.9.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.9.0
|
|
136
149
|
[0.8.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.8.0
|
|
137
150
|
[0.7.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.7.0
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/eclectic-coding/gem_changelog_diff.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/eclectic-coding/gem_changelog_diff.git
|
|
9
|
+
cd gem_changelog_diff
|
|
10
|
+
bin/setup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running Tests
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle exec rake # Run all checks (rubocop, bundler-audit, rspec)
|
|
17
|
+
bundle exec rake spec # Run tests only
|
|
18
|
+
bundle exec rspec spec/path_spec.rb # Run a single file
|
|
19
|
+
bundle exec rspec spec/path_spec.rb:42 # Run a single example
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Integration tests use VCR cassettes committed to the repo. To re-record:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
GITHUB_TOKEN=ghp_... bundle exec rspec spec/integration/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Linting
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle exec rubocop # Check style
|
|
32
|
+
bundle exec rubocop -a # Auto-correct
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
The pipeline flows through these stages:
|
|
38
|
+
|
|
39
|
+
1. **Detection** -- `Detector` runs `bundle outdated` (or `LockfileParser` reads `Gemfile.lock`) to find outdated gems
|
|
40
|
+
2. **Resolution** -- `RubygemsClient` queries the RubyGems API, `UriResolver` extracts the GitHub slug
|
|
41
|
+
3. **Fetching** -- `GithubClient` fetches releases from the GitHub API; `ChangelogParser` parses `CHANGELOG.md` as a fallback. `SourceResolver` orchestrates both
|
|
42
|
+
4. **Concurrency** -- `ConcurrentFetcher` runs fetches in a thread pool
|
|
43
|
+
5. **Formatting** -- `Formatters::Text`, `Json`, or `Markdown` render the output
|
|
44
|
+
6. **CLI** -- `CLI` (Thor) ties everything together with flags, config, and exit codes
|
|
45
|
+
|
|
46
|
+
## Branch Workflow
|
|
47
|
+
|
|
48
|
+
All feature work lives on `feature/<version>-<scope>` branches. Every branch produces two commits:
|
|
49
|
+
|
|
50
|
+
1. **Feature commit** -- implementation + specs. Run `bundle exec rake` and fix all failures before committing.
|
|
51
|
+
2. **Docs commit** -- update `CHANGELOG.md`, remove shipped items from `ROADMAP.md`, update `README.md` if needed.
|
|
52
|
+
|
|
53
|
+
## Pull Request Requirements
|
|
54
|
+
|
|
55
|
+
- All CI checks must pass (lint, security audit, tests on Ruby 3.3, 3.4, and 4.0)
|
|
56
|
+
- 100% line coverage required
|
|
57
|
+
- RuboCop must report zero offenses
|
data/README.md
CHANGED
|
@@ -25,8 +25,10 @@ CLI that shows you the changelog diff for each gem before you `bundle update`, p
|
|
|
25
25
|
- [Concurrency](#concurrency)
|
|
26
26
|
- [Exit Codes](#exit-codes)
|
|
27
27
|
- [Configuration File](#configuration-file)
|
|
28
|
+
- [Stability](#stability)
|
|
28
29
|
- [Development](#development)
|
|
29
30
|
- [Contributing](#contributing)
|
|
31
|
+
- [Security](#security)
|
|
30
32
|
- [License](#license)
|
|
31
33
|
|
|
32
34
|
## Installation
|
|
@@ -73,7 +75,11 @@ export GITHUB_TOKEN=ghp_your_token
|
|
|
73
75
|
gem_changelog_diff
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
Token resolution priority: `--token` flag → `GITHUB_TOKEN` env → Rails credentials → config file.
|
|
78
|
+
Token resolution priority: `--token` flag → `GITHUB_TOKEN` env → Rails credentials → `gh auth token` → config file.
|
|
79
|
+
|
|
80
|
+
#### GitHub CLI
|
|
81
|
+
|
|
82
|
+
If you have the [GitHub CLI](https://cli.github.com/) installed and authenticated (`gh auth login`), the token is picked up automatically — no configuration needed.
|
|
77
83
|
|
|
78
84
|
#### Rails Credentials
|
|
79
85
|
|
|
@@ -249,6 +255,14 @@ CLI flags always take priority over config file values.
|
|
|
249
255
|
|
|
250
256
|
[Back to top](#gemchangelogdiff)
|
|
251
257
|
|
|
258
|
+
## Stability
|
|
259
|
+
|
|
260
|
+
Starting with version 1.0.0, this gem follows [Semantic Versioning](https://semver.org/). The public API is frozen — breaking changes require a major version bump.
|
|
261
|
+
|
|
262
|
+
A future Bundler plugin (`bundler-changelog-diff`) may provide deeper integration, but the standalone CLI will remain the primary interface.
|
|
263
|
+
|
|
264
|
+
[Back to top](#gemchangelogdiff)
|
|
265
|
+
|
|
252
266
|
## Development
|
|
253
267
|
|
|
254
268
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
@@ -259,7 +273,13 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
|
259
273
|
|
|
260
274
|
## Contributing
|
|
261
275
|
|
|
262
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/eclectic-coding/gem_changelog_diff.
|
|
276
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/eclectic-coding/gem_changelog_diff. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
277
|
+
|
|
278
|
+
[Back to top](#gemchangelogdiff)
|
|
279
|
+
|
|
280
|
+
## Security
|
|
281
|
+
|
|
282
|
+
To report a security vulnerability, see [SECURITY.md](SECURITY.md).
|
|
263
283
|
|
|
264
284
|
[Back to top](#gemchangelogdiff)
|
|
265
285
|
|
data/ROADMAP.md
CHANGED
|
@@ -2,16 +2,3 @@
|
|
|
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
|
-
## 1.0.0 -- Stable Release
|
|
6
|
-
|
|
7
|
-
Public API is frozen. Semantic versioning contract begins.
|
|
8
|
-
|
|
9
|
-
- API stability guarantee: breaking changes require a major version bump
|
|
10
|
-
- YARD documentation on all public classes and methods
|
|
11
|
-
- `CONTRIBUTING.md` with development setup, testing, and architecture overview
|
|
12
|
-
- `SECURITY.md` with vulnerability reporting instructions
|
|
13
|
-
- Document future Bundler plugin possibility (`bundler-changelog-diff`)
|
|
14
|
-
|
|
15
|
-
**Dependencies:** `yard` (development)
|
|
16
|
-
|
|
17
|
-
---
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|---------|-----------|
|
|
7
|
+
| 1.x | Yes |
|
|
8
|
+
| < 1.0 | No |
|
|
9
|
+
|
|
10
|
+
## Reporting a Vulnerability
|
|
11
|
+
|
|
12
|
+
If you discover a security vulnerability in gem_changelog_diff, please report it responsibly.
|
|
13
|
+
|
|
14
|
+
**Email:** chuck@eclecticcoding.com
|
|
15
|
+
|
|
16
|
+
Please include:
|
|
17
|
+
|
|
18
|
+
- A description of the vulnerability
|
|
19
|
+
- Steps to reproduce the issue
|
|
20
|
+
- The potential impact
|
|
21
|
+
|
|
22
|
+
**Expected response time:** You should receive an acknowledgment within 48 hours. A fix or mitigation plan will be communicated within 7 days.
|
|
23
|
+
|
|
24
|
+
## Scope
|
|
25
|
+
|
|
26
|
+
Security issues relevant to this gem include:
|
|
27
|
+
|
|
28
|
+
- Command injection via gem names or user-supplied arguments
|
|
29
|
+
- Credential leakage (GitHub tokens in logs, cached responses, or error messages)
|
|
30
|
+
- Unsafe deserialization of cached data or API responses
|
|
31
|
+
- Path traversal in file operations (cache, config, output)
|
|
32
|
+
|
|
33
|
+
Issues related to the GitHub API, RubyGems API, or upstream dependencies should be reported to their respective maintainers.
|
|
@@ -6,6 +6,7 @@ require "json"
|
|
|
6
6
|
require "net/http"
|
|
7
7
|
|
|
8
8
|
module GemChangelogDiff
|
|
9
|
+
# Disk-based HTTP response cache with ETag revalidation.
|
|
9
10
|
class Cache
|
|
10
11
|
DEFAULT_DIR = File.join(Dir.home, ".cache", "gem_changelog_diff")
|
|
11
12
|
DEFAULT_TTL = 86_400
|
|
@@ -16,6 +17,10 @@ module GemChangelogDiff
|
|
|
16
17
|
@enabled = enabled
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
# Fetches a response, returning cached data when available.
|
|
21
|
+
# @param uri [URI::Generic] the request URI
|
|
22
|
+
# @param headers [Hash<String, String>] additional HTTP headers
|
|
23
|
+
# @return [Net::HTTPResponse, CachedResponse]
|
|
19
24
|
def get(uri, headers: {})
|
|
20
25
|
return fetch_from_network(uri, headers) unless @enabled
|
|
21
26
|
|
|
@@ -31,6 +36,8 @@ module GemChangelogDiff
|
|
|
31
36
|
end
|
|
32
37
|
end
|
|
33
38
|
|
|
39
|
+
# Deletes all cached data.
|
|
40
|
+
# @return [void]
|
|
34
41
|
def clear
|
|
35
42
|
FileUtils.rm_rf(@cache_dir)
|
|
36
43
|
end
|
|
@@ -104,7 +111,10 @@ module GemChangelogDiff
|
|
|
104
111
|
end
|
|
105
112
|
end
|
|
106
113
|
|
|
114
|
+
# Lightweight stand-in for Net::HTTPResponse built from cached data.
|
|
107
115
|
class CachedResponse
|
|
116
|
+
# @return [String] the response body
|
|
117
|
+
# @return [String] the HTTP status code
|
|
108
118
|
attr_reader :body, :code
|
|
109
119
|
|
|
110
120
|
def initialize(body, code)
|
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
6
|
module GemChangelogDiff
|
|
7
|
+
# Parses CHANGELOG.md files from GitHub repos as a fallback source.
|
|
7
8
|
class ChangelogParser
|
|
8
9
|
CONTENTS_URL = "https://api.github.com/repos/%<repo>s/contents/%<path>s"
|
|
9
10
|
FILENAMES = %w[CHANGELOG.md CHANGES.md History.md NEWS.md].freeze
|
|
@@ -13,6 +14,12 @@ module GemChangelogDiff
|
|
|
13
14
|
@cache = cache
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
# Parses changelog entries between two versions from a repo's changelog file.
|
|
18
|
+
# @param repo [String] GitHub "owner/repo" slug
|
|
19
|
+
# @param current_version [String] currently locked version (exclusive)
|
|
20
|
+
# @param newest_version [String] target version (inclusive)
|
|
21
|
+
# @return [Array<Hash>] release hashes with :tag_name, :name, :published_at, :body
|
|
22
|
+
# @raise [NetworkError] on HTTP connection failures
|
|
16
23
|
def entries_between(repo, current_version, newest_version)
|
|
17
24
|
content = fetch_changelog(repo)
|
|
18
25
|
return [] unless content
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
3
4
|
require "thor"
|
|
4
5
|
|
|
5
6
|
module GemChangelogDiff
|
|
7
|
+
# Thor-based command-line interface.
|
|
6
8
|
class CLI < Thor
|
|
7
9
|
def self.exit_on_failure?
|
|
8
10
|
true
|
|
@@ -29,6 +31,7 @@ module GemChangelogDiff
|
|
|
29
31
|
class_option :timeout, type: :numeric, desc: "Per-request timeout in seconds (default: 10)"
|
|
30
32
|
|
|
31
33
|
desc "check [GEM...]", "Show changelog diffs for outdated gems"
|
|
34
|
+
# @param gem_names [Array<String>] optional gem names to filter
|
|
32
35
|
def check(*gem_names)
|
|
33
36
|
setup_environment
|
|
34
37
|
gems = filter_gems(detect_gems, gem_names)
|
|
@@ -42,6 +45,9 @@ module GemChangelogDiff
|
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
desc "show GEM FROM_VERSION TO_VERSION", "Show changelog between two versions of a gem"
|
|
48
|
+
# @param gem_name [String] the gem to look up
|
|
49
|
+
# @param from_version [String] current version (exclusive)
|
|
50
|
+
# @param to_version [String] target version (inclusive)
|
|
45
51
|
def show(gem_name, from_version, to_version)
|
|
46
52
|
setup_environment
|
|
47
53
|
gem = OutdatedGem.new(name: gem_name, current_version: from_version, newest_version: to_version)
|
|
@@ -97,6 +103,7 @@ module GemChangelogDiff
|
|
|
97
103
|
def configure_token
|
|
98
104
|
token = options[:token] || ENV.fetch("GITHUB_TOKEN", nil)
|
|
99
105
|
token ||= rails_credentials_token
|
|
106
|
+
token ||= gh_cli_token
|
|
100
107
|
token ||= GemChangelogDiff.configuration.github_token
|
|
101
108
|
GemChangelogDiff.configuration.github_token = token if token
|
|
102
109
|
end
|
|
@@ -106,6 +113,14 @@ module GemChangelogDiff
|
|
|
106
113
|
GemChangelogDiff.configuration.request_timeout = timeout
|
|
107
114
|
end
|
|
108
115
|
|
|
116
|
+
def gh_cli_token
|
|
117
|
+
output, status = Open3.capture2("gh", "auth", "token")
|
|
118
|
+
token = output.strip
|
|
119
|
+
token if status.success? && !token.empty?
|
|
120
|
+
rescue Errno::ENOENT
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
109
124
|
def rails_credentials_token
|
|
110
125
|
return unless defined?(Rails) && Rails.application.respond_to?(:credentials)
|
|
111
126
|
|
|
@@ -3,11 +3,17 @@
|
|
|
3
3
|
require "timeout"
|
|
4
4
|
|
|
5
5
|
module GemChangelogDiff
|
|
6
|
+
# Thread pool for fetching multiple gems in parallel.
|
|
6
7
|
class ConcurrentFetcher
|
|
7
8
|
def initialize(concurrency: 4)
|
|
8
9
|
@concurrency = concurrency
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
# Processes items concurrently, returning results in order.
|
|
13
|
+
# @param items [Array] items to process
|
|
14
|
+
# @yield [item] block called for each item
|
|
15
|
+
# @return [Array] results in the same order as items
|
|
16
|
+
# @raise [NetworkError] if the total timeout is exceeded
|
|
11
17
|
def fetch_all(items, &)
|
|
12
18
|
return items.map(&) if @concurrency <= 1
|
|
13
19
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "yaml"
|
|
4
4
|
|
|
5
5
|
module GemChangelogDiff
|
|
6
|
+
# Loads and merges YAML config from user and project locations.
|
|
6
7
|
class ConfigLoader
|
|
7
8
|
USER_CONFIG_DIR = File.join(Dir.home, ".config", "gem_changelog_diff")
|
|
8
9
|
USER_CONFIG_PATH = File.join(USER_CONFIG_DIR, "config.yml")
|
|
@@ -12,6 +13,8 @@ module GemChangelogDiff
|
|
|
12
13
|
@project_dir = project_dir
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
# Loads config from user and project files, with project taking priority.
|
|
17
|
+
# @return [Hash<Symbol, Object>]
|
|
15
18
|
def load
|
|
16
19
|
user_config = load_file(USER_CONFIG_PATH)
|
|
17
20
|
project_config = load_file(project_config_path)
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GemChangelogDiff
|
|
4
|
+
# Holds runtime settings for the gem (token, cache, format, timeouts).
|
|
4
5
|
class Configuration
|
|
6
|
+
# @return [String, nil] GitHub personal access token
|
|
7
|
+
# @return [Boolean] whether disk caching is enabled
|
|
8
|
+
# @return [Integer] cache time-to-live in seconds
|
|
9
|
+
# @return [String] default output format ("text", "json", "markdown")
|
|
10
|
+
# @return [Integer] number of concurrent fetch threads
|
|
11
|
+
# @return [Array<String>] gem names to skip
|
|
12
|
+
# @return [Boolean] whether to disable colored output
|
|
13
|
+
# @return [Integer] per-request HTTP timeout in seconds
|
|
14
|
+
# @return [Integer] total operation timeout in seconds
|
|
5
15
|
attr_accessor :github_token, :cache_enabled, :cache_ttl,
|
|
6
16
|
:default_format, :concurrency, :ignore_gems, :no_color,
|
|
7
17
|
:request_timeout, :total_timeout
|
|
@@ -20,6 +30,9 @@ module GemChangelogDiff
|
|
|
20
30
|
@total_timeout = 120
|
|
21
31
|
end
|
|
22
32
|
|
|
33
|
+
# Applies a hash of settings, ignoring unknown keys and nil values.
|
|
34
|
+
# @param hash [Hash<Symbol, Object>] configuration key-value pairs
|
|
35
|
+
# @return [void]
|
|
23
36
|
def apply(hash)
|
|
24
37
|
hash.each do |key, value|
|
|
25
38
|
public_send(:"#{key}=", value) if VALID_KEYS.include?(key) && !value.nil?
|
|
@@ -27,14 +40,21 @@ module GemChangelogDiff
|
|
|
27
40
|
end
|
|
28
41
|
end
|
|
29
42
|
|
|
43
|
+
# Returns the global configuration instance.
|
|
44
|
+
# @return [Configuration]
|
|
30
45
|
def self.configuration
|
|
31
46
|
@configuration ||= Configuration.new
|
|
32
47
|
end
|
|
33
48
|
|
|
49
|
+
# Yields the global configuration for modification.
|
|
50
|
+
# @yieldparam config [Configuration]
|
|
51
|
+
# @return [void]
|
|
34
52
|
def self.configure
|
|
35
53
|
yield(configuration)
|
|
36
54
|
end
|
|
37
55
|
|
|
56
|
+
# Resets the global configuration to defaults.
|
|
57
|
+
# @return [Configuration]
|
|
38
58
|
def self.reset_configuration!
|
|
39
59
|
@configuration = Configuration.new
|
|
40
60
|
end
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
require "open3"
|
|
4
4
|
|
|
5
5
|
module GemChangelogDiff
|
|
6
|
+
# Detects outdated gems by parsing `bundle outdated --parseable` output.
|
|
6
7
|
class Detector
|
|
7
8
|
PARSEABLE_REGEX = /\A(\S+)\s+\(newest\s+([^,]+),\s+installed\s+([^,)]+)/
|
|
8
9
|
|
|
10
|
+
# Runs bundle outdated and returns the list of outdated gems.
|
|
11
|
+
# @return [Array<OutdatedGem>]
|
|
12
|
+
# @raise [Error] if bundle outdated fails
|
|
9
13
|
def detect
|
|
10
14
|
output = run_bundle_outdated
|
|
11
15
|
parse(output)
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GemChangelogDiff
|
|
4
|
+
# Raised when a gem's source repository cannot be found on GitHub.
|
|
4
5
|
class RepoNotFoundError < Error; end
|
|
6
|
+
|
|
7
|
+
# Raised when the GitHub API returns an unexpected error response.
|
|
5
8
|
class GitHubAPIError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when the GitHub API rate limit is exceeded.
|
|
6
11
|
class RateLimitError < GitHubAPIError; end
|
|
12
|
+
|
|
13
|
+
# Raised on HTTP connection failures (timeouts, DNS, SSL).
|
|
7
14
|
class NetworkError < Error; end
|
|
8
15
|
end
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GemChangelogDiff
|
|
4
|
+
# Output formatters for rendering gem reports.
|
|
4
5
|
module Formatters
|
|
6
|
+
# Builds a formatter instance for the given format name.
|
|
7
|
+
# @param format [String] "text", "json", or "markdown"
|
|
8
|
+
# @param color [Boolean] whether to enable ANSI colors
|
|
9
|
+
# @return [Text, Json, Markdown]
|
|
10
|
+
# @raise [ArgumentError] for unknown formats
|
|
5
11
|
def self.build(format:, color: false)
|
|
6
12
|
case format
|
|
7
13
|
when "text" then Text.new(color: color)
|
|
@@ -11,11 +17,15 @@ module GemChangelogDiff
|
|
|
11
17
|
end
|
|
12
18
|
end
|
|
13
19
|
|
|
20
|
+
# Abstract base class for output formatters.
|
|
14
21
|
class Base
|
|
15
22
|
def initialize(color: false)
|
|
16
23
|
@color = color
|
|
17
24
|
end
|
|
18
25
|
|
|
26
|
+
# Formats gem reports into a string.
|
|
27
|
+
# @param _gem_reports [Array<Hash>] list of gem report hashes
|
|
28
|
+
# @return [String]
|
|
19
29
|
def format(_gem_reports)
|
|
20
30
|
raise NotImplementedError, "#{self.class}#format must be implemented"
|
|
21
31
|
end
|
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
6
|
module GemChangelogDiff
|
|
7
|
+
# Fetches release notes from the GitHub Releases API with pagination.
|
|
7
8
|
class GithubClient
|
|
8
9
|
RELEASES_URL = "https://api.github.com/repos/%<repo>s/releases"
|
|
9
10
|
RATE_LIMIT_WARNING_THRESHOLD = 10
|
|
@@ -13,6 +14,11 @@ module GemChangelogDiff
|
|
|
13
14
|
@cache = cache
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
# Returns releases between two versions, sorted newest first.
|
|
18
|
+
# @param repo [String] GitHub "owner/repo" slug
|
|
19
|
+
# @param current_version [String] currently locked version (exclusive)
|
|
20
|
+
# @param newest_version [String] target version (inclusive)
|
|
21
|
+
# @return [Array<Hash>] release hashes with :tag_name, :name, :published_at, :body
|
|
16
22
|
def releases_between(repo, current_version, newest_version)
|
|
17
23
|
gem_name = repo.split("/").last
|
|
18
24
|
@active_matcher = TagMatcher.new(gem_name: gem_name)
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
require "tty-prompt"
|
|
4
4
|
|
|
5
5
|
module GemChangelogDiff
|
|
6
|
+
# Presents a multi-select prompt for choosing which gems to check.
|
|
6
7
|
class Interactive
|
|
7
8
|
def initialize(gems:)
|
|
8
9
|
@gems = gems
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
# Displays the selection prompt and returns chosen gems.
|
|
13
|
+
# @return [Array<OutdatedGem>]
|
|
11
14
|
def select
|
|
12
15
|
prompt = TTY::Prompt.new
|
|
13
16
|
prompt.multi_select("Select gems to check:",
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
require "bundler"
|
|
4
4
|
|
|
5
5
|
module GemChangelogDiff
|
|
6
|
+
# Detects outdated gems by comparing Gemfile.lock specs to RubyGems.
|
|
6
7
|
class LockfileParser
|
|
7
8
|
def initialize(rubygems_client: RubygemsClient.new)
|
|
8
9
|
@rubygems_client = rubygems_client
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
# Parses the lockfile and returns gems with newer versions available.
|
|
13
|
+
# @param lockfile_path [String] path to Gemfile.lock
|
|
14
|
+
# @return [Array<OutdatedGem>]
|
|
15
|
+
# @raise [Error] if the lockfile is not found
|
|
11
16
|
def detect(lockfile_path: "Gemfile.lock")
|
|
12
17
|
content = File.read(lockfile_path)
|
|
13
18
|
parser = Bundler::LockfileParser.new(content)
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GemChangelogDiff
|
|
4
|
+
# Immutable value object representing a gem with an available update.
|
|
5
|
+
# @!attribute [r] name
|
|
6
|
+
# @return [String] the gem name
|
|
7
|
+
# @!attribute [r] current_version
|
|
8
|
+
# @return [String] the currently locked version
|
|
9
|
+
# @!attribute [r] newest_version
|
|
10
|
+
# @return [String] the latest available version
|
|
4
11
|
OutdatedGem = Data.define(:name, :current_version, :newest_version)
|
|
5
12
|
end
|
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "json"
|
|
5
5
|
|
|
6
6
|
module GemChangelogDiff
|
|
7
|
+
# Queries the RubyGems.org API for gem metadata and source repository URLs.
|
|
7
8
|
class RubygemsClient
|
|
8
9
|
RUBYGEMS_API = "https://rubygems.org/api/v1/gems/%<name>s.json"
|
|
9
10
|
|
|
@@ -12,6 +13,9 @@ module GemChangelogDiff
|
|
|
12
13
|
@uri_resolver = uri_resolver
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
# Looks up the GitHub repository slug for a gem.
|
|
17
|
+
# @param gem_name [String]
|
|
18
|
+
# @return [String, nil] "owner/repo" slug, or nil if not found
|
|
15
19
|
def repo_url(gem_name)
|
|
16
20
|
data = fetch_gem_data(gem_name)
|
|
17
21
|
return nil unless data
|
|
@@ -19,6 +23,9 @@ module GemChangelogDiff
|
|
|
19
23
|
@uri_resolver.resolve(data)
|
|
20
24
|
end
|
|
21
25
|
|
|
26
|
+
# Returns the latest version string for a gem from RubyGems.
|
|
27
|
+
# @param gem_name [String]
|
|
28
|
+
# @return [String, nil]
|
|
22
29
|
def latest_version(gem_name)
|
|
23
30
|
data = fetch_gem_data(gem_name)
|
|
24
31
|
data&.dig("version")
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GemChangelogDiff
|
|
4
|
+
# Fetches release notes, trying GitHub Releases first then changelog files.
|
|
4
5
|
class SourceResolver
|
|
5
6
|
def initialize(github_client: GithubClient.new, changelog_parser: ChangelogParser.new)
|
|
6
7
|
@github_client = github_client
|
|
7
8
|
@changelog_parser = changelog_parser
|
|
8
9
|
end
|
|
9
10
|
|
|
11
|
+
# Returns release entries between two versions for a given repo.
|
|
12
|
+
# @param repo [String] GitHub "owner/repo" slug
|
|
13
|
+
# @param current_version [String] currently locked version (exclusive)
|
|
14
|
+
# @param newest_version [String] target version (inclusive)
|
|
15
|
+
# @return [Array<Hash>] release hashes with :tag_name, :name, :published_at, :body
|
|
10
16
|
def resolve(repo, current_version, newest_version)
|
|
11
17
|
releases = @github_client.releases_between(repo, current_version, newest_version)
|
|
12
18
|
return releases unless releases.empty?
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GemChangelogDiff
|
|
4
|
+
# Extracts version strings from various Git tag formats.
|
|
4
5
|
class TagMatcher
|
|
5
6
|
STANDARD_PATTERN = /\A(?:release-)?v?(\d+\..+)\z/
|
|
6
7
|
|
|
@@ -8,6 +9,9 @@ module GemChangelogDiff
|
|
|
8
9
|
@gem_name = gem_name
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
# Parses a version string from a tag name.
|
|
13
|
+
# @param tag [String, nil] the Git tag name
|
|
14
|
+
# @return [String, nil] extracted version, or nil if unparseable
|
|
11
15
|
def extract_version(tag)
|
|
12
16
|
return nil if tag.nil? || tag.strip.empty?
|
|
13
17
|
|
|
@@ -4,6 +4,7 @@ require "net/http"
|
|
|
4
4
|
require "uri"
|
|
5
5
|
|
|
6
6
|
module GemChangelogDiff
|
|
7
|
+
# Resolves a gem's RubyGems metadata to a GitHub owner/repo slug.
|
|
7
8
|
class UriResolver
|
|
8
9
|
GITHUB_REGEX = %r{github\.com/([^/]+)/([^/]+)}
|
|
9
10
|
NON_GITHUB_HOSTS = {
|
|
@@ -15,6 +16,10 @@ module GemChangelogDiff
|
|
|
15
16
|
URI_FIELDS = %w[source_code_uri homepage_uri bug_tracker_uri].freeze
|
|
16
17
|
MAX_REDIRECTS = 3
|
|
17
18
|
|
|
19
|
+
# Extracts a GitHub slug from gem metadata, following redirects.
|
|
20
|
+
# @param gem_data [Hash<String, Object>] RubyGems API response data
|
|
21
|
+
# @return [String, nil] "owner/repo" slug, or nil if not on GitHub
|
|
22
|
+
# @raise [RepoNotFoundError] if hosted on a non-GitHub platform
|
|
18
23
|
def resolve(gem_data)
|
|
19
24
|
uris = extract_uris(gem_data)
|
|
20
25
|
return nil if uris.empty?
|
data/lib/gem_changelog_diff.rb
CHANGED
|
@@ -4,7 +4,9 @@ require_relative "gem_changelog_diff/version"
|
|
|
4
4
|
require_relative "gem_changelog_diff/configuration"
|
|
5
5
|
require_relative "gem_changelog_diff/config_loader"
|
|
6
6
|
|
|
7
|
+
# CLI that shows changelog diffs for outdated gems before you bundle update.
|
|
7
8
|
module GemChangelogDiff
|
|
9
|
+
# Base error class for all gem_changelog_diff errors.
|
|
8
10
|
class Error < StandardError; end
|
|
9
11
|
end
|
|
10
12
|
|
data/sig/gem_changelog_diff.rbs
CHANGED
|
@@ -317,6 +317,7 @@ module GemChangelogDiff
|
|
|
317
317
|
def dry_run_output: (Array[OutdatedGem] gems) -> void
|
|
318
318
|
def format_dry_run: (Array[OutdatedGem] gems) -> String
|
|
319
319
|
def configure_timeout: () -> void
|
|
320
|
+
def gh_cli_token: () -> String?
|
|
320
321
|
def rails_credentials_token: () -> String?
|
|
321
322
|
def apply_interactive: (Array[OutdatedGem] gems) -> Array[OutdatedGem]
|
|
322
323
|
def output_results: (Array[OutdatedGem] gems) -> Array[gem_report]
|
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: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -76,12 +76,15 @@ extra_rdoc_files: []
|
|
|
76
76
|
files:
|
|
77
77
|
- ".github/workflows/main.yml"
|
|
78
78
|
- ".github/workflows/publish.yml"
|
|
79
|
+
- ".yardopts"
|
|
79
80
|
- CHANGELOG.md
|
|
80
81
|
- CLAUDE.md
|
|
82
|
+
- CONTRIBUTING.md
|
|
81
83
|
- LICENSE.txt
|
|
82
84
|
- README.md
|
|
83
85
|
- ROADMAP.md
|
|
84
86
|
- Rakefile
|
|
87
|
+
- SECURITY.md
|
|
85
88
|
- codecov.yml
|
|
86
89
|
- exe/gem_changelog_diff
|
|
87
90
|
- lib/gem_changelog_diff.rb
|
|
@@ -117,6 +120,7 @@ metadata:
|
|
|
117
120
|
homepage_uri: https://github.com/eclectic-coding/gem_changelog_diff
|
|
118
121
|
source_code_uri: https://github.com/eclectic-coding/gem_changelog_diff
|
|
119
122
|
changelog_uri: https://github.com/eclectic-coding/gem_changelog_diff/blob/main/CHANGELOG.md
|
|
123
|
+
documentation_uri: https://rubydoc.info/gems/gem_changelog_diff
|
|
120
124
|
rubygems_mfa_required: 'true'
|
|
121
125
|
rdoc_options: []
|
|
122
126
|
require_paths:
|