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 +7 -0
- data/.github/workflows/main.yml +80 -0
- data/.github/workflows/publish.yml +41 -0
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +47 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/ROADMAP.md +130 -0
- data/Rakefile +12 -0
- data/codecov.yml +14 -0
- data/exe/gem_changelog_diff +6 -0
- data/lib/gem_changelog_diff/cli.rb +50 -0
- data/lib/gem_changelog_diff/detector.rb +36 -0
- data/lib/gem_changelog_diff/formatter.rb +32 -0
- data/lib/gem_changelog_diff/github_client.rb +63 -0
- data/lib/gem_changelog_diff/outdated_gem.rb +5 -0
- data/lib/gem_changelog_diff/rubygems_client.rb +38 -0
- data/lib/gem_changelog_diff/version.rb +5 -0
- data/lib/gem_changelog_diff.rb +13 -0
- data/sig/gem_changelog_diff.rbs +85 -0
- metadata +80 -0
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
|
+
[](https://github.com/eclectic-coding/gem_changelog_diff/actions/workflows/main.yml)
|
|
4
|
+
[](https://rubygems.org/gems/gem_changelog_diff)
|
|
5
|
+
[](https://rubygems.org/gems/gem_changelog_diff)
|
|
6
|
+
[](https://www.ruby-lang.org)
|
|
7
|
+
[](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,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,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,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: []
|