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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78f93c3813cf5f8ec4540a11d5901f4832667fc76ac449c2cbc61899d61844af
4
- data.tar.gz: a713dcab3bf69bd8ba1bf3aa7ff26d841e2beff17a15ba5a4b33918338b4d093
3
+ metadata.gz: e9932e2c3197ac2e3a514ac213380f97c336f85c8c302745db6754f70f7c1a79
4
+ data.tar.gz: 11f4b85563611e4921eba0666f29181bb26560d19ddb65dd0673e331276dd7bd
5
5
  SHA512:
6
- metadata.gz: '07888386c6149bbef7a607766cd92c59d34c2f38bdddd6dc06a97fb1734f21b9b5775c47fe5f54c81e71a459931b24dac9f3c3dbd30d49d63bdca8bd19fed3cc'
7
- data.tar.gz: 223b899768d2c1d6e7ad2bbfd5df74679e119e8d0d79b235b401035587e23dba52c624b86f831686f5d93fbd91177b245b60cb582291d22c5b3520ff11733f3e
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.1.0 (Beta) - Current
128
- - ✅ Ruby and Rails version detection
129
- - ✅ EOL data fetching and caching
130
- - ✅ CLI with scan and update commands
131
- - ✅ Color-coded status display
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.2.0 - Planned
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 [PATH]", "Scan a project directory and detect versions"
17
- option :path, type: :string, aliases: "-p", desc: "Path to project directory"
18
- def scan(path = nil)
19
- project_path = path || options[:path] || Dir.pwd
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
- say "Scanning #{project_path}...", :cyan
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
- desc "show", "Show EOL status for tracked projects"
72
- def show
73
- say "Show command coming soon!", :yellow
74
- say "Use 'harbinger scan' to check a project's EOL status", :white
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
- desc "update", "Force refresh EOL data from endoflife.date"
78
- def update
79
- say "Updating EOL data...", :cyan
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
- fetcher = EolFetcher.new
82
- products = %w[ruby rails]
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
- products.each do |product|
85
- say "Fetching #{product}...", :white
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Harbinger
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.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, Rails, PostgreSQL and other technologies
83
- in your stack. Auto-detects versions from your projects and alerts you before support
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