gem-digest 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/DEVELOPMENT.md +241 -0
- data/LICENSE.txt +21 -0
- data/README.md +131 -0
- data/REQUIREMENTS.md +151 -0
- data/Rakefile +12 -0
- data/exe/gemd +7 -0
- data/gem-digest.gemspec +38 -0
- data/lib/gem_digest/analyzer.rb +51 -0
- data/lib/gem_digest/categorizer.rb +58 -0
- data/lib/gem_digest/changelog_fetcher.rb +51 -0
- data/lib/gem_digest/cli.rb +85 -0
- data/lib/gem_digest/reporters/base.rb +36 -0
- data/lib/gem_digest/reporters/console.rb +80 -0
- data/lib/gem_digest/reporters/markdown.rb +102 -0
- data/lib/gem_digest/version.rb +5 -0
- data/lib/gem_digest.rb +32 -0
- metadata +163 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 59ea827e8f6823c9eb9b56aad62ac5216b5153bd86c540c6639a95ac490572f8
|
|
4
|
+
data.tar.gz: c20cb62a7397459d636ef26306cb5261d9b7a9bdd6ddb2f564d2f1d73f6bcf39
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 55e07ba173fab062e7a3416db93caa2259baa1b3a51146d310266d94b92d1c9cdee9d74f8ae9f4c9306741a40f02ca00fd3d73374ea4fb6c18f6159075e62d56
|
|
7
|
+
data.tar.gz: 4d42813be1e897c0393756886a10352b35961e5ef7c94bf71201b51e538bf1c55708f5957e1f7c947bc77797ce8a40ce131be81b3d20587810719ebfbd4fae05
|
data/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# ๐ ๏ธ Development Guide
|
|
2
|
+
|
|
3
|
+
This document provides detailed information for developers working on gem-digest.
|
|
4
|
+
|
|
5
|
+
## ๐๏ธ Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
gem-digest/
|
|
9
|
+
โโโ .github/workflows/release.yml # CI/CD pipeline
|
|
10
|
+
โโโ bin/console # Interactive console
|
|
11
|
+
โโโ exe/gemd # Main executable
|
|
12
|
+
โโโ lib/
|
|
13
|
+
โ โโโ gem_digest.rb # Main module
|
|
14
|
+
โ โโโ gem_digest/
|
|
15
|
+
โ โโโ analyzer.rb # Gemfile.lock parser
|
|
16
|
+
โ โโโ categorizer.rb # Version categorization
|
|
17
|
+
โ โโโ changelog_fetcher.rb # Changelog integration
|
|
18
|
+
โ โโโ cli.rb # Command-line interface
|
|
19
|
+
โ โโโ reporters/ # Output formatters
|
|
20
|
+
โ โ โโโ base.rb
|
|
21
|
+
โ โ โโโ console.rb
|
|
22
|
+
โ โ โโโ markdown.rb
|
|
23
|
+
โ โโโ version.rb # Version constant
|
|
24
|
+
โโโ spec/ # Test suite
|
|
25
|
+
โโโ output/ # Generated reports
|
|
26
|
+
โโโ config/ # Configuration files
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## ๐งช Testing
|
|
30
|
+
|
|
31
|
+
### Running Tests
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Install dependencies
|
|
35
|
+
bundle install
|
|
36
|
+
|
|
37
|
+
# Run all tests
|
|
38
|
+
bundle exec rspec
|
|
39
|
+
|
|
40
|
+
# Run specific test files
|
|
41
|
+
bundle exec rspec spec/gem_digest/analyzer_spec.rb
|
|
42
|
+
bundle exec rspec spec/integration_spec.rb
|
|
43
|
+
|
|
44
|
+
# Run with coverage
|
|
45
|
+
bundle exec rspec --format documentation
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Test Structure
|
|
49
|
+
|
|
50
|
+
- **Unit Tests**: Test individual classes and methods
|
|
51
|
+
- **Integration Tests**: Test the complete workflow
|
|
52
|
+
- **Mock Network Calls**: Avoid hitting RubyGems API during tests
|
|
53
|
+
|
|
54
|
+
### Adding New Tests
|
|
55
|
+
|
|
56
|
+
1. Create test files in `spec/gem_digest/`
|
|
57
|
+
2. Follow the naming convention: `*_spec.rb`
|
|
58
|
+
3. Use RSpec best practices
|
|
59
|
+
4. Mock external dependencies
|
|
60
|
+
|
|
61
|
+
## ๐ง Development Workflow
|
|
62
|
+
|
|
63
|
+
### Local Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Clone the repository
|
|
67
|
+
git clone https://github.com/ND-Zyth/gem-digest.git
|
|
68
|
+
cd gem-digest
|
|
69
|
+
|
|
70
|
+
# Install dependencies
|
|
71
|
+
bundle install
|
|
72
|
+
|
|
73
|
+
# Run the tool locally
|
|
74
|
+
bundle exec ruby -Ilib exe/gemd analyze
|
|
75
|
+
|
|
76
|
+
# Or use the simple CLI for testing
|
|
77
|
+
ruby simple_cli.rb test_Gemfile.lock
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Code Quality
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Run RuboCop for style checking
|
|
84
|
+
bundle exec rubocop
|
|
85
|
+
|
|
86
|
+
# Auto-fix issues
|
|
87
|
+
bundle exec rubocop -a
|
|
88
|
+
|
|
89
|
+
# Check specific files
|
|
90
|
+
bundle exec rubocop lib/gem_digest/analyzer.rb
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Building and Testing the Gem
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Build the gem
|
|
97
|
+
gem build gem-digest.gemspec
|
|
98
|
+
|
|
99
|
+
# Install locally for testing
|
|
100
|
+
gem install gem-digest-*.gem
|
|
101
|
+
|
|
102
|
+
# Test the installed gem
|
|
103
|
+
gemd analyze
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## ๐ฆ Release Process
|
|
107
|
+
|
|
108
|
+
### Version Management
|
|
109
|
+
|
|
110
|
+
1. Update version in `lib/gem_digest/version.rb`
|
|
111
|
+
2. Update `CHANGELOG.md` with new features/fixes
|
|
112
|
+
3. Commit changes: `git commit -m "Bump version to X.Y.Z"`
|
|
113
|
+
4. Create and push tag: `git tag vX.Y.Z && git push origin vX.Y.Z`
|
|
114
|
+
|
|
115
|
+
### Automated Release
|
|
116
|
+
|
|
117
|
+
The GitHub Actions workflow automatically:
|
|
118
|
+
1. Runs tests on multiple Ruby versions and platforms
|
|
119
|
+
2. Builds gems for each platform
|
|
120
|
+
3. Creates a GitHub release with attached gem files
|
|
121
|
+
4. Publishes to RubyGems (requires `RUBYGEMS_API_KEY` secret)
|
|
122
|
+
|
|
123
|
+
## ๐๏ธ Architecture
|
|
124
|
+
|
|
125
|
+
### Core Components
|
|
126
|
+
|
|
127
|
+
1. **Analyzer**: Parses `Gemfile.lock` using Bundler and fetches latest versions from RubyGems API
|
|
128
|
+
2. **Categorizer**: Compares versions using semantic versioning rules
|
|
129
|
+
3. **Reporters**: Generate output in different formats (console, markdown)
|
|
130
|
+
4. **CLI**: Provides user-friendly command-line interface
|
|
131
|
+
|
|
132
|
+
### Data Flow
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Gemfile.lock โ Analyzer โ Categorizer โ Reporter โ Output
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### API Design
|
|
139
|
+
|
|
140
|
+
The gem provides both CLI and programmatic interfaces:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# Programmatic usage
|
|
144
|
+
GemDigest.analyze("path/to/Gemfile.lock", format: "console")
|
|
145
|
+
|
|
146
|
+
# Individual components
|
|
147
|
+
analyzer = GemDigest::Analyzer.new("Gemfile.lock")
|
|
148
|
+
gems_data = analyzer.analyze
|
|
149
|
+
|
|
150
|
+
categorizer = GemDigest::Categorizer.new
|
|
151
|
+
categorized = categorizer.categorize(gems_data)
|
|
152
|
+
|
|
153
|
+
reporter = GemDigest::Reporters::Console.new
|
|
154
|
+
reporter.generate_report(categorized)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## ๐ Adding New Features
|
|
158
|
+
|
|
159
|
+
### New Output Format
|
|
160
|
+
|
|
161
|
+
1. Create new reporter class in `lib/gem_digest/reporters/`
|
|
162
|
+
2. Inherit from `GemDigest::Reporters::Base`
|
|
163
|
+
3. Implement `generate_report` method
|
|
164
|
+
4. Add format option to CLI and main module
|
|
165
|
+
|
|
166
|
+
### New Analysis Features
|
|
167
|
+
|
|
168
|
+
1. Extend `Analyzer` class with new methods
|
|
169
|
+
2. Update `Categorizer` if new categorization is needed
|
|
170
|
+
3. Add corresponding tests
|
|
171
|
+
4. Update documentation
|
|
172
|
+
|
|
173
|
+
### Configuration Support
|
|
174
|
+
|
|
175
|
+
1. Add configuration file parsing
|
|
176
|
+
2. Extend CLI options
|
|
177
|
+
3. Update reporters to use configuration
|
|
178
|
+
4. Add configuration validation
|
|
179
|
+
|
|
180
|
+
## ๐ Debugging
|
|
181
|
+
|
|
182
|
+
### Common Issues
|
|
183
|
+
|
|
184
|
+
1. **Network Timeouts**: RubyGems API calls may timeout
|
|
185
|
+
- Solution: Add retry logic and better error handling
|
|
186
|
+
|
|
187
|
+
2. **Version Parsing**: Complex version strings may not parse correctly
|
|
188
|
+
- Solution: Improve version parsing in `Categorizer`
|
|
189
|
+
|
|
190
|
+
3. **Large Gemfiles**: Performance issues with many gems
|
|
191
|
+
- Solution: Add parallel processing or caching
|
|
192
|
+
|
|
193
|
+
### Debug Mode
|
|
194
|
+
|
|
195
|
+
Enable verbose output for debugging:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
gemd analyze --verbose
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Logging
|
|
202
|
+
|
|
203
|
+
Add debug logging to track execution:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
puts "Debug: Processing gem #{gem_name}" if options[:verbose]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## ๐ Dependencies
|
|
210
|
+
|
|
211
|
+
### Runtime Dependencies
|
|
212
|
+
|
|
213
|
+
- `bundler ~> 2.0`: For parsing Gemfile.lock
|
|
214
|
+
- `commander ~> 4.5`: CLI framework
|
|
215
|
+
- `pastel ~> 0.8`: Colored output
|
|
216
|
+
- `tty-table ~> 0.12`: Formatted tables
|
|
217
|
+
|
|
218
|
+
### Development Dependencies
|
|
219
|
+
|
|
220
|
+
- `rspec ~> 3.0`: Testing framework
|
|
221
|
+
- `rake ~> 13.0`: Build tasks
|
|
222
|
+
- `rubocop ~> 1.21`: Code style checking
|
|
223
|
+
|
|
224
|
+
## ๐ค Contributing
|
|
225
|
+
|
|
226
|
+
1. Fork the repository
|
|
227
|
+
2. Create a feature branch: `git checkout -b feature/new-feature`
|
|
228
|
+
3. Make changes and add tests
|
|
229
|
+
4. Run tests: `bundle exec rspec`
|
|
230
|
+
5. Check code style: `bundle exec rubocop`
|
|
231
|
+
6. Commit changes: `git commit -m "Add new feature"`
|
|
232
|
+
7. Push branch: `git push origin feature/new-feature`
|
|
233
|
+
8. Create pull request
|
|
234
|
+
|
|
235
|
+
### Code Style
|
|
236
|
+
|
|
237
|
+
- Follow Ruby community standards
|
|
238
|
+
- Use RuboCop for style checking
|
|
239
|
+
- Write descriptive commit messages
|
|
240
|
+
- Add tests for new features
|
|
241
|
+
- Update documentation as needed
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ND-Zyth
|
|
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,131 @@
|
|
|
1
|
+
# ๐ Gem Digest
|
|
2
|
+
|
|
3
|
+
A powerful CLI tool that analyzes your `Gemfile.lock`, fetches the latest gem versions from RubyGems, and categorizes updates by semantic versioning (major, minor, patch).
|
|
4
|
+
|
|
5
|
+
## ๐ Features
|
|
6
|
+
|
|
7
|
+
- **Smart Analysis**: Parses `Gemfile.lock` and compares with latest versions from RubyGems
|
|
8
|
+
- **Semantic Categorization**: Groups updates by major, minor, and patch versions
|
|
9
|
+
- **Multiple Output Formats**: Console output with colors and tables, or Markdown reports
|
|
10
|
+
- **Comprehensive Reporting**: Shows current vs latest versions with source information
|
|
11
|
+
- **Cross-Platform**: Works on Linux, macOS, and Windows
|
|
12
|
+
|
|
13
|
+
## ๐ฆ Installation
|
|
14
|
+
|
|
15
|
+
Add this line to your application's Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'gem-digest'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
And then execute:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
$ bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install it yourself as:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ gem install gem-digest
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## ๐ ๏ธ Usage
|
|
34
|
+
|
|
35
|
+
### Basic Analysis
|
|
36
|
+
|
|
37
|
+
Analyze your current directory's `Gemfile.lock`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
$ gemd analyze
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Advanced Options
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Specify a different Gemfile.lock path
|
|
47
|
+
$ gemd analyze --gemfile-lock /path/to/Gemfile.lock
|
|
48
|
+
|
|
49
|
+
# Generate a markdown report
|
|
50
|
+
$ gemd analyze --format markdown --output-dir reports
|
|
51
|
+
|
|
52
|
+
# Show gems that are already up to date
|
|
53
|
+
$ gemd analyze --show-up-to-date
|
|
54
|
+
|
|
55
|
+
# Verbose output
|
|
56
|
+
$ gemd analyze --verbose
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Example Output
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
๐ Gem Digest Analysis Summary
|
|
63
|
+
==================================================
|
|
64
|
+
๐ฆ Total gems analyzed: 25
|
|
65
|
+
๐ด Major updates available: 3
|
|
66
|
+
๐ก Minor updates available: 8
|
|
67
|
+
๐ข Patch updates available: 12
|
|
68
|
+
โ
Up to date: 2
|
|
69
|
+
|
|
70
|
+
Major Updates (3)
|
|
71
|
+
โโโโโโโโโโโโโโโโโ
|
|
72
|
+
โโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโฌโโโโโโโโโโโโ
|
|
73
|
+
โ Gem โ Current โ Latest โ Source โ
|
|
74
|
+
โโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโผโโโโโโโโโโโโค
|
|
75
|
+
โ rails โ 6.1.7 โ 7.0.4 โ rubygems โ
|
|
76
|
+
โ rspec โ 3.11.0 โ 4.0.0 โ rubygems โ
|
|
77
|
+
โ sidekiq โ 6.5.8 โ 7.0.2 โ rubygems โ
|
|
78
|
+
โโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโดโโโโโโโโโโโโ
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## ๐๏ธ Development
|
|
82
|
+
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
86
|
+
|
|
87
|
+
### Running Tests
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
$ bundle exec rspec
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Code Quality
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
$ bundle exec rubocop
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ๐ค Contributing
|
|
100
|
+
|
|
101
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ND-Zyth/gem-digest.
|
|
102
|
+
|
|
103
|
+
## ๐ License
|
|
104
|
+
|
|
105
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
106
|
+
|
|
107
|
+
## ๐ง API Reference
|
|
108
|
+
|
|
109
|
+
### GemDigest Module
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# Analyze gems programmatically
|
|
113
|
+
GemDigest.analyze("path/to/Gemfile.lock", format: "console")
|
|
114
|
+
GemDigest.analyze("path/to/Gemfile.lock", format: "markdown", output_dir: "reports")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Classes
|
|
118
|
+
|
|
119
|
+
- `GemDigest::Analyzer` - Parses Gemfile.lock and fetches latest versions
|
|
120
|
+
- `GemDigest::Categorizer` - Categorizes gems by update type
|
|
121
|
+
- `GemDigest::Reporters::Console` - Generates colored console output
|
|
122
|
+
- `GemDigest::Reporters::Markdown` - Generates markdown reports
|
|
123
|
+
- `GemDigest::CLI` - Command-line interface
|
|
124
|
+
|
|
125
|
+
## ๐ฏ Roadmap
|
|
126
|
+
|
|
127
|
+
- [ ] Support for private gem sources
|
|
128
|
+
- [ ] Changelog integration
|
|
129
|
+
- [ ] Security vulnerability detection
|
|
130
|
+
- [ ] Custom update policies
|
|
131
|
+
- [ ] Integration with CI/CD pipelines
|
data/REQUIREMENTS.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
|
|
2
|
+
### ๐ Exact `gem-digest` Project Structure
|
|
3
|
+
|
|
4
|
+
```
|
|
5
|
+
gem-digest/
|
|
6
|
+
โโโ .github/
|
|
7
|
+
โ โโโ workflows/
|
|
8
|
+
โ โโโ release.yml
|
|
9
|
+
โโโ bin/
|
|
10
|
+
โ โโโ console
|
|
11
|
+
โ โโโ gemd
|
|
12
|
+
โโโ config/
|
|
13
|
+
โโโ exe/
|
|
14
|
+
โ โโโ gemd
|
|
15
|
+
โโโ lib/
|
|
16
|
+
โ โโโ gem_digest/
|
|
17
|
+
โ โ โโโ analyzer.rb
|
|
18
|
+
โ โ โโโ categorizer.rb
|
|
19
|
+
โ โ โโโ changelog_fetcher.rb
|
|
20
|
+
โ โ โโโ cli.rb
|
|
21
|
+
โ โ โโโ reporters/
|
|
22
|
+
โ โ โ โโโ base.rb
|
|
23
|
+
โ โ โ โโโ console.rb
|
|
24
|
+
โ โ โ โโโ markdown.rb
|
|
25
|
+
โ โ โโโ version.rb
|
|
26
|
+
โ โโโ gem_digest.rb
|
|
27
|
+
โโโ output/
|
|
28
|
+
โโโ spec/
|
|
29
|
+
โ โโโ gem_digest/
|
|
30
|
+
โ โ โโโ analyzer_spec.rb
|
|
31
|
+
โ โ โโโ categorizer_spec.rb
|
|
32
|
+
โ โ โโโ cli_spec.rb
|
|
33
|
+
โ โโโ integration_spec.rb
|
|
34
|
+
โ โโโ spec_helper.rb
|
|
35
|
+
โโโ .gitignore
|
|
36
|
+
โโโ .rspec
|
|
37
|
+
โโโ .rubocop.yml
|
|
38
|
+
โโโ CHANGELOG.md
|
|
39
|
+
โโโ Gemfile
|
|
40
|
+
โโโ LICENSE.txt
|
|
41
|
+
โโโ Rakefile
|
|
42
|
+
โโโ README.md
|
|
43
|
+
โโโ gem-digest.gemspec
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### ๐ Key Files and Directories Explained
|
|
47
|
+
|
|
48
|
+
| **File/Directory** | **Core Purpose** |
|
|
49
|
+
| :--- | :--- |
|
|
50
|
+
| **`exe/gemd`** | Main executable CLI entry point. Uses `#!/usr/bin/env ruby` shebang . |
|
|
51
|
+
| **`lib/gem_digest.rb`** | Primary require file that loads the module . |
|
|
52
|
+
| **`lib/gem_digest/cli.rb`** | Uses the `commander` gem to define commands, options, and help messages . |
|
|
53
|
+
| **`lib/gem_digest/analyzer.rb`** | Parses `Gemfile.lock` (using `Bundler` gem) and fetches latest versions from RubyGems. |
|
|
54
|
+
| **`lib/gem_digest/categorizer.rb`** | Compares versions and categorizes updates by semver (major, minor, patch). |
|
|
55
|
+
| **`lib/gem_digest/reporters/`** | Housedifferent output formats (console, markdown) . |
|
|
56
|
+
| **`gem-digest.gemspec`** | Defines gem metadata, version, and dependencies (e.g., `commander`, `bundler`) . |
|
|
57
|
+
| **`spec/`** | Contains unit and integration tests using RSpec . |
|
|
58
|
+
| **`.github/workflows/release.yml`** | CI/CD workflow to run tests on push and build/release cross-platform gems on tag. |
|
|
59
|
+
|
|
60
|
+
### โ๏ธ Core Components and Requirements
|
|
61
|
+
|
|
62
|
+
Your `gem-digest.gemspec` file should specify these core dependencies:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
spec.add_dependency "bundler", "~> 2.0" # For parsing Gemfile.lock
|
|
66
|
+
spec.add_dependency "commander", "~> 4.5" # For building the CLI structure
|
|
67
|
+
spec.add_dependency "pastel", "~> 0.8" # For colored console output
|
|
68
|
+
spec.add_dependency "tty-table", "~> 0.12" # For formatted tables in the console
|
|
69
|
+
|
|
70
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
71
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The main executable `exe/gemd` should start with:
|
|
75
|
+
```ruby
|
|
76
|
+
#!/usr/bin/env ruby
|
|
77
|
+
require 'gem_digest'
|
|
78
|
+
require 'commander/import'
|
|
79
|
+
|
|
80
|
+
# ... CLI program definition using commander
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### ๐ ๏ธ Development Setup
|
|
84
|
+
|
|
85
|
+
1. **Install dependencies locally**: Run `bundle install` to install all required gems.
|
|
86
|
+
2. **Test during development**: Use `bundle exec ruby -Ilib exe/gemd` to run your tool with the correct load path without installing it . The `bin/console` script can provide an interactive environment for testing methods.
|
|
87
|
+
3. **Run tests**: Execute `bundle exec rspec` to run your test suite.
|
|
88
|
+
|
|
89
|
+
### ๐ Cross-Platform Build & Release via GitHub Actions
|
|
90
|
+
|
|
91
|
+
Create `.github/workflows/release.yml` to automatically build and release your gem for multiple platforms and Ruby versions. This workflow should:
|
|
92
|
+
|
|
93
|
+
- **Trigger on tag pushes** (e.g., `v1.0.0`)
|
|
94
|
+
- **Run tests** across multiple OS (Ubuntu, macOS, Windows) and Ruby versions
|
|
95
|
+
- **Build the gem** for each Ruby version
|
|
96
|
+
- **Create a GitHub Release** and attach all built `.gem` files
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
name: Build and Release Gem
|
|
100
|
+
|
|
101
|
+
on:
|
|
102
|
+
push:
|
|
103
|
+
tags:
|
|
104
|
+
- 'v*'
|
|
105
|
+
|
|
106
|
+
jobs:
|
|
107
|
+
build:
|
|
108
|
+
name: Build Gem on ${{ matrix.os }} (Ruby ${{ matrix.ruby }})
|
|
109
|
+
runs-on: ${{ matrix.os }}-latest
|
|
110
|
+
strategy:
|
|
111
|
+
matrix:
|
|
112
|
+
os: [ubuntu, macos, windows]
|
|
113
|
+
ruby: ['3.1', '3.2', '3.3']
|
|
114
|
+
|
|
115
|
+
steps:
|
|
116
|
+
- name: Checkout code
|
|
117
|
+
uses: actions/checkout@v4
|
|
118
|
+
|
|
119
|
+
- name: Set up Ruby
|
|
120
|
+
uses: ruby/setup-ruby@v1
|
|
121
|
+
with:
|
|
122
|
+
ruby-version: ${{ matrix.ruby }}
|
|
123
|
+
bundler-cache: true
|
|
124
|
+
|
|
125
|
+
- name: Run tests
|
|
126
|
+
run: bundle exec rspec
|
|
127
|
+
|
|
128
|
+
- name: Build Gem
|
|
129
|
+
run: gem build gem-digest.gemspec
|
|
130
|
+
|
|
131
|
+
- name: Upload Gem Artifact
|
|
132
|
+
uses: actions/upload-artifact@v4
|
|
133
|
+
with:
|
|
134
|
+
name: gem-digest-${{ matrix.os }}-ruby${{ matrix.ruby }}
|
|
135
|
+
path: "*.gem"
|
|
136
|
+
|
|
137
|
+
release:
|
|
138
|
+
name: Create Release
|
|
139
|
+
needs: build
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
steps:
|
|
142
|
+
- name: Download all built gems
|
|
143
|
+
uses: actions/download-artifact@v4
|
|
144
|
+
with:
|
|
145
|
+
path: artifacts
|
|
146
|
+
|
|
147
|
+
- name: Create GitHub Release
|
|
148
|
+
uses: softprops/action-gh-release@v1
|
|
149
|
+
with:
|
|
150
|
+
files: "artifacts/**/*.gem"
|
|
151
|
+
```
|
data/Rakefile
ADDED
data/exe/gemd
ADDED
data/gem-digest.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/gem_digest/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "gem-digest"
|
|
7
|
+
spec.version = GemDigest::VERSION
|
|
8
|
+
spec.authors = ["ND-Zyth"]
|
|
9
|
+
spec.email = ["NuclearDog@zohomail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Analyze and categorize gem updates from Gemfile.lock"
|
|
12
|
+
spec.description = "A CLI tool that parses Gemfile.lock files, fetches latest gem versions from RubyGems, and categorizes updates by semantic versioning (major, minor, patch)."
|
|
13
|
+
spec.homepage = "https://github.com/ND-Zyth/gem-digest"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/ND-Zyth/gem-digest"
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/ND-Zyth/gem-digest/blob/main/CHANGELOG.md"
|
|
21
|
+
|
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
|
23
|
+
spec.files = Dir["lib/**/*", "exe/*", "*.md", "*.txt", "Rakefile", "*.gemspec"].select { |f| File.file?(f) }
|
|
24
|
+
spec.bindir = "exe"
|
|
25
|
+
spec.executables = ["gemd"]
|
|
26
|
+
spec.require_paths = ["lib"]
|
|
27
|
+
|
|
28
|
+
# Core dependencies
|
|
29
|
+
spec.add_dependency "bundler", "~> 2.0"
|
|
30
|
+
spec.add_dependency "commander", "~> 4.5"
|
|
31
|
+
spec.add_dependency "pastel", "~> 0.8"
|
|
32
|
+
spec.add_dependency "tty-table", "~> 0.12"
|
|
33
|
+
|
|
34
|
+
# Development dependencies
|
|
35
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
36
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
37
|
+
spec.add_development_dependency "rubocop", "~> 1.21"
|
|
38
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "json"
|
|
6
|
+
require "uri"
|
|
7
|
+
|
|
8
|
+
module GemDigest
|
|
9
|
+
class Analyzer
|
|
10
|
+
def initialize(gemfile_lock_path = "Gemfile.lock")
|
|
11
|
+
@gemfile_lock_path = gemfile_lock_path
|
|
12
|
+
raise Error, "Gemfile.lock not found at #{gemfile_lock_path}" unless File.exist?(gemfile_lock_path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def analyze
|
|
16
|
+
lockfile = Bundler::LockfileParser.new(File.read(@gemfile_lock_path))
|
|
17
|
+
gems_data = []
|
|
18
|
+
|
|
19
|
+
lockfile.specs.each do |spec|
|
|
20
|
+
latest_version = fetch_latest_version(spec.name)
|
|
21
|
+
next unless latest_version
|
|
22
|
+
|
|
23
|
+
gems_data << {
|
|
24
|
+
name: spec.name,
|
|
25
|
+
current_version: spec.version.to_s,
|
|
26
|
+
latest_version: latest_version,
|
|
27
|
+
source: spec.source&.to_s || "rubygems"
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
gems_data
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def fetch_latest_version(gem_name)
|
|
37
|
+
uri = URI("https://rubygems.org/api/v1/gems/#{gem_name}.json")
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
response = Net::HTTP.get_response(uri)
|
|
41
|
+
return nil unless response.code == "200"
|
|
42
|
+
|
|
43
|
+
gem_info = JSON.parse(response.body)
|
|
44
|
+
gem_info["version"]
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
warn "Warning: Could not fetch latest version for #{gem_name}: #{e.message}"
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GemDigest
|
|
4
|
+
class Categorizer
|
|
5
|
+
def categorize(gems_data)
|
|
6
|
+
categorized = {
|
|
7
|
+
major: [],
|
|
8
|
+
minor: [],
|
|
9
|
+
patch: [],
|
|
10
|
+
up_to_date: []
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
gems_data.each do |gem_data|
|
|
14
|
+
current = parse_version(gem_data[:current_version])
|
|
15
|
+
latest = parse_version(gem_data[:latest_version])
|
|
16
|
+
|
|
17
|
+
next unless current && latest
|
|
18
|
+
|
|
19
|
+
category = determine_update_category(current, latest)
|
|
20
|
+
categorized[category] << gem_data
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
categorized
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def parse_version(version_string)
|
|
29
|
+
# Handle version strings like "1.2.3", "1.2.3.pre", etc.
|
|
30
|
+
parts = version_string.split(/[.-]/).map(&:to_i)
|
|
31
|
+
return nil if parts.empty?
|
|
32
|
+
|
|
33
|
+
# Ensure we have at least 3 parts (major, minor, patch)
|
|
34
|
+
while parts.length < 3
|
|
35
|
+
parts << 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
parts[0..2] # Return only major, minor, patch
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def determine_update_category(current, latest)
|
|
42
|
+
return :up_to_date if current == latest
|
|
43
|
+
|
|
44
|
+
major_current, minor_current, patch_current = current
|
|
45
|
+
major_latest, minor_latest, patch_latest = latest
|
|
46
|
+
|
|
47
|
+
if major_latest > major_current
|
|
48
|
+
:major
|
|
49
|
+
elsif minor_latest > minor_current
|
|
50
|
+
:minor
|
|
51
|
+
elsif patch_latest > patch_current
|
|
52
|
+
:patch
|
|
53
|
+
else
|
|
54
|
+
:up_to_date
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module GemDigest
|
|
8
|
+
class ChangelogFetcher
|
|
9
|
+
def initialize
|
|
10
|
+
@cache = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fetch_changelog_url(gem_name)
|
|
14
|
+
return @cache[gem_name] if @cache.key?(gem_name)
|
|
15
|
+
|
|
16
|
+
uri = URI("https://rubygems.org/api/v1/gems/#{gem_name}.json")
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
response = Net::HTTP.get_response(uri)
|
|
20
|
+
return nil unless response.code == "200"
|
|
21
|
+
|
|
22
|
+
gem_info = JSON.parse(response.body)
|
|
23
|
+
changelog_url = gem_info["changelog_uri"] ||
|
|
24
|
+
gem_info["homepage_uri"] ||
|
|
25
|
+
gem_info["source_code_uri"]
|
|
26
|
+
|
|
27
|
+
@cache[gem_name] = changelog_url
|
|
28
|
+
changelog_url
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
warn "Warning: Could not fetch changelog for #{gem_name}: #{e.message}"
|
|
31
|
+
@cache[gem_name] = nil
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def fetch_release_notes(gem_name, from_version, to_version)
|
|
37
|
+
# This is a placeholder for more advanced changelog parsing
|
|
38
|
+
# In a real implementation, you might parse GitHub releases, CHANGELOG files, etc.
|
|
39
|
+
changelog_url = fetch_changelog_url(gem_name)
|
|
40
|
+
return nil unless changelog_url
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
gem_name: gem_name,
|
|
44
|
+
from_version: from_version,
|
|
45
|
+
to_version: to_version,
|
|
46
|
+
changelog_url: changelog_url,
|
|
47
|
+
notes: "See changelog for details about changes from #{from_version} to #{to_version}"
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "commander/import"
|
|
5
|
+
require_relative "../gem_digest"
|
|
6
|
+
|
|
7
|
+
module GemDigest
|
|
8
|
+
class CLI
|
|
9
|
+
def self.start
|
|
10
|
+
program :name, "gemd"
|
|
11
|
+
program :version, GemDigest::VERSION
|
|
12
|
+
program :description, "Analyze and categorize gem updates from Gemfile.lock"
|
|
13
|
+
|
|
14
|
+
command :analyze do |c|
|
|
15
|
+
c.syntax = "gemd analyze [options]"
|
|
16
|
+
c.summary = "Analyze Gemfile.lock and show available gem updates"
|
|
17
|
+
c.description = "Parse Gemfile.lock, fetch latest versions from RubyGems, and categorize updates by semantic versioning"
|
|
18
|
+
|
|
19
|
+
c.option "--gemfile-lock PATH", String, "Path to Gemfile.lock (default: ./Gemfile.lock)"
|
|
20
|
+
c.option "--format FORMAT", String, "Output format: console or markdown (default: console)"
|
|
21
|
+
c.option "--output-dir DIR", String, "Output directory for reports (default: ./output)"
|
|
22
|
+
c.option "--show-up-to-date", "Show gems that are already up to date"
|
|
23
|
+
c.option "--verbose", "Show detailed output"
|
|
24
|
+
|
|
25
|
+
c.action do |args, options|
|
|
26
|
+
options.default(
|
|
27
|
+
gemfile_lock: "Gemfile.lock",
|
|
28
|
+
format: "console",
|
|
29
|
+
output_dir: "output",
|
|
30
|
+
show_up_to_date: false,
|
|
31
|
+
verbose: false
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
begin
|
|
35
|
+
puts "๐ Analyzing gems..." if options.verbose
|
|
36
|
+
|
|
37
|
+
analyzer = GemDigest::Analyzer.new(options.gemfile_lock)
|
|
38
|
+
gems_data = analyzer.analyze
|
|
39
|
+
|
|
40
|
+
puts "๐ Categorizing updates..." if options.verbose
|
|
41
|
+
|
|
42
|
+
categorizer = GemDigest::Categorizer.new
|
|
43
|
+
categorized_gems = categorizer.categorize(gems_data)
|
|
44
|
+
|
|
45
|
+
puts "๐ Generating report..." if options.verbose
|
|
46
|
+
|
|
47
|
+
reporter_class = case options.format
|
|
48
|
+
when "markdown"
|
|
49
|
+
GemDigest::Reporters::Markdown
|
|
50
|
+
else
|
|
51
|
+
GemDigest::Reporters::Console
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
reporter_options = {
|
|
55
|
+
output_dir: options.output_dir,
|
|
56
|
+
show_up_to_date: options.show_up_to_date,
|
|
57
|
+
verbose: options.verbose
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
reporter = reporter_class.new(reporter_options)
|
|
61
|
+
reporter.generate_report(categorized_gems)
|
|
62
|
+
|
|
63
|
+
rescue GemDigest::Error => e
|
|
64
|
+
puts "โ Error: #{e.message}"
|
|
65
|
+
exit 1
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
puts "โ Unexpected error: #{e.message}"
|
|
68
|
+
puts e.backtrace if options.verbose
|
|
69
|
+
exit 1
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
command :version do |c|
|
|
75
|
+
c.syntax = "gemd version"
|
|
76
|
+
c.summary = "Show gem-digest version"
|
|
77
|
+
c.action do
|
|
78
|
+
puts "gem-digest version #{GemDigest::VERSION}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
default_command :analyze
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GemDigest
|
|
4
|
+
module Reporters
|
|
5
|
+
class Base
|
|
6
|
+
def initialize(options = {})
|
|
7
|
+
@options = options
|
|
8
|
+
@output_dir = options[:output_dir] || "output"
|
|
9
|
+
ensure_output_directory
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate_report(categorized_gems)
|
|
13
|
+
raise NotImplementedError, "Subclasses must implement generate_report"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
def ensure_output_directory
|
|
19
|
+
Dir.mkdir(@output_dir) unless Dir.exist?(@output_dir)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def count_updates(categorized_gems)
|
|
23
|
+
{
|
|
24
|
+
major: categorized_gems[:major].length,
|
|
25
|
+
minor: categorized_gems[:minor].length,
|
|
26
|
+
patch: categorized_gems[:patch].length,
|
|
27
|
+
up_to_date: categorized_gems[:up_to_date].length
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def total_gems(categorized_gems)
|
|
32
|
+
categorized_gems.values.flatten.length
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
require "tty-table"
|
|
5
|
+
|
|
6
|
+
module GemDigest
|
|
7
|
+
module Reporters
|
|
8
|
+
class Console < Base
|
|
9
|
+
def initialize(options = {})
|
|
10
|
+
super
|
|
11
|
+
@pastel = Pastel.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate_report(categorized_gems)
|
|
15
|
+
print_summary(categorized_gems)
|
|
16
|
+
print_category_tables(categorized_gems)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def print_summary(categorized_gems)
|
|
22
|
+
counts = count_updates(categorized_gems)
|
|
23
|
+
total = total_gems(categorized_gems)
|
|
24
|
+
|
|
25
|
+
puts @pastel.bold.cyan("\n๐ Gem Digest Analysis Summary")
|
|
26
|
+
puts @pastel.dim("=" * 50)
|
|
27
|
+
|
|
28
|
+
puts "๐ฆ Total gems analyzed: #{@pastel.bold(total)}"
|
|
29
|
+
puts "๐ด Major updates available: #{@pastel.red.bold(counts[:major])}"
|
|
30
|
+
puts "๐ก Minor updates available: #{@pastel.yellow.bold(counts[:minor])}"
|
|
31
|
+
puts "๐ข Patch updates available: #{@pastel.green.bold(counts[:patch])}"
|
|
32
|
+
puts "โ
Up to date: #{@pastel.dim(counts[:up_to_date])}"
|
|
33
|
+
puts
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def print_category_tables(categorized_gems)
|
|
37
|
+
print_category("Major Updates", categorized_gems[:major], :red) if categorized_gems[:major].any?
|
|
38
|
+
print_category("Minor Updates", categorized_gems[:minor], :yellow) if categorized_gems[:minor].any?
|
|
39
|
+
print_category("Patch Updates", categorized_gems[:patch], :green) if categorized_gems[:patch].any?
|
|
40
|
+
|
|
41
|
+
if @options[:show_up_to_date] && categorized_gems[:up_to_date].any?
|
|
42
|
+
print_category("Up to Date", categorized_gems[:up_to_date], :dim)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def print_category(title, gems, color)
|
|
47
|
+
return if gems.empty?
|
|
48
|
+
|
|
49
|
+
puts @pastel.bold.send(color, "#{title} (#{gems.length})")
|
|
50
|
+
puts @pastel.dim("-" * (title.length + gems.length.to_s.length + 3))
|
|
51
|
+
|
|
52
|
+
table = TTY::Table.new(header: ["Gem", "Current", "Latest", "Source"])
|
|
53
|
+
|
|
54
|
+
gems.each do |gem_data|
|
|
55
|
+
table << [
|
|
56
|
+
gem_data[:name],
|
|
57
|
+
gem_data[:current_version],
|
|
58
|
+
gem_data[:latest_version],
|
|
59
|
+
format_source(gem_data[:source])
|
|
60
|
+
]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
puts table.render(:unicode, padding: [0, 1])
|
|
64
|
+
puts
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def format_source(source)
|
|
68
|
+
return "rubygems" if source.nil? || source.empty? || source.include?("rubygems")
|
|
69
|
+
|
|
70
|
+
if source.include?("git")
|
|
71
|
+
"git"
|
|
72
|
+
elsif source.include?("path")
|
|
73
|
+
"local"
|
|
74
|
+
else
|
|
75
|
+
"other"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GemDigest
|
|
4
|
+
module Reporters
|
|
5
|
+
class Markdown < Base
|
|
6
|
+
def generate_report(categorized_gems)
|
|
7
|
+
content = build_markdown_content(categorized_gems)
|
|
8
|
+
output_file = File.join(@output_dir, "gem-digest-report.md")
|
|
9
|
+
|
|
10
|
+
File.write(output_file, content)
|
|
11
|
+
puts "๐ Markdown report generated: #{output_file}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def build_markdown_content(categorized_gems)
|
|
17
|
+
content = []
|
|
18
|
+
content << "# ๐ Gem Digest Report"
|
|
19
|
+
content << ""
|
|
20
|
+
content << "Generated on: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
21
|
+
content << ""
|
|
22
|
+
|
|
23
|
+
content << build_summary_section(categorized_gems)
|
|
24
|
+
content << build_details_section(categorized_gems)
|
|
25
|
+
|
|
26
|
+
content.join("\n")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_summary_section(categorized_gems)
|
|
30
|
+
counts = count_updates(categorized_gems)
|
|
31
|
+
total = total_gems(categorized_gems)
|
|
32
|
+
|
|
33
|
+
summary = []
|
|
34
|
+
summary << "## ๐ Summary"
|
|
35
|
+
summary << ""
|
|
36
|
+
summary << "| Category | Count |"
|
|
37
|
+
summary << "|----------|-------|"
|
|
38
|
+
summary << "| ๐ฆ Total gems | #{total} |"
|
|
39
|
+
summary << "| ๐ด Major updates | #{counts[:major]} |"
|
|
40
|
+
summary << "| ๐ก Minor updates | #{counts[:minor]} |"
|
|
41
|
+
summary << "| ๐ข Patch updates | #{counts[:patch]} |"
|
|
42
|
+
summary << "| โ
Up to date | #{counts[:up_to_date]} |"
|
|
43
|
+
summary << ""
|
|
44
|
+
|
|
45
|
+
summary.join("\n")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_details_section(categorized_gems)
|
|
49
|
+
details = []
|
|
50
|
+
details << "## ๐ Detailed Analysis"
|
|
51
|
+
details << ""
|
|
52
|
+
|
|
53
|
+
if categorized_gems[:major].any?
|
|
54
|
+
details << build_category_section("๐ด Major Updates", categorized_gems[:major])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if categorized_gems[:minor].any?
|
|
58
|
+
details << build_category_section("๐ก Minor Updates", categorized_gems[:minor])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if categorized_gems[:patch].any?
|
|
62
|
+
details << build_category_section("๐ข Patch Updates", categorized_gems[:patch])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if @options[:show_up_to_date] && categorized_gems[:up_to_date].any?
|
|
66
|
+
details << build_category_section("โ
Up to Date", categorized_gems[:up_to_date])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
details.join("\n")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_category_section(title, gems)
|
|
73
|
+
return "" if gems.empty?
|
|
74
|
+
|
|
75
|
+
section = []
|
|
76
|
+
section << "### #{title}"
|
|
77
|
+
section << ""
|
|
78
|
+
section << "| Gem | Current Version | Latest Version | Source |"
|
|
79
|
+
section << "|-----|-----------------|----------------|--------|"
|
|
80
|
+
|
|
81
|
+
gems.each do |gem_data|
|
|
82
|
+
section << "| #{gem_data[:name]} | #{gem_data[:current_version]} | #{gem_data[:latest_version]} | #{format_source(gem_data[:source])} |"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
section << ""
|
|
86
|
+
section.join("\n")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def format_source(source)
|
|
90
|
+
return "RubyGems" if source.nil? || source.empty? || source.include?("rubygems")
|
|
91
|
+
|
|
92
|
+
if source.include?("git")
|
|
93
|
+
"Git"
|
|
94
|
+
elsif source.include?("path")
|
|
95
|
+
"Local"
|
|
96
|
+
else
|
|
97
|
+
"Other"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/gem_digest.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "gem_digest/version"
|
|
4
|
+
require_relative "gem_digest/analyzer"
|
|
5
|
+
require_relative "gem_digest/categorizer"
|
|
6
|
+
require_relative "gem_digest/changelog_fetcher"
|
|
7
|
+
require_relative "gem_digest/cli"
|
|
8
|
+
require_relative "gem_digest/reporters/base"
|
|
9
|
+
require_relative "gem_digest/reporters/console"
|
|
10
|
+
require_relative "gem_digest/reporters/markdown"
|
|
11
|
+
|
|
12
|
+
module GemDigest
|
|
13
|
+
class Error < StandardError; end
|
|
14
|
+
|
|
15
|
+
def self.analyze(gemfile_lock_path = "Gemfile.lock", options = {})
|
|
16
|
+
analyzer = Analyzer.new(gemfile_lock_path)
|
|
17
|
+
gems_data = analyzer.analyze
|
|
18
|
+
|
|
19
|
+
categorizer = Categorizer.new
|
|
20
|
+
categorized_gems = categorizer.categorize(gems_data)
|
|
21
|
+
|
|
22
|
+
reporter_class = case options[:format]
|
|
23
|
+
when "markdown"
|
|
24
|
+
Reporters::Markdown
|
|
25
|
+
else
|
|
26
|
+
Reporters::Console
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
reporter = reporter_class.new(options)
|
|
30
|
+
reporter.generate_report(categorized_gems)
|
|
31
|
+
end
|
|
32
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gem-digest
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- ND-Zyth
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-10-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: commander
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.5'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '4.5'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: pastel
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0.8'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0.8'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: tty-table
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.12'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.12'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rake
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '13.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '13.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rubocop
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '1.21'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '1.21'
|
|
111
|
+
description: A CLI tool that parses Gemfile.lock files, fetches latest gem versions
|
|
112
|
+
from RubyGems, and categorizes updates by semantic versioning (major, minor, patch).
|
|
113
|
+
email:
|
|
114
|
+
- NuclearDog@zohomail.com
|
|
115
|
+
executables:
|
|
116
|
+
- gemd
|
|
117
|
+
extensions: []
|
|
118
|
+
extra_rdoc_files: []
|
|
119
|
+
files:
|
|
120
|
+
- DEVELOPMENT.md
|
|
121
|
+
- LICENSE.txt
|
|
122
|
+
- README.md
|
|
123
|
+
- REQUIREMENTS.md
|
|
124
|
+
- Rakefile
|
|
125
|
+
- exe/gemd
|
|
126
|
+
- gem-digest.gemspec
|
|
127
|
+
- lib/gem_digest.rb
|
|
128
|
+
- lib/gem_digest/analyzer.rb
|
|
129
|
+
- lib/gem_digest/categorizer.rb
|
|
130
|
+
- lib/gem_digest/changelog_fetcher.rb
|
|
131
|
+
- lib/gem_digest/cli.rb
|
|
132
|
+
- lib/gem_digest/reporters/base.rb
|
|
133
|
+
- lib/gem_digest/reporters/console.rb
|
|
134
|
+
- lib/gem_digest/reporters/markdown.rb
|
|
135
|
+
- lib/gem_digest/version.rb
|
|
136
|
+
homepage: https://github.com/ND-Zyth/gem-digest
|
|
137
|
+
licenses:
|
|
138
|
+
- MIT
|
|
139
|
+
metadata:
|
|
140
|
+
allowed_push_host: https://rubygems.org
|
|
141
|
+
homepage_uri: https://github.com/ND-Zyth/gem-digest
|
|
142
|
+
source_code_uri: https://github.com/ND-Zyth/gem-digest
|
|
143
|
+
changelog_uri: https://github.com/ND-Zyth/gem-digest/blob/main/CHANGELOG.md
|
|
144
|
+
post_install_message:
|
|
145
|
+
rdoc_options: []
|
|
146
|
+
require_paths:
|
|
147
|
+
- lib
|
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 2.6.0
|
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - ">="
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '0'
|
|
158
|
+
requirements: []
|
|
159
|
+
rubygems_version: 3.0.3.1
|
|
160
|
+
signing_key:
|
|
161
|
+
specification_version: 4
|
|
162
|
+
summary: Analyze and categorize gem updates from Gemfile.lock
|
|
163
|
+
test_files: []
|