stackharbinger 0.2.0 ā 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/README.md +68 -24
- data/docs/index.html +32 -14
- data/lib/harbinger/analyzers/database_detector.rb +123 -0
- data/lib/harbinger/analyzers/mysql_detector.rb +83 -0
- data/lib/harbinger/analyzers/postgres_detector.rb +65 -0
- data/lib/harbinger/cli.rb +138 -15
- data/lib/harbinger/eol_fetcher.rb +6 -1
- data/lib/harbinger/version.rb +1 -1
- data/lib/harbinger.rb +3 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0625ffb650537306abc4297407d0373c204c69412c7610254488c1006461bbcc
|
|
4
|
+
data.tar.gz: 65106dadaa487141d44e7772c57d9b0c59e18ab478517f0e94cad1440b8ab8b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3922e4f871e0782ebf821ef3e01f91aebba249c908f81a3b8f94e550d0d7d68a8525118d61f78b326b801aeecb801fbd89f975b48c2c95bbafd12c7703ebcf7a
|
|
7
|
+
data.tar.gz: c50866bca2f2b69d995d01735d62da1cb44f436aa546e5a069d7f8b50b8d25e63d95969f4df85ed008cd7d893a7e8d92959efe12e29e7834199f7322455a9cb9
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-01-18
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **PostgreSQL detection**: Detects PostgreSQL versions from Rails projects
|
|
14
|
+
- Checks `config/database.yml` for `adapter: postgresql`
|
|
15
|
+
- Runs `psql --version` for local databases
|
|
16
|
+
- Falls back to `pg` gem version from `Gemfile.lock`
|
|
17
|
+
- Skips shell commands for remote databases (AWS RDS, etc.) to avoid client vs server version mismatch
|
|
18
|
+
- **MySQL detection**: Detects MySQL versions from Rails projects
|
|
19
|
+
- Supports both `mysql2` and `trilogy` adapters (Rails 7.1+)
|
|
20
|
+
- Runs `mysql --version` or `mysqld --version` for local databases
|
|
21
|
+
- Falls back to gem version from `Gemfile.lock`
|
|
22
|
+
- Smart remote database detection
|
|
23
|
+
- **Rescan command**: `harbinger rescan` to bulk update all tracked projects
|
|
24
|
+
- Updates all projects with latest detected versions
|
|
25
|
+
- Automatically removes projects with missing directories
|
|
26
|
+
- `--verbose` flag for detailed output
|
|
27
|
+
- Progress counter shows scan status
|
|
28
|
+
- **Enhanced dashboard**: PostgreSQL and MySQL columns in `harbinger show`
|
|
29
|
+
- Database versions displayed in table
|
|
30
|
+
- Database EOL dates included in status calculation
|
|
31
|
+
- Dashboard prioritizes database EOL issues
|
|
32
|
+
- **Database EOL tracking**: Fetches EOL data for PostgreSQL and MySQL
|
|
33
|
+
- `harbinger update` now fetches database EOL data
|
|
34
|
+
- Supports major-only version cycles (PostgreSQL) and major.minor cycles (MySQL)
|
|
35
|
+
- **Multi-database support**: Handles Rails 6+ multi-database configurations
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- `EolFetcher` now supports major-only version matching (PostgreSQL uses "16", not "16.11")
|
|
39
|
+
- Scan output includes database versions with aligned formatting
|
|
40
|
+
- Status calculation includes database EOL dates
|
|
41
|
+
|
|
42
|
+
### Technical
|
|
43
|
+
- Added `DatabaseDetector` abstract base class for database detection
|
|
44
|
+
- Added `PostgresDetector` with local/remote detection (116 total tests passing)
|
|
45
|
+
- Added `MysqlDetector` with trilogy and mysql2 support
|
|
46
|
+
- Test fixtures for various database.yml configurations
|
|
47
|
+
- Remote database detection to avoid shell command mismatches
|
|
48
|
+
- Support for single and multi-database Rails configurations
|
|
49
|
+
|
|
10
50
|
## [0.2.0] - 2026-01-18
|
|
11
51
|
|
|
12
52
|
### Added
|
data/README.md
CHANGED
|
@@ -2,20 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
**Track End-of-Life dates for your tech stack and stay ahead of deprecations.**
|
|
4
4
|
|
|
5
|
-
Harbinger is a CLI tool that scans your Ruby
|
|
5
|
+
Harbinger is a CLI tool that scans your Ruby, Rails, PostgreSQL, and MySQL versions, and warns you about upcoming EOL (End-of-Life) dates. Never get caught off-guard by unsupported dependencies again.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- š **Auto-detects versions** from `.ruby-version`, `Gemfile`,
|
|
9
|
+
- š **Auto-detects versions** from `.ruby-version`, `Gemfile`, `Gemfile.lock`, and `config/database.yml`
|
|
10
|
+
- š **Database detection** for PostgreSQL and MySQL (mysql2/trilogy adapters)
|
|
10
11
|
- š
**Fetches EOL data** from [endoflife.date](https://endoflife.date)
|
|
11
12
|
- šØ **Color-coded warnings** (red: already EOL, yellow: <6 months, green: safe)
|
|
12
13
|
- ā” **Smart caching** (24-hour cache, works offline after first fetch)
|
|
13
14
|
- š **Track multiple projects** with `--save` and view dashboard with `harbinger show`
|
|
14
|
-
- š **Bulk
|
|
15
|
+
- š **Bulk operations** with `--recursive` scan and `rescan` command
|
|
15
16
|
- š **Zero configuration** - just run `harbinger scan`
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
20
|
+
### Homebrew (macOS)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
brew tap RichD/harbinger
|
|
24
|
+
brew install stackharbinger
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### RubyGems
|
|
28
|
+
|
|
19
29
|
```bash
|
|
20
30
|
gem install stackharbinger
|
|
21
31
|
```
|
|
@@ -26,7 +36,7 @@ Or add to your Gemfile:
|
|
|
26
36
|
gem 'stackharbinger'
|
|
27
37
|
```
|
|
28
38
|
|
|
29
|
-
The command is
|
|
39
|
+
The command is `harbinger` (shorter to type).
|
|
30
40
|
|
|
31
41
|
## Usage
|
|
32
42
|
|
|
@@ -52,8 +62,9 @@ harbinger scan --path ~/Projects --recursive --save
|
|
|
52
62
|
Scanning /Users/you/Projects/my-app...
|
|
53
63
|
|
|
54
64
|
Detected versions:
|
|
55
|
-
Ruby:
|
|
56
|
-
Rails:
|
|
65
|
+
Ruby: 3.2.0
|
|
66
|
+
Rails: 7.0.8
|
|
67
|
+
PostgreSQL: 16.11
|
|
57
68
|
|
|
58
69
|
Fetching EOL data...
|
|
59
70
|
|
|
@@ -64,6 +75,10 @@ Ruby 3.2.0:
|
|
|
64
75
|
Rails 7.0.8:
|
|
65
76
|
EOL Date: 2025-06-01
|
|
66
77
|
Status: ALREADY EOL (474 days ago)
|
|
78
|
+
|
|
79
|
+
PostgreSQL 16.11:
|
|
80
|
+
EOL Date: 2028-11-09
|
|
81
|
+
Status: 1026 days remaining
|
|
67
82
|
```
|
|
68
83
|
|
|
69
84
|
### View tracked projects
|
|
@@ -78,14 +93,24 @@ harbinger show
|
|
|
78
93
|
```
|
|
79
94
|
Tracked Projects (10)
|
|
80
95
|
================================================================================
|
|
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
|
-
|
|
96
|
+
āāāāāāāāāāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāāāā¬āāāāāāāāāāāāā¬āāāāāāāā¬āāāāāāāāāāāāāā
|
|
97
|
+
ā Project ā Ruby ā Rails ā PostgreSQL ā MySQL ā Status ā
|
|
98
|
+
āāāāāāāāāāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāāāā¼āāāāāāāāāāāāā¼āāāāāāāā¼āāāāāāāāāāāāāā¤
|
|
99
|
+
ā ledger ā 3.3.0 ā 6.1.7.10 ā - ā - ā ā Rails EOL ā
|
|
100
|
+
ā option_tracker ā 3.3.0 ā 7.0.8.7 ā - ā - ā ā Rails EOL ā
|
|
101
|
+
ā CarCal ā - ā 8.0.2 ā - ā - ā ā Current ā
|
|
102
|
+
ā job_tracker ā 3.3.0 ā 8.0.4 ā 16.11 ā - ā ā Current ā
|
|
103
|
+
āāāāāāāāāāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāāāā“āāāāāāāāāāāāā“āāāāāāāā“āāāāāāāāāāāāāā
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Re-scan all tracked projects
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Update all tracked projects with latest versions
|
|
110
|
+
harbinger rescan
|
|
111
|
+
|
|
112
|
+
# Show detailed output for each project
|
|
113
|
+
harbinger rescan --verbose
|
|
89
114
|
```
|
|
90
115
|
|
|
91
116
|
### Update EOL data
|
|
@@ -106,6 +131,8 @@ harbinger version
|
|
|
106
131
|
1. **Detection**: Harbinger looks for version info in your project:
|
|
107
132
|
- Ruby: `.ruby-version`, `Gemfile` (`ruby "x.x.x"`), `Gemfile.lock` (RUBY VERSION)
|
|
108
133
|
- Rails: `Gemfile.lock` (rails gem)
|
|
134
|
+
- PostgreSQL: `config/database.yml` (adapter check) + `psql --version` or `pg` gem
|
|
135
|
+
- MySQL: `config/database.yml` (mysql2/trilogy adapter) + `mysql --version` or gem version
|
|
109
136
|
|
|
110
137
|
2. **EOL Data**: Fetches official EOL dates from [endoflife.date](https://endoflife.date) API
|
|
111
138
|
|
|
@@ -130,6 +157,22 @@ Ruby: Present (version not specified - add .ruby-version or ruby declaration in
|
|
|
130
157
|
|
|
131
158
|
Parses `Gemfile.lock` for the rails gem version.
|
|
132
159
|
|
|
160
|
+
### PostgreSQL Detection
|
|
161
|
+
|
|
162
|
+
1. Checks `config/database.yml` for `adapter: postgresql`
|
|
163
|
+
2. Tries `psql --version` for local databases (skips for remote hosts)
|
|
164
|
+
3. Falls back to `pg` gem version from `Gemfile.lock`
|
|
165
|
+
|
|
166
|
+
**Note**: For remote databases (AWS RDS, etc.), shows gem version since shell command would give local client version, not server version.
|
|
167
|
+
|
|
168
|
+
### MySQL Detection
|
|
169
|
+
|
|
170
|
+
1. Checks `config/database.yml` for `adapter: mysql2` or `adapter: trilogy`
|
|
171
|
+
2. Tries `mysql --version` or `mysqld --version` for local databases
|
|
172
|
+
3. Falls back to `mysql2` or `trilogy` gem version from `Gemfile.lock`
|
|
173
|
+
|
|
174
|
+
**Supported adapters**: `mysql2` (traditional) and `trilogy` (Rails 7.1+)
|
|
175
|
+
|
|
133
176
|
## Requirements
|
|
134
177
|
|
|
135
178
|
- Ruby >= 3.1.0
|
|
@@ -154,23 +197,24 @@ bundle exec exe/harbinger scan .
|
|
|
154
197
|
|
|
155
198
|
## Roadmap
|
|
156
199
|
|
|
157
|
-
### V0.
|
|
158
|
-
- ā
|
|
159
|
-
- ā
|
|
160
|
-
- ā
|
|
161
|
-
- ā
Enhanced
|
|
200
|
+
### V0.3.0 - Current
|
|
201
|
+
- ā
PostgreSQL version detection with local/remote database handling
|
|
202
|
+
- ā
MySQL version detection (mysql2 and trilogy adapters)
|
|
203
|
+
- ā
Rescan command to update all tracked projects
|
|
204
|
+
- ā
Enhanced dashboard with database columns
|
|
205
|
+
- ā
EOL tracking for PostgreSQL and MySQL
|
|
162
206
|
|
|
163
|
-
### V0.
|
|
164
|
-
- š PostgreSQL version detection
|
|
165
|
-
- šļø MySQL version detection
|
|
166
|
-
- š Rescan command to update all tracked projects
|
|
207
|
+
### V0.4.0 - Planned
|
|
167
208
|
- š Export reports to JSON/CSV
|
|
209
|
+
- š³ Docker Compose database version detection
|
|
210
|
+
- š“ Redis version detection
|
|
211
|
+
- š MongoDB version detection
|
|
168
212
|
|
|
169
213
|
### V1.0 - Future
|
|
170
214
|
- š Python support (pyproject.toml, requirements.txt)
|
|
171
215
|
- š¦ Node.js support (package.json, .nvmrc)
|
|
172
216
|
- š¦ Rust support (Cargo.toml)
|
|
173
|
-
-
|
|
217
|
+
- š Go support (go.mod)
|
|
174
218
|
|
|
175
219
|
### V2.0 - Vision
|
|
176
220
|
- š¤ AI-powered upgrade summaries
|
data/docs/index.html
CHANGED
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
|
|
22
22
|
<div class="flex flex-col sm:flex-row gap-4">
|
|
23
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">
|
|
24
|
+
<div class="mb-2"><span class="text-gray-400">$</span> <span class="text-green-400">brew tap RichD/harbinger</span></div>
|
|
25
|
+
<div><span class="text-gray-400">$</span> <span class="text-green-400">brew install stackharbinger</span></div>
|
|
25
26
|
</div>
|
|
26
27
|
<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
28
|
View on GitHub ā
|
|
@@ -59,10 +60,20 @@
|
|
|
59
60
|
<h3 class="text-xl font-semibold mb-2">Works Offline</h3>
|
|
60
61
|
<p class="text-gray-600">Smart caching means it works offline after initial data fetch</p>
|
|
61
62
|
</div>
|
|
63
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
64
|
+
<div class="text-4xl mb-4">š</div>
|
|
65
|
+
<h3 class="text-xl font-semibold mb-2">Dashboard View</h3>
|
|
66
|
+
<p class="text-gray-600">Track multiple projects and see EOL status at a glance with harbinger show</p>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
69
|
+
<div class="text-4xl mb-4">š</div>
|
|
70
|
+
<h3 class="text-xl font-semibold mb-2">Bulk Scanning</h3>
|
|
71
|
+
<p class="text-gray-600">Recursively scan entire directories to find and track all your projects</p>
|
|
72
|
+
</div>
|
|
62
73
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
63
74
|
<div class="text-4xl mb-4">š§Ŗ</div>
|
|
64
75
|
<h3 class="text-xl font-semibold mb-2">Well Tested</h3>
|
|
65
|
-
<p class="text-gray-600">
|
|
76
|
+
<p class="text-gray-600">52 RSpec tests with 100% pass rate, built with TDD</p>
|
|
66
77
|
</div>
|
|
67
78
|
</div>
|
|
68
79
|
</div>
|
|
@@ -73,7 +84,11 @@
|
|
|
73
84
|
<h2 class="text-3xl font-bold mb-8">Quick Start</h2>
|
|
74
85
|
|
|
75
86
|
<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>
|
|
87
|
+
<div class="text-gray-400"># Install via Homebrew</div>
|
|
88
|
+
<div class="mb-1"><span class="text-gray-400">$</span> <span class="text-green-400">brew tap RichD/harbinger</span></div>
|
|
89
|
+
<div class="mb-4"><span class="text-gray-400">$</span> <span class="text-green-400">brew install stackharbinger</span></div>
|
|
90
|
+
|
|
91
|
+
<div class="text-gray-400"># Or via RubyGems</div>
|
|
77
92
|
<div class="mb-4"><span class="text-gray-400">$</span> <span class="text-green-400">gem install stackharbinger</span></div>
|
|
78
93
|
|
|
79
94
|
<div class="text-gray-400"># Scan your project</div>
|
|
@@ -96,7 +111,10 @@
|
|
|
96
111
|
<div class="prose max-w-none">
|
|
97
112
|
<h3 class="text-2xl font-semibold mb-4">Commands</h3>
|
|
98
113
|
<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>
|
|
114
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan --path [PATH]</code> - Scan a project and show EOL status</li>
|
|
115
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan --save</code> - Save project for tracking</li>
|
|
116
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger scan --recursive</code> - Scan all projects in a directory</li>
|
|
117
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show</code> - View dashboard of all tracked projects</li>
|
|
100
118
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger update</code> - Force refresh EOL data from API</li>
|
|
101
119
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger version</code> - Show harbinger version</li>
|
|
102
120
|
</ul>
|
|
@@ -109,21 +127,21 @@
|
|
|
109
127
|
<h2 class="text-3xl font-bold mb-8 text-center">Roadmap</h2>
|
|
110
128
|
<div class="grid md:grid-cols-3 gap-8">
|
|
111
129
|
<div>
|
|
112
|
-
<h3 class="text-xl font-semibold mb-4 text-green-600">ā
V0.
|
|
130
|
+
<h3 class="text-xl font-semibold mb-4 text-green-600">ā
V0.2.0 (Current)</h3>
|
|
113
131
|
<ul class="space-y-2 text-gray-600">
|
|
114
|
-
<li>ā¢
|
|
115
|
-
<li>ā¢
|
|
116
|
-
<li>ā¢
|
|
117
|
-
<li>ā¢
|
|
132
|
+
<li>⢠Dashboard view</li>
|
|
133
|
+
<li>⢠Project tracking</li>
|
|
134
|
+
<li>⢠Recursive scanning</li>
|
|
135
|
+
<li>⢠Homebrew distribution</li>
|
|
118
136
|
</ul>
|
|
119
137
|
</div>
|
|
120
138
|
<div>
|
|
121
|
-
<h3 class="text-xl font-semibold mb-4 text-blue-600">š V0.
|
|
139
|
+
<h3 class="text-xl font-semibold mb-4 text-blue-600">š V0.3.0 (Planned)</h3>
|
|
122
140
|
<ul class="space-y-2 text-gray-600">
|
|
123
|
-
<li>⢠Dashboard view</li>
|
|
124
|
-
<li>⢠Config management</li>
|
|
125
141
|
<li>⢠PostgreSQL detection</li>
|
|
126
142
|
<li>⢠MySQL detection</li>
|
|
143
|
+
<li>⢠Rescan command</li>
|
|
144
|
+
<li>⢠Export to JSON/CSV</li>
|
|
127
145
|
</ul>
|
|
128
146
|
</div>
|
|
129
147
|
<div>
|
|
@@ -131,8 +149,8 @@
|
|
|
131
149
|
<ul class="space-y-2 text-gray-600">
|
|
132
150
|
<li>⢠Python support</li>
|
|
133
151
|
<li>⢠Node.js support</li>
|
|
134
|
-
<li>⢠Go
|
|
135
|
-
<li>ā¢
|
|
152
|
+
<li>⢠Go support</li>
|
|
153
|
+
<li>⢠Rust support</li>
|
|
136
154
|
</ul>
|
|
137
155
|
</div>
|
|
138
156
|
</div>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Abstract base class for database version detection in Rails projects
|
|
8
|
+
# Provides common functionality for detecting database versions from Rails projects
|
|
9
|
+
class DatabaseDetector
|
|
10
|
+
attr_reader :project_path
|
|
11
|
+
|
|
12
|
+
def initialize(project_path)
|
|
13
|
+
@project_path = project_path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Main detection method - returns version string or nil
|
|
17
|
+
def detect
|
|
18
|
+
return nil unless database_detected?
|
|
19
|
+
|
|
20
|
+
# Try shell command first (actual database version)
|
|
21
|
+
version = detect_from_shell
|
|
22
|
+
return version if version
|
|
23
|
+
|
|
24
|
+
# Fallback to gem version from Gemfile.lock
|
|
25
|
+
detect_from_gemfile_lock
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if database.yml indicates this database is used
|
|
29
|
+
def database_detected?
|
|
30
|
+
return false unless database_yml_exists?
|
|
31
|
+
|
|
32
|
+
config = parse_database_yml
|
|
33
|
+
return false unless config
|
|
34
|
+
|
|
35
|
+
# Check production or default section
|
|
36
|
+
section = config["production"] || config["default"] || config[config.keys.first]
|
|
37
|
+
return false unless section
|
|
38
|
+
|
|
39
|
+
adapter = extract_adapter_from_section(section)
|
|
40
|
+
return false unless adapter
|
|
41
|
+
|
|
42
|
+
Array(adapter_name).any? { |name| adapter == name }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
protected
|
|
46
|
+
|
|
47
|
+
# Extract adapter from database config section
|
|
48
|
+
# Handles both single-database and multi-database (Rails 6+) configurations
|
|
49
|
+
def extract_adapter_from_section(section)
|
|
50
|
+
# Single database: { "adapter" => "postgresql", ... }
|
|
51
|
+
return section["adapter"] if section["adapter"]
|
|
52
|
+
|
|
53
|
+
# Multi-database: { "primary" => { "adapter" => "postgresql", ... }, "cache" => { ... } }
|
|
54
|
+
# Check primary first, then fall back to first nested config
|
|
55
|
+
nested_config = section["primary"] || section.values.find { |v| v.is_a?(Hash) && v["adapter"] }
|
|
56
|
+
nested_config["adapter"] if nested_config
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Abstract method - must be implemented by subclasses
|
|
60
|
+
# Returns the adapter name(s) to look for in database.yml
|
|
61
|
+
def adapter_name
|
|
62
|
+
raise NotImplementedError, "Subclasses must implement adapter_name"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Abstract method - must be implemented by subclasses
|
|
66
|
+
# Detects version from shell command (e.g., psql --version)
|
|
67
|
+
def detect_from_shell
|
|
68
|
+
raise NotImplementedError, "Subclasses must implement detect_from_shell"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Abstract method - must be implemented by subclasses
|
|
72
|
+
# Detects version from Gemfile.lock gem version
|
|
73
|
+
def detect_from_gemfile_lock
|
|
74
|
+
raise NotImplementedError, "Subclasses must implement detect_from_gemfile_lock"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Read and parse config/database.yml
|
|
78
|
+
def parse_database_yml
|
|
79
|
+
database_yml_path = File.join(project_path, "config", "database.yml")
|
|
80
|
+
return nil unless File.exist?(database_yml_path)
|
|
81
|
+
|
|
82
|
+
content = File.read(database_yml_path)
|
|
83
|
+
YAML.safe_load(content, aliases: true)
|
|
84
|
+
rescue Psych::SyntaxError, StandardError
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if database.yml exists
|
|
89
|
+
def database_yml_exists?
|
|
90
|
+
File.exist?(File.join(project_path, "config", "database.yml"))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Read and parse Gemfile.lock
|
|
94
|
+
def parse_gemfile_lock
|
|
95
|
+
gemfile_lock_path = File.join(project_path, "Gemfile.lock")
|
|
96
|
+
return nil unless File.exist?(gemfile_lock_path)
|
|
97
|
+
|
|
98
|
+
File.read(gemfile_lock_path)
|
|
99
|
+
rescue StandardError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Extract gem version from Gemfile.lock content
|
|
104
|
+
def extract_gem_version(gemfile_lock_content, gem_name)
|
|
105
|
+
return nil unless gemfile_lock_content
|
|
106
|
+
|
|
107
|
+
# Match pattern like: " pg (1.5.4)"
|
|
108
|
+
match = gemfile_lock_content.match(/^\s{4}#{Regexp.escape(gem_name)}\s+\(([^)]+)\)/)
|
|
109
|
+
match[1] if match
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Execute shell command safely
|
|
113
|
+
def execute_command(command)
|
|
114
|
+
output = `#{command} 2>&1`.strip
|
|
115
|
+
return nil unless $?.success?
|
|
116
|
+
|
|
117
|
+
output
|
|
118
|
+
rescue StandardError
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "database_detector"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Detects MySQL version from Rails projects
|
|
8
|
+
# Supports both mysql2 and trilogy adapters
|
|
9
|
+
class MysqlDetector < DatabaseDetector
|
|
10
|
+
protected
|
|
11
|
+
|
|
12
|
+
def adapter_name
|
|
13
|
+
["mysql2", "trilogy"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def detect_from_shell
|
|
17
|
+
# Skip shell command if database is remote
|
|
18
|
+
return nil if remote_database?
|
|
19
|
+
|
|
20
|
+
# Try mysql command first, then mysqld
|
|
21
|
+
output = execute_command("mysql --version") || execute_command("mysqld --version")
|
|
22
|
+
return nil unless output
|
|
23
|
+
|
|
24
|
+
# Parse: "mysql Ver 8.0.33" or "mysqld Ver 8.0.33"
|
|
25
|
+
# Also handles MariaDB: "mysql Ver 15.1 Distrib 10.11.2-MariaDB"
|
|
26
|
+
match = output.match(/Ver\s+(?:\d+\.\d+\s+Distrib\s+)?(\d+\.\d+\.\d+)/)
|
|
27
|
+
match[1] if match
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def detect_from_gemfile_lock
|
|
31
|
+
content = parse_gemfile_lock
|
|
32
|
+
return nil unless content
|
|
33
|
+
|
|
34
|
+
# Check which adapter is being used
|
|
35
|
+
config = parse_database_yml
|
|
36
|
+
return nil unless config
|
|
37
|
+
|
|
38
|
+
section = config["production"] || config["default"] || config[config.keys.first]
|
|
39
|
+
return nil unless section
|
|
40
|
+
|
|
41
|
+
adapter = extract_adapter_from_section(section)
|
|
42
|
+
|
|
43
|
+
# Return appropriate gem version based on adapter
|
|
44
|
+
if adapter == "trilogy"
|
|
45
|
+
version = extract_gem_version(content, "trilogy")
|
|
46
|
+
version ? "#{version} (trilogy gem)" : nil
|
|
47
|
+
else
|
|
48
|
+
version = extract_gem_version(content, "mysql2")
|
|
49
|
+
version ? "#{version} (mysql2 gem)" : nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Check if database configuration indicates a remote database
|
|
56
|
+
# (same logic as PostgresDetector)
|
|
57
|
+
def remote_database?
|
|
58
|
+
config = parse_database_yml
|
|
59
|
+
return false unless config
|
|
60
|
+
|
|
61
|
+
section = config["production"] || config["default"] || config[config.keys.first]
|
|
62
|
+
return false unless section
|
|
63
|
+
|
|
64
|
+
db_config = if section["adapter"]
|
|
65
|
+
section
|
|
66
|
+
else
|
|
67
|
+
section["primary"] || section.values.find { |v| v.is_a?(Hash) && v["adapter"] }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return false unless db_config
|
|
71
|
+
|
|
72
|
+
host = db_config["host"]
|
|
73
|
+
|
|
74
|
+
# No host specified = localhost (Unix socket)
|
|
75
|
+
return false if host.nil? || host.empty?
|
|
76
|
+
|
|
77
|
+
# Explicit localhost indicators
|
|
78
|
+
local_hosts = ["localhost", "127.0.0.1", "::1", "0.0.0.0"]
|
|
79
|
+
!local_hosts.include?(host.to_s.downcase)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "database_detector"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Detects PostgreSQL version from Rails projects
|
|
8
|
+
class PostgresDetector < DatabaseDetector
|
|
9
|
+
protected
|
|
10
|
+
|
|
11
|
+
def adapter_name
|
|
12
|
+
"postgresql"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def detect_from_shell
|
|
16
|
+
# Skip shell command if database is remote
|
|
17
|
+
# (shell gives client version, not server version)
|
|
18
|
+
return nil if remote_database?
|
|
19
|
+
|
|
20
|
+
output = execute_command("psql --version")
|
|
21
|
+
return nil unless output
|
|
22
|
+
|
|
23
|
+
# Parse: "psql (PostgreSQL) 15.3" or "psql (PostgreSQL) 15.3 (Ubuntu 15.3-1)"
|
|
24
|
+
match = output.match(/PostgreSQL\)\s+(\d+\.\d+)/)
|
|
25
|
+
match[1] if match
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def detect_from_gemfile_lock
|
|
29
|
+
content = parse_gemfile_lock
|
|
30
|
+
version = extract_gem_version(content, "pg")
|
|
31
|
+
version ? "#{version} (pg gem)" : nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Check if database configuration indicates a remote database
|
|
37
|
+
def remote_database?
|
|
38
|
+
config = parse_database_yml
|
|
39
|
+
return false unless config
|
|
40
|
+
|
|
41
|
+
# Get the section with database config
|
|
42
|
+
section = config["production"] || config["default"] || config[config.keys.first]
|
|
43
|
+
return false unless section
|
|
44
|
+
|
|
45
|
+
# Handle multi-database config
|
|
46
|
+
db_config = if section["adapter"]
|
|
47
|
+
section
|
|
48
|
+
else
|
|
49
|
+
section["primary"] || section.values.find { |v| v.is_a?(Hash) && v["adapter"] }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return false unless db_config
|
|
53
|
+
|
|
54
|
+
host = db_config["host"]
|
|
55
|
+
|
|
56
|
+
# No host specified = localhost (Unix socket)
|
|
57
|
+
return false if host.nil? || host.empty?
|
|
58
|
+
|
|
59
|
+
# Explicit localhost indicators
|
|
60
|
+
local_hosts = ["localhost", "127.0.0.1", "::1", "0.0.0.0"]
|
|
61
|
+
!local_hosts.include?(host.to_s.downcase)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/harbinger/cli.rb
CHANGED
|
@@ -6,6 +6,9 @@ require "tty-table"
|
|
|
6
6
|
require_relative "version"
|
|
7
7
|
require "harbinger/analyzers/ruby_detector"
|
|
8
8
|
require "harbinger/analyzers/rails_analyzer"
|
|
9
|
+
require "harbinger/analyzers/database_detector"
|
|
10
|
+
require "harbinger/analyzers/postgres_detector"
|
|
11
|
+
require "harbinger/analyzers/mysql_detector"
|
|
9
12
|
require "harbinger/eol_fetcher"
|
|
10
13
|
require "harbinger/config_manager"
|
|
11
14
|
|
|
@@ -54,6 +57,8 @@ module Harbinger
|
|
|
54
57
|
projects.each do |name, data|
|
|
55
58
|
ruby_version = data["ruby"]
|
|
56
59
|
rails_version = data["rails"]
|
|
60
|
+
postgres_version = data["postgres"]
|
|
61
|
+
mysql_version = data["mysql"]
|
|
57
62
|
|
|
58
63
|
# Determine worst EOL status
|
|
59
64
|
worst_status = :green
|
|
@@ -87,15 +92,45 @@ module Harbinger
|
|
|
87
92
|
end
|
|
88
93
|
end
|
|
89
94
|
|
|
95
|
+
if postgres_version && !postgres_version.empty? && !postgres_version.include?("gem")
|
|
96
|
+
postgres_eol = fetcher.eol_date_for("postgresql", postgres_version)
|
|
97
|
+
if postgres_eol
|
|
98
|
+
days = days_until(postgres_eol)
|
|
99
|
+
status = eol_color(days)
|
|
100
|
+
worst_status = status if status_priority(status) > status_priority(worst_status)
|
|
101
|
+
if days < 0
|
|
102
|
+
status_text = "ā PostgreSQL EOL"
|
|
103
|
+
elsif days < 180 && !status_text.include?("EOL")
|
|
104
|
+
status_text = "ā PostgreSQL ending soon"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if mysql_version && !mysql_version.empty? && !mysql_version.include?("gem")
|
|
110
|
+
mysql_eol = fetcher.eol_date_for("mysql", mysql_version)
|
|
111
|
+
if mysql_eol
|
|
112
|
+
days = days_until(mysql_eol)
|
|
113
|
+
status = eol_color(days)
|
|
114
|
+
worst_status = status if status_priority(status) > status_priority(worst_status)
|
|
115
|
+
if days < 0
|
|
116
|
+
status_text = "ā MySQL EOL"
|
|
117
|
+
elsif days < 180 && !status_text.include?("EOL")
|
|
118
|
+
status_text = "ā MySQL ending soon"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
90
123
|
ruby_display = ruby_version && !ruby_version.empty? ? ruby_version : "-"
|
|
91
124
|
rails_display = rails_version && !rails_version.empty? ? rails_version : "-"
|
|
125
|
+
postgres_display = postgres_version && !postgres_version.empty? ? postgres_version : "-"
|
|
126
|
+
mysql_display = mysql_version && !mysql_version.empty? ? mysql_version : "-"
|
|
92
127
|
|
|
93
|
-
rows << [name, ruby_display, rails_display, colorize_status(status_text, worst_status)]
|
|
128
|
+
rows << [name, ruby_display, rails_display, postgres_display, mysql_display, colorize_status(status_text, worst_status)]
|
|
94
129
|
end
|
|
95
130
|
|
|
96
131
|
# Sort by status priority (worst first), then by name
|
|
97
132
|
rows.sort_by! do |row|
|
|
98
|
-
status = row[
|
|
133
|
+
status = row[5] # Status is now in column 5 (0-indexed)
|
|
99
134
|
priority = if status.include?("ā")
|
|
100
135
|
0
|
|
101
136
|
elsif status.include?("ā ")
|
|
@@ -107,7 +142,7 @@ module Harbinger
|
|
|
107
142
|
end
|
|
108
143
|
|
|
109
144
|
table = TTY::Table.new(
|
|
110
|
-
header: ["Project", "Ruby", "Rails", "Status"],
|
|
145
|
+
header: ["Project", "Ruby", "Rails", "PostgreSQL", "MySQL", "Status"],
|
|
111
146
|
rows: rows
|
|
112
147
|
)
|
|
113
148
|
|
|
@@ -121,7 +156,7 @@ module Harbinger
|
|
|
121
156
|
say "Updating EOL data...", :cyan
|
|
122
157
|
|
|
123
158
|
fetcher = EolFetcher.new
|
|
124
|
-
products = %w[ruby rails]
|
|
159
|
+
products = %w[ruby rails postgresql mysql]
|
|
125
160
|
|
|
126
161
|
products.each do |product|
|
|
127
162
|
say "Fetching #{product}...", :white
|
|
@@ -137,6 +172,68 @@ module Harbinger
|
|
|
137
172
|
say "\nEOL data updated successfully!", :green
|
|
138
173
|
end
|
|
139
174
|
|
|
175
|
+
desc "rescan", "Re-scan all tracked projects and update versions"
|
|
176
|
+
option :verbose, type: :boolean, aliases: "-v", desc: "Show detailed output for each project"
|
|
177
|
+
def rescan
|
|
178
|
+
config_manager = ConfigManager.new
|
|
179
|
+
projects = config_manager.list_projects
|
|
180
|
+
|
|
181
|
+
if projects.empty?
|
|
182
|
+
say "No projects tracked yet.", :yellow
|
|
183
|
+
say "Use 'harbinger scan --save' to add projects", :cyan
|
|
184
|
+
return
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
say "Re-scanning #{projects.size} tracked project(s)...\n\n", :cyan
|
|
188
|
+
|
|
189
|
+
updated_count = 0
|
|
190
|
+
removed_count = 0
|
|
191
|
+
|
|
192
|
+
projects.each_with_index do |(name, data), index|
|
|
193
|
+
project_path = data["path"]
|
|
194
|
+
|
|
195
|
+
unless File.directory?(project_path)
|
|
196
|
+
say "[#{index + 1}/#{projects.size}] #{name}: Path not found, removing from config", :yellow
|
|
197
|
+
config_manager.remove_project(name)
|
|
198
|
+
removed_count += 1
|
|
199
|
+
next
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if options[:verbose]
|
|
203
|
+
say "=" * 60, :cyan
|
|
204
|
+
say "[#{index + 1}/#{projects.size}] Re-scanning #{name}", :cyan
|
|
205
|
+
say "=" * 60, :cyan
|
|
206
|
+
scan_single(project_path)
|
|
207
|
+
else
|
|
208
|
+
say "[#{index + 1}/#{projects.size}] #{name}...", :white
|
|
209
|
+
|
|
210
|
+
# Detect versions quietly
|
|
211
|
+
ruby_detector = Analyzers::RubyDetector.new(project_path)
|
|
212
|
+
rails_analyzer = Analyzers::RailsAnalyzer.new(project_path)
|
|
213
|
+
postgres_detector = Analyzers::PostgresDetector.new(project_path)
|
|
214
|
+
mysql_detector = Analyzers::MysqlDetector.new(project_path)
|
|
215
|
+
|
|
216
|
+
ruby_version = ruby_detector.detect
|
|
217
|
+
rails_version = rails_analyzer.detect
|
|
218
|
+
postgres_version = postgres_detector.detect
|
|
219
|
+
mysql_version = mysql_detector.detect
|
|
220
|
+
|
|
221
|
+
# Save to config
|
|
222
|
+
config_manager.save_project(
|
|
223
|
+
name: name,
|
|
224
|
+
path: project_path,
|
|
225
|
+
versions: { ruby: ruby_version, rails: rails_version, postgres: postgres_version, mysql: mysql_version }.compact
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
updated_count += 1
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
say "\nā Updated #{updated_count} project(s)", :green
|
|
233
|
+
say "ā Removed #{removed_count} project(s) with missing directories", :yellow if removed_count > 0
|
|
234
|
+
say "\nView updated projects with: harbinger show", :cyan
|
|
235
|
+
end
|
|
236
|
+
|
|
140
237
|
desc "version", "Show harbinger version"
|
|
141
238
|
def version
|
|
142
239
|
say "Harbinger version #{Harbinger::VERSION}", :cyan
|
|
@@ -179,33 +276,51 @@ module Harbinger
|
|
|
179
276
|
# Detect versions
|
|
180
277
|
ruby_detector = Analyzers::RubyDetector.new(project_path)
|
|
181
278
|
rails_analyzer = Analyzers::RailsAnalyzer.new(project_path)
|
|
279
|
+
postgres_detector = Analyzers::PostgresDetector.new(project_path)
|
|
280
|
+
mysql_detector = Analyzers::MysqlDetector.new(project_path)
|
|
182
281
|
|
|
183
282
|
ruby_version = ruby_detector.detect
|
|
184
283
|
rails_version = rails_analyzer.detect
|
|
284
|
+
postgres_version = postgres_detector.detect
|
|
285
|
+
mysql_version = mysql_detector.detect
|
|
185
286
|
|
|
186
287
|
ruby_present = ruby_detector.ruby_detected?
|
|
187
288
|
rails_present = rails_analyzer.rails_detected?
|
|
289
|
+
postgres_present = postgres_detector.database_detected?
|
|
290
|
+
mysql_present = mysql_detector.database_detected?
|
|
188
291
|
|
|
189
292
|
# Display results
|
|
190
293
|
say "\nDetected versions:", :green
|
|
191
294
|
if ruby_version
|
|
192
|
-
say " Ruby:
|
|
295
|
+
say " Ruby: #{ruby_version}", :white
|
|
193
296
|
elsif ruby_present
|
|
194
|
-
say " Ruby:
|
|
297
|
+
say " Ruby: Present (version not specified - add .ruby-version or ruby declaration in Gemfile)", :yellow
|
|
195
298
|
else
|
|
196
|
-
say " Ruby:
|
|
299
|
+
say " Ruby: Not a Ruby project", :red
|
|
197
300
|
end
|
|
198
301
|
|
|
199
302
|
if rails_version
|
|
200
|
-
say " Rails:
|
|
303
|
+
say " Rails: #{rails_version}", :white
|
|
201
304
|
elsif rails_present
|
|
202
|
-
say " Rails:
|
|
305
|
+
say " Rails: Present (version not found in Gemfile.lock)", :yellow
|
|
203
306
|
else
|
|
204
|
-
say " Rails:
|
|
307
|
+
say " Rails: Not detected", :yellow
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
if postgres_version
|
|
311
|
+
say " PostgreSQL: #{postgres_version}", :white
|
|
312
|
+
elsif postgres_present
|
|
313
|
+
say " PostgreSQL: Present (version not detected)", :yellow
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if mysql_version
|
|
317
|
+
say " MySQL: #{mysql_version}", :white
|
|
318
|
+
elsif mysql_present
|
|
319
|
+
say " MySQL: Present (version not detected)", :yellow
|
|
205
320
|
end
|
|
206
321
|
|
|
207
322
|
# Fetch and display EOL dates
|
|
208
|
-
if ruby_version || rails_version
|
|
323
|
+
if ruby_version || rails_version || postgres_version || mysql_version
|
|
209
324
|
say "\nFetching EOL data...", :cyan
|
|
210
325
|
fetcher = EolFetcher.new
|
|
211
326
|
|
|
@@ -216,11 +331,19 @@ module Harbinger
|
|
|
216
331
|
if rails_version
|
|
217
332
|
display_eol_info(fetcher, "Rails", rails_version)
|
|
218
333
|
end
|
|
334
|
+
|
|
335
|
+
if postgres_version && !postgres_version.include?("gem")
|
|
336
|
+
display_eol_info(fetcher, "PostgreSQL", postgres_version)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
if mysql_version && !mysql_version.include?("gem")
|
|
340
|
+
display_eol_info(fetcher, "MySQL", mysql_version)
|
|
341
|
+
end
|
|
219
342
|
end
|
|
220
343
|
|
|
221
344
|
# Save to config if --save flag is used
|
|
222
345
|
if options[:save] && !options[:recursive]
|
|
223
|
-
save_to_config(project_path, ruby_version, rails_version)
|
|
346
|
+
save_to_config(project_path, ruby_version, rails_version, postgres_version, mysql_version)
|
|
224
347
|
elsif options[:save] && options[:recursive]
|
|
225
348
|
# In recursive mode, save without the confirmation message for each project
|
|
226
349
|
config_manager = ConfigManager.new
|
|
@@ -228,19 +351,19 @@ module Harbinger
|
|
|
228
351
|
config_manager.save_project(
|
|
229
352
|
name: project_name,
|
|
230
353
|
path: project_path,
|
|
231
|
-
versions: { ruby: ruby_version, rails: rails_version }.compact
|
|
354
|
+
versions: { ruby: ruby_version, rails: rails_version, postgres: postgres_version, mysql: mysql_version }.compact
|
|
232
355
|
)
|
|
233
356
|
end
|
|
234
357
|
end
|
|
235
358
|
|
|
236
|
-
def save_to_config(project_path, ruby_version, rails_version)
|
|
359
|
+
def save_to_config(project_path, ruby_version, rails_version, postgres_version = nil, mysql_version = nil)
|
|
237
360
|
config_manager = ConfigManager.new
|
|
238
361
|
project_name = File.basename(project_path)
|
|
239
362
|
|
|
240
363
|
config_manager.save_project(
|
|
241
364
|
name: project_name,
|
|
242
365
|
path: project_path,
|
|
243
|
-
versions: { ruby: ruby_version, rails: rails_version }.compact
|
|
366
|
+
versions: { ruby: ruby_version, rails: rails_version, postgres: postgres_version, mysql: mysql_version }.compact
|
|
244
367
|
)
|
|
245
368
|
|
|
246
369
|
say "\nā Saved to config as '#{project_name}'", :green
|
|
@@ -40,9 +40,14 @@ module Harbinger
|
|
|
40
40
|
# Extract major.minor from version (e.g., "3.2.1" -> "3.2")
|
|
41
41
|
version_parts = version.split(".")
|
|
42
42
|
major_minor = "#{version_parts[0]}.#{version_parts[1]}"
|
|
43
|
+
major = version_parts[0]
|
|
43
44
|
|
|
44
|
-
#
|
|
45
|
+
# Try major.minor first (e.g., "8.0" for MySQL, "3.2" for Ruby)
|
|
45
46
|
entry = data.find { |item| item["cycle"] == major_minor }
|
|
47
|
+
return entry["eol"] if entry
|
|
48
|
+
|
|
49
|
+
# Fall back to major only (e.g., "16" for PostgreSQL)
|
|
50
|
+
entry = data.find { |item| item["cycle"] == major }
|
|
46
51
|
entry ? entry["eol"] : nil
|
|
47
52
|
end
|
|
48
53
|
|
data/lib/harbinger/version.rb
CHANGED
data/lib/harbinger.rb
CHANGED
|
@@ -4,6 +4,9 @@ require_relative "harbinger/version"
|
|
|
4
4
|
require_relative "harbinger/cli"
|
|
5
5
|
require_relative "harbinger/analyzers/ruby_detector"
|
|
6
6
|
require_relative "harbinger/analyzers/rails_analyzer"
|
|
7
|
+
require_relative "harbinger/analyzers/database_detector"
|
|
8
|
+
require_relative "harbinger/analyzers/postgres_detector"
|
|
9
|
+
require_relative "harbinger/analyzers/mysql_detector"
|
|
7
10
|
require_relative "harbinger/eol_fetcher"
|
|
8
11
|
|
|
9
12
|
module Harbinger
|
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rich Dabrowski
|
|
@@ -95,6 +95,9 @@ files:
|
|
|
95
95
|
- docs/index.html
|
|
96
96
|
- exe/harbinger
|
|
97
97
|
- lib/harbinger.rb
|
|
98
|
+
- lib/harbinger/analyzers/database_detector.rb
|
|
99
|
+
- lib/harbinger/analyzers/mysql_detector.rb
|
|
100
|
+
- lib/harbinger/analyzers/postgres_detector.rb
|
|
98
101
|
- lib/harbinger/analyzers/rails_analyzer.rb
|
|
99
102
|
- lib/harbinger/analyzers/ruby_detector.rb
|
|
100
103
|
- lib/harbinger/cli.rb
|