gem_changelog_diff 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 157c1e084602c25e10cec4fde2c24b1999a9894ebe1232901cb9536bae50f33b
4
- data.tar.gz: aa0be940c412f3b1a9b3ee08190d8a6e64ad0bed9cf1b2011979650b87264640
3
+ metadata.gz: 05a598faafc36b6f7a81836a1454668ab885425c63f8efa6b019190efc3c1614
4
+ data.tar.gz: 4f9eeeb3664f4ebde09921007292a2ad1af06caa0a2d738de04e6d6b28b67551
5
5
  SHA512:
6
- metadata.gz: 0714bc35cee74f49ac0b09cda00e58b931c90a9f0a7688e122f5c8f2c790aedd269244d6e5a0af2dd538a33e8a91e2f8024b313f2c94b6227a4b3fa6901a160c
7
- data.tar.gz: 8fb0ab418ac9c7e42adfd240360594ef1134536fe9ffa04a2dad8052d3355620d79d224b8f4342336bf853875ac60491ed6968ad8d105860dd770bccb6682fae
6
+ metadata.gz: 1544e2ec1db09d8dc3f34a7a24e69d55b9f7b7cbd8a5730aaa85faa94778e87de3551686912e0c83554204af3926b4b358eef0b86b5533ad952d8a11a170b88c
7
+ data.tar.gz: bdfe56aa40447f182fa846015d653570990308a3375d4edb25f8398dcbc5caa90abfbcf4d37383f391a93ba6b446a48f123410fa55e18e4a33fb47ed075d34f7
data/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-06-18
11
+
12
+ ### Added
13
+
14
+ - CHANGELOG.md fallback: fetches and parses changelog files when GitHub Releases are unavailable
15
+ - Tries common filename variants: `CHANGELOG.md`, `CHANGES.md`, `History.md`, `NEWS.md`
16
+ - `SourceResolver` orchestrates releases-first, changelog-fallback strategy
17
+ - Colorized terminal output via `tty-color`; respects `$NO_COLOR` and `--no-color` flag
18
+ - Summary line: "X gems outdated, Y with changelogs found, Z skipped"
19
+
10
20
  ## [0.2.0] - 2026-06-17
11
21
 
12
22
  ### Added
@@ -31,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
31
41
  - Plain text formatter for changelog output
32
42
  - Full end-to-end pipeline: detect → lookup → fetch → format
33
43
 
34
- [Unreleased]: https://github.com/eclectic-coding/gem_changelog_diff/compare/v0.2.0...HEAD
44
+ [Unreleased]: https://github.com/eclectic-coding/gem_changelog_diff/compare/v0.3.0...HEAD
45
+ [0.3.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.3.0
35
46
  [0.2.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.2.0
36
47
  [0.1.0]: https://github.com/eclectic-coding/gem_changelog_diff/releases/tag/v0.1.0
data/README.md CHANGED
@@ -8,6 +8,16 @@
8
8
 
9
9
  CLI that shows you the changelog diff for each gem before you `bundle update`, pulled from GitHub releases.
10
10
 
11
+ ## Table of Contents
12
+
13
+ - [Installation](#installation)
14
+ - [Usage](#usage)
15
+ - [GitHub Authentication](#github-authentication)
16
+ - [Output Control](#output-control)
17
+ - [Development](#development)
18
+ - [Contributing](#contributing)
19
+ - [License](#license)
20
+
11
21
  ## Installation
12
22
 
13
23
  Install the gem by executing:
@@ -22,6 +32,8 @@ Or add it to your Gemfile:
22
32
  bundle add gem_changelog_diff
23
33
  ```
24
34
 
35
+ [Back to top](#gemchangelogdiff)
36
+
25
37
  ## Usage
26
38
 
27
39
  ```bash
@@ -49,20 +61,29 @@ gem_changelog_diff
49
61
  ### Output Control
50
62
 
51
63
  ```bash
52
- gem_changelog_diff --verbose # Show detailed status messages
53
- gem_changelog_diff --quiet # Suppress warnings
64
+ gem_changelog_diff --verbose # Show detailed status messages
65
+ gem_changelog_diff --quiet # Suppress warnings
66
+ gem_changelog_diff --no-color # Disable colored output
54
67
  ```
55
68
 
69
+ The tool also respects the `$NO_COLOR` environment variable.
70
+
71
+ [Back to top](#gemchangelogdiff)
72
+
56
73
  ## Development
57
74
 
58
75
  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.
59
76
 
60
77
  To install this gem onto your local machine, run `bundle exec rake install`.
61
78
 
79
+ [Back to top](#gemchangelogdiff)
80
+
62
81
  ## Contributing
63
82
 
64
83
  Bug reports and pull requests are welcome on GitHub at https://github.com/eclectic-coding/gem_changelog_diff.
65
84
 
85
+ [Back to top](#gemchangelogdiff)
86
+
66
87
  ## License
67
88
 
68
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
89
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/ROADMAP.md CHANGED
@@ -2,20 +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.3.0 -- CHANGELOG.md Fallback & Colored Output
6
-
7
- Many gems do not use GitHub Releases. Fall back to parsing CHANGELOG.md from the repository.
8
-
9
- - Fetch raw CHANGELOG.md via GitHub Contents API; try common variants (`CHANGELOG.md`, `CHANGES.md`, `History.md`, `NEWS.md`)
10
- - Parse Keep-a-Changelog and common freeform formats; extract entries between current and target versions
11
- - Colorized terminal output via `tty-color` (gem names, versions, warnings); respect `$NO_COLOR` and `--no-color`
12
- - Summary line: "X gems outdated, Y with changelogs found, Z skipped"
13
-
14
- **New files:** `changelog_parser.rb`, `source_resolver.rb`
15
- **Dependencies:** `tty-color` (runtime)
16
-
17
- ---
18
-
19
5
  ## 0.4.0 -- Lockfile Parsing Fallback & Filtering
20
6
 
21
7
  Support environments where `bundle outdated` is unavailable. Let users narrow which gems to inspect.
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+
6
+ module GemChangelogDiff
7
+ class ChangelogParser
8
+ CONTENTS_URL = "https://api.github.com/repos/%<repo>s/contents/%<path>s"
9
+ FILENAMES = %w[CHANGELOG.md CHANGES.md History.md NEWS.md].freeze
10
+ VERSION_HEADING = /^##\s+\[?v?(\d+[^\]\s]+)/
11
+
12
+ def entries_between(repo, current_version, newest_version)
13
+ content = fetch_changelog(repo)
14
+ return [] unless content
15
+
16
+ parse_entries(content, current_version, newest_version)
17
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
18
+ Net::OpenTimeout, Net::ReadTimeout => e
19
+ raise NetworkError, "GitHub API request failed: #{e.message}"
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_changelog(repo)
25
+ FILENAMES.each do |filename|
26
+ content = fetch_file(repo, filename)
27
+ return content if content
28
+ end
29
+
30
+ nil
31
+ end
32
+
33
+ def fetch_file(repo, path)
34
+ uri = URI(format(CONTENTS_URL, repo: repo, path: path))
35
+ response = execute_request(uri)
36
+ return nil unless response.is_a?(Net::HTTPSuccess)
37
+
38
+ data = JSON.parse(response.body)
39
+ data["content"].unpack1("m")
40
+ end
41
+
42
+ def execute_request(uri)
43
+ request = Net::HTTP::Get.new(uri)
44
+ request["Accept"] = "application/vnd.github.v3+json"
45
+ request["User-Agent"] = "gem_changelog_diff/#{VERSION}"
46
+
47
+ token = GemChangelogDiff.configuration.github_token
48
+ request["Authorization"] = "token #{token}" if token
49
+
50
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
51
+ end
52
+
53
+ def parse_entries(content, current_version, newest_version)
54
+ current = Gem::Version.new(current_version)
55
+ newest = Gem::Version.new(newest_version)
56
+ sections = split_sections(content)
57
+
58
+ matched = sections.filter_map { |v, body| build_entry(v, body, current, newest) }
59
+ matched.sort_by { |e| Gem::Version.new(e[:tag_name]) }.reverse
60
+ end
61
+
62
+ def build_entry(version_str, body, current, newest)
63
+ gem_version = Gem::Version.new(version_str)
64
+ return unless gem_version > current && gem_version <= newest
65
+
66
+ { tag_name: version_str, name: version_str, published_at: nil, body: body.strip }
67
+ end
68
+
69
+ def split_sections(content)
70
+ sections = []
71
+ current_version = nil
72
+ current_body = []
73
+
74
+ content.each_line do |line|
75
+ current_version, current_body = process_line(line, sections, current_version, current_body)
76
+ end
77
+
78
+ flush_section(sections, current_version, current_body)
79
+ sections
80
+ end
81
+
82
+ def process_line(line, sections, current_version, current_body)
83
+ match = line.match(VERSION_HEADING)
84
+ if match
85
+ flush_section(sections, current_version, current_body)
86
+ [clean_version(match[1]), []]
87
+ else
88
+ current_body << line if current_version
89
+ [current_version, current_body]
90
+ end
91
+ end
92
+
93
+ def flush_section(sections, version, body)
94
+ sections << [version, body.join] if version
95
+ end
96
+
97
+ def clean_version(raw)
98
+ raw.sub(/\]\s*-?\s*\d{4}-\d{2}-\d{2}.*/, "").strip
99
+ end
100
+ end
101
+ end
@@ -13,6 +13,7 @@ module GemChangelogDiff
13
13
  class_option :token, type: :string, desc: "GitHub personal access token"
14
14
  class_option :verbose, type: :boolean, default: false, desc: "Show detailed output"
15
15
  class_option :quiet, type: :boolean, default: false, desc: "Suppress warnings"
16
+ class_option :no_color, type: :boolean, default: false, desc: "Disable colored output"
16
17
 
17
18
  desc "check", "Show changelog diffs for outdated gems"
18
19
  def check
@@ -25,7 +26,8 @@ module GemChangelogDiff
25
26
  end
26
27
 
27
28
  reports = build_reports(gems)
28
- say Formatter.new.format(reports)
29
+ formatter = Formatter.new(color: color_enabled?)
30
+ say formatter.format(reports)
29
31
  end
30
32
 
31
33
  desc "version", "Print version"
@@ -44,24 +46,28 @@ module GemChangelogDiff
44
46
 
45
47
  def build_reports(gems)
46
48
  rubygems_client = RubygemsClient.new
47
- github_client = GithubClient.new
49
+ source_resolver = SourceResolver.new
48
50
 
49
- gems.map { |gem| build_gem_report(gem, rubygems_client, github_client) }
51
+ gems.map { |gem| build_gem_report(gem, rubygems_client, source_resolver) }
50
52
  end
51
53
 
52
- def build_gem_report(gem, rubygems_client, github_client)
54
+ def build_gem_report(gem, rubygems_client, source_resolver)
53
55
  log "Checking #{gem.name}..."
54
56
  repo = rubygems_client.repo_url(gem.name)
55
57
  return { gem: gem, releases: [], error: " Could not find GitHub repository." } if repo.nil?
56
58
 
57
59
  log " Found repo: #{repo}"
58
- releases = github_client.releases_between(repo, gem.current_version, gem.newest_version)
60
+ releases = source_resolver.resolve(repo, gem.current_version, gem.newest_version)
59
61
  { gem: gem, releases: releases }
60
62
  rescue GemChangelogDiff::Error => e
61
63
  log_warning " Skipping #{gem.name}: #{e.message}"
62
64
  { gem: gem, releases: [], error: " #{e.message}" }
63
65
  end
64
66
 
67
+ def color_enabled?
68
+ !options[:no_color]
69
+ end
70
+
65
71
  def log(message)
66
72
  warn message if options[:verbose]
67
73
  end
@@ -1,18 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tty-color"
4
+
3
5
  module GemChangelogDiff
4
6
  class Formatter
7
+ BOLD = "\e[1m"
8
+ CYAN = "\e[36m"
9
+ GREEN = "\e[32m"
10
+ YELLOW = "\e[33m"
11
+ RED = "\e[31m"
12
+ RESET = "\e[0m"
13
+
14
+ def initialize(color: default_color?)
15
+ @color = color
16
+ end
17
+
5
18
  def format(gem_reports)
6
- gem_reports.map { |report| format_gem(report) }.join("\n")
19
+ output = gem_reports.map { |report| format_gem(report) }.join("\n")
20
+ output + summary(gem_reports)
7
21
  end
8
22
 
9
23
  private
10
24
 
25
+ def default_color?
26
+ TTY::Color.color? && ENV.fetch("NO_COLOR", nil).nil?
27
+ end
28
+
11
29
  def format_gem(report)
12
- header = "== #{report[:gem].name} (#{report[:gem].current_version} → #{report[:gem].newest_version}) =="
30
+ gem = report[:gem]
31
+ header = colorize("== #{gem.name} (#{gem.current_version} → #{gem.newest_version}) ==", BOLD, CYAN)
13
32
 
14
33
  body = if report[:releases].empty?
15
- report[:error] || " No GitHub releases found."
34
+ colorize(report[:error] || " No changelog entries found.", RED)
16
35
  else
17
36
  report[:releases].map { |r| format_release(r) }.join("\n")
18
37
  end
@@ -24,9 +43,26 @@ module GemChangelogDiff
24
43
  title = "--- #{release[:tag_name]}"
25
44
  title += " (#{release[:published_at][0..9]})" if release[:published_at]
26
45
  title += " ---"
46
+ title = colorize(title, YELLOW)
47
+
27
48
  body = release[:body]&.strip.to_s
28
49
  body = "(no release notes)" if body.empty?
29
50
  "#{title}\n#{body}\n"
30
51
  end
52
+
53
+ def summary(gem_reports)
54
+ total = gem_reports.size
55
+ with_changelogs = gem_reports.count { |r| !r[:releases].empty? }
56
+ skipped = gem_reports.count { |r| r[:error] }
57
+
58
+ line = "\n#{total} gems outdated, #{with_changelogs} with changelogs found, #{skipped} skipped"
59
+ colorize(line, BOLD, GREEN)
60
+ end
61
+
62
+ def colorize(text, *codes)
63
+ return text unless @color
64
+
65
+ "#{codes.join}#{text}#{RESET}"
66
+ end
31
67
  end
32
68
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemChangelogDiff
4
+ class SourceResolver
5
+ def initialize(github_client: GithubClient.new, changelog_parser: ChangelogParser.new)
6
+ @github_client = github_client
7
+ @changelog_parser = changelog_parser
8
+ end
9
+
10
+ def resolve(repo, current_version, newest_version)
11
+ releases = @github_client.releases_between(repo, current_version, newest_version)
12
+ return releases unless releases.empty?
13
+
14
+ @changelog_parser.entries_between(repo, current_version, newest_version)
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GemChangelogDiff
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -12,5 +12,7 @@ require_relative "gem_changelog_diff/outdated_gem"
12
12
  require_relative "gem_changelog_diff/detector"
13
13
  require_relative "gem_changelog_diff/rubygems_client"
14
14
  require_relative "gem_changelog_diff/github_client"
15
+ require_relative "gem_changelog_diff/changelog_parser"
16
+ require_relative "gem_changelog_diff/source_resolver"
15
17
  require_relative "gem_changelog_diff/formatter"
16
18
  require_relative "gem_changelog_diff/cli"
@@ -63,6 +63,31 @@ module GemChangelogDiff
63
63
  body: String?
64
64
  }
65
65
 
66
+ class ChangelogParser
67
+ CONTENTS_URL: String
68
+ FILENAMES: Array[String]
69
+ VERSION_HEADING: Regexp
70
+
71
+ def entries_between: (String repo, String current_version, String newest_version) -> Array[release_hash]
72
+
73
+ private
74
+
75
+ def fetch_changelog: (String repo) -> String?
76
+ def fetch_file: (String repo, String path) -> String?
77
+ def execute_request: (URI::Generic uri) -> Net::HTTPResponse
78
+ def parse_entries: (String content, String current_version, String newest_version) -> Array[release_hash]
79
+ def build_entry: (String version_str, String body, Gem::Version current, Gem::Version newest) -> release_hash?
80
+ def split_sections: (String content) -> Array[[String, String]]
81
+ def process_line: (String line, Array[[String, String]] sections, String? current_version, Array[String] current_body) -> [String?, Array[String]]
82
+ def flush_section: (Array[[String, String]] sections, String? version, Array[String] body) -> void
83
+ def clean_version: (String raw) -> String
84
+ end
85
+
86
+ class SourceResolver
87
+ def initialize: (?github_client: GithubClient, ?changelog_parser: ChangelogParser) -> void
88
+ def resolve: (String repo, String current_version, String newest_version) -> Array[release_hash]
89
+ end
90
+
66
91
  class GithubClient
67
92
  RELEASES_URL: String
68
93
  TAG_VERSION_REGEX: Regexp
@@ -89,12 +114,23 @@ module GemChangelogDiff
89
114
  }
90
115
 
91
116
  class Formatter
117
+ BOLD: String
118
+ CYAN: String
119
+ GREEN: String
120
+ YELLOW: String
121
+ RED: String
122
+ RESET: String
123
+
124
+ def initialize: (?color: bool) -> void
92
125
  def format: (Array[gem_report] gem_reports) -> String
93
126
 
94
127
  private
95
128
 
129
+ def default_color?: () -> bool
96
130
  def format_gem: (gem_report report) -> String
97
131
  def format_release: (release_hash release) -> String
132
+ def summary: (Array[gem_report] gem_reports) -> String
133
+ def colorize: (String text, *String codes) -> String
98
134
  end
99
135
 
100
136
  class CLI < Thor
@@ -105,9 +141,10 @@ module GemChangelogDiff
105
141
 
106
142
  private
107
143
 
144
+ def color_enabled?: () -> bool
108
145
  def configure_token: () -> void
109
146
  def build_reports: (Array[OutdatedGem] gems) -> Array[gem_report]
110
- def build_gem_report: (OutdatedGem gem, RubygemsClient rubygems_client, GithubClient github_client) -> gem_report
147
+ def build_gem_report: (OutdatedGem gem, RubygemsClient rubygems_client, SourceResolver source_resolver) -> gem_report
111
148
  def log: (String message) -> void
112
149
  def log_warning: (String message) -> void
113
150
  end
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuck Smith
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: tty-color
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.6'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.6'
26
40
  description: CLI that shows you the changelog diff for each gem before you bundle
27
41
  update, pulled from GitHub releases.
28
42
  email:
@@ -43,6 +57,7 @@ files:
43
57
  - codecov.yml
44
58
  - exe/gem_changelog_diff
45
59
  - lib/gem_changelog_diff.rb
60
+ - lib/gem_changelog_diff/changelog_parser.rb
46
61
  - lib/gem_changelog_diff/cli.rb
47
62
  - lib/gem_changelog_diff/configuration.rb
48
63
  - lib/gem_changelog_diff/detector.rb
@@ -51,6 +66,7 @@ files:
51
66
  - lib/gem_changelog_diff/github_client.rb
52
67
  - lib/gem_changelog_diff/outdated_gem.rb
53
68
  - lib/gem_changelog_diff/rubygems_client.rb
69
+ - lib/gem_changelog_diff/source_resolver.rb
54
70
  - lib/gem_changelog_diff/version.rb
55
71
  - sig/gem_changelog_diff.rbs
56
72
  homepage: https://github.com/eclectic-coding/gem_changelog_diff