stackharbinger 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +39 -9
- data/docs/CNAME +1 -0
- data/docs/index.html +161 -0
- data/lib/harbinger/cli.rb +203 -34
- data/lib/harbinger/config_manager.rb +71 -0
- data/lib/harbinger/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9932e2c3197ac2e3a514ac213380f97c336f85c8c302745db6754f70f7c1a79
|
|
4
|
+
data.tar.gz: 11f4b85563611e4921eba0666f29181bb26560d19ddb65dd0673e331276dd7bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0271ed33509bcf4fa3bce6a9f7b214753107131d3dfddbc248d68d5aea7cdb0b6a11eee4f4c2367f3060f1f1f7a7b81bca27b07e0f67a427c75eb0b0cd0efd5
|
|
7
|
+
data.tar.gz: 71178783539fdf85565e517bad7ebc983f188ff13bfa179d77d4fc6dcea953308c8c93ccb7fe22441ea8af51d00e4a437708a2769a345f04667e4de6768f16fb
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-01-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Project tracking**: Save and track multiple projects with `--save` flag
|
|
14
|
+
- **Dashboard view**: `harbinger show` command displays all tracked projects in a table
|
|
15
|
+
- **Recursive scanning**: `--recursive` flag to scan all subdirectories with Gemfiles
|
|
16
|
+
- **Config management**: Projects stored in `~/.harbinger/config.yml`
|
|
17
|
+
- **Bulk operations**: Scan entire directories like `~/Projects` and save all at once
|
|
18
|
+
- **Enhanced UI**: TTY::Table for beautiful table formatting in dashboard
|
|
19
|
+
- **Color-coded dashboard**: Red for EOL projects, yellow for ending soon, green for current
|
|
20
|
+
- **Smart sorting**: Dashboard prioritizes EOL projects at the top
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `scan` command now uses `--path` flag instead of positional argument for consistency
|
|
24
|
+
- ConfigManager API uses extensible `versions: {}` hash for future product support
|
|
25
|
+
- Enhanced test coverage (52 passing tests)
|
|
26
|
+
|
|
27
|
+
### Technical
|
|
28
|
+
- Added ConfigManager with YAML persistence
|
|
29
|
+
- ISO8601 timestamp format for YAML safety
|
|
30
|
+
- Extensible architecture for future language/database support
|
|
31
|
+
|
|
10
32
|
## [0.1.0] - 2026-01-18
|
|
11
33
|
|
|
12
34
|
### Added
|
data/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Harbinger is a CLI tool that scans your Ruby and Rails projects, detects version
|
|
|
10
10
|
- 📅 **Fetches EOL data** from [endoflife.date](https://endoflife.date)
|
|
11
11
|
- 🎨 **Color-coded warnings** (red: already EOL, yellow: <6 months, green: safe)
|
|
12
12
|
- ⚡ **Smart caching** (24-hour cache, works offline after first fetch)
|
|
13
|
+
- 📊 **Track multiple projects** with `--save` and view dashboard with `harbinger show`
|
|
14
|
+
- 🔄 **Bulk scanning** with `--recursive` flag to scan entire directories
|
|
13
15
|
- 🚀 **Zero configuration** - just run `harbinger scan`
|
|
14
16
|
|
|
15
17
|
## Installation
|
|
@@ -35,7 +37,13 @@ The command is still `harbinger` (shorter to type).
|
|
|
35
37
|
harbinger scan
|
|
36
38
|
|
|
37
39
|
# Scan specific project
|
|
38
|
-
harbinger scan ~/Projects/my-rails-app
|
|
40
|
+
harbinger scan --path ~/Projects/my-rails-app
|
|
41
|
+
|
|
42
|
+
# Save project for tracking
|
|
43
|
+
harbinger scan --save
|
|
44
|
+
|
|
45
|
+
# Scan all Ruby projects in a directory recursively
|
|
46
|
+
harbinger scan --path ~/Projects --recursive --save
|
|
39
47
|
```
|
|
40
48
|
|
|
41
49
|
**Example output:**
|
|
@@ -58,6 +66,28 @@ Rails 7.0.8:
|
|
|
58
66
|
Status: ALREADY EOL (474 days ago)
|
|
59
67
|
```
|
|
60
68
|
|
|
69
|
+
### View tracked projects
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Show dashboard of all tracked projects
|
|
73
|
+
harbinger show
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Example output:**
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Tracked Projects (10)
|
|
80
|
+
================================================================================
|
|
81
|
+
┌───────────────────┬───────┬──────────┬─────────────┐
|
|
82
|
+
│ Project │ Ruby │ Rails │ Status │
|
|
83
|
+
├───────────────────┼───────┼──────────┼─────────────┤
|
|
84
|
+
│ ledger │ 3.3.0 │ 6.1.7.10 │ ✗ Rails EOL │
|
|
85
|
+
│ option_tracker │ 3.3.0 │ 7.0.8.7 │ ✗ Rails EOL │
|
|
86
|
+
│ CarCal │ - │ 8.0.2 │ ✓ Current │
|
|
87
|
+
│ job_tracker │ 3.3.0 │ 8.0.4 │ ✓ Current │
|
|
88
|
+
└───────────────────┴───────┴──────────┴─────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
61
91
|
### Update EOL data
|
|
62
92
|
|
|
63
93
|
```bash
|
|
@@ -124,17 +154,17 @@ bundle exec exe/harbinger scan .
|
|
|
124
154
|
|
|
125
155
|
## Roadmap
|
|
126
156
|
|
|
127
|
-
### V0.
|
|
128
|
-
- ✅
|
|
129
|
-
- ✅
|
|
130
|
-
- ✅
|
|
131
|
-
- ✅
|
|
157
|
+
### V0.2.0 - Current
|
|
158
|
+
- ✅ Dashboard: `harbinger show` to see all tracked projects
|
|
159
|
+
- ✅ Config management: Save and track multiple projects with `--save`
|
|
160
|
+
- ✅ Recursive scanning: `--recursive` flag to scan multiple projects at once
|
|
161
|
+
- ✅ Enhanced project tracking with YAML config
|
|
132
162
|
|
|
133
|
-
### V0.
|
|
134
|
-
- 📊 Dashboard: `harbinger show` to see all tracked projects
|
|
135
|
-
- 💾 Config management: Save and track multiple projects
|
|
163
|
+
### V0.3.0 - Planned
|
|
136
164
|
- 🐘 PostgreSQL version detection
|
|
137
165
|
- 🗄️ MySQL version detection
|
|
166
|
+
- 🔄 Rescan command to update all tracked projects
|
|
167
|
+
- 📋 Export reports to JSON/CSV
|
|
138
168
|
|
|
139
169
|
### V1.0 - Future
|
|
140
170
|
- 🐍 Python support (pyproject.toml, requirements.txt)
|
data/docs/CNAME
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
stackharbinger.com
|
data/docs/index.html
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Harbinger - Track EOL Dates for Your Tech Stack</title>
|
|
7
|
+
<meta name="description" content="Never get caught off-guard by unsupported dependencies. Harbinger tracks End-of-Life dates for Ruby, Rails, and more.">
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<style>
|
|
10
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;900&display=swap');
|
|
11
|
+
body { font-family: 'Inter', sans-serif; }
|
|
12
|
+
</style>
|
|
13
|
+
</head>
|
|
14
|
+
<body class="bg-gray-50">
|
|
15
|
+
<!-- Hero Section -->
|
|
16
|
+
<div class="bg-gradient-to-br from-blue-600 to-blue-800 text-white">
|
|
17
|
+
<div class="max-w-6xl mx-auto px-4 py-20">
|
|
18
|
+
<h1 class="text-5xl md:text-7xl font-black mb-6">Harbinger</h1>
|
|
19
|
+
<p class="text-2xl md:text-3xl mb-8 text-blue-100">Track End-of-Life dates for your tech stack</p>
|
|
20
|
+
<p class="text-xl mb-12 text-blue-100 max-w-2xl">Never get caught off-guard by unsupported dependencies. Harbinger scans your Ruby and Rails projects and warns you before support ends.</p>
|
|
21
|
+
|
|
22
|
+
<div class="flex flex-col sm:flex-row gap-4">
|
|
23
|
+
<div class="bg-gray-900 rounded-lg p-4 font-mono text-sm">
|
|
24
|
+
<span class="text-gray-400">$</span> <span class="text-green-400">gem install stackharbinger</span>
|
|
25
|
+
</div>
|
|
26
|
+
<a href="https://github.com/RichD/harbinger" class="inline-block bg-white text-blue-600 px-8 py-4 rounded-lg font-semibold hover:bg-blue-50 transition text-center">
|
|
27
|
+
View on GitHub →
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Features Section -->
|
|
34
|
+
<div class="max-w-6xl mx-auto px-4 py-16">
|
|
35
|
+
<h2 class="text-3xl font-bold mb-12 text-center">Features</h2>
|
|
36
|
+
<div class="grid md:grid-cols-3 gap-8">
|
|
37
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
38
|
+
<div class="text-4xl mb-4">🔍</div>
|
|
39
|
+
<h3 class="text-xl font-semibold mb-2">Auto-Detection</h3>
|
|
40
|
+
<p class="text-gray-600">Automatically detects Ruby and Rails versions from .ruby-version, Gemfile, and Gemfile.lock</p>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
43
|
+
<div class="text-4xl mb-4">📅</div>
|
|
44
|
+
<h3 class="text-xl font-semibold mb-2">EOL Data</h3>
|
|
45
|
+
<p class="text-gray-600">Fetches official EOL dates from endoflife.date with smart 24-hour caching</p>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
48
|
+
<div class="text-4xl mb-4">🎨</div>
|
|
49
|
+
<h3 class="text-xl font-semibold mb-2">Color-Coded</h3>
|
|
50
|
+
<p class="text-gray-600">Visual warnings: red for EOL, yellow for <6 months, green for safe</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
53
|
+
<div class="text-4xl mb-4">⚡</div>
|
|
54
|
+
<h3 class="text-xl font-semibold mb-2">Zero Config</h3>
|
|
55
|
+
<p class="text-gray-600">Just run harbinger scan - no setup or configuration required</p>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
58
|
+
<div class="text-4xl mb-4">💾</div>
|
|
59
|
+
<h3 class="text-xl font-semibold mb-2">Works Offline</h3>
|
|
60
|
+
<p class="text-gray-600">Smart caching means it works offline after initial data fetch</p>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
63
|
+
<div class="text-4xl mb-4">🧪</div>
|
|
64
|
+
<h3 class="text-xl font-semibold mb-2">Well Tested</h3>
|
|
65
|
+
<p class="text-gray-600">36 RSpec tests with 100% pass rate, built with TDD</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Usage Section -->
|
|
71
|
+
<div class="bg-white py-16">
|
|
72
|
+
<div class="max-w-4xl mx-auto px-4">
|
|
73
|
+
<h2 class="text-3xl font-bold mb-8">Quick Start</h2>
|
|
74
|
+
|
|
75
|
+
<div class="bg-gray-900 rounded-lg p-6 mb-8 font-mono text-sm overflow-x-auto">
|
|
76
|
+
<div class="text-gray-400"># Install</div>
|
|
77
|
+
<div class="mb-4"><span class="text-gray-400">$</span> <span class="text-green-400">gem install stackharbinger</span></div>
|
|
78
|
+
|
|
79
|
+
<div class="text-gray-400"># Scan your project</div>
|
|
80
|
+
<div class="mb-4"><span class="text-gray-400">$</span> <span class="text-green-400">harbinger scan</span></div>
|
|
81
|
+
|
|
82
|
+
<div class="text-gray-400"># Output:</div>
|
|
83
|
+
<div class="text-white">
|
|
84
|
+
<div class="mb-2">Detected versions:</div>
|
|
85
|
+
<div class="ml-4 text-white">Ruby: 3.2.0</div>
|
|
86
|
+
<div class="ml-4 text-white mb-2">Rails: 7.0.8</div>
|
|
87
|
+
<div class="mb-2">Ruby 3.2.0:</div>
|
|
88
|
+
<div class="ml-4 text-green-400">EOL Date: 2026-03-31</div>
|
|
89
|
+
<div class="ml-4 text-green-400 mb-2">Status: 437 days remaining</div>
|
|
90
|
+
<div class="mb-2">Rails 7.0.8:</div>
|
|
91
|
+
<div class="ml-4 text-red-400">EOL Date: 2025-06-01</div>
|
|
92
|
+
<div class="ml-4 text-red-400">Status: ALREADY EOL</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="prose max-w-none">
|
|
97
|
+
<h3 class="text-2xl font-semibold mb-4">Commands</h3>
|
|
98
|
+
<ul class="space-y-3 text-gray-700">
|
|
99
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan [PATH]</code> - Scan a project and show EOL status</li>
|
|
100
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger update</code> - Force refresh EOL data from API</li>
|
|
101
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger version</code> - Show harbinger version</li>
|
|
102
|
+
</ul>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Roadmap Section -->
|
|
108
|
+
<div class="max-w-6xl mx-auto px-4 py-16">
|
|
109
|
+
<h2 class="text-3xl font-bold mb-8 text-center">Roadmap</h2>
|
|
110
|
+
<div class="grid md:grid-cols-3 gap-8">
|
|
111
|
+
<div>
|
|
112
|
+
<h3 class="text-xl font-semibold mb-4 text-green-600">✅ V0.1.0 (Current)</h3>
|
|
113
|
+
<ul class="space-y-2 text-gray-600">
|
|
114
|
+
<li>• Ruby & Rails detection</li>
|
|
115
|
+
<li>• EOL data fetching</li>
|
|
116
|
+
<li>• CLI with color output</li>
|
|
117
|
+
<li>• Smart caching</li>
|
|
118
|
+
</ul>
|
|
119
|
+
</div>
|
|
120
|
+
<div>
|
|
121
|
+
<h3 class="text-xl font-semibold mb-4 text-blue-600">📋 V0.2.0 (Planned)</h3>
|
|
122
|
+
<ul class="space-y-2 text-gray-600">
|
|
123
|
+
<li>• Dashboard view</li>
|
|
124
|
+
<li>• Config management</li>
|
|
125
|
+
<li>• PostgreSQL detection</li>
|
|
126
|
+
<li>• MySQL detection</li>
|
|
127
|
+
</ul>
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<h3 class="text-xl font-semibold mb-4 text-purple-600">🚀 V1.0 (Future)</h3>
|
|
131
|
+
<ul class="space-y-2 text-gray-600">
|
|
132
|
+
<li>• Python support</li>
|
|
133
|
+
<li>• Node.js support</li>
|
|
134
|
+
<li>• Go & Rust support</li>
|
|
135
|
+
<li>• Homebrew distribution</li>
|
|
136
|
+
</ul>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- Links Section -->
|
|
142
|
+
<div class="bg-gray-900 text-white py-12">
|
|
143
|
+
<div class="max-w-6xl mx-auto px-4 text-center">
|
|
144
|
+
<div class="flex flex-col sm:flex-row justify-center gap-6 mb-8">
|
|
145
|
+
<a href="https://github.com/RichD/harbinger" class="bg-white text-gray-900 px-6 py-3 rounded-lg font-semibold hover:bg-gray-100 transition">
|
|
146
|
+
GitHub
|
|
147
|
+
</a>
|
|
148
|
+
<a href="https://rubygems.org/gems/stackharbinger" class="bg-red-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-red-700 transition">
|
|
149
|
+
RubyGems
|
|
150
|
+
</a>
|
|
151
|
+
<a href="https://github.com/RichD/harbinger/blob/main/README.md" class="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 transition">
|
|
152
|
+
Documentation
|
|
153
|
+
</a>
|
|
154
|
+
</div>
|
|
155
|
+
<p class="text-gray-400 text-sm">
|
|
156
|
+
Built with ❤️ using Ruby and Thor | EOL data from <a href="https://endoflife.date" class="underline hover:text-white">endoflife.date</a>
|
|
157
|
+
</p>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</body>
|
|
161
|
+
</html>
|
data/lib/harbinger/cli.rb
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
4
|
require "date"
|
|
5
|
+
require "tty-table"
|
|
5
6
|
require_relative "version"
|
|
6
7
|
require "harbinger/analyzers/ruby_detector"
|
|
7
8
|
require "harbinger/analyzers/rails_analyzer"
|
|
8
9
|
require "harbinger/eol_fetcher"
|
|
10
|
+
require "harbinger/config_manager"
|
|
9
11
|
|
|
10
12
|
module Harbinger
|
|
11
13
|
class CLI < Thor
|
|
@@ -13,17 +15,166 @@ module Harbinger
|
|
|
13
15
|
true
|
|
14
16
|
end
|
|
15
17
|
|
|
16
|
-
desc "scan
|
|
17
|
-
option :path, type: :string, aliases: "-p", desc: "Path to project directory"
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
desc "scan", "Scan a project directory and detect versions"
|
|
19
|
+
option :path, type: :string, aliases: "-p", desc: "Path to project directory (defaults to current directory)"
|
|
20
|
+
option :save, type: :boolean, aliases: "-s", desc: "Save project to config for dashboard"
|
|
21
|
+
option :recursive, type: :boolean, aliases: "-r", desc: "Recursively scan all subdirectories with Gemfiles"
|
|
22
|
+
def scan
|
|
23
|
+
project_path = options[:path] || Dir.pwd
|
|
20
24
|
|
|
21
25
|
unless File.directory?(project_path)
|
|
22
26
|
say "Error: #{project_path} is not a valid directory", :red
|
|
23
27
|
exit 1
|
|
24
28
|
end
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
if options[:recursive]
|
|
31
|
+
scan_recursive(project_path)
|
|
32
|
+
else
|
|
33
|
+
scan_single(project_path)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "show", "Show EOL status for tracked projects"
|
|
38
|
+
def show
|
|
39
|
+
config_manager = ConfigManager.new
|
|
40
|
+
projects = config_manager.list_projects
|
|
41
|
+
|
|
42
|
+
if projects.empty?
|
|
43
|
+
say "No projects tracked yet.", :yellow
|
|
44
|
+
say "Use 'harbinger scan --save' to add projects", :cyan
|
|
45
|
+
return
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
say "Tracked Projects (#{projects.size})", :cyan
|
|
49
|
+
say "=" * 80, :cyan
|
|
50
|
+
|
|
51
|
+
fetcher = EolFetcher.new
|
|
52
|
+
rows = []
|
|
53
|
+
|
|
54
|
+
projects.each do |name, data|
|
|
55
|
+
ruby_version = data["ruby"]
|
|
56
|
+
rails_version = data["rails"]
|
|
57
|
+
|
|
58
|
+
# Determine worst EOL status
|
|
59
|
+
worst_status = :green
|
|
60
|
+
status_text = "✓ Current"
|
|
61
|
+
|
|
62
|
+
if ruby_version && !ruby_version.empty?
|
|
63
|
+
ruby_eol = fetcher.eol_date_for("ruby", ruby_version)
|
|
64
|
+
if ruby_eol
|
|
65
|
+
days = days_until(ruby_eol)
|
|
66
|
+
status = eol_color(days)
|
|
67
|
+
worst_status = status if status_priority(status) > status_priority(worst_status)
|
|
68
|
+
if days < 0
|
|
69
|
+
status_text = "✗ Ruby EOL"
|
|
70
|
+
elsif days < 180
|
|
71
|
+
status_text = "⚠ Ruby ending soon"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if rails_version && !rails_version.empty?
|
|
77
|
+
rails_eol = fetcher.eol_date_for("rails", rails_version)
|
|
78
|
+
if rails_eol
|
|
79
|
+
days = days_until(rails_eol)
|
|
80
|
+
status = eol_color(days)
|
|
81
|
+
worst_status = status if status_priority(status) > status_priority(worst_status)
|
|
82
|
+
if days < 0
|
|
83
|
+
status_text = "✗ Rails EOL"
|
|
84
|
+
elsif days < 180 && !status_text.include?("EOL")
|
|
85
|
+
status_text = "⚠ Rails ending soon"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
ruby_display = ruby_version && !ruby_version.empty? ? ruby_version : "-"
|
|
91
|
+
rails_display = rails_version && !rails_version.empty? ? rails_version : "-"
|
|
92
|
+
|
|
93
|
+
rows << [name, ruby_display, rails_display, colorize_status(status_text, worst_status)]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Sort by status priority (worst first), then by name
|
|
97
|
+
rows.sort_by! do |row|
|
|
98
|
+
status = row[3]
|
|
99
|
+
priority = if status.include?("✗")
|
|
100
|
+
0
|
|
101
|
+
elsif status.include?("⚠")
|
|
102
|
+
1
|
|
103
|
+
else
|
|
104
|
+
2
|
|
105
|
+
end
|
|
106
|
+
[priority, row[0]]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
table = TTY::Table.new(
|
|
110
|
+
header: ["Project", "Ruby", "Rails", "Status"],
|
|
111
|
+
rows: rows
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
puts table.render(:unicode, padding: [0, 1])
|
|
115
|
+
|
|
116
|
+
say "\nUse 'harbinger scan --path <project>' to update a project", :cyan
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
desc "update", "Force refresh EOL data from endoflife.date"
|
|
120
|
+
def update
|
|
121
|
+
say "Updating EOL data...", :cyan
|
|
122
|
+
|
|
123
|
+
fetcher = EolFetcher.new
|
|
124
|
+
products = %w[ruby rails]
|
|
125
|
+
|
|
126
|
+
products.each do |product|
|
|
127
|
+
say "Fetching #{product}...", :white
|
|
128
|
+
data = fetcher.fetch(product)
|
|
129
|
+
|
|
130
|
+
if data
|
|
131
|
+
say " ✓ #{product.capitalize}: #{data.length} versions cached", :green
|
|
132
|
+
else
|
|
133
|
+
say " ✗ #{product.capitalize}: Failed to fetch", :red
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
say "\nEOL data updated successfully!", :green
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
desc "version", "Show harbinger version"
|
|
141
|
+
def version
|
|
142
|
+
say "Harbinger version #{Harbinger::VERSION}", :cyan
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def scan_recursive(base_path)
|
|
148
|
+
say "Scanning #{base_path} recursively for Ruby projects...", :cyan
|
|
149
|
+
|
|
150
|
+
# Find all directories with Gemfiles
|
|
151
|
+
gemfile_dirs = Dir.glob(File.join(base_path, "**/Gemfile"))
|
|
152
|
+
.map { |f| File.dirname(f) }
|
|
153
|
+
.sort
|
|
154
|
+
|
|
155
|
+
if gemfile_dirs.empty?
|
|
156
|
+
say "\nNo Ruby projects found (no Gemfile detected)", :yellow
|
|
157
|
+
return
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
say "Found #{gemfile_dirs.length} project(s)\n\n", :green
|
|
161
|
+
|
|
162
|
+
gemfile_dirs.each_with_index do |project_path, index|
|
|
163
|
+
say "=" * 60, :cyan
|
|
164
|
+
say "[#{index + 1}/#{gemfile_dirs.length}] #{project_path}", :cyan
|
|
165
|
+
say "=" * 60, :cyan
|
|
166
|
+
scan_single(project_path)
|
|
167
|
+
say "\n" unless index == gemfile_dirs.length - 1
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if options[:save]
|
|
171
|
+
say "\n✓ Saved #{gemfile_dirs.length} project(s) to config", :green
|
|
172
|
+
say "View all tracked projects with: harbinger show", :cyan
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def scan_single(project_path)
|
|
177
|
+
say "Scanning #{project_path}...", :cyan unless options[:recursive]
|
|
27
178
|
|
|
28
179
|
# Detect versions
|
|
29
180
|
ruby_detector = Analyzers::RubyDetector.new(project_path)
|
|
@@ -66,42 +217,36 @@ module Harbinger
|
|
|
66
217
|
display_eol_info(fetcher, "Rails", rails_version)
|
|
67
218
|
end
|
|
68
219
|
end
|
|
69
|
-
end
|
|
70
220
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
221
|
+
# Save to config if --save flag is used
|
|
222
|
+
if options[:save] && !options[:recursive]
|
|
223
|
+
save_to_config(project_path, ruby_version, rails_version)
|
|
224
|
+
elsif options[:save] && options[:recursive]
|
|
225
|
+
# In recursive mode, save without the confirmation message for each project
|
|
226
|
+
config_manager = ConfigManager.new
|
|
227
|
+
project_name = File.basename(project_path)
|
|
228
|
+
config_manager.save_project(
|
|
229
|
+
name: project_name,
|
|
230
|
+
path: project_path,
|
|
231
|
+
versions: { ruby: ruby_version, rails: rails_version }.compact
|
|
232
|
+
)
|
|
233
|
+
end
|
|
75
234
|
end
|
|
76
235
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
236
|
+
def save_to_config(project_path, ruby_version, rails_version)
|
|
237
|
+
config_manager = ConfigManager.new
|
|
238
|
+
project_name = File.basename(project_path)
|
|
80
239
|
|
|
81
|
-
|
|
82
|
-
|
|
240
|
+
config_manager.save_project(
|
|
241
|
+
name: project_name,
|
|
242
|
+
path: project_path,
|
|
243
|
+
versions: { ruby: ruby_version, rails: rails_version }.compact
|
|
244
|
+
)
|
|
83
245
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
data = fetcher.fetch(product)
|
|
87
|
-
|
|
88
|
-
if data
|
|
89
|
-
say " ✓ #{product.capitalize}: #{data.length} versions cached", :green
|
|
90
|
-
else
|
|
91
|
-
say " ✗ #{product.capitalize}: Failed to fetch", :red
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
say "\nEOL data updated successfully!", :green
|
|
246
|
+
say "\n✓ Saved to config as '#{project_name}'", :green
|
|
247
|
+
say "View all tracked projects with: harbinger show", :cyan
|
|
96
248
|
end
|
|
97
249
|
|
|
98
|
-
desc "version", "Show harbinger version"
|
|
99
|
-
def version
|
|
100
|
-
say "Harbinger version #{Harbinger::VERSION}", :cyan
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
private
|
|
104
|
-
|
|
105
250
|
def display_eol_info(fetcher, product, version)
|
|
106
251
|
product_key = product.downcase
|
|
107
252
|
eol_date = fetcher.eol_date_for(product_key, version)
|
|
@@ -145,5 +290,29 @@ module Harbinger
|
|
|
145
290
|
"#{days} days remaining"
|
|
146
291
|
end
|
|
147
292
|
end
|
|
293
|
+
|
|
294
|
+
def status_priority(color)
|
|
295
|
+
case color
|
|
296
|
+
when :red
|
|
297
|
+
2
|
|
298
|
+
when :yellow
|
|
299
|
+
1
|
|
300
|
+
else
|
|
301
|
+
0
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def colorize_status(text, color)
|
|
306
|
+
case color
|
|
307
|
+
when :red
|
|
308
|
+
"\e[31m#{text}\e[0m"
|
|
309
|
+
when :yellow
|
|
310
|
+
"\e[33m#{text}\e[0m"
|
|
311
|
+
when :green
|
|
312
|
+
"\e[32m#{text}\e[0m"
|
|
313
|
+
else
|
|
314
|
+
text
|
|
315
|
+
end
|
|
316
|
+
end
|
|
148
317
|
end
|
|
149
318
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module Harbinger
|
|
8
|
+
class ConfigManager
|
|
9
|
+
def initialize(config_dir: default_config_dir)
|
|
10
|
+
@config_dir = config_dir
|
|
11
|
+
@config_file = File.join(@config_dir, "config.yml")
|
|
12
|
+
FileUtils.mkdir_p(@config_dir)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def save_project(name:, path:, versions: {})
|
|
16
|
+
config = load_config
|
|
17
|
+
config["projects"] ||= {}
|
|
18
|
+
|
|
19
|
+
config["projects"][name] = {
|
|
20
|
+
"path" => path,
|
|
21
|
+
"last_scanned" => Time.now.iso8601
|
|
22
|
+
}.merge(versions.transform_keys(&:to_s))
|
|
23
|
+
|
|
24
|
+
write_config(config)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def list_projects
|
|
28
|
+
config = load_config
|
|
29
|
+
config["projects"] || {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_project(name)
|
|
33
|
+
list_projects[name]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def remove_project(name)
|
|
37
|
+
config = load_config
|
|
38
|
+
return unless config["projects"]
|
|
39
|
+
|
|
40
|
+
config["projects"].delete(name)
|
|
41
|
+
write_config(config)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def project_count
|
|
45
|
+
list_projects.size
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :config_dir, :config_file
|
|
51
|
+
|
|
52
|
+
def default_config_dir
|
|
53
|
+
File.join(Dir.home, ".harbinger")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def load_config
|
|
57
|
+
return {} unless File.exist?(config_file)
|
|
58
|
+
|
|
59
|
+
YAML.load_file(config_file) || {}
|
|
60
|
+
rescue Psych::SyntaxError, StandardError
|
|
61
|
+
{}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def write_config(config)
|
|
65
|
+
File.write(config_file, YAML.dump(config))
|
|
66
|
+
rescue StandardError
|
|
67
|
+
# Silently fail if we can't write
|
|
68
|
+
nil
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/harbinger/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stackharbinger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rich Dabrowski
|
|
@@ -79,9 +79,8 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0.21'
|
|
82
|
-
description: Harbinger monitors EOL dates for Ruby
|
|
83
|
-
|
|
84
|
-
ends.
|
|
82
|
+
description: Harbinger monitors EOL dates for Ruby and Rails. Auto-detects versions
|
|
83
|
+
from your projects and alerts you before support ends.
|
|
85
84
|
email:
|
|
86
85
|
- engineering@richd.net
|
|
87
86
|
executables:
|
|
@@ -92,11 +91,14 @@ files:
|
|
|
92
91
|
- CHANGELOG.md
|
|
93
92
|
- README.md
|
|
94
93
|
- Rakefile
|
|
94
|
+
- docs/CNAME
|
|
95
|
+
- docs/index.html
|
|
95
96
|
- exe/harbinger
|
|
96
97
|
- lib/harbinger.rb
|
|
97
98
|
- lib/harbinger/analyzers/rails_analyzer.rb
|
|
98
99
|
- lib/harbinger/analyzers/ruby_detector.rb
|
|
99
100
|
- lib/harbinger/cli.rb
|
|
101
|
+
- lib/harbinger/config_manager.rb
|
|
100
102
|
- lib/harbinger/eol_fetcher.rb
|
|
101
103
|
- lib/harbinger/version.rb
|
|
102
104
|
- sig/harbinger.rbs
|