end_of_life 0.5.1 → 1.0.0.alpha
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/.standard.yml +3 -0
- data/CHANGELOG.md +19 -1
- data/Gemfile +2 -1
- data/Gemfile.lock +23 -9
- data/README.md +95 -27
- data/bin/end_of_life +1 -0
- data/end_of_life.gemspec +5 -1
- data/lib/end_of_life/api.rb +3 -0
- data/lib/end_of_life/check.rb +46 -0
- data/lib/end_of_life/cli/command/registry.rb +46 -0
- data/lib/end_of_life/cli.rb +109 -14
- data/lib/end_of_life/helpers/terminal.rb +3 -3
- data/lib/end_of_life/helpers/text.rb +11 -0
- data/lib/end_of_life/helpers/time.rb +35 -0
- data/lib/end_of_life/parsers/mise_toml.rb +15 -0
- data/lib/end_of_life/parsers/tool_versions.rb +1 -0
- data/lib/end_of_life/product/registry.rb +4 -3
- data/lib/end_of_life/product/release.rb +22 -1
- data/lib/end_of_life/product.rb +10 -9
- data/lib/end_of_life/repository/search.rb +13 -2
- data/lib/end_of_life/repository.rb +3 -0
- data/lib/end_of_life/scanner/report.rb +37 -0
- data/lib/end_of_life/scanner.rb +16 -10
- data/lib/end_of_life/version.rb +1 -1
- data/lib/end_of_life/version_detector.rb +2 -2
- data/lib/end_of_life/version_detectors/nodejs.rb +31 -0
- data/lib/end_of_life/version_detectors/ruby.rb +7 -0
- data/lib/end_of_life/version_detectors.rb +0 -2
- data/lib/end_of_life.rb +3 -7
- metadata +75 -10
- data/.tool-versions +0 -1
- data/lib/end_of_life/options.rb +0 -63
- data/lib/end_of_life/report.rb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31c4b9042f127a4857ab815c550b47c84a749d079a7661778180527094f3ebb9
|
|
4
|
+
data.tar.gz: 7c5ec81d9f45348408fb73bb0c218080ef0f5b6da60eb7b1541300ec0124c373
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cc4cdf54b0ac364cbcc8560b43697c14e340c9ff6cbb912a69260559339a5dd064e762844ab41f0430a212cc309960f78f84467f8fefe47f110edf299a591909
|
|
7
|
+
data.tar.gz: 65815bb7004eee6f7579c39cdc4453a4aa85da248f4e5efa1656ff8f118af6b40246f9c3f3b85656587abb14f4efeaa313d4798ed1d1a46652b228b7e234d7c4
|
data/.standard.yml
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
## [Unreleased]
|
|
1
|
+
<!-- ## [Unreleased] -->
|
|
2
|
+
|
|
3
|
+
## [1.0.0.alpha] - 2025-10-24
|
|
4
|
+
|
|
5
|
+
- Revamp CLI
|
|
6
|
+
- Now the main command is `end_of_life scan <product>` instead of `end_of_life scan --product=<product>`
|
|
7
|
+
- 🎉 Add support for scanning EOL Node.js versions!
|
|
8
|
+
- Add support for detecting versions from `mise.toml`.
|
|
9
|
+
- Add `check` command to check if specific product releases are EOL:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
$ end_of_life check ruby@3 nodejs@18
|
|
13
|
+
┌─────────────────┬───────────┬───────────────────────────┐
|
|
14
|
+
│ Product Release │ Status │ EOL Date │
|
|
15
|
+
├─────────────────┼───────────┼───────────────────────────┤
|
|
16
|
+
│ ruby@3.4.7 │ Supported │ 2028-03-31 (in 2 years) │
|
|
17
|
+
│ nodejs@18.20.8 │ EOL │ 2025-04-30 (5 months ago) │
|
|
18
|
+
└─────────────────┴───────────┴───────────────────────────┘
|
|
19
|
+
```
|
|
2
20
|
|
|
3
21
|
## [0.5.1] - 2025-09-10
|
|
4
22
|
|
data/Gemfile
CHANGED
|
@@ -6,10 +6,11 @@ source "https://rubygems.org"
|
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
8
|
gem "climate_control", "~> 1.0"
|
|
9
|
+
gem "ostruct"
|
|
9
10
|
gem "rake", "~> 13.0"
|
|
10
11
|
gem "rspec", "~> 3.10"
|
|
11
12
|
gem "rspec-mocks", "~> 3.10"
|
|
12
13
|
gem "simplecov", "~> 0.22.0"
|
|
13
14
|
gem "standard", github: "testdouble/standard"
|
|
14
|
-
gem "vcr",
|
|
15
|
+
gem "vcr", github: "vcr/vcr"
|
|
15
16
|
gem "webmock", "~> 3.13"
|
data/Gemfile.lock
CHANGED
|
@@ -9,15 +9,25 @@ GIT
|
|
|
9
9
|
standard-custom (~> 1.0.0)
|
|
10
10
|
standard-performance (~> 1.8)
|
|
11
11
|
|
|
12
|
+
GIT
|
|
13
|
+
remote: https://github.com/vcr/vcr.git
|
|
14
|
+
revision: ce35c236fe48899f02ddf780973b44cdb756c0ee
|
|
15
|
+
specs:
|
|
16
|
+
vcr (6.3.1)
|
|
17
|
+
|
|
12
18
|
PATH
|
|
13
19
|
remote: .
|
|
14
20
|
specs:
|
|
15
|
-
end_of_life (0.
|
|
21
|
+
end_of_life (1.0.0.alpha)
|
|
22
|
+
argument_parser (~> 0.1.0)
|
|
16
23
|
async
|
|
17
|
-
|
|
24
|
+
base64
|
|
25
|
+
bundler (>= 2.7.2, < 3)
|
|
18
26
|
dry-monads (~> 1.3)
|
|
27
|
+
faraday-retry (~> 2.0)
|
|
19
28
|
octokit (~> 9.0)
|
|
20
29
|
pastel (~> 0.8.0)
|
|
30
|
+
perfect_toml (~> 0.9.0)
|
|
21
31
|
tty-spinner (~> 0.9.0)
|
|
22
32
|
tty-table (~> 0.12.0)
|
|
23
33
|
zeitwerk (~> 2.7)
|
|
@@ -27,14 +37,15 @@ GEM
|
|
|
27
37
|
specs:
|
|
28
38
|
addressable (2.8.7)
|
|
29
39
|
public_suffix (>= 2.0.2, < 7.0)
|
|
40
|
+
argument_parser (0.1.3)
|
|
30
41
|
ast (2.4.3)
|
|
31
|
-
async (2.
|
|
42
|
+
async (2.32.0)
|
|
32
43
|
console (~> 1.29)
|
|
33
44
|
fiber-annotation
|
|
34
45
|
io-event (~> 1.11)
|
|
35
46
|
metrics (~> 0.12)
|
|
36
47
|
traces (~> 0.18)
|
|
37
|
-
base64 (0.
|
|
48
|
+
base64 (0.3.0)
|
|
38
49
|
bigdecimal (3.1.9)
|
|
39
50
|
climate_control (1.2.0)
|
|
40
51
|
concurrent-ruby (1.3.5)
|
|
@@ -61,6 +72,8 @@ GEM
|
|
|
61
72
|
logger
|
|
62
73
|
faraday-net_http (3.4.1)
|
|
63
74
|
net-http (>= 0.5.0)
|
|
75
|
+
faraday-retry (2.3.2)
|
|
76
|
+
faraday (~> 2.0)
|
|
64
77
|
fiber-annotation (0.2.0)
|
|
65
78
|
fiber-local (1.1.0)
|
|
66
79
|
fiber-storage
|
|
@@ -77,19 +90,21 @@ GEM
|
|
|
77
90
|
octokit (9.2.0)
|
|
78
91
|
faraday (>= 1, < 3)
|
|
79
92
|
sawyer (~> 0.9)
|
|
93
|
+
ostruct (0.6.3)
|
|
80
94
|
parallel (1.27.0)
|
|
81
95
|
parser (3.3.9.0)
|
|
82
96
|
ast (~> 2.4.1)
|
|
83
97
|
racc
|
|
84
98
|
pastel (0.8.0)
|
|
85
99
|
tty-color (~> 0.5)
|
|
100
|
+
perfect_toml (0.9.0)
|
|
86
101
|
prism (1.4.0)
|
|
87
102
|
public_suffix (6.0.1)
|
|
88
103
|
racc (1.8.1)
|
|
89
104
|
rainbow (3.1.1)
|
|
90
105
|
rake (13.2.1)
|
|
91
106
|
regexp_parser (2.11.2)
|
|
92
|
-
rexml (3.4.
|
|
107
|
+
rexml (3.4.4)
|
|
93
108
|
rspec (3.13.0)
|
|
94
109
|
rspec-core (~> 3.13.0)
|
|
95
110
|
rspec-expectations (~> 3.13.0)
|
|
@@ -155,8 +170,6 @@ GEM
|
|
|
155
170
|
unicode-display_width (2.6.0)
|
|
156
171
|
unicode_utils (1.4.0)
|
|
157
172
|
uri (1.0.3)
|
|
158
|
-
vcr (6.3.1)
|
|
159
|
-
base64
|
|
160
173
|
webmock (3.24.0)
|
|
161
174
|
addressable (>= 2.8.0)
|
|
162
175
|
crack (>= 0.3.2)
|
|
@@ -169,13 +182,14 @@ PLATFORMS
|
|
|
169
182
|
DEPENDENCIES
|
|
170
183
|
climate_control (~> 1.0)
|
|
171
184
|
end_of_life!
|
|
185
|
+
ostruct
|
|
172
186
|
rake (~> 13.0)
|
|
173
187
|
rspec (~> 3.10)
|
|
174
188
|
rspec-mocks (~> 3.10)
|
|
175
189
|
simplecov (~> 0.22.0)
|
|
176
190
|
standard!
|
|
177
|
-
vcr
|
|
191
|
+
vcr!
|
|
178
192
|
webmock (~> 3.13)
|
|
179
193
|
|
|
180
194
|
BUNDLED WITH
|
|
181
|
-
2.
|
|
195
|
+
2.7.2
|
data/README.md
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
# End of Life
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
|
|
3
|
+
This tool lists GitHub repositories using end-of-life software.
|
|
4
|
+
|
|
5
|
+
We currently support Ruby, Rails, and Node.js. If you want to add support for
|
|
6
|
+
more products, please check out the [Contributing](#contributing) section.
|
|
5
7
|
|
|
6
8
|

|
|
7
9
|
|
|
8
10
|
## Installation
|
|
9
11
|
|
|
12
|
+
If you have Ruby installed, you can install End of Life as a gem with:
|
|
13
|
+
|
|
10
14
|
```sh
|
|
11
15
|
gem install end_of_life
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
## Usage
|
|
15
19
|
|
|
20
|
+
### Scanning your repositories
|
|
21
|
+
|
|
16
22
|
1. Set up a [GitHub access token][] (we recommend using a read-only token);
|
|
17
23
|
|
|
18
24
|
[github access token]:
|
|
@@ -21,12 +27,12 @@ gem install end_of_life
|
|
|
21
27
|
2. Export the `GITHUB_TOKEN` environment variable or set it when calling
|
|
22
28
|
`end_of_life`;
|
|
23
29
|
|
|
24
|
-
3. Use the `end_of_life` command to list the repositories:
|
|
30
|
+
3. Use the `end_of_life scan` command to list the repositories:
|
|
25
31
|
|
|
26
32
|
```sh
|
|
27
|
-
$ GITHUB_TOKEN=something end_of_life
|
|
28
|
-
[✔] Searching repositories
|
|
29
|
-
[✔]
|
|
33
|
+
$ GITHUB_TOKEN=something end_of_life scan ruby
|
|
34
|
+
[✔] Searching repositories that might use Ruby...
|
|
35
|
+
[✔] Scanning 27 repositories for EOL Ruby...
|
|
30
36
|
|
|
31
37
|
Found 2 repositories using EOL Ruby (<= 3.1.7):
|
|
32
38
|
┌───┬──────────────────────────────────────────────┬──────────────┐
|
|
@@ -37,44 +43,94 @@ Found 2 repositories using EOL Ruby (<= 3.1.7):
|
|
|
37
43
|
└───┴──────────────────────────────────────────────┴──────────────┘
|
|
38
44
|
```
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
> [!TIP]
|
|
47
|
+
> You can use the shorthand `eol` instead of `end_of_life` if your platform
|
|
48
|
+
> supports symlinks.
|
|
49
|
+
|
|
50
|
+
#### Options for `scan`
|
|
41
51
|
|
|
42
52
|
There are some options to help you filter down the results:
|
|
43
53
|
|
|
54
|
+
```sh
|
|
55
|
+
Usage: end_of_life scan PRODUCT [OPTIONS]
|
|
56
|
+
--exclude=NAME,NAME2 Exclude repositories containing a certain word in their name. You can specify up to five words.
|
|
57
|
+
--public-only Searches only public repositories
|
|
58
|
+
--private-only Searches only private repositories
|
|
59
|
+
--repo, --repository=USER/REPO Searches a specific repository
|
|
60
|
+
--org, --organization=ORG,ORG2 Searches within specific organizations
|
|
61
|
+
-u, --user=NAME Sets the user used on the repository search
|
|
62
|
+
--max-eol-days-away NUMBER Sets the maximum number of days away a version can be from EOL.
|
|
63
|
+
--include-archived Includes archived repositories on the search
|
|
64
|
+
-h, --help Show this help message
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Checking if a specific product version is EOL
|
|
68
|
+
|
|
69
|
+
> [!IMPORTANT]
|
|
70
|
+
> You don't need a GitHub token to use this command.
|
|
71
|
+
|
|
72
|
+
You can also check if a specific product version is end-of-life with the
|
|
73
|
+
`end_of_life check` command:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
$ end_of_life check ruby@2.5.8 # exits with status code 1 on EOL
|
|
77
|
+
┌─────────────────┬────────┬──────────────────────────┐
|
|
78
|
+
│ Product Release │ Status │ EOL Date │
|
|
79
|
+
├─────────────────┼────────┼──────────────────────────┤
|
|
80
|
+
│ ruby@2.5.9 │ EOL │ 2021-03-31 (4 years ago) │
|
|
81
|
+
└─────────────────┴────────┴──────────────────────────┘
|
|
44
82
|
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
83
|
+
|
|
84
|
+
You can pass multiple products to check at once:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
$ end_of_life check ruby@2.5.8 nodejs@18
|
|
88
|
+
┌─────────────────┬────────┬───────────────────────────┐
|
|
89
|
+
│ Product Release │ Status │ EOL Date │
|
|
90
|
+
├─────────────────┼────────┼───────────────────────────┤
|
|
91
|
+
│ ruby@2.5.9 │ EOL │ 2021-03-31 (4 years ago) │
|
|
92
|
+
│ nodejs@18.20.8 │ EOL │ 2025-04-30 (4 months ago) │
|
|
93
|
+
└─────────────────┴────────┴───────────────────────────┘
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Options for `check`
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
Usage: end_of_life check PRODUCT@VERSION PRODUCT2@VERSION... [OPTIONS]
|
|
100
|
+
--max-eol-days-away NUMBER Sets the maximum number of days away a version can be from EOL.
|
|
101
|
+
-h, --help Show this help message
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
> [!TIP]
|
|
105
|
+
> You can use check with the `--max-eol-days-away` option on your CI to be
|
|
106
|
+
> alerted when your current version is close to its end-of-life date:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
$ end_of_life check ruby@$(ruby -v | awk '{print $2}') --max-eol-days-away=365
|
|
110
|
+
┌─────────────────┬──────────┬──────────────────────────┐
|
|
111
|
+
│ Product Release │ Status │ EOL Date │
|
|
112
|
+
├─────────────────┼──────────┼──────────────────────────┤
|
|
113
|
+
│ ruby@3.2.9 │ Near EOL │ 2026-03-31 (in 6 months) │
|
|
114
|
+
└─────────────────┴──────────┴──────────────────────────┘
|
|
57
115
|
```
|
|
58
116
|
|
|
59
117
|
## How it works
|
|
60
118
|
|
|
61
119
|
This gem fetches all your GitHub repositories that contain code for the
|
|
62
120
|
specified product, then searches for files that may contain version information.
|
|
63
|
-
For Ruby, those files
|
|
64
|
-
`.tool-version`. End of Life parses these files and extracts
|
|
65
|
-
used in
|
|
121
|
+
For Ruby, those files include `.ruby-version`, `Gemfile`, `Gemfile.lock`,
|
|
122
|
+
`mise.toml`, and `.tool-version`. End of Life parses these files and extracts
|
|
123
|
+
the minimum version used in each repository.
|
|
66
124
|
|
|
67
|
-
The EOL version information is provided by https://endoflife.date
|
|
68
|
-
[fallback].
|
|
125
|
+
The EOL version information is provided by https://endoflife.date/.
|
|
69
126
|
|
|
70
|
-
> [!
|
|
127
|
+
> [!CAUTION]
|
|
71
128
|
> To parse Gemfiles, we need to execute the code inside them.
|
|
72
129
|
> **Be careful** because this may be a security risk. We plan to add secure
|
|
73
130
|
> parsers for these files in the future.
|
|
74
131
|
|
|
75
132
|
Some other limitations are listed on the [issues page].
|
|
76
133
|
|
|
77
|
-
[fallback]: ./lib/end_of_life.json
|
|
78
134
|
[issues page]: https://github.com/MatheusRich/end_of_life/issues
|
|
79
135
|
|
|
80
136
|
## Development
|
|
@@ -95,7 +151,19 @@ Bug reports and pull requests are welcome on GitHub at
|
|
|
95
151
|
https://github.com/MatheusRich/end_of_life. If you want to add a new product,
|
|
96
152
|
[check out this commit for reference].
|
|
97
153
|
|
|
98
|
-
[check out this commit for reference]:
|
|
154
|
+
[check out this commit for reference]: https://github.com/MatheusRich/end_of_life/commit/bfb6f9ceb5afb338fa5553a1266aa2c063e61200
|
|
155
|
+
|
|
156
|
+
## About thoughtbot
|
|
157
|
+
|
|
158
|
+

|
|
159
|
+
|
|
160
|
+
The development of this project is funded by thoughtbot, inc.
|
|
161
|
+
|
|
162
|
+
We love open source software! See [our other projects][community]. We are
|
|
163
|
+
[available for hire][hire].
|
|
164
|
+
|
|
165
|
+
[community]: https://thoughtbot.com/community?utm_source=github
|
|
166
|
+
[hire]: https://thoughtbot.com/hire-us?utm_source=github
|
|
99
167
|
|
|
100
168
|
## License
|
|
101
169
|
|
data/bin/end_of_life
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
exe/end_of_life
|
data/end_of_life.gemspec
CHANGED
|
@@ -34,10 +34,14 @@ Gem::Specification.new do |spec|
|
|
|
34
34
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
35
35
|
spec.require_paths = ["lib"]
|
|
36
36
|
|
|
37
|
+
spec.add_dependency "argument_parser", "~> 0.1.0"
|
|
37
38
|
spec.add_dependency "async"
|
|
38
|
-
spec.add_dependency "
|
|
39
|
+
spec.add_dependency "base64"
|
|
40
|
+
spec.add_dependency "bundler", ">= 2.7.2", "< 3"
|
|
39
41
|
spec.add_dependency "dry-monads", "~> 1.3"
|
|
42
|
+
spec.add_dependency "perfect_toml", "~> 0.9.0"
|
|
40
43
|
spec.add_dependency "octokit", "~> 9.0"
|
|
44
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
|
41
45
|
spec.add_dependency "pastel", "~> 0.8.0"
|
|
42
46
|
spec.add_dependency "tty-spinner", "~> 0.9.0"
|
|
43
47
|
spec.add_dependency "tty-table", "~> 0.12.0"
|
data/lib/end_of_life/api.rb
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "argument_parser"
|
|
2
|
+
|
|
3
|
+
module EndOfLife
|
|
4
|
+
module Check
|
|
5
|
+
include Helpers::Terminal
|
|
6
|
+
include Helpers::Time
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
def run(releases, options)
|
|
10
|
+
report_for releases.map { |release_string| build_row(release_string, options) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def build_row(release_string, options)
|
|
16
|
+
product_release = Product::Release.parse!(release_string)
|
|
17
|
+
cycle_release = product_release.latest_release_in_cycle or raise(
|
|
18
|
+
ArgumentError,
|
|
19
|
+
"Unknown product release: #{release_string}"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
status = if cycle_release.supported?(at: options[:max_eol_date])
|
|
23
|
+
"Supported"
|
|
24
|
+
elsif cycle_release.supported?(at: Date.today)
|
|
25
|
+
"Near EOL"
|
|
26
|
+
else
|
|
27
|
+
"EOL"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
eol_date = if cycle_release.eol_date
|
|
31
|
+
eol_days_away = relative_time_in_words(cycle_release.eol_date)
|
|
32
|
+
"#{cycle_release.eol_date} (#{eol_days_away})"
|
|
33
|
+
else
|
|
34
|
+
"N/A"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
[cycle_release.to_s, status, eol_date]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
HEADERS = ["Product Release", "Status", "EOL Date"].freeze
|
|
41
|
+
def report_for(rows)
|
|
42
|
+
puts table(HEADERS, rows)
|
|
43
|
+
exit 1 if rows.any? { |_, status, _| status != "Supported" }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
|
|
3
|
+
module EndOfLife
|
|
4
|
+
class CLI
|
|
5
|
+
module Command::Registry
|
|
6
|
+
Command = Data.define(:name, :summary, :parser, :action) do
|
|
7
|
+
include Helpers::Terminal
|
|
8
|
+
|
|
9
|
+
def run(argv)
|
|
10
|
+
action.call(argv, parser)
|
|
11
|
+
rescue OptionParser::ParseError, ArgumentParser::ParseError => e
|
|
12
|
+
abort "#{error_msg(e.message.capitalize)}\n\n#{parser}"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.included(base)
|
|
17
|
+
base.extend ClassMethods
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module ClassMethods
|
|
21
|
+
def command_registry
|
|
22
|
+
@command_registry ||= {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def command(name, summary, &action)
|
|
26
|
+
option_parser = OptionParser.new("", 35, " " * 2) do |opt_parser|
|
|
27
|
+
opt_parser.on_tail("-h", "--help", "Show this help message") do
|
|
28
|
+
puts "#{summary}\n\n#{opt_parser}"
|
|
29
|
+
exit
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
command_registry[name.to_s] = Command.new(name: name.to_s, summary:, parser: option_parser, action:)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def commands = command_registry.values
|
|
36
|
+
|
|
37
|
+
def summarize_commands
|
|
38
|
+
max_length = commands.map { |cmd| cmd.name.length }.max
|
|
39
|
+
commands.map { |it| " #{it.name.ljust(max_length)} #{it.summary}" }.join("\n")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def command(name) = self.class.command_registry.fetch(name.to_s)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/end_of_life/cli.rb
CHANGED
|
@@ -1,29 +1,124 @@
|
|
|
1
|
+
require "argument_parser"
|
|
2
|
+
require "date"
|
|
3
|
+
|
|
1
4
|
module EndOfLife
|
|
2
5
|
class CLI
|
|
6
|
+
include Command::Registry
|
|
7
|
+
extend Helpers::Terminal
|
|
3
8
|
include Helpers::Terminal
|
|
4
9
|
|
|
10
|
+
command :scan, "Find projects using end-of-life software" do |argv, opt_parser|
|
|
11
|
+
options = {max_eol_date: Date.today, skip_archived: true}
|
|
12
|
+
|
|
13
|
+
opt_parser.banner = "Usage: end_of_life scan PRODUCT [OPTIONS]"
|
|
14
|
+
opt_parser.on("--exclude=NAME,NAME2", Array, "Exclude repositories containing a certain word in their name. You can specify up to five words.") do |excludes|
|
|
15
|
+
options[:excludes] = excludes.first(5)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
opt_parser.on("--public-only", "Searches only public repositories") do
|
|
19
|
+
options[:visibility] = :public
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
opt_parser.on("--private-only", "Searches only private repositories") do
|
|
23
|
+
options[:visibility] = :private
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
opt_parser.on("--repo=USER/REPO", "--repository=USER/REPO", "Searches a specific repository") do |repository|
|
|
27
|
+
options[:repository] = repository
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
opt_parser.on("--org=ORG,ORG2...", "--organization=ORG,ORG2", Array, "Searches within specific organizations") do |organizations|
|
|
31
|
+
options[:organizations] = organizations
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
opt_parser.on("-u NAME", "--user=NAME", "Sets the user used on the repository search") do |user|
|
|
35
|
+
options[:user] = user
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
opt_parser.on("--max-eol-days-away NUMBER", "Sets the maximum number of days away a version can be from EOL.") do |days|
|
|
39
|
+
options[:max_eol_date] = Date.today + days.to_i.abs
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
opt_parser.on("--include-archived", "Includes archived repositories on the search") do
|
|
43
|
+
options[:skip_archived] = false
|
|
44
|
+
end
|
|
45
|
+
opt_parser.parse!(argv)
|
|
46
|
+
|
|
47
|
+
argument_parser = ArgumentParser.build do
|
|
48
|
+
required :product, pattern: EndOfLife.products_pattern
|
|
49
|
+
end
|
|
50
|
+
name = argument_parser.parse!(argv).fetch(:product)
|
|
51
|
+
|
|
52
|
+
Scanner.scan(Product.find(name), options)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
command :check, "Check if specific product releases are end-of-life" do |argv, opt_parser|
|
|
56
|
+
options = {max_eol_date: Date.today}
|
|
57
|
+
opt_parser.banner = "Usage: end_of_life check PRODUCT@VERSION PRODUCT2@VERSION... [OPTIONS]"
|
|
58
|
+
opt_parser.on("--max-eol-days-away NUMBER", "Sets the maximum number of days away a version can be from EOL.") do |days|
|
|
59
|
+
options[:max_eol_date] = Date.today + days.to_i.abs
|
|
60
|
+
end
|
|
61
|
+
opt_parser.parse!(argv)
|
|
62
|
+
|
|
63
|
+
argument_parser = ArgumentParser.build do
|
|
64
|
+
rest :releases, pattern: EndOfLife.products_pattern(suffix: "@"), min: 1
|
|
65
|
+
end
|
|
66
|
+
args = argument_parser.parse!(argv)
|
|
67
|
+
|
|
68
|
+
Check.run(args[:releases], options)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
command :help, "Show this help message" do |args, _|
|
|
72
|
+
io = args.include?("--error") ? $stderr : $stdout
|
|
73
|
+
|
|
74
|
+
io.puts <<~HELP
|
|
75
|
+
Usage: end_of_life COMMAND [OPTIONS]
|
|
76
|
+
|
|
77
|
+
Commands:
|
|
78
|
+
#{summarize_commands}
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
-h, --help Show this help message
|
|
82
|
+
-v, --version Show end_of_life version
|
|
83
|
+
|
|
84
|
+
Pass -h/--help to commands to see their specific options.
|
|
85
|
+
HELP
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
command :version, "Show end_of_life version" do
|
|
89
|
+
puts "end_of_life v#{EndOfLife::VERSION}"
|
|
90
|
+
end
|
|
91
|
+
|
|
5
92
|
def call(argv)
|
|
6
|
-
|
|
7
|
-
|
|
93
|
+
find_command(argv).run(argv)
|
|
94
|
+
rescue ArgumentError, ArgumentParser::ParseError => e
|
|
95
|
+
abort_with(e.message.capitalize)
|
|
8
96
|
end
|
|
9
97
|
|
|
10
98
|
private
|
|
11
99
|
|
|
12
|
-
def
|
|
13
|
-
|
|
100
|
+
def find_command(argv)
|
|
101
|
+
parse_args(argv).then { |args| command(args[:command]) }
|
|
14
102
|
end
|
|
15
103
|
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Scanner.scan(options)
|
|
104
|
+
def parse_args(argv)
|
|
105
|
+
argument_parser = ArgumentParser.build do
|
|
106
|
+
required :command, pattern: {
|
|
107
|
+
"-h" => :help,
|
|
108
|
+
"-v" => :version,
|
|
109
|
+
"--help" => :help,
|
|
110
|
+
"--version" => :version,
|
|
111
|
+
**EndOfLife::CLI.commands.to_h { |cmd| [cmd.name, cmd.name] }
|
|
112
|
+
}
|
|
26
113
|
end
|
|
114
|
+
|
|
115
|
+
argument_parser.parse!(argv)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def abort_with(message)
|
|
119
|
+
warn error_msg(message)
|
|
120
|
+
command(:help).run(["--error"])
|
|
121
|
+
exit 1
|
|
27
122
|
end
|
|
28
123
|
end
|
|
29
124
|
end
|
|
@@ -26,11 +26,11 @@ module EndOfLife
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def paint
|
|
29
|
-
|
|
29
|
+
Pastel.new(enabled: TTY::Color.support?)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def new_spinner(message
|
|
33
|
-
TTY::Spinner.new("[:spinner] #{message}",
|
|
32
|
+
def new_spinner(message)
|
|
33
|
+
TTY::Spinner.new("[:spinner] #{message}", success_mark: paint.green("✔"), error_mark: paint.red("✖"))
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module EndOfLife
|
|
2
|
+
module Helpers::Time
|
|
3
|
+
include Helpers::Text
|
|
4
|
+
|
|
5
|
+
def relative_time_in_words(date)
|
|
6
|
+
days_away = (date - Date.today).to_i
|
|
7
|
+
return "today" if days_away.zero?
|
|
8
|
+
|
|
9
|
+
duration = duration_in_words(days_away.abs)
|
|
10
|
+
|
|
11
|
+
if days_away.positive?
|
|
12
|
+
"in #{duration}"
|
|
13
|
+
else
|
|
14
|
+
"#{duration} ago"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def duration_in_words(number_of_days)
|
|
21
|
+
if number_of_days >= 365
|
|
22
|
+
years = (number_of_days / 365.0).floor
|
|
23
|
+
pluralize(years, "year")
|
|
24
|
+
elsif number_of_days >= 30
|
|
25
|
+
months = (number_of_days / 30.0).floor
|
|
26
|
+
pluralize(months, "month")
|
|
27
|
+
elsif number_of_days >= 14
|
|
28
|
+
weeks = (number_of_days / 7.0).floor
|
|
29
|
+
pluralize(weeks, "week")
|
|
30
|
+
else
|
|
31
|
+
pluralize(number_of_days, "day")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|