next_rails 1.5.0 → 1.7.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/.github/workflows/main.yml +1 -1
- data/.rspec +1 -0
- data/CHANGELOG.md +18 -1
- data/CONTRIBUTING.md +4 -0
- data/README.md +199 -104
- data/exe/deprecations +45 -22
- data/lib/deprecation_tracker/shard_merger.rb +64 -0
- data/lib/deprecation_tracker/valid_modes.rb +22 -0
- data/lib/deprecation_tracker.rb +65 -32
- data/lib/next_rails/bundle_report/cli.rb +2 -1
- data/lib/next_rails/bundle_report/rails_version_compatibility.rb +13 -11
- data/lib/next_rails/bundle_report/ruby_version_compatibility.rb +9 -9
- data/lib/next_rails/bundle_report.rb +18 -11
- data/lib/next_rails/gem_info.rb +11 -0
- data/lib/next_rails/tint.rb +48 -0
- data/lib/next_rails/version.rb +1 -1
- data/next_rails.gemspec +2 -3
- metadata +7 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 683bb3cbd05a568e913cdbecabc84aa9046f534ebfee134eae53b5ae98d3690f
|
|
4
|
+
data.tar.gz: 83e65946157ab5de2ee05b9d73f735e6d638df7c14bda1ddaff6bd489dec1bd6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38021c508b777bbef26be6b897b43f60150ad1658b06d07795b370173c4e1b0f75307e78d95cc147737313d9f263966e0cf7b94370dbd998ba50ed94b31fcd72
|
|
7
|
+
data.tar.gz: 751730c5320f1fa4dcbdba5b26cd1d2deac911fa5de9abd42ced0224ebc9f811827b3dd4de9f2c63181187c32a6beb3a6956ca0b176f655b1a2ad6a382d125ec
|
data/.github/workflows/main.yml
CHANGED
|
@@ -13,7 +13,7 @@ jobs:
|
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
strategy:
|
|
15
15
|
matrix:
|
|
16
|
-
ruby-version: ["3.4", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6", "2.5", "2.4", "2.3"]
|
|
16
|
+
ruby-version: ["4.0", "3.4", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6", "2.5", "2.4", "2.3"]
|
|
17
17
|
|
|
18
18
|
steps:
|
|
19
19
|
- uses: actions/checkout@v4
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,26 @@
|
|
|
1
|
-
# main [(unreleased)](https://github.com/fastruby/next_rails/compare/v1.
|
|
1
|
+
# main [(unreleased)](https://github.com/fastruby/next_rails/compare/v1.7.0...main)
|
|
2
2
|
|
|
3
3
|
- [BUGFIX: example](https://github.com/fastruby/next_rails/pull/<number>)
|
|
4
4
|
|
|
5
5
|
* Your changes/patches go here.
|
|
6
6
|
|
|
7
|
+
# v1.7.0 / 2026-08-05 [(commits)](https://github.com/fastruby/next_rails/compare/v1.6.0...v1.7.0)
|
|
8
|
+
|
|
9
|
+
- [FEATURE: Add support for SimpleCov 1.0](https://github.com/fastruby/next_rails/pull/196)
|
|
10
|
+
- [FEATURE: Validate the DeprecationTracker mode at initialization, treating a blank mode as the default `save`](https://github.com/fastruby/next_rails/pull/186)
|
|
11
|
+
- [BUGFIX: `bundle_report outdated` no longer confuses a locally-sourced (`path:`) gem with a same-named public gem on rubygems; local gems are excluded from the out-of-date check and counted separately](https://github.com/fastruby/next_rails/pull/189)
|
|
12
|
+
- [BUGFIX: The `deprecations` executable no longer requires the undeclared `rainbow` gem, which made it fail to load on a clean install](https://github.com/fastruby/next_rails/pull/197)
|
|
13
|
+
- [BUGFIX: `deprecations run` no longer raises `NoMethodError` before doing any work](https://github.com/fastruby/next_rails/pull/198)
|
|
14
|
+
- [DOC: Explain `BUNDLE_GEMFILE` usage for the next Gemfile](https://github.com/fastruby/next_rails/pull/187)
|
|
15
|
+
|
|
16
|
+
# v1.6.0 / 2026-05-07 [(commits)](https://github.com/fastruby/next_rails/compare/v1.5.0...v1.6.0)
|
|
17
|
+
|
|
18
|
+
- [CHORE: Drop `rainbow` gem dependency in favor of a native `NextRails::Tint` ANSI wrapper](https://github.com/fastruby/next_rails/pull/183)
|
|
19
|
+
- [BUGFIX: Compare mode now checks only buckets the current process ran, fixing parallel test support](https://github.com/fastruby/next_rails/pull/179)
|
|
20
|
+
- [FEATURE: Add `deprecations merge` command to combine parallel CI shards](https://github.com/fastruby/next_rails/pull/177)
|
|
21
|
+
- [FEATURE: Add parallel CI support for DeprecationTracker](https://github.com/fastruby/next_rails/pull/176)
|
|
22
|
+
- [CHORE: Add Ruby 4.0 to the test matrix](https://github.com/fastruby/next_rails/pull/178)
|
|
23
|
+
|
|
7
24
|
# v1.5.0 / 2026-04-01 [(commits)](https://github.com/fastruby/next_rails/compare/v1.4.8...v1.5.0)
|
|
8
25
|
|
|
9
26
|
- [FEATURE: Add `NextRails.current?` as the inverse of `NextRails.next?`](https://github.com/fastruby/next_rails/pull/174)
|
data/CONTRIBUTING.md
CHANGED
|
@@ -34,6 +34,10 @@ To run all of the tests, simply run:
|
|
|
34
34
|
bundle exec rake
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
### Testing against specific Ruby versions
|
|
38
|
+
|
|
39
|
+
See the [Docker Development Environment for Old Ruby Versions](https://github.com/fastruby/next_rails/wiki/Docker-Development-Environment-for-Old-Ruby-Versions) wiki page for setup and examples.
|
|
40
|
+
|
|
37
41
|
## A word on the changelog
|
|
38
42
|
|
|
39
43
|
You may also notice that we have a changelog in the form of [CHANGELOG.md](CHANGELOG.md). We use a format based on [Keep A Changelog](https://keepachangelog.com/en/1.0.0/).
|
data/README.md
CHANGED
|
@@ -1,83 +1,157 @@
|
|
|
1
1
|
# Next Rails
|
|
2
2
|
|
|
3
3
|
[](https://github.com/fastruby/next_rails/actions/workflows/main.yml)
|
|
4
|
+
[](https://rubygems.org/gems/next_rails)
|
|
5
|
+
[](LICENSE.txt)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
set up dual booting, track deprecation warnings, and get a report on outdated
|
|
7
|
-
dependencies for any Rails application.
|
|
7
|
+
A toolkit to upgrade your next Rails application. It helps you set up **dual booting**, **track deprecation warnings**, and get a **compatibility report** on outdated dependencies for any Rails application. [Learn more](https://www.fastruby.io/blog/next-rails-gem.html).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Features
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- **Dual Boot** — Run your app against two sets of dependencies (e.g. Rails 7.1 and Rails 7.2) side by side
|
|
12
|
+
- **Deprecation Tracking** — Capture and compare deprecation warnings across test runs (RSpec & Minitest)
|
|
13
|
+
- **Bundle Report** — Check gem compatibility with a target Rails or Ruby version
|
|
14
|
+
- **Ruby Check** — Find the minimum Ruby version compatible with a target Rails version
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
conference talk by Jordan Raine.
|
|
16
|
+
## Installation
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
> complete Rails upgrades.
|
|
18
|
+
Add this line to your application's Gemfile:
|
|
18
19
|
|
|
19
|
-
>
|
|
20
|
+
> [!WARNING]
|
|
21
|
+
> We recommend adding `next_rails` in the root of your Gemfile, not inside a group.
|
|
22
|
+
> This ensures `NextRails.next?` and `NextRails.current?` are available everywhere in your codebase.
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
```ruby
|
|
25
|
+
gem 'next_rails'
|
|
26
|
+
```
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
Then run:
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
```bash
|
|
31
|
+
bundle install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Dual Boot
|
|
35
|
+
|
|
36
|
+
We recommend upgrading **one minor version at a time** (e.g. 7.1 → 7.2, not 6.1 → 7.0). This keeps changes small and manageable.
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
### Setup
|
|
39
|
+
|
|
40
|
+
> [!NOTE]
|
|
41
|
+
> The `next_rails --init` command will add a `next?` helper method to the top of your Gemfile, which you can use to conditionally set gem versions.
|
|
30
42
|
|
|
31
43
|
```bash
|
|
32
|
-
#
|
|
33
|
-
|
|
44
|
+
# Initialize dual boot (creates Gemfile.next and Gemfile.next.lock)
|
|
45
|
+
next_rails --init
|
|
34
46
|
|
|
35
|
-
#
|
|
36
|
-
|
|
47
|
+
# Edit your Gemfile to conditionally set gem versions using `next?`
|
|
48
|
+
vim Gemfile
|
|
37
49
|
|
|
38
|
-
#
|
|
39
|
-
|
|
50
|
+
# Install dependencies for the next version
|
|
51
|
+
next bundle install
|
|
40
52
|
|
|
41
|
-
#
|
|
42
|
-
|
|
53
|
+
# Start your server using the next Gemfile
|
|
54
|
+
next rails server
|
|
55
|
+
```
|
|
43
56
|
|
|
44
|
-
|
|
45
|
-
bundle_report compatibility --ruby-version=3.0
|
|
57
|
+
### How `next` works (and using `BUNDLE_GEMFILE` directly)
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
The `next` command is a thin wrapper. It runs your command with `BUNDLE_GEMFILE`
|
|
60
|
+
set so Bundler reads the next dependency set instead of the default one. So these
|
|
61
|
+
two are equivalent:
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
```bash
|
|
64
|
+
next rails server
|
|
65
|
+
|
|
66
|
+
BUNDLE_GEMFILE=Gemfile.next rails server
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Set `BUNDLE_GEMFILE=Gemfile.next` directly when you can't use the `next` wrapper,
|
|
70
|
+
for example in CI jobs, IDE run configurations, Docker, or other tooling that
|
|
71
|
+
calls Bundler on its own:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Run the test suite against the next Gemfile in CI
|
|
75
|
+
BUNDLE_GEMFILE=Gemfile.next bundle exec rspec
|
|
76
|
+
|
|
77
|
+
# Export it for a whole shell session
|
|
78
|
+
export BUNDLE_GEMFILE=Gemfile.next
|
|
79
|
+
bundle install
|
|
80
|
+
rails server
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
When updating `Gemfile.next.lock`, prefer a conservative update so you only bump
|
|
84
|
+
what you intend to and keep the diff small:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Update a single gem against the next Gemfile, leaving everything else pinned
|
|
88
|
+
BUNDLE_GEMFILE=Gemfile.next bundle update rails --conservative
|
|
52
89
|
```
|
|
53
90
|
|
|
54
|
-
|
|
91
|
+
> [!IMPORTANT]
|
|
92
|
+
> Whenever you update `Gemfile.lock`, update `Gemfile.next.lock` at the same time
|
|
93
|
+
> (run the equivalent command with `BUNDLE_GEMFILE=Gemfile.next`). Keeping the two
|
|
94
|
+
> lockfiles in sync avoids surprising differences between the current and next
|
|
95
|
+
> dependency sets.
|
|
96
|
+
|
|
97
|
+
### Conditional code
|
|
55
98
|
|
|
56
|
-
|
|
57
|
-
application:
|
|
99
|
+
When your Gemfile targets two versions, you may need to branch application code as well:
|
|
58
100
|
|
|
59
101
|
```ruby
|
|
60
102
|
if NextRails.next?
|
|
61
|
-
# Do things "the Rails 7 way"
|
|
103
|
+
# Do things "the Rails 7.2 way"
|
|
62
104
|
else
|
|
63
|
-
# Do things "the Rails
|
|
105
|
+
# Do things "the Rails 7.1 way"
|
|
64
106
|
end
|
|
65
107
|
```
|
|
66
108
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
109
|
+
Or use `NextRails.current?` for the inverse check:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
if NextRails.current?
|
|
113
|
+
# Do things "the Rails 7.1 way"
|
|
114
|
+
else
|
|
115
|
+
# Do things "the Rails 7.2 way"
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Both methods check your environment (e.g. `ENV['BUNDLE_GEMFILE']`) to determine which dependency set is active. This is useful for injecting [Ruby or Rails shims](https://www.fastruby.io/blog/rails/upgrades/rails-upgrade-shims.html).
|
|
120
|
+
|
|
121
|
+
## Bundle Report
|
|
122
|
+
|
|
123
|
+
Inspect your Gemfile and check compatibility with a target Rails or Ruby version.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Show all out-of-date gems
|
|
127
|
+
bundle_report outdated
|
|
128
|
+
|
|
129
|
+
# Show all out-of-date gems in JSON format
|
|
130
|
+
bundle_report outdated --json
|
|
131
|
+
|
|
132
|
+
# Show gems incompatible with Rails 7.2
|
|
133
|
+
bundle_report compatibility --rails-version=7.2
|
|
134
|
+
|
|
135
|
+
# Show gems incompatible with Ruby 3.3
|
|
136
|
+
bundle_report compatibility --ruby-version=3.3
|
|
137
|
+
|
|
138
|
+
# Find minimum Ruby version compatible with Rails 7.2
|
|
139
|
+
bundle_report ruby_check --rails-version=7.2
|
|
140
|
+
|
|
141
|
+
# Help
|
|
142
|
+
bundle_report --help
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Deprecation Tracking
|
|
70
146
|
|
|
71
|
-
|
|
72
|
-
[Ruby or Rails shims](https://www.fastruby.io/blog/rails/upgrades/rails-upgrade-shims.html).
|
|
147
|
+
Track deprecation warnings in your test suite so you can monitor and fix them incrementally.
|
|
73
148
|
|
|
74
|
-
###
|
|
149
|
+
### RSpec
|
|
75
150
|
|
|
76
|
-
|
|
151
|
+
Add to `rails_helper.rb` or `spec_helper.rb`:
|
|
77
152
|
|
|
78
153
|
```ruby
|
|
79
154
|
RSpec.configure do |config|
|
|
80
|
-
# Tracker deprecation messages in each file
|
|
81
155
|
if ENV["DEPRECATION_TRACKER"]
|
|
82
156
|
DeprecationTracker.track_rspec(
|
|
83
157
|
config,
|
|
@@ -89,10 +163,11 @@ RSpec.configure do |config|
|
|
|
89
163
|
end
|
|
90
164
|
```
|
|
91
165
|
|
|
92
|
-
|
|
166
|
+
### Minitest
|
|
167
|
+
|
|
168
|
+
Add near the top of `test_helper.rb`:
|
|
93
169
|
|
|
94
170
|
```ruby
|
|
95
|
-
# Tracker deprecation messages in each file
|
|
96
171
|
if ENV["DEPRECATION_TRACKER"]
|
|
97
172
|
DeprecationTracker.track_minitest(
|
|
98
173
|
shitlist_path: "test/support/deprecation_warning.shitlist.json",
|
|
@@ -102,105 +177,125 @@ if ENV["DEPRECATION_TRACKER"]
|
|
|
102
177
|
end
|
|
103
178
|
```
|
|
104
179
|
|
|
105
|
-
>
|
|
180
|
+
> [!NOTE]
|
|
181
|
+
> This is currently not compatible with the `minitest/parallel_fork` gem.
|
|
106
182
|
|
|
107
|
-
|
|
183
|
+
### Running deprecation tracking
|
|
108
184
|
|
|
109
185
|
```bash
|
|
110
|
-
#
|
|
186
|
+
# Save current deprecations to the shitlist
|
|
111
187
|
DEPRECATION_TRACKER=save rspec
|
|
112
|
-
|
|
188
|
+
|
|
189
|
+
# Fail if deprecations have changed since the last save
|
|
113
190
|
DEPRECATION_TRACKER=compare rspec
|
|
114
191
|
```
|
|
115
192
|
|
|
116
|
-
|
|
193
|
+
### Parallel CI support
|
|
117
194
|
|
|
118
|
-
|
|
195
|
+
When running tests across parallel CI nodes, each node can write to its own shard file to avoid conflicts. The tracker auto-detects the node index from common CI environment variables (`CI_NODE_INDEX`, `CIRCLE_NODE_INDEX`, `BUILDKITE_PARALLEL_JOB`, `SEMAPHORE_JOB_INDEX`, `CI_NODE_INDEX` for GitLab), or you can set it explicitly via the `node_index` option.
|
|
119
196
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
197
|
+
#### RSpec
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
RSpec.configure do |config|
|
|
201
|
+
if ENV["DEPRECATION_TRACKER"]
|
|
202
|
+
DeprecationTracker.track_rspec(
|
|
203
|
+
config,
|
|
204
|
+
node_index: ENV["CI_NODE_INDEX"]
|
|
205
|
+
)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
125
208
|
```
|
|
126
209
|
|
|
127
|
-
|
|
210
|
+
The `node_index` option is only used in save mode. When set, the tracker writes to a shard file (e.g. `deprecation_warning.shitlist.node-0.json`) instead of the canonical file. Compare mode does not support `node_index` and will raise an error if passed—compare should only run after merging shards on the final canonical shitlist.
|
|
128
211
|
|
|
129
|
-
####
|
|
212
|
+
#### Merging shards
|
|
130
213
|
|
|
131
|
-
|
|
214
|
+
After all parallel nodes finish saving, merge shards into the canonical file:
|
|
132
215
|
|
|
133
216
|
```bash
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
```
|
|
217
|
+
# Merge all shard files and remove them afterwards
|
|
218
|
+
deprecations merge --delete-shards
|
|
137
219
|
|
|
138
|
-
|
|
220
|
+
# Or use --next to merge shards for the next Rails version
|
|
221
|
+
deprecations merge --next --delete-shards
|
|
222
|
+
```
|
|
139
223
|
|
|
140
|
-
|
|
224
|
+
You can also merge shards programmatically:
|
|
141
225
|
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
226
|
+
```ruby
|
|
227
|
+
DeprecationTracker.merge_shards(
|
|
228
|
+
"spec/support/deprecation_warning.shitlist.json",
|
|
229
|
+
delete_shards: true
|
|
230
|
+
)
|
|
147
231
|
```
|
|
148
232
|
|
|
149
|
-
|
|
233
|
+
#### Example CI workflow
|
|
150
234
|
|
|
151
|
-
|
|
235
|
+
```yaml
|
|
236
|
+
# 1. Save phase — each parallel node writes its own shard
|
|
237
|
+
# (runs on every node)
|
|
238
|
+
DEPRECATION_TRACKER=save CI_NODE_INDEX=$NODE bundle exec rspec <subset>
|
|
152
239
|
|
|
153
|
-
|
|
240
|
+
# 2. Merge phase — fan-in step, runs once after all nodes finish
|
|
241
|
+
deprecations merge --delete-shards
|
|
154
242
|
|
|
155
|
-
|
|
156
|
-
|
|
243
|
+
# 3. Compare phase — each parallel node checks only its own buckets
|
|
244
|
+
# against the merged canonical file (no CI_NODE_INDEX needed)
|
|
245
|
+
DEPRECATION_TRACKER=compare bundle exec rspec <subset>
|
|
157
246
|
```
|
|
158
247
|
|
|
159
|
-
|
|
248
|
+
### `deprecations` command
|
|
160
249
|
|
|
161
|
-
|
|
250
|
+
View, filter, and manage stored deprecation warnings:
|
|
162
251
|
|
|
163
|
-
|
|
252
|
+
```bash
|
|
253
|
+
deprecations info
|
|
254
|
+
deprecations info --pattern "ActiveRecord::Base"
|
|
255
|
+
deprecations merge --delete-shards
|
|
256
|
+
deprecations run
|
|
257
|
+
deprecations --help
|
|
258
|
+
```
|
|
164
259
|
|
|
165
|
-
|
|
260
|
+
## CLI Reference
|
|
166
261
|
|
|
167
|
-
|
|
262
|
+
```bash
|
|
263
|
+
bundle exec next_rails --init # Set up dual boot
|
|
264
|
+
bundle exec next_rails --version # Show gem version
|
|
265
|
+
bundle exec next_rails --help # Show help
|
|
266
|
+
```
|
|
168
267
|
|
|
169
|
-
|
|
268
|
+
## Contributing
|
|
170
269
|
|
|
171
|
-
|
|
270
|
+
Bug reports and pull requests are welcome! See the [Contributing guide](CONTRIBUTING.md) for setup instructions and guidelines.
|
|
172
271
|
|
|
173
|
-
|
|
174
|
-
The Gemfile.next.lock is initialized with the contents of your existing
|
|
175
|
-
Gemfile.lock lock file. We initialize the Gemfile.next.lock to prevent
|
|
176
|
-
major version jumps when running the next version of Rails.
|
|
272
|
+
## Releases
|
|
177
273
|
|
|
178
|
-
|
|
274
|
+
`next_rails` follows [Semantic Versioning](https://semver.org). Given a version number `MAJOR.MINOR.PATCH`, we increment the:
|
|
179
275
|
|
|
180
|
-
|
|
276
|
+
- **MAJOR** version for incompatible API changes
|
|
277
|
+
- **MINOR** version for backwards-compatible new functionality
|
|
278
|
+
- **PATCH** version for backwards-compatible bug fixes
|
|
181
279
|
|
|
182
|
-
|
|
280
|
+
### Steps to release a new version
|
|
183
281
|
|
|
184
|
-
|
|
282
|
+
1. Update the version number in `lib/next_rails/version.rb`
|
|
283
|
+
2. Update `CHANGELOG.md` with the appropriate headers and entries
|
|
284
|
+
3. Commit your changes to a `release/v1.x.x` branch
|
|
285
|
+
4. Push your changes and submit a pull request `Release v1.x.x`
|
|
286
|
+
5. Merge your pull request to the `main` branch
|
|
287
|
+
6. Tag the latest version on `main`: `git tag v1.x.x`
|
|
288
|
+
7. Push the tag to GitHub: `git push --tags`
|
|
289
|
+
8. Build the gem: `gem build next_rails.gemspec`
|
|
290
|
+
9. Push to RubyGems: `gem push next_rails-1.x.x.gem`
|
|
185
291
|
|
|
186
|
-
|
|
292
|
+
## Maintainers
|
|
187
293
|
|
|
188
|
-
|
|
189
|
-
2. MINOR version when you add functionality in a backwards compatible manner, and
|
|
190
|
-
3. PATCH version when you make backwards compatible bug fixes.
|
|
294
|
+
Maintained by [OmbuLabs / FastRuby.io](https://www.fastruby.io).
|
|
191
295
|
|
|
192
|
-
|
|
296
|
+
## History
|
|
193
297
|
|
|
194
|
-
|
|
195
|
-
2. Update `CHANGELOG.md` to have the right headers
|
|
196
|
-
3. Commit your changes to a `release/v-1-1-0` branch
|
|
197
|
-
4. Push your changes and submit a pull request
|
|
198
|
-
5. Merge your pull request to the `main` branch
|
|
199
|
-
6. Git tag the latest version of the `main` branch (`git tag v1.1.0`)
|
|
200
|
-
7. Push tags to GitHub (`git push --tags`)
|
|
201
|
-
8. Build the gem (`gem build next_rails.gemspec`)
|
|
202
|
-
9. Push the .gem package to Rubygems.org (`gem push next_rails-1.1.0.gem`)
|
|
203
|
-
10. You are all done!
|
|
298
|
+
This gem started as a fork of [`ten_years_rails`](https://github.com/clio/ten_years_rails), a companion to the "[Ten Years of Rails Upgrades](https://www.youtube.com/watch?v=6aCfc0DkSFo)" conference talk by Jordan Raine.
|
|
204
299
|
|
|
205
300
|
## License
|
|
206
301
|
|
data/exe/deprecations
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
require "json"
|
|
3
|
-
require "rainbow"
|
|
4
3
|
require "optparse"
|
|
5
4
|
require "set"
|
|
5
|
+
require_relative "../lib/next_rails/tint"
|
|
6
|
+
require_relative "../lib/deprecation_tracker/valid_modes"
|
|
7
|
+
require_relative "../lib/deprecation_tracker/shard_merger"
|
|
6
8
|
|
|
7
9
|
def run_tests(deprecation_warnings, opts = {})
|
|
8
|
-
tracker_mode = opts[:tracker_mode]
|
|
10
|
+
tracker_mode = DeprecationTracker.sanitize_mode(opts[:tracker_mode])
|
|
11
|
+
if tracker_mode && !DeprecationTracker.valid_mode?(tracker_mode)
|
|
12
|
+
abort "Invalid --tracker-mode #{tracker_mode.inspect}. Must be one of: #{DeprecationTracker.valid_modes_display}."
|
|
13
|
+
end
|
|
9
14
|
next_mode = opts[:next_mode]
|
|
10
15
|
rspec_command = if next_mode
|
|
11
16
|
"bin/next rspec"
|
|
@@ -28,11 +33,11 @@ def print_info(deprecation_warnings, opts = {})
|
|
|
28
33
|
end
|
|
29
34
|
end.sort_by {|message, data| data[:occurrences] }.reverse.to_h
|
|
30
35
|
|
|
31
|
-
puts
|
|
36
|
+
puts NextRails::Tint("Ten most common deprecation warnings:").underline
|
|
32
37
|
frequency_by_message.take(10).each do |message, data|
|
|
33
|
-
puts
|
|
38
|
+
puts NextRails::Tint("Occurrences: #{data.fetch(:occurrences)}").bold
|
|
34
39
|
puts "Test files: #{data.fetch(:test_files).to_a.join(" ")}" if verbose
|
|
35
|
-
puts
|
|
40
|
+
puts NextRails::Tint(message).red
|
|
36
41
|
puts "----------"
|
|
37
42
|
end
|
|
38
43
|
end
|
|
@@ -49,6 +54,7 @@ option_parser = OptionParser.new do |opts|
|
|
|
49
54
|
bin/deprecations --next info # Show top ten deprecations for Rails 5
|
|
50
55
|
bin/deprecations --pattern "ActiveRecord::Base" --verbose info # Show full details on deprecations matching pattern
|
|
51
56
|
bin/deprecations --tracker-mode save --pattern "pass" run # Run tests that output deprecations matching pattern and update shitlist
|
|
57
|
+
bin/deprecations merge --delete-shards # Merge parallel CI shards and remove shard files
|
|
52
58
|
|
|
53
59
|
Modes:
|
|
54
60
|
info
|
|
@@ -57,6 +63,9 @@ option_parser = OptionParser.new do |opts|
|
|
|
57
63
|
run
|
|
58
64
|
Run tests that are known to cause deprecation warnings. Use --pattern to filter what tests are run.
|
|
59
65
|
|
|
66
|
+
merge
|
|
67
|
+
Merge parallel CI shard files into the canonical shitlist. Use with --delete-shards to remove shard files after merging.
|
|
68
|
+
|
|
60
69
|
Options:
|
|
61
70
|
MESSAGE
|
|
62
71
|
|
|
@@ -64,7 +73,7 @@ option_parser = OptionParser.new do |opts|
|
|
|
64
73
|
options[:next] = next_mode
|
|
65
74
|
end
|
|
66
75
|
|
|
67
|
-
opts.on("--tracker-mode MODE", "Set DEPRECATION_TRACKER in test mode. Options:
|
|
76
|
+
opts.on("--tracker-mode MODE", "Set DEPRECATION_TRACKER in test mode. Options: #{DeprecationTracker.valid_modes_display}") do |tracker_mode|
|
|
68
77
|
options[:tracker_mode] = tracker_mode
|
|
69
78
|
end
|
|
70
79
|
|
|
@@ -76,6 +85,10 @@ option_parser = OptionParser.new do |opts|
|
|
|
76
85
|
options[:verbose] = true
|
|
77
86
|
end
|
|
78
87
|
|
|
88
|
+
opts.on("--delete-shards", "Delete shard files after merging") do
|
|
89
|
+
options[:delete_shards] = true
|
|
90
|
+
end
|
|
91
|
+
|
|
79
92
|
opts.on_tail("-h", "--help", "Prints this help") do
|
|
80
93
|
puts opts
|
|
81
94
|
exit
|
|
@@ -87,27 +100,37 @@ option_parser.parse!
|
|
|
87
100
|
options[:mode] = ARGV.last
|
|
88
101
|
path = options[:next] ? "spec/support/deprecation_warning.next.shitlist.json" : "spec/support/deprecation_warning.shitlist.json"
|
|
89
102
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
case options[:mode]
|
|
104
|
+
when "merge"
|
|
105
|
+
output = DeprecationTracker::ShardMerger.new(path, delete_shards: !!options[:delete_shards]).merge
|
|
106
|
+
shards = output[:shards]
|
|
107
|
+
result = output[:result]
|
|
108
|
+
total_messages = result.values.map(&:size).reduce(0, :+)
|
|
109
|
+
puts "Merged #{shards} shard files into #{path} (#{result.size} buckets, #{total_messages} deprecation messages)"
|
|
110
|
+
when "run", "info"
|
|
111
|
+
pattern_string = options.fetch(:pattern, ".+")
|
|
112
|
+
pattern = /#{pattern_string}/
|
|
113
|
+
|
|
114
|
+
deprecation_warnings = JSON.parse(File.read(path)).each_with_object({}) do |(test_file, messages), hash|
|
|
115
|
+
filtered_messages = messages.select {|message| message.match(pattern) }
|
|
116
|
+
hash[test_file] = filtered_messages if !filtered_messages.empty?
|
|
117
|
+
end
|
|
97
118
|
|
|
98
|
-
if deprecation_warnings.empty?
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
119
|
+
if deprecation_warnings.empty?
|
|
120
|
+
abort "No test files with deprecations matching #{pattern.inspect}."
|
|
121
|
+
exit 2
|
|
122
|
+
end
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
if options[:mode] == "run"
|
|
125
|
+
run_tests(deprecation_warnings, next_mode: options[:next], tracker_mode: options[:tracker_mode])
|
|
126
|
+
else
|
|
127
|
+
print_info(deprecation_warnings, verbose: options[:verbose])
|
|
128
|
+
end
|
|
106
129
|
when nil
|
|
107
|
-
STDERR.puts
|
|
130
|
+
STDERR.puts NextRails::Tint("Must pass a mode: run, info, or merge").red
|
|
108
131
|
puts option_parser
|
|
109
132
|
exit 1
|
|
110
133
|
else
|
|
111
|
-
STDERR.puts
|
|
134
|
+
STDERR.puts NextRails::Tint("Unknown mode: #{options[:mode]}").red
|
|
112
135
|
exit 1
|
|
113
136
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
class DeprecationTracker
|
|
5
|
+
class ShardMerger
|
|
6
|
+
attr_reader :base_path, :delete_shards
|
|
7
|
+
|
|
8
|
+
def initialize(base_path, delete_shards: false)
|
|
9
|
+
@base_path = base_path
|
|
10
|
+
@delete_shards = delete_shards
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def merge
|
|
14
|
+
dirname = File.dirname(base_path)
|
|
15
|
+
unless File.directory?(dirname)
|
|
16
|
+
warn "Directory does not exist: #{dirname}"
|
|
17
|
+
return { shards: 0, result: {} }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
shard_files = Dir.glob(shard_glob).sort
|
|
21
|
+
|
|
22
|
+
if shard_files.empty?
|
|
23
|
+
warn "No shards found at #{shard_glob}"
|
|
24
|
+
return { shards: 0, result: {} }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
merged = {}
|
|
28
|
+
shard_files.each do |file|
|
|
29
|
+
parse_shard(file).each do |bucket, messages|
|
|
30
|
+
merged[bucket] = (merged[bucket] || []).concat(Array(messages))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
result = {}
|
|
35
|
+
merged.sort.each do |k, v|
|
|
36
|
+
result[k] = v.sort
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
File.write(base_path, JSON.pretty_generate(result))
|
|
41
|
+
rescue Errno::EACCES => e
|
|
42
|
+
raise "Cannot write to #{base_path}: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
shard_files.each { |f| File.delete(f) } if delete_shards
|
|
46
|
+
|
|
47
|
+
{ shards: shard_files.size, result: result }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def shard_glob
|
|
53
|
+
"#{base_path.chomp('.json')}.node-*.json"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def parse_shard(file)
|
|
57
|
+
JSON.parse(File.read(file))
|
|
58
|
+
rescue Errno::ENOENT
|
|
59
|
+
raise "Shard file not found: #{file}"
|
|
60
|
+
rescue JSON::ParserError => e
|
|
61
|
+
raise "Invalid JSON in shard file #{file}: #{e.message}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class DeprecationTracker
|
|
2
|
+
VALID_MODES = %i[save compare].freeze
|
|
3
|
+
|
|
4
|
+
# Human-readable list of valid modes for error messages and CLI help text.
|
|
5
|
+
def self.valid_modes_display
|
|
6
|
+
@valid_modes_display ||= VALID_MODES.map(&:to_s).join(", ")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.valid_mode?(mode)
|
|
10
|
+
mode && VALID_MODES.include?(mode.to_sym)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns the mode as-is, or nil when it is blank. A blank DEPRECATION_TRACKER
|
|
14
|
+
# (e.g. `DEPRECATION_TRACKER= rspec`) is truthy in Ruby, so callers can use this
|
|
15
|
+
# to treat an empty value as unset and fall back to the default mode.
|
|
16
|
+
def self.sanitize_mode(mode)
|
|
17
|
+
return if mode.nil?
|
|
18
|
+
|
|
19
|
+
stripped = mode.to_s.strip
|
|
20
|
+
stripped.empty? ? nil : stripped
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/deprecation_tracker.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
require_relative "next_rails/tint"
|
|
2
2
|
require "json"
|
|
3
3
|
|
|
4
4
|
# A shitlist for deprecation warnings during test runs. It has two modes: "save" and "compare"
|
|
@@ -10,6 +10,8 @@ require "json"
|
|
|
10
10
|
# Tracks deprecation warnings, grouped by spec file. After the test run, compare against shitlist of expected
|
|
11
11
|
# deprecation warnings. If anything is added or removed, raise an error with a diff of the changes.
|
|
12
12
|
#
|
|
13
|
+
require_relative "deprecation_tracker/valid_modes"
|
|
14
|
+
|
|
13
15
|
class DeprecationTracker
|
|
14
16
|
UnexpectedDeprecations = Class.new(StandardError)
|
|
15
17
|
|
|
@@ -73,11 +75,14 @@ class DeprecationTracker
|
|
|
73
75
|
end
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
DEFAULT_PATH = "spec/support/deprecation_warning.shitlist.json"
|
|
79
|
+
|
|
76
80
|
def self.init_tracker(opts = {})
|
|
77
|
-
shitlist_path = opts[:shitlist_path]
|
|
78
|
-
mode = opts[:mode]
|
|
81
|
+
shitlist_path = opts[:shitlist_path] || DEFAULT_PATH
|
|
82
|
+
mode = sanitize_mode(opts[:mode] || ENV["DEPRECATION_TRACKER"]) || :save
|
|
79
83
|
transform_message = opts[:transform_message]
|
|
80
|
-
|
|
84
|
+
node_index = opts[:node_index]
|
|
85
|
+
deprecation_tracker = DeprecationTracker.new(shitlist_path, transform_message, mode, node_index: node_index)
|
|
81
86
|
# Since Rails 7.1 the preferred way to track deprecations is to use the deprecation trackers via
|
|
82
87
|
# `Rails.application.deprecators`.
|
|
83
88
|
# We fallback to tracking deprecations via the ActiveSupport singleton object if Rails.application.deprecators is
|
|
@@ -126,13 +131,38 @@ class DeprecationTracker
|
|
|
126
131
|
ActiveSupport::TestCase.include(MinitestExtension.new(tracker))
|
|
127
132
|
end
|
|
128
133
|
|
|
129
|
-
|
|
134
|
+
def self.merge_shards(base_path, delete_shards: false)
|
|
135
|
+
require_relative "deprecation_tracker/shard_merger"
|
|
136
|
+
ShardMerger.new(base_path, delete_shards: delete_shards).merge[:result]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
attr_reader :deprecation_messages, :shitlist_path, :transform_message, :bucket, :mode, :node_index
|
|
130
140
|
|
|
131
|
-
def initialize(shitlist_path, transform_message = nil, mode = :save)
|
|
141
|
+
def initialize(shitlist_path, transform_message = nil, mode = :save, node_index: nil)
|
|
132
142
|
@shitlist_path = shitlist_path
|
|
133
143
|
@transform_message = transform_message || -> (message) { message }
|
|
134
144
|
@deprecation_messages = {}
|
|
135
|
-
@mode = mode.to_sym
|
|
145
|
+
@mode = mode ? mode.to_sym : :save
|
|
146
|
+
unless self.class.valid_mode?(@mode)
|
|
147
|
+
raise ArgumentError, "mode must be one of: #{self.class.valid_modes_display}. Got: #{mode.inspect}"
|
|
148
|
+
end
|
|
149
|
+
if @mode == :compare && node_index
|
|
150
|
+
raise ArgumentError, "node_index cannot be used with compare mode"
|
|
151
|
+
end
|
|
152
|
+
@node_index = node_index
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def parallel?
|
|
156
|
+
!@node_index.nil?
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def shard_path
|
|
160
|
+
ext = File.extname(shitlist_path)
|
|
161
|
+
"#{shitlist_path.chomp(ext)}.node-#{node_index}#{ext}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def target_path
|
|
165
|
+
parallel? ? shard_path : shitlist_path
|
|
136
166
|
end
|
|
137
167
|
|
|
138
168
|
def add(message)
|
|
@@ -155,16 +185,17 @@ class DeprecationTracker
|
|
|
155
185
|
end
|
|
156
186
|
|
|
157
187
|
def compare
|
|
158
|
-
|
|
188
|
+
stored = read_json(shitlist_path)
|
|
159
189
|
|
|
160
190
|
changed_buckets = []
|
|
191
|
+
|
|
161
192
|
normalized_deprecation_messages.each do |bucket, messages|
|
|
162
|
-
if
|
|
193
|
+
if stored[bucket] != messages
|
|
163
194
|
changed_buckets << bucket
|
|
164
195
|
end
|
|
165
196
|
end
|
|
166
197
|
|
|
167
|
-
if changed_buckets.
|
|
198
|
+
if changed_buckets.any?
|
|
168
199
|
message = <<-MESSAGE
|
|
169
200
|
⚠️ Deprecation warnings have changed!
|
|
170
201
|
|
|
@@ -183,33 +214,33 @@ class DeprecationTracker
|
|
|
183
214
|
See \e[4;37mdev-docs/testing/deprecation_tracker.md\e[0;31m for more information.
|
|
184
215
|
MESSAGE
|
|
185
216
|
|
|
186
|
-
raise UnexpectedDeprecations,
|
|
217
|
+
raise UnexpectedDeprecations, NextRails::Tint(message).red
|
|
187
218
|
end
|
|
188
219
|
end
|
|
189
220
|
|
|
190
221
|
def diff
|
|
191
|
-
|
|
192
|
-
`git diff --no-index #{shitlist_path} #{
|
|
222
|
+
temp_file = create_temp_file
|
|
223
|
+
`git diff --no-index #{shitlist_path} #{temp_file.path}`
|
|
193
224
|
ensure
|
|
194
|
-
|
|
225
|
+
temp_file.delete
|
|
195
226
|
end
|
|
196
227
|
|
|
197
228
|
def save
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
FileUtils.cp(
|
|
229
|
+
temp_file = create_temp_file
|
|
230
|
+
create_if_path_does_not_exist(target_path)
|
|
231
|
+
FileUtils.cp(temp_file.path, target_path)
|
|
201
232
|
ensure
|
|
202
|
-
|
|
233
|
+
temp_file.delete if temp_file
|
|
203
234
|
end
|
|
204
235
|
|
|
205
|
-
def
|
|
206
|
-
dirname = File.dirname(
|
|
236
|
+
def create_if_path_does_not_exist(path)
|
|
237
|
+
dirname = File.dirname(path)
|
|
207
238
|
unless File.directory?(dirname)
|
|
208
239
|
FileUtils.mkdir_p(dirname)
|
|
209
240
|
end
|
|
210
241
|
end
|
|
211
242
|
|
|
212
|
-
def
|
|
243
|
+
def create_temp_file
|
|
213
244
|
temp_file = Tempfile.new("temp-deprecation-tracker-shitlist")
|
|
214
245
|
temp_file.write(JSON.pretty_generate(normalized_deprecation_messages))
|
|
215
246
|
temp_file.flush
|
|
@@ -219,22 +250,24 @@ class DeprecationTracker
|
|
|
219
250
|
|
|
220
251
|
# Normalize deprecation messages to reduce noise from file output and test files to be tracked with separate test runs
|
|
221
252
|
def normalized_deprecation_messages
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
@normalized_deprecation_messages ||= begin
|
|
254
|
+
normalized = read_json(target_path).merge(deprecation_messages).each_with_object({}) do |(bucket, messages), hash|
|
|
255
|
+
hash[bucket] = messages.sort
|
|
256
|
+
end
|
|
225
257
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
258
|
+
# not using `to_h` here to support older ruby versions
|
|
259
|
+
{}.tap do |h|
|
|
260
|
+
normalized.reject {|_key, value| value.empty? }.sort_by {|key, _value| key }.each do |k ,v|
|
|
261
|
+
h[k] = v
|
|
262
|
+
end
|
|
230
263
|
end
|
|
231
264
|
end
|
|
232
265
|
end
|
|
233
266
|
|
|
234
|
-
def
|
|
235
|
-
return {} unless File.exist?(
|
|
236
|
-
JSON.parse(File.read(
|
|
267
|
+
def read_json(path)
|
|
268
|
+
return {} unless File.exist?(path)
|
|
269
|
+
JSON.parse(File.read(path))
|
|
237
270
|
rescue JSON::ParserError => e
|
|
238
|
-
raise "#{
|
|
271
|
+
raise "#{path} is not valid JSON: #{e.message}"
|
|
239
272
|
end
|
|
240
273
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
require "next_rails"
|
|
5
5
|
require "next_rails/bundle_report"
|
|
6
|
+
require "next_rails/tint"
|
|
6
7
|
|
|
7
8
|
class NextRails::BundleReport::CLI
|
|
8
9
|
def initialize(argv)
|
|
@@ -89,7 +90,7 @@ class NextRails::BundleReport::CLI
|
|
|
89
90
|
begin
|
|
90
91
|
option_parser.parse!(@argv)
|
|
91
92
|
rescue OptionParser::ParseError => e
|
|
92
|
-
warn
|
|
93
|
+
warn NextRails::Tint(e.message).red
|
|
93
94
|
puts option_parser
|
|
94
95
|
exit 1
|
|
95
96
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "next_rails/tint"
|
|
2
|
+
|
|
1
3
|
class NextRails::BundleReport::RailsVersionCompatibility
|
|
2
4
|
def initialize(gems: NextRails::GemInfo.all, options: {})
|
|
3
5
|
@gems = gems
|
|
@@ -20,8 +22,8 @@ class NextRails::BundleReport::RailsVersionCompatibility
|
|
|
20
22
|
def erb_output
|
|
21
23
|
template = <<-ERB
|
|
22
24
|
<% if incompatible_gems_by_state[:found_compatible] -%>
|
|
23
|
-
<%=
|
|
24
|
-
<%=
|
|
25
|
+
<%= NextRails::Tint("=> Incompatible with Rails #{rails_version} (with new versions that are compatible):").white.bold %>
|
|
26
|
+
<%= NextRails::Tint("These gems will need to be upgraded before upgrading to Rails #{rails_version}.").italic %>
|
|
25
27
|
|
|
26
28
|
<% incompatible_gems_by_state[:found_compatible].each do |gem| -%>
|
|
27
29
|
<%= gem_header(gem) %> - upgrade to <%= gem.latest_compatible_version.version %>
|
|
@@ -29,8 +31,8 @@ class NextRails::BundleReport::RailsVersionCompatibility
|
|
|
29
31
|
|
|
30
32
|
<% end -%>
|
|
31
33
|
<% if incompatible_gems_by_state[:incompatible] -%>
|
|
32
|
-
<%=
|
|
33
|
-
<%=
|
|
34
|
+
<%= NextRails::Tint("=> Incompatible with Rails #{rails_version} (with no new compatible versions):").white.bold %>
|
|
35
|
+
<%= NextRails::Tint("These gems will need to be removed or replaced before upgrading to Rails #{rails_version}.").italic %>
|
|
34
36
|
|
|
35
37
|
<% incompatible_gems_by_state[:incompatible].each do |gem| -%>
|
|
36
38
|
<%= gem_header(gem) %> - new version, <%= gem.latest_version.version %>, is not compatible with Rails #{rails_version}
|
|
@@ -38,16 +40,16 @@ class NextRails::BundleReport::RailsVersionCompatibility
|
|
|
38
40
|
|
|
39
41
|
<% end -%>
|
|
40
42
|
<% if incompatible_gems_by_state[:no_new_version] -%>
|
|
41
|
-
<%=
|
|
42
|
-
<%=
|
|
43
|
-
<%=
|
|
43
|
+
<%= NextRails::Tint("=> Incompatible with Rails #{rails_version} (with no new versions):").white.bold %>
|
|
44
|
+
<%= NextRails::Tint("These gems will need to be upgraded by us or removed before upgrading to Rails #{rails_version}.").italic %>
|
|
45
|
+
<%= NextRails::Tint("This list is likely to contain internal gems, like Cuddlefish.").italic %>
|
|
44
46
|
|
|
45
47
|
<% incompatible_gems_by_state[:no_new_version].each do |gem| -%>
|
|
46
48
|
<%= gem_header(gem) %> - new version not found
|
|
47
49
|
<% end -%>
|
|
48
50
|
|
|
49
51
|
<% end -%>
|
|
50
|
-
<%=
|
|
52
|
+
<%= NextRails::Tint(incompatible_gems.length.to_s).red %> gems incompatible with Rails <%= rails_version %>
|
|
51
53
|
ERB
|
|
52
54
|
|
|
53
55
|
erb_version = ERB.version
|
|
@@ -63,9 +65,9 @@ class NextRails::BundleReport::RailsVersionCompatibility
|
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def gem_header(_gem)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
parts = [NextRails::Tint("#{_gem.name} #{_gem.version}").bold]
|
|
69
|
+
parts << NextRails::Tint(" (loaded from git)").magenta if _gem.sourced_from_git?
|
|
70
|
+
parts.join
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
def incompatible_gems
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "next_rails/tint"
|
|
2
2
|
|
|
3
3
|
class NextRails::BundleReport::RubyVersionCompatibility
|
|
4
4
|
MINIMAL_VERSION = 1.0
|
|
@@ -10,20 +10,20 @@ class NextRails::BundleReport::RubyVersionCompatibility
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def generate
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
message
|
|
13
|
+
(valid? ? message : invalid_message).to_s
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
private
|
|
19
17
|
|
|
20
18
|
def message
|
|
21
|
-
|
|
19
|
+
noun = incompatible.one? ? "gem" : "gems"
|
|
20
|
+
parts = [NextRails::Tint("=> Incompatible gems with Ruby #{ruby_version}:").white.bold]
|
|
22
21
|
incompatible.each do |gem|
|
|
23
|
-
|
|
22
|
+
parts << NextRails::Tint("#{gem.name} - required Ruby version: #{gem.gem_specification.required_ruby_version}").magenta
|
|
24
23
|
end
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
parts << ""
|
|
25
|
+
parts << NextRails::Tint("#{incompatible.length} incompatible #{noun} with Ruby #{ruby_version}").red
|
|
26
|
+
parts.join("\n")
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def incompatible
|
|
@@ -35,7 +35,7 @@ class NextRails::BundleReport::RubyVersionCompatibility
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def invalid_message
|
|
38
|
-
|
|
38
|
+
NextRails::Tint("=> Invalid Ruby version: #{options[:ruby_version]}.").red.bold
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def valid?
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "next_rails/tint"
|
|
2
2
|
require "cgi"
|
|
3
3
|
require "erb"
|
|
4
4
|
require "json"
|
|
@@ -63,22 +63,27 @@ module NextRails
|
|
|
63
63
|
|
|
64
64
|
def outdated(format = nil)
|
|
65
65
|
gems = NextRails::GemInfo.all
|
|
66
|
-
|
|
66
|
+
sourced_locally = gems.select(&:sourced_locally?)
|
|
67
67
|
sourced_from_git = gems.select(&:sourced_from_git?)
|
|
68
68
|
|
|
69
|
+
# Locally-sourced gems (e.g. `path:` engines) are excluded from the
|
|
70
|
+
# out-of-date check: looking them up by name on rubygems can match an
|
|
71
|
+
# unrelated public gem with the same name and report a bogus upgrade.
|
|
72
|
+
out_of_date_gems = (gems - sourced_locally).reject(&:up_to_date?).sort_by(&:created_at)
|
|
73
|
+
|
|
69
74
|
if format == 'json'
|
|
70
|
-
output_to_json(out_of_date_gems, gems.count, sourced_from_git.count)
|
|
75
|
+
output_to_json(out_of_date_gems, gems.count, sourced_from_git.count, sourced_locally.count)
|
|
71
76
|
else
|
|
72
|
-
output_to_stdout(out_of_date_gems, gems.count, sourced_from_git.count)
|
|
77
|
+
output_to_stdout(out_of_date_gems, gems.count, sourced_from_git.count, sourced_locally.count)
|
|
73
78
|
end
|
|
74
79
|
end
|
|
75
80
|
|
|
76
|
-
def output_to_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
|
|
77
|
-
obj = build_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
|
|
81
|
+
def output_to_json(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
|
|
82
|
+
obj = build_json(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
|
|
78
83
|
puts JSON.pretty_generate(obj)
|
|
79
84
|
end
|
|
80
85
|
|
|
81
|
-
def build_json(out_of_date_gems, total_gem_count, sourced_from_git_count)
|
|
86
|
+
def build_json(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
|
|
82
87
|
output = Hash.new { [] }
|
|
83
88
|
out_of_date_gems.each do |gem|
|
|
84
89
|
output[:outdated_gems] += [
|
|
@@ -95,24 +100,26 @@ module NextRails
|
|
|
95
100
|
output.merge(
|
|
96
101
|
{
|
|
97
102
|
sourced_from_git_count: sourced_from_git_count,
|
|
103
|
+
sourced_locally_count: sourced_locally_count,
|
|
98
104
|
total_gem_count: total_gem_count
|
|
99
105
|
}
|
|
100
106
|
)
|
|
101
107
|
end
|
|
102
108
|
|
|
103
|
-
def output_to_stdout(out_of_date_gems, total_gem_count, sourced_from_git_count)
|
|
109
|
+
def output_to_stdout(out_of_date_gems, total_gem_count, sourced_from_git_count, sourced_locally_count)
|
|
104
110
|
out_of_date_gems.each do |gem|
|
|
105
111
|
header = "#{gem.name} #{gem.version}"
|
|
106
112
|
|
|
107
113
|
puts <<-MESSAGE
|
|
108
|
-
#{
|
|
114
|
+
#{NextRails::Tint(header).bold.white}: released #{gem.age} (latest version, #{gem.latest_version.version}, released #{gem.latest_version.age})
|
|
109
115
|
MESSAGE
|
|
110
116
|
end
|
|
111
117
|
|
|
112
118
|
percentage_out_of_date = ((out_of_date_gems.count / total_gem_count.to_f) * 100).round
|
|
113
119
|
footer = <<-MESSAGE
|
|
114
|
-
#{
|
|
115
|
-
#{
|
|
120
|
+
#{NextRails::Tint(sourced_from_git_count.to_s).yellow} gems are sourced from git
|
|
121
|
+
#{NextRails::Tint(sourced_locally_count.to_s).yellow} gems are sourced from a local path
|
|
122
|
+
#{NextRails::Tint(out_of_date_gems.count.to_s).red} of the #{total_gem_count} gems are out-of-date (#{percentage_out_of_date}%)
|
|
116
123
|
MESSAGE
|
|
117
124
|
|
|
118
125
|
puts ''
|
data/lib/next_rails/gem_info.rb
CHANGED
|
@@ -66,6 +66,17 @@ module NextRails
|
|
|
66
66
|
!!gem_specification.git_version
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
def sourced_locally?
|
|
70
|
+
# Bundler defines Source::Path and patches Gem::Specification#source to return the
|
|
71
|
+
# gem's real source. Without Bundler loaded, #source is RubyGems' own and returns a
|
|
72
|
+
# Gem::Source::Installed, so no path source can exist; treat the gem as not local.
|
|
73
|
+
return false unless defined?(Bundler::Source::Path)
|
|
74
|
+
|
|
75
|
+
source = gem_specification.source
|
|
76
|
+
# Git sources subclass Path, so exclude them; they are reported via #sourced_from_git?.
|
|
77
|
+
source.is_a?(Bundler::Source::Path) && !source.is_a?(Bundler::Source::Git)
|
|
78
|
+
end
|
|
79
|
+
|
|
69
80
|
def created_at
|
|
70
81
|
@created_at ||= gem_specification.date
|
|
71
82
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NextRails
|
|
4
|
+
# Lightweight ANSI color/style wrapper with chainable style methods.
|
|
5
|
+
# Wrap a string with `NextRails::Tint("text")` then chain styles:
|
|
6
|
+
#
|
|
7
|
+
# NextRails::Tint("hello").red.bold
|
|
8
|
+
#
|
|
9
|
+
# Instances are effectively immutable: each style method returns a new
|
|
10
|
+
# `Tint` rather than mutating the receiver, so a reference can be reused
|
|
11
|
+
# without styles accumulating across chains.
|
|
12
|
+
class Tint
|
|
13
|
+
CODES = {
|
|
14
|
+
bold: 1,
|
|
15
|
+
italic: 3,
|
|
16
|
+
underline: 4,
|
|
17
|
+
red: 31,
|
|
18
|
+
green: 32,
|
|
19
|
+
yellow: 33,
|
|
20
|
+
blue: 34,
|
|
21
|
+
magenta: 35,
|
|
22
|
+
cyan: 36,
|
|
23
|
+
white: 37
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
def initialize(string, codes = [])
|
|
27
|
+
@string = string.to_s
|
|
28
|
+
@codes = codes
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
CODES.each_key do |style|
|
|
32
|
+
define_method(style) do
|
|
33
|
+
self.class.new(@string, @codes + [CODES[style]])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_s
|
|
38
|
+
return @string if @codes.empty?
|
|
39
|
+
|
|
40
|
+
"\e[#{@codes.join(";")}m#{@string}\e[0m"
|
|
41
|
+
end
|
|
42
|
+
alias_method :to_str, :to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.Tint(string)
|
|
46
|
+
Tint.new(string)
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/next_rails/version.rb
CHANGED
data/next_rails.gemspec
CHANGED
|
@@ -22,11 +22,10 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
23
23
|
spec.require_paths = ["lib"]
|
|
24
24
|
|
|
25
|
-
spec.
|
|
26
|
-
spec.add_development_dependency "bundler", ">= 1.16", "< 3.0"
|
|
25
|
+
spec.add_development_dependency "bundler", ">= 1.16"
|
|
27
26
|
spec.add_development_dependency "rake"
|
|
28
27
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
29
|
-
spec.add_development_dependency "simplecov", "
|
|
28
|
+
spec.add_development_dependency "simplecov", ">= 0.17.1"
|
|
30
29
|
spec.add_development_dependency "timecop", "~> 0.9.1"
|
|
31
30
|
spec.add_development_dependency "byebug"
|
|
32
31
|
spec.add_development_dependency "rexml", "3.2.5" # limited on purpose, new versions don't work with old rubies
|
metadata
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: next_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ernesto Tagwerker
|
|
8
8
|
- Luis Sagastume
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: rainbow
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '3'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '3'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: bundler
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -31,9 +17,6 @@ dependencies:
|
|
|
31
17
|
- - ">="
|
|
32
18
|
- !ruby/object:Gem::Version
|
|
33
19
|
version: '1.16'
|
|
34
|
-
- - "<"
|
|
35
|
-
- !ruby/object:Gem::Version
|
|
36
|
-
version: '3.0'
|
|
37
20
|
type: :development
|
|
38
21
|
prerelease: false
|
|
39
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -41,9 +24,6 @@ dependencies:
|
|
|
41
24
|
- - ">="
|
|
42
25
|
- !ruby/object:Gem::Version
|
|
43
26
|
version: '1.16'
|
|
44
|
-
- - "<"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '3.0'
|
|
47
27
|
- !ruby/object:Gem::Dependency
|
|
48
28
|
name: rake
|
|
49
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -76,14 +56,14 @@ dependencies:
|
|
|
76
56
|
name: simplecov
|
|
77
57
|
requirement: !ruby/object:Gem::Requirement
|
|
78
58
|
requirements:
|
|
79
|
-
- - "
|
|
59
|
+
- - ">="
|
|
80
60
|
- !ruby/object:Gem::Version
|
|
81
61
|
version: 0.17.1
|
|
82
62
|
type: :development
|
|
83
63
|
prerelease: false
|
|
84
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
85
65
|
requirements:
|
|
86
|
-
- - "
|
|
66
|
+
- - ">="
|
|
87
67
|
- !ruby/object:Gem::Version
|
|
88
68
|
version: 0.17.1
|
|
89
69
|
- !ruby/object:Gem::Dependency
|
|
@@ -194,6 +174,8 @@ files:
|
|
|
194
174
|
- exe/next.sh
|
|
195
175
|
- exe/next_rails
|
|
196
176
|
- lib/deprecation_tracker.rb
|
|
177
|
+
- lib/deprecation_tracker/shard_merger.rb
|
|
178
|
+
- lib/deprecation_tracker/valid_modes.rb
|
|
197
179
|
- lib/next_rails.rb
|
|
198
180
|
- lib/next_rails/bundle_report.rb
|
|
199
181
|
- lib/next_rails/bundle_report/cli.rb
|
|
@@ -201,6 +183,7 @@ files:
|
|
|
201
183
|
- lib/next_rails/bundle_report/ruby_version_compatibility.rb
|
|
202
184
|
- lib/next_rails/gem_info.rb
|
|
203
185
|
- lib/next_rails/init.rb
|
|
186
|
+
- lib/next_rails/tint.rb
|
|
204
187
|
- lib/next_rails/version.rb
|
|
205
188
|
- next_rails.gemspec
|
|
206
189
|
- pull_request_template.md
|