stackharbinger 0.2.0 → 0.4.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 +101 -25
- data/docs/index.html +38 -16
- data/lib/harbinger/analyzers/database_detector.rb +133 -0
- data/lib/harbinger/analyzers/docker_compose_detector.rb +121 -0
- data/lib/harbinger/analyzers/mongo_detector.rb +104 -0
- data/lib/harbinger/analyzers/mysql_detector.rb +90 -0
- data/lib/harbinger/analyzers/postgres_detector.rb +71 -0
- data/lib/harbinger/analyzers/redis_detector.rb +98 -0
- data/lib/harbinger/analyzers/ruby_detector.rb +9 -1
- data/lib/harbinger/cli.rb +362 -48
- data/lib/harbinger/eol_fetcher.rb +22 -8
- data/lib/harbinger/exporters/base_exporter.rb +97 -0
- data/lib/harbinger/exporters/csv_exporter.rb +36 -0
- data/lib/harbinger/exporters/json_exporter.rb +21 -0
- data/lib/harbinger/version.rb +1 -1
- data/lib/harbinger.rb +3 -0
- metadata +15 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 663f135c9d728ba59e2f33d33ddd73e3be1b37143c7533db0534db995301aa8e
|
|
4
|
+
data.tar.gz: de00d8ba69996d960b57bc96d49c79da0f9221a90ac870805fd9f5aad16b506f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2505777f55661fe67224a94351e8eb00d56cb2ef834c19a86f00e9be6fd45cf4aea5abb872242750c5f4dc3608e4707e0ecb8899538407d348ff7fdec7254a3c
|
|
7
|
+
data.tar.gz: 1ee3dcbca6a3cd75ede6a720fdb50bc8969a070c4bbb0c8a17cb4abacc0b9cb4536dd78fe0b78cb7609d56e15fc5affdcebac1ec5a906911cd3e323341b882bf
|
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
|
|
@@ -71,6 +86,31 @@ Rails 7.0.8:
|
|
|
71
86
|
```bash
|
|
72
87
|
# Show dashboard of all tracked projects
|
|
73
88
|
harbinger show
|
|
89
|
+
|
|
90
|
+
# Filter to specific project(s) by name or path
|
|
91
|
+
harbinger show budget
|
|
92
|
+
harbinger show job
|
|
93
|
+
|
|
94
|
+
# Show project paths with verbose mode
|
|
95
|
+
harbinger show -v
|
|
96
|
+
harbinger show job --verbose
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Export data
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Export to JSON (stdout)
|
|
103
|
+
harbinger show --format json
|
|
104
|
+
|
|
105
|
+
# Export to CSV (stdout)
|
|
106
|
+
harbinger show --format csv
|
|
107
|
+
|
|
108
|
+
# Save to file
|
|
109
|
+
harbinger show --format json -o report.json
|
|
110
|
+
harbinger show --format csv --output eol-report.csv
|
|
111
|
+
|
|
112
|
+
# Export filtered projects
|
|
113
|
+
harbinger show myproject --format json
|
|
74
114
|
```
|
|
75
115
|
|
|
76
116
|
**Example output:**
|
|
@@ -78,14 +118,31 @@ harbinger show
|
|
|
78
118
|
```
|
|
79
119
|
Tracked Projects (10)
|
|
80
120
|
================================================================================
|
|
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
|
-
|
|
121
|
+
┌───────────────────┬───────┬──────────┬────────────┬───────┬─────────────┐
|
|
122
|
+
│ Project │ Ruby │ Rails │ PostgreSQL │ MySQL │ Status │
|
|
123
|
+
├───────────────────┼───────┼──────────┼────────────┼───────┼─────────────┤
|
|
124
|
+
│ ledger │ 3.3.0 │ 6.1.7.10 │ - │ - │ ✗ Rails EOL │
|
|
125
|
+
│ option_tracker │ 3.3.0 │ 7.0.8.7 │ - │ - │ ✗ Rails EOL │
|
|
126
|
+
│ CarCal │ - │ 8.0.2 │ - │ - │ ✓ Current │
|
|
127
|
+
│ job_tracker │ 3.3.0 │ 8.0.4 │ 16.11 │ - │ ✓ Current │
|
|
128
|
+
└───────────────────┴───────┴──────────┴────────────┴───────┴─────────────┘
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Re-scan all tracked projects
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Update all tracked projects with latest versions
|
|
135
|
+
harbinger rescan
|
|
136
|
+
|
|
137
|
+
# Show detailed output for each project
|
|
138
|
+
harbinger rescan --verbose
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Remove a project
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Remove a project from tracking
|
|
145
|
+
harbinger remove my-project
|
|
89
146
|
```
|
|
90
147
|
|
|
91
148
|
### Update EOL data
|
|
@@ -106,6 +163,8 @@ harbinger version
|
|
|
106
163
|
1. **Detection**: Harbinger looks for version info in your project:
|
|
107
164
|
- Ruby: `.ruby-version`, `Gemfile` (`ruby "x.x.x"`), `Gemfile.lock` (RUBY VERSION)
|
|
108
165
|
- Rails: `Gemfile.lock` (rails gem)
|
|
166
|
+
- PostgreSQL: `config/database.yml` (adapter check) + `psql --version` or `pg` gem
|
|
167
|
+
- MySQL: `config/database.yml` (mysql2/trilogy adapter) + `mysql --version` or gem version
|
|
109
168
|
|
|
110
169
|
2. **EOL Data**: Fetches official EOL dates from [endoflife.date](https://endoflife.date) API
|
|
111
170
|
|
|
@@ -130,6 +189,22 @@ Ruby: Present (version not specified - add .ruby-version or ruby declaration in
|
|
|
130
189
|
|
|
131
190
|
Parses `Gemfile.lock` for the rails gem version.
|
|
132
191
|
|
|
192
|
+
### PostgreSQL Detection
|
|
193
|
+
|
|
194
|
+
1. Checks `config/database.yml` for `adapter: postgresql`
|
|
195
|
+
2. Tries `psql --version` for local databases (skips for remote hosts)
|
|
196
|
+
3. Falls back to `pg` gem version from `Gemfile.lock`
|
|
197
|
+
|
|
198
|
+
**Note**: For remote databases (AWS RDS, etc.), shows gem version since shell command would give local client version, not server version.
|
|
199
|
+
|
|
200
|
+
### MySQL Detection
|
|
201
|
+
|
|
202
|
+
1. Checks `config/database.yml` for `adapter: mysql2` or `adapter: trilogy`
|
|
203
|
+
2. Tries `mysql --version` or `mysqld --version` for local databases
|
|
204
|
+
3. Falls back to `mysql2` or `trilogy` gem version from `Gemfile.lock`
|
|
205
|
+
|
|
206
|
+
**Supported adapters**: `mysql2` (traditional) and `trilogy` (Rails 7.1+)
|
|
207
|
+
|
|
133
208
|
## Requirements
|
|
134
209
|
|
|
135
210
|
- Ruby >= 3.1.0
|
|
@@ -154,23 +229,24 @@ bundle exec exe/harbinger scan .
|
|
|
154
229
|
|
|
155
230
|
## Roadmap
|
|
156
231
|
|
|
157
|
-
### V0.
|
|
158
|
-
- ✅
|
|
159
|
-
- ✅
|
|
160
|
-
- ✅
|
|
161
|
-
- ✅
|
|
232
|
+
### V0.4.0 - Current
|
|
233
|
+
- ✅ Export reports to JSON/CSV
|
|
234
|
+
- ✅ Docker Compose database version detection
|
|
235
|
+
- ✅ Redis version detection
|
|
236
|
+
- ✅ MongoDB version detection
|
|
162
237
|
|
|
163
|
-
### V0.3.0
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
238
|
+
### V0.3.0
|
|
239
|
+
- ✅ PostgreSQL version detection with local/remote database handling
|
|
240
|
+
- ✅ MySQL version detection (mysql2 and trilogy adapters)
|
|
241
|
+
- ✅ Rescan command to update all tracked projects
|
|
242
|
+
- ✅ Enhanced dashboard with database columns
|
|
243
|
+
- ✅ EOL tracking for PostgreSQL and MySQL
|
|
168
244
|
|
|
169
245
|
### V1.0 - Future
|
|
170
246
|
- 🐍 Python support (pyproject.toml, requirements.txt)
|
|
171
247
|
- 📦 Node.js support (package.json, .nvmrc)
|
|
172
248
|
- 🦀 Rust support (Cargo.toml)
|
|
173
|
-
-
|
|
249
|
+
- 🐘 Go support (go.mod)
|
|
174
250
|
|
|
175
251
|
### V2.0 - Vision
|
|
176
252
|
- 🤖 AI-powered upgrade summaries
|
data/docs/index.html
CHANGED
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
<div class="max-w-6xl mx-auto px-4 py-20">
|
|
18
18
|
<h1 class="text-5xl md:text-7xl font-black mb-6">Harbinger</h1>
|
|
19
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
|
|
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, Rails, PostgreSQL, and MySQL versions and warns you before support ends.</p>
|
|
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 →
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
38
39
|
<div class="text-4xl mb-4">🔍</div>
|
|
39
40
|
<h3 class="text-xl font-semibold mb-2">Auto-Detection</h3>
|
|
40
|
-
<p class="text-gray-600">
|
|
41
|
+
<p class="text-gray-600">Detects Ruby, Rails, PostgreSQL, and MySQL versions from project files and database configs</p>
|
|
41
42
|
</div>
|
|
42
43
|
<div class="bg-white p-6 rounded-lg shadow-sm">
|
|
43
44
|
<div class="text-4xl mb-4">📅</div>
|
|
@@ -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">Smart Dashboard</h3>
|
|
66
|
+
<p class="text-gray-600">Dynamic columns show only relevant data - columns and projects without matches are hidden automatically</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">116 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,14 @@
|
|
|
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 [PROJECT] [-v]</code> - View dashboard (filter by name/path, -v shows paths)</li>
|
|
118
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show --format json|csv</code> - Export data to JSON or CSV</li>
|
|
119
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger show --format json -o report.json</code> - Save export to file</li>
|
|
120
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger rescan</code> - Re-scan all tracked projects and update versions</li>
|
|
121
|
+
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger remove PROJECT</code> - Remove a project from tracking</li>
|
|
100
122
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger update</code> - Force refresh EOL data from API</li>
|
|
101
123
|
<li><code class="bg-gray-100 px-2 py-1 rounded">harbinger version</code> - Show harbinger version</li>
|
|
102
124
|
</ul>
|
|
@@ -109,21 +131,21 @@
|
|
|
109
131
|
<h2 class="text-3xl font-bold mb-8 text-center">Roadmap</h2>
|
|
110
132
|
<div class="grid md:grid-cols-3 gap-8">
|
|
111
133
|
<div>
|
|
112
|
-
<h3 class="text-xl font-semibold mb-4 text-green-600">✅ V0.
|
|
134
|
+
<h3 class="text-xl font-semibold mb-4 text-green-600">✅ V0.4.0 (Current)</h3>
|
|
113
135
|
<ul class="space-y-2 text-gray-600">
|
|
114
|
-
<li>•
|
|
115
|
-
<li>•
|
|
116
|
-
<li>•
|
|
117
|
-
<li>•
|
|
136
|
+
<li>• Export to JSON/CSV</li>
|
|
137
|
+
<li>• Docker Compose detection</li>
|
|
138
|
+
<li>• Redis detection</li>
|
|
139
|
+
<li>• MongoDB detection</li>
|
|
118
140
|
</ul>
|
|
119
141
|
</div>
|
|
120
142
|
<div>
|
|
121
|
-
<h3 class="text-xl font-semibold mb-4 text-blue-600">📋 V0.
|
|
143
|
+
<h3 class="text-xl font-semibold mb-4 text-blue-600">📋 V0.3.0</h3>
|
|
122
144
|
<ul class="space-y-2 text-gray-600">
|
|
123
|
-
<li>• Dashboard view</li>
|
|
124
|
-
<li>• Config management</li>
|
|
125
145
|
<li>• PostgreSQL detection</li>
|
|
126
146
|
<li>• MySQL detection</li>
|
|
147
|
+
<li>• Rescan command</li>
|
|
148
|
+
<li>• Smart dashboard filtering</li>
|
|
127
149
|
</ul>
|
|
128
150
|
</div>
|
|
129
151
|
<div>
|
|
@@ -131,8 +153,8 @@
|
|
|
131
153
|
<ul class="space-y-2 text-gray-600">
|
|
132
154
|
<li>• Python support</li>
|
|
133
155
|
<li>• Node.js support</li>
|
|
134
|
-
<li>• Go
|
|
135
|
-
<li>•
|
|
156
|
+
<li>• Go support</li>
|
|
157
|
+
<li>• Rust support</li>
|
|
136
158
|
</ul>
|
|
137
159
|
</div>
|
|
138
160
|
</div>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "English"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module Harbinger
|
|
7
|
+
module Analyzers
|
|
8
|
+
# Abstract base class for database version detection in Rails projects
|
|
9
|
+
# Provides common functionality for detecting database versions from Rails projects
|
|
10
|
+
class DatabaseDetector
|
|
11
|
+
attr_reader :project_path
|
|
12
|
+
|
|
13
|
+
def initialize(project_path)
|
|
14
|
+
@project_path = project_path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Main detection method - returns version string or nil
|
|
18
|
+
def detect
|
|
19
|
+
return nil unless database_detected?
|
|
20
|
+
|
|
21
|
+
# Try docker-compose.yml first (most accurate for Docker-based projects)
|
|
22
|
+
version = detect_from_docker_compose
|
|
23
|
+
return version if version
|
|
24
|
+
|
|
25
|
+
# Try shell command (actual database version for non-Docker setups)
|
|
26
|
+
version = detect_from_shell
|
|
27
|
+
return version if version
|
|
28
|
+
|
|
29
|
+
# Fallback to gem version from Gemfile.lock
|
|
30
|
+
detect_from_gemfile_lock
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check if database.yml indicates this database is used
|
|
34
|
+
def database_detected?
|
|
35
|
+
return false unless database_yml_exists?
|
|
36
|
+
|
|
37
|
+
config = parse_database_yml
|
|
38
|
+
return false unless config
|
|
39
|
+
|
|
40
|
+
# Check production or default section
|
|
41
|
+
section = config["production"] || config["default"] || config[config.keys.first]
|
|
42
|
+
return false unless section
|
|
43
|
+
|
|
44
|
+
adapter = extract_adapter_from_section(section)
|
|
45
|
+
return false unless adapter
|
|
46
|
+
|
|
47
|
+
Array(adapter_name).any? { |name| adapter == name }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
# Extract adapter from database config section
|
|
53
|
+
# Handles both single-database and multi-database (Rails 6+) configurations
|
|
54
|
+
def extract_adapter_from_section(section)
|
|
55
|
+
# Single database: { "adapter" => "postgresql", ... }
|
|
56
|
+
return section["adapter"] if section["adapter"]
|
|
57
|
+
|
|
58
|
+
# Multi-database: { "primary" => { "adapter" => "postgresql", ... }, "cache" => { ... } }
|
|
59
|
+
# Check primary first, then fall back to first nested config
|
|
60
|
+
nested_config = section["primary"] || section.values.find { |v| v.is_a?(Hash) && v["adapter"] }
|
|
61
|
+
nested_config["adapter"] if nested_config
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Abstract method - must be implemented by subclasses
|
|
65
|
+
# Returns the adapter name(s) to look for in database.yml
|
|
66
|
+
def adapter_name
|
|
67
|
+
raise NotImplementedError, "Subclasses must implement adapter_name"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Abstract method - must be implemented by subclasses
|
|
71
|
+
# Detects version from shell command (e.g., psql --version)
|
|
72
|
+
def detect_from_shell
|
|
73
|
+
raise NotImplementedError, "Subclasses must implement detect_from_shell"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Optional method - subclasses can override to detect from docker-compose.yml
|
|
77
|
+
def detect_from_docker_compose
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Abstract method - must be implemented by subclasses
|
|
82
|
+
# Detects version from Gemfile.lock gem version
|
|
83
|
+
def detect_from_gemfile_lock
|
|
84
|
+
raise NotImplementedError, "Subclasses must implement detect_from_gemfile_lock"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Read and parse config/database.yml
|
|
88
|
+
def parse_database_yml
|
|
89
|
+
database_yml_path = File.join(project_path, "config", "database.yml")
|
|
90
|
+
return nil unless File.exist?(database_yml_path)
|
|
91
|
+
|
|
92
|
+
content = File.read(database_yml_path)
|
|
93
|
+
YAML.safe_load(content, aliases: true)
|
|
94
|
+
rescue StandardError
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check if database.yml exists
|
|
99
|
+
def database_yml_exists?
|
|
100
|
+
File.exist?(File.join(project_path, "config", "database.yml"))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Read and parse Gemfile.lock
|
|
104
|
+
def parse_gemfile_lock
|
|
105
|
+
gemfile_lock_path = File.join(project_path, "Gemfile.lock")
|
|
106
|
+
return nil unless File.exist?(gemfile_lock_path)
|
|
107
|
+
|
|
108
|
+
File.read(gemfile_lock_path)
|
|
109
|
+
rescue StandardError
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Extract gem version from Gemfile.lock content
|
|
114
|
+
def extract_gem_version(gemfile_lock_content, gem_name)
|
|
115
|
+
return nil unless gemfile_lock_content
|
|
116
|
+
|
|
117
|
+
# Match pattern like: " pg (1.5.4)"
|
|
118
|
+
match = gemfile_lock_content.match(/^\s{4}#{Regexp.escape(gem_name)}\s+\(([^)]+)\)/)
|
|
119
|
+
match[1] if match
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Execute shell command safely
|
|
123
|
+
def execute_command(command)
|
|
124
|
+
output = `#{command} 2>&1`.strip
|
|
125
|
+
return nil unless $CHILD_STATUS.success?
|
|
126
|
+
|
|
127
|
+
output
|
|
128
|
+
rescue StandardError
|
|
129
|
+
nil
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Harbinger
|
|
6
|
+
module Analyzers
|
|
7
|
+
# Detects versions from docker-compose.yml and Dockerfile
|
|
8
|
+
class DockerComposeDetector
|
|
9
|
+
attr_reader :project_path
|
|
10
|
+
|
|
11
|
+
def initialize(project_path)
|
|
12
|
+
@project_path = project_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Extract version from a Docker image in docker-compose.yml
|
|
16
|
+
# e.g., "postgres:16-alpine" => "16", "mysql:8.0" => "8.0"
|
|
17
|
+
def image_version(image_pattern)
|
|
18
|
+
compose = parse_docker_compose
|
|
19
|
+
return nil unless compose
|
|
20
|
+
|
|
21
|
+
services = compose["services"]
|
|
22
|
+
return nil unless services
|
|
23
|
+
|
|
24
|
+
services.each_value do |service|
|
|
25
|
+
image = service["image"]
|
|
26
|
+
next unless image
|
|
27
|
+
|
|
28
|
+
if image.match?(/^#{image_pattern}[:\d]/)
|
|
29
|
+
version = extract_version_from_image(image)
|
|
30
|
+
return version if version
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Extract Ruby version from Dockerfile
|
|
38
|
+
# e.g., "FROM ruby:3.4.7-slim" => "3.4.7"
|
|
39
|
+
def ruby_version_from_dockerfile
|
|
40
|
+
dockerfile = read_dockerfile
|
|
41
|
+
return nil unless dockerfile
|
|
42
|
+
|
|
43
|
+
# Match patterns like:
|
|
44
|
+
# FROM ruby:3.4.7
|
|
45
|
+
# FROM ruby:3.4.7-slim
|
|
46
|
+
# FROM ruby:3.4.7-alpine
|
|
47
|
+
match = dockerfile.match(/^FROM\s+ruby:(\d+\.\d+(?:\.\d+)?)/i)
|
|
48
|
+
match[1] if match
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extract Rails version from Dockerfile (rare, but possible)
|
|
52
|
+
def rails_version_from_dockerfile
|
|
53
|
+
dockerfile = read_dockerfile
|
|
54
|
+
return nil unless dockerfile
|
|
55
|
+
|
|
56
|
+
# Match patterns like:
|
|
57
|
+
# FROM rails:7.0
|
|
58
|
+
# ARG RAILS_VERSION=7.0.8
|
|
59
|
+
match = dockerfile.match(/^FROM\s+rails:(\d+\.\d+(?:\.\d+)?)/i)
|
|
60
|
+
return match[1] if match
|
|
61
|
+
|
|
62
|
+
match = dockerfile.match(/RAILS_VERSION[=:](\d+\.\d+(?:\.\d+)?)/i)
|
|
63
|
+
match[1] if match
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Check if docker-compose.yml exists
|
|
67
|
+
def docker_compose_exists?
|
|
68
|
+
docker_compose_path != nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if Dockerfile exists
|
|
72
|
+
def dockerfile_exists?
|
|
73
|
+
File.exist?(File.join(project_path, "Dockerfile"))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def docker_compose_path
|
|
79
|
+
paths = [
|
|
80
|
+
File.join(project_path, "docker-compose.yml"),
|
|
81
|
+
File.join(project_path, "docker-compose.yaml"),
|
|
82
|
+
File.join(project_path, "compose.yml"),
|
|
83
|
+
File.join(project_path, "compose.yaml")
|
|
84
|
+
]
|
|
85
|
+
paths.find { |p| File.exist?(p) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def parse_docker_compose
|
|
89
|
+
path = docker_compose_path
|
|
90
|
+
return nil unless path
|
|
91
|
+
|
|
92
|
+
YAML.safe_load(File.read(path), aliases: true)
|
|
93
|
+
rescue StandardError
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def read_dockerfile
|
|
98
|
+
path = File.join(project_path, "Dockerfile")
|
|
99
|
+
return nil unless File.exist?(path)
|
|
100
|
+
|
|
101
|
+
File.read(path)
|
|
102
|
+
rescue StandardError
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Extract version number from Docker image tag
|
|
107
|
+
# "postgres:16-alpine" => "16"
|
|
108
|
+
# "postgres:16.2" => "16.2"
|
|
109
|
+
# "mysql:8.0.33" => "8.0.33"
|
|
110
|
+
# "redis:7-alpine" => "7"
|
|
111
|
+
def extract_version_from_image(image)
|
|
112
|
+
return nil unless image.include?(":")
|
|
113
|
+
|
|
114
|
+
tag = image.split(":").last
|
|
115
|
+
# Extract leading version number (digits and dots)
|
|
116
|
+
match = tag.match(/^(\d+(?:\.\d+)*)/)
|
|
117
|
+
match[1] if match
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|