gem_changelog_diff 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f774074fe0186c0efa8cd2aef588301c81441f20de1c1b75cd64e0da40bdcb0a
4
+ data.tar.gz: cca9f1421c6e505a7b137727e8948e3d5fa7749c226e6e43c51fcd29d1781a2c
5
+ SHA512:
6
+ metadata.gz: cfe4e6aadaa4310be3a007ed61ae85d0a3aad6938599030b9914f35f41b066e9ed04cef679762faedfc8ebd406156c45688e13f84864645e9168aa73eaf22a54
7
+ data.tar.gz: 76f9ea402f5cab49fbe8ecf9466ee73fc633e4a3a35c8dc77aee54fe79763cacd40f775c039c1cde51f8e8f49efbe8cebaa8420da6d7cac549097f4f18a2a85a
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ env:
14
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
15
+
16
+ jobs:
17
+ lint:
18
+ name: Lint
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - name: Check out repository
23
+ uses: actions/checkout@v5
24
+
25
+ - name: Set up Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: "3.3"
29
+ bundler-cache: true
30
+
31
+ - name: Run Rubocop
32
+ run: bundle exec rubocop
33
+
34
+ security:
35
+ name: Security audit
36
+ runs-on: ubuntu-latest
37
+
38
+ steps:
39
+ - name: Check out repository
40
+ uses: actions/checkout@v5
41
+
42
+ - name: Set up Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: "3.3"
46
+ bundler-cache: true
47
+
48
+ - name: Run bundler-audit
49
+ run: bundle exec rake bundle:audit:update bundle:audit:check
50
+
51
+ test:
52
+ name: Ruby ${{ matrix.ruby }}
53
+ runs-on: ubuntu-latest
54
+ strategy:
55
+ fail-fast: false
56
+ matrix:
57
+ ruby:
58
+ - "3.3"
59
+ - "3.4"
60
+ - "4.0"
61
+
62
+ steps:
63
+ - name: Check out repository
64
+ uses: actions/checkout@v5
65
+
66
+ - name: Set up Ruby
67
+ uses: ruby/setup-ruby@v1
68
+ with:
69
+ ruby-version: ${{ matrix.ruby }}
70
+ bundler-cache: true
71
+
72
+ - name: Run test suite
73
+ run: bundle exec rake spec
74
+
75
+ - name: Upload coverage to Codecov
76
+ uses: codecov/codecov-action@v6
77
+ if: matrix.ruby == '3.4'
78
+ with:
79
+ token: ${{ secrets.CODECOV_TOKEN }}
80
+ files: coverage/coverage.json
@@ -0,0 +1,41 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ env:
9
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
10
+
11
+ jobs:
12
+ publish:
13
+ name: Publish to RubyGems
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+ id-token: write
18
+
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: "3.4"
25
+ bundler-cache: true
26
+
27
+ - name: Run test suite
28
+ run: bundle exec rake
29
+
30
+ - name: Create GitHub Release
31
+ env:
32
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
+ run: |
34
+ gh release create "${{ github.ref_name }}" \
35
+ --title "${{ github.ref_name }}" \
36
+ --notes "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." \
37
+ --verify-tag
38
+
39
+ - name: Publish to RubyGems
40
+ uses: rubygems/release-gem@v1
41
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-06-17
11
+
12
+ ### Added
13
+
14
+ - CLI entry point via Thor (`exe/gem_changelog_diff`) with `check` default command and `version` command
15
+ - Outdated gem detection by parsing `bundle outdated --parseable`
16
+ - `OutdatedGem` data object for representing outdated gem info
17
+ - RubyGems API client to look up each gem's GitHub repository
18
+ - GitHub API client to fetch releases between locked and latest versions
19
+ - Plain text formatter for changelog output
20
+ - Full end-to-end pipeline: detect → lookup → fetch → format
data/CLAUDE.md ADDED
@@ -0,0 +1,47 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
7
+ gem_changelog_diff is a Ruby gem CLI that shows changelog diffs for each gem before you `bundle update`, pulled from GitHub releases. Requires Ruby >= 3.3.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ bundle exec rake # Run all checks (bundler-audit, rubocop, rspec)
13
+ bundle exec rake spec # Run tests only
14
+ bundle exec rspec spec/path_spec.rb # Run a single test file
15
+ bundle exec rspec spec/path_spec.rb:42 # Run a single example by line
16
+ bundle exec rubocop # Lint
17
+ bundle exec rubocop -a # Lint with auto-correct
18
+ bundle exec rake bundle:audit:check # Security audit
19
+ ```
20
+
21
+ ## Workflow
22
+
23
+ All feature work lives on `feature/<version>-<scope>` branches (e.g. `feature/0.1.0-core`). Every branch produces two commits before pushing:
24
+
25
+ 1. **Feature commit** — implementation + specs. Run `bundle exec rake` and fix all failures before committing.
26
+ 2. **Docs commit** — add shipped items to `CHANGELOG.md`, remove them from `ROADMAP.md`, update `README.md`. Run `bundle exec rake` again before this commit.
27
+
28
+ ## Code Style
29
+
30
+ - RuboCop with `rubocop-rake` plugin; double quotes for strings
31
+ - Target Ruby version: 3.3
32
+ - `frozen_string_literal: true` on all Ruby files
33
+ - `Style/Documentation` is disabled
34
+
35
+ ## Testing
36
+
37
+ - RSpec with `--format documentation`
38
+ - SimpleCov for coverage (HTML + JSON output); JSON uploaded to Codecov in CI
39
+ - Coverage filters: `spec/` and `version.rb` excluded; tracks `lib/**/*.rb`
40
+
41
+ ## CI
42
+
43
+ GitHub Actions runs on push to main and PRs: Lint, Security audit, and tests across Ruby 3.3, 3.4, and 4.0. Branch protection requires all five jobs to pass.
44
+
45
+ ## Releasing
46
+
47
+ Update `lib/gem_changelog_diff/version.rb`, tag with `v*`, and push. The publish workflow runs tests, creates a GitHub Release, and publishes to RubyGems via trusted publishing.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Chuck Smith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # GemChangelogDiff
2
+
3
+ [![CI](https://github.com/eclectic-coding/gem_changelog_diff/actions/workflows/main.yml/badge.svg)](https://github.com/eclectic-coding/gem_changelog_diff/actions/workflows/main.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/gem_changelog_diff)](https://rubygems.org/gems/gem_changelog_diff)
5
+ [![Gem Downloads](https://img.shields.io/gem/dt/gem_changelog_diff)](https://rubygems.org/gems/gem_changelog_diff)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.3.0-ruby)](https://www.ruby-lang.org)
7
+ [![Codecov](https://img.shields.io/codecov/c/github/eclectic-coding/gem_changelog_diff)](https://codecov.io/gh/eclectic-coding/gem_changelog_diff)
8
+
9
+ CLI that shows you the changelog diff for each gem before you `bundle update`, pulled from GitHub releases.
10
+
11
+ ## Installation
12
+
13
+ Install the gem by executing:
14
+
15
+ ```bash
16
+ gem install gem_changelog_diff
17
+ ```
18
+
19
+ Or add it to your Gemfile:
20
+
21
+ ```bash
22
+ bundle add gem_changelog_diff
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ gem_changelog_diff
29
+ ```
30
+
31
+ Run from a project directory with a `Gemfile.lock`. The tool detects outdated gems via `bundle outdated`, looks up their GitHub repositories, fetches release notes, and displays a formatted changelog diff.
32
+
33
+ ```bash
34
+ gem_changelog_diff version # Print version
35
+ gem_changelog_diff --version # Same as above
36
+ ```
37
+
38
+ ## Development
39
+
40
+ 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.
41
+
42
+ To install this gem onto your local machine, run `bundle exec rake install`.
43
+
44
+ ## Contributing
45
+
46
+ Bug reports and pull requests are welcome on GitHub at https://github.com/eclectic-coding/gem_changelog_diff.
47
+
48
+ ## License
49
+
50
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/ROADMAP.md ADDED
@@ -0,0 +1,130 @@
1
+ # Roadmap
2
+
3
+ Feature roadmap for gem_changelog_diff. Each section is auto-pruned by `bin/release` when that version ships.
4
+
5
+ ## 0.2.0 -- Error Handling & GitHub Authentication
6
+
7
+ Handle real-world failures and unblock power users hitting the 60 req/hr unauthenticated rate limit.
8
+
9
+ - GitHub personal access token via `--token` flag or `GITHUB_TOKEN` env var
10
+ - Custom error hierarchy (`RepoNotFoundError`, `GitHubAPIError`, `RateLimitError`, `NetworkError`)
11
+ - Graceful degradation: skip failed gems with a warning, do not abort the run
12
+ - Rate limit awareness: read `X-RateLimit-Remaining` headers, warn when approaching the limit
13
+ - `--verbose` and `--quiet` flags
14
+
15
+ **New files:** `errors.rb`, `configuration.rb`
16
+
17
+ ---
18
+
19
+ ## 0.3.0 -- CHANGELOG.md Fallback & Colored Output
20
+
21
+ Many gems do not use GitHub Releases. Fall back to parsing CHANGELOG.md from the repository.
22
+
23
+ - Fetch raw CHANGELOG.md via GitHub Contents API; try common variants (`CHANGELOG.md`, `CHANGES.md`, `History.md`, `NEWS.md`)
24
+ - Parse Keep-a-Changelog and common freeform formats; extract entries between current and target versions
25
+ - Colorized terminal output via `tty-color` (gem names, versions, warnings); respect `$NO_COLOR` and `--no-color`
26
+ - Summary line: "X gems outdated, Y with changelogs found, Z skipped"
27
+
28
+ **New files:** `changelog_parser.rb`, `source_resolver.rb`
29
+ **Dependencies:** `tty-color` (runtime)
30
+
31
+ ---
32
+
33
+ ## 0.4.0 -- Lockfile Parsing Fallback & Filtering
34
+
35
+ Support environments where `bundle outdated` is unavailable. Let users narrow which gems to inspect.
36
+
37
+ - Parse `Gemfile.lock` directly via `Bundler::LockfileParser` and query RubyGems API for latest versions
38
+ - Automatic fallback when `bundle outdated` fails
39
+ - Positional args to inspect specific gems: `gem_changelog_diff check rails sidekiq`
40
+ - `--group`, `--ignore`, `--lockfile`, `--strategy` flags
41
+
42
+ **New files:** `lockfile_parser.rb`
43
+
44
+ ---
45
+
46
+ ## 0.5.0 -- Caching & Performance
47
+
48
+ Avoid redundant API calls. Make repeated runs fast on large dependency trees.
49
+
50
+ - Disk cache at `~/.cache/gem_changelog_diff/` with configurable TTL (default 24h)
51
+ - ETag conditional requests to avoid consuming rate limit on revalidation
52
+ - Concurrent fetching via Ruby threads (default concurrency: 4, configurable via `--concurrency`)
53
+ - Progress indicator via `tty-spinner`
54
+ - `cache clear` subcommand, `--no-cache` and `--cache-ttl` flags
55
+
56
+ **New files:** `cache.rb`, `concurrent_fetcher.rb`
57
+ **Dependencies:** `tty-spinner` (runtime)
58
+
59
+ ---
60
+
61
+ ## 0.6.0 -- Interactive Mode & Output Formats
62
+
63
+ Let users selectively browse changelogs. Support machine-readable output for CI and scripting.
64
+
65
+ - Interactive gem selection via `tty-prompt` (`--interactive` / `-i` flag)
66
+ - JSON output (`--format=json`) for piping to `jq` or CI tools
67
+ - Markdown output (`--format=markdown`) for PR descriptions
68
+ - `show` subcommand: `gem_changelog_diff show rails 7.0.0 7.1.0`
69
+ - `--output` flag to write to a file
70
+
71
+ **New files:** `interactive.rb`, `formatters/base.rb`, `formatters/text.rb`, `formatters/json.rb`, `formatters/markdown.rb`
72
+ **Dependencies:** `tty-prompt` (runtime)
73
+
74
+ ---
75
+
76
+ ## 0.7.0 -- Configuration File & Polish
77
+
78
+ Persistent preferences so users don't repeat flags every run.
79
+
80
+ - Config file: `.gem_changelog_diff.yml` (project root) and `~/.config/gem_changelog_diff/config.yml` (user); project overrides user
81
+ - Supported keys: `github_token`, `default_format`, `cache_ttl`, `concurrency`, `ignore_gems`, `no_color`
82
+ - `init` subcommand: generate a commented config template
83
+ - `version` subcommand
84
+ - `--dry-run` flag: show which gems would be checked without fetching
85
+
86
+ **New files:** `config_loader.rb`
87
+
88
+ ---
89
+
90
+ ## 0.8.0 -- Robustness & Edge Cases
91
+
92
+ Handle the long tail of real-world gem repository patterns.
93
+
94
+ - Source URI resolution: detect and skip GitLab/Codeberg gracefully, follow redirects for renamed repos, handle monorepo subdirectory URIs
95
+ - Tag format normalization: `v1.2.3`, `1.2.3`, `gem_name-1.2.3`, `release-1.2.3`
96
+ - Proper version comparison via `Gem::Version` (handles pre-release: `1.0.0.rc1`, `1.0.0.beta2`)
97
+ - GitHub API pagination for gems with 100+ releases
98
+ - Per-request timeout (10s default), total timeout (120s default), configurable via `--timeout`
99
+
100
+ **New files:** `tag_matcher.rb`, `uri_resolver.rb`
101
+
102
+ ---
103
+
104
+ ## 0.9.0 -- Pre-1.0 Stabilization
105
+
106
+ Freeze the public API. Harden the test suite. Prepare documentation for stable release.
107
+
108
+ - Integration test suite with VCR-recorded HTTP fixtures against well-known gems
109
+ - RBS type signatures in `sig/gem_changelog_diff.rbs`
110
+ - Defined exit codes: 0 (success), 1 (error), 2 (partial failure)
111
+ - Add `rubocop-rspec` for spec linting
112
+ - README overhaul with examples for every subcommand and flag
113
+
114
+ **Dependencies:** `vcr`, `rubocop-rspec` (development)
115
+
116
+ ---
117
+
118
+ ## 1.0.0 -- Stable Release
119
+
120
+ Public API is frozen. Semantic versioning contract begins.
121
+
122
+ - API stability guarantee: breaking changes require a major version bump
123
+ - YARD documentation on all public classes and methods
124
+ - `CONTRIBUTING.md` with development setup, testing, and architecture overview
125
+ - `SECURITY.md` with vulnerability reporting instructions
126
+ - Document future Bundler plugin possibility (`bundler-changelog-diff`)
127
+
128
+ **Dependencies:** `yard` (development)
129
+
130
+ ---
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "bundler/audit/task"
5
+ require "rspec/core/rake_task"
6
+ require "rubocop/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+ RuboCop::RakeTask.new
10
+ Bundler::Audit::Task.new
11
+
12
+ task default: ["bundle:audit:update", "bundle:audit:check", :rubocop, :spec]
data/codecov.yml ADDED
@@ -0,0 +1,14 @@
1
+ comment:
2
+ layout: "reach, diff, flags, files"
3
+ behavior: default
4
+ # Only post or update the comment if the coverage drops
5
+ require_changes: "coverage_drop"
6
+
7
+ coverage:
8
+ status:
9
+ project:
10
+ default:
11
+ informational: false
12
+ patch:
13
+ default:
14
+ informational: false
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "gem_changelog_diff"
5
+
6
+ GemChangelogDiff::CLI.start(ARGV)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module GemChangelogDiff
6
+ class CLI < Thor
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ default_task :check
12
+
13
+ desc "check", "Show changelog diffs for outdated gems"
14
+ def check
15
+ gems = Detector.new.detect
16
+
17
+ if gems.empty?
18
+ say "All gems are up to date!"
19
+ return
20
+ end
21
+
22
+ reports = build_reports(gems)
23
+ say Formatter.new.format(reports)
24
+ end
25
+
26
+ desc "version", "Print version"
27
+ def version
28
+ say "gem_changelog_diff #{VERSION}"
29
+ end
30
+ map "--version" => :version
31
+ map "-v" => :version
32
+
33
+ private
34
+
35
+ def build_reports(gems)
36
+ rubygems_client = RubygemsClient.new
37
+ github_client = GithubClient.new
38
+
39
+ gems.map { |gem| build_gem_report(gem, rubygems_client, github_client) }
40
+ end
41
+
42
+ def build_gem_report(gem, rubygems_client, github_client)
43
+ repo = rubygems_client.repo_url(gem.name)
44
+ return { gem: gem, releases: [], error: " Could not find GitHub repository." } if repo.nil?
45
+
46
+ releases = github_client.releases_between(repo, gem.current_version, gem.newest_version)
47
+ { gem: gem, releases: releases }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module GemChangelogDiff
6
+ class Detector
7
+ PARSEABLE_REGEX = /\A(\S+)\s+\(newest\s+([^,]+),\s+installed\s+([^,)]+)/
8
+
9
+ def detect
10
+ output = run_bundle_outdated
11
+ parse(output)
12
+ end
13
+
14
+ private
15
+
16
+ def run_bundle_outdated
17
+ output, status = Open3.capture2("bundle", "outdated", "--parseable")
18
+ raise Error, "bundle outdated failed (exit #{status.exitstatus})" unless [0, 1].include?(status.exitstatus)
19
+
20
+ output
21
+ end
22
+
23
+ def parse(output)
24
+ output.each_line.filter_map do |line|
25
+ match = line.match(PARSEABLE_REGEX)
26
+ next unless match
27
+
28
+ OutdatedGem.new(
29
+ name: match[1],
30
+ current_version: match[3],
31
+ newest_version: match[2]
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemChangelogDiff
4
+ class Formatter
5
+ def format(gem_reports)
6
+ gem_reports.map { |report| format_gem(report) }.join("\n")
7
+ end
8
+
9
+ private
10
+
11
+ def format_gem(report)
12
+ header = "== #{report[:gem].name} (#{report[:gem].current_version} → #{report[:gem].newest_version}) =="
13
+
14
+ body = if report[:releases].empty?
15
+ report[:error] || " No GitHub releases found."
16
+ else
17
+ report[:releases].map { |r| format_release(r) }.join("\n")
18
+ end
19
+
20
+ "#{header}\n#{body}\n"
21
+ end
22
+
23
+ def format_release(release)
24
+ title = "--- #{release[:tag_name]}"
25
+ title += " (#{release[:published_at][0..9]})" if release[:published_at]
26
+ title += " ---"
27
+ body = release[:body]&.strip.to_s
28
+ body = "(no release notes)" if body.empty?
29
+ "#{title}\n#{body}\n"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module GemChangelogDiff
7
+ class GithubClient
8
+ RELEASES_URL = "https://api.github.com/repos/%<repo>s/releases"
9
+ TAG_VERSION_REGEX = /\Av?(\d+\..+)\z/
10
+
11
+ def releases_between(repo, current_version, newest_version)
12
+ releases = fetch_releases(repo)
13
+ filter_releases(releases, current_version, newest_version)
14
+ end
15
+
16
+ private
17
+
18
+ def fetch_releases(repo)
19
+ uri = URI(format(RELEASES_URL, repo: repo))
20
+ uri.query = URI.encode_www_form(per_page: 30)
21
+
22
+ request = Net::HTTP::Get.new(uri)
23
+ request["Accept"] = "application/vnd.github.v3+json"
24
+ request["User-Agent"] = "gem_changelog_diff/#{VERSION}"
25
+
26
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
27
+ http.request(request)
28
+ end
29
+
30
+ return [] unless response.is_a?(Net::HTTPSuccess)
31
+
32
+ JSON.parse(response.body)
33
+ end
34
+
35
+ def filter_releases(releases, current_version, newest_version)
36
+ current = Gem::Version.new(current_version)
37
+ newest = Gem::Version.new(newest_version)
38
+
39
+ matched = releases.filter_map { |r| build_release(r, current, newest) }
40
+ sort_releases(matched)
41
+ end
42
+
43
+ def build_release(release, current, newest)
44
+ version = extract_version(release["tag_name"])
45
+ return unless version
46
+
47
+ gem_version = Gem::Version.new(version)
48
+ return unless gem_version > current && gem_version <= newest
49
+
50
+ { tag_name: release["tag_name"], name: release["name"],
51
+ published_at: release["published_at"], body: release["body"] }
52
+ end
53
+
54
+ def sort_releases(releases)
55
+ releases.sort_by { |r| Gem::Version.new(extract_version(r[:tag_name])) }.reverse
56
+ end
57
+
58
+ def extract_version(tag)
59
+ match = tag&.match(TAG_VERSION_REGEX)
60
+ match ? match[1] : nil
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemChangelogDiff
4
+ OutdatedGem = Data.define(:name, :current_version, :newest_version)
5
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module GemChangelogDiff
7
+ class RubygemsClient
8
+ RUBYGEMS_API = "https://rubygems.org/api/v1/gems/%<name>s.json"
9
+ GITHUB_REPO_REGEX = %r{github\.com/([^/]+)/([^/]+)}
10
+
11
+ def repo_url(gem_name)
12
+ uri = URI(format(RUBYGEMS_API, name: gem_name))
13
+ response = Net::HTTP.get_response(uri)
14
+ return nil unless response.is_a?(Net::HTTPSuccess)
15
+
16
+ data = JSON.parse(response.body)
17
+ extract_github_repo(data)
18
+ end
19
+
20
+ private
21
+
22
+ def extract_github_repo(data)
23
+ %w[source_code_uri homepage_uri bug_tracker_uri].each do |field|
24
+ url = data[field]
25
+ next if url.nil? || url.empty?
26
+
27
+ match = url.match(GITHUB_REPO_REGEX)
28
+ next unless match
29
+
30
+ owner = match[1]
31
+ repo = match[2].sub(/\.git\z/, "").sub(%r{/.*}, "")
32
+ return "#{owner}/#{repo}"
33
+ end
34
+
35
+ nil
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemChangelogDiff
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_changelog_diff/version"
4
+ require_relative "gem_changelog_diff/outdated_gem"
5
+ require_relative "gem_changelog_diff/detector"
6
+ require_relative "gem_changelog_diff/rubygems_client"
7
+ require_relative "gem_changelog_diff/github_client"
8
+ require_relative "gem_changelog_diff/formatter"
9
+ require_relative "gem_changelog_diff/cli"
10
+
11
+ module GemChangelogDiff
12
+ class Error < StandardError; end
13
+ end
@@ -0,0 +1,85 @@
1
+ module GemChangelogDiff
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ class OutdatedGem
8
+ attr_reader name: String
9
+ attr_reader current_version: String
10
+ attr_reader newest_version: String
11
+
12
+ def self.new: (name: String, current_version: String, newest_version: String) -> OutdatedGem
13
+ end
14
+
15
+ class Detector
16
+ PARSEABLE_REGEX: Regexp
17
+
18
+ def detect: () -> Array[OutdatedGem]
19
+
20
+ private
21
+
22
+ def run_bundle_outdated: () -> String
23
+ def parse: (String output) -> Array[OutdatedGem]
24
+ end
25
+
26
+ class RubygemsClient
27
+ RUBYGEMS_API: String
28
+ GITHUB_REPO_REGEX: Regexp
29
+
30
+ def repo_url: (String gem_name) -> String?
31
+
32
+ private
33
+
34
+ def extract_github_repo: (Hash[String, untyped] data) -> String?
35
+ end
36
+
37
+ type release_hash = {
38
+ tag_name: String,
39
+ name: String?,
40
+ published_at: String?,
41
+ body: String?
42
+ }
43
+
44
+ class GithubClient
45
+ RELEASES_URL: String
46
+ TAG_VERSION_REGEX: Regexp
47
+
48
+ def releases_between: (String repo, String current_version, String newest_version) -> Array[release_hash]
49
+
50
+ private
51
+
52
+ def fetch_releases: (String repo) -> Array[Hash[String, untyped]]
53
+ def filter_releases: (Array[Hash[String, untyped]] releases, String current_version, String newest_version) -> Array[release_hash]
54
+ def build_release: (Hash[String, untyped] release, Gem::Version current, Gem::Version newest) -> release_hash?
55
+ def sort_releases: (Array[release_hash] releases) -> Array[release_hash]
56
+ def extract_version: (String? tag) -> String?
57
+ end
58
+
59
+ type gem_report = {
60
+ gem: OutdatedGem,
61
+ releases: Array[release_hash],
62
+ ?error: String
63
+ }
64
+
65
+ class Formatter
66
+ def format: (Array[gem_report] gem_reports) -> String
67
+
68
+ private
69
+
70
+ def format_gem: (gem_report report) -> String
71
+ def format_release: (release_hash release) -> String
72
+ end
73
+
74
+ class CLI < Thor
75
+ def self.exit_on_failure?: () -> bool
76
+
77
+ def check: () -> void
78
+ def version: () -> void
79
+
80
+ private
81
+
82
+ def build_reports: (Array[OutdatedGem] gems) -> Array[gem_report]
83
+ def build_gem_report: (OutdatedGem gem, RubygemsClient rubygems_client, GithubClient github_client) -> gem_report
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gem_changelog_diff
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chuck Smith
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ description: CLI that shows you the changelog diff for each gem before you bundle
27
+ update, pulled from GitHub releases.
28
+ email:
29
+ - eclectic-coding@users.noreply.github.com
30
+ executables:
31
+ - gem_changelog_diff
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".github/workflows/main.yml"
36
+ - ".github/workflows/publish.yml"
37
+ - CHANGELOG.md
38
+ - CLAUDE.md
39
+ - LICENSE.txt
40
+ - README.md
41
+ - ROADMAP.md
42
+ - Rakefile
43
+ - codecov.yml
44
+ - exe/gem_changelog_diff
45
+ - lib/gem_changelog_diff.rb
46
+ - lib/gem_changelog_diff/cli.rb
47
+ - lib/gem_changelog_diff/detector.rb
48
+ - lib/gem_changelog_diff/formatter.rb
49
+ - lib/gem_changelog_diff/github_client.rb
50
+ - lib/gem_changelog_diff/outdated_gem.rb
51
+ - lib/gem_changelog_diff/rubygems_client.rb
52
+ - lib/gem_changelog_diff/version.rb
53
+ - sig/gem_changelog_diff.rbs
54
+ homepage: https://github.com/eclectic-coding/gem_changelog_diff
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ allowed_push_host: https://rubygems.org
59
+ homepage_uri: https://github.com/eclectic-coding/gem_changelog_diff
60
+ source_code_uri: https://github.com/eclectic-coding/gem_changelog_diff
61
+ changelog_uri: https://github.com/eclectic-coding/gem_changelog_diff/blob/main/CHANGELOG.md
62
+ rubygems_mfa_required: 'true'
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 3.3.0
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.6.9
78
+ specification_version: 4
79
+ summary: Show changelog diffs for outdated gems before you bundle update.
80
+ test_files: []