rake-gem-maintenance 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CLAUDE.md +7 -7
- data/Gemfile.lock +2 -2
- data/README.md +9 -9
- data/TODO.md +4 -1
- data/lib/rake/gem/maintenance/api_key_renewer.rb +37 -35
- data/lib/rake/gem/maintenance/ci_environment.rb +7 -5
- data/lib/rake/gem/maintenance/credential_store.rb +46 -44
- data/lib/rake/gem/maintenance/gem_publisher.rb +86 -84
- data/lib/rake/gem/maintenance/gem_push.rb +44 -42
- data/lib/rake/gem/maintenance/install_tasks.rb +5 -5
- data/lib/rake/gem/maintenance/otp_provider.rb +33 -31
- data/lib/rake/gem/maintenance/renew_api_key_task.rb +111 -109
- data/lib/rake/gem/maintenance/repos.rb +81 -79
- data/lib/rake/gem/maintenance/ruby_gems_api_key_creator.rb +39 -37
- data/lib/rake/gem/maintenance/upgrade_task.rb +245 -242
- data/lib/rake/gem/maintenance/version.rb +4 -2
- data/lib/rake/gem/maintenance/version_bump_task.rb +83 -81
- data/lib/rake/gem/maintenance/woodpecker_secret_store.rb +69 -67
- data/scripts/ci_publish_rubygems.rb +3 -3
- metadata +1 -1
|
@@ -10,311 +10,314 @@ require_relative "gem_publisher"
|
|
|
10
10
|
require_relative "repos"
|
|
11
11
|
|
|
12
12
|
module Rake
|
|
13
|
-
module
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
module Gem
|
|
14
|
+
module Maintenance
|
|
15
|
+
# Defines Rake tasks for upgrading gem dependencies and publishing to multiple repositories.
|
|
16
|
+
#
|
|
17
|
+
# Creates: upgrade, upgrade:auto, upgrade:branch, upgrade:gems, upgrade:commit,
|
|
18
|
+
# upgrade:prepare_version, upgrade:push
|
|
19
|
+
# rubocop:disable Metrics/ClassLength, Metrics/MethodLength
|
|
20
|
+
class UpgradeTask < ::Rake::TaskLib
|
|
21
|
+
attr_accessor :name, :main_branch, :upgrade_branch, :commit_message,
|
|
22
|
+
:files_to_commit, :verification_task, :release_task,
|
|
23
|
+
:version_bump_task, :update_rubygems, :update_gems,
|
|
24
|
+
:run_bundle_audit, :auto_pipeline, :gem_repositories,
|
|
25
|
+
:gem_publisher_class, :gem_name, :gem_version
|
|
26
|
+
|
|
27
|
+
attr_writer :renew_api_key_task_class
|
|
28
|
+
|
|
29
|
+
def renew_api_key_task_class
|
|
30
|
+
@renew_api_key_task_class || RenewApiKeyTask
|
|
31
|
+
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
def initialize(name = :upgrade)
|
|
34
|
+
super()
|
|
35
|
+
apply_default_configuration(name)
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
yield self if block_given?
|
|
38
|
+
define_tasks
|
|
39
|
+
end
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
41
|
+
def apply_default_configuration(name)
|
|
42
|
+
@name = name
|
|
43
|
+
@main_branch = "main"
|
|
44
|
+
@upgrade_branch = "upgrade/gems"
|
|
45
|
+
@commit_message = "chore(deps): upgrade gems"
|
|
46
|
+
@files_to_commit = %w[Gemfile Gemfile.lock]
|
|
47
|
+
@verification_task = :verify
|
|
48
|
+
@release_task = :release
|
|
49
|
+
@version_bump_task = "version:bump"
|
|
50
|
+
@update_rubygems = true
|
|
51
|
+
@update_gems = true
|
|
52
|
+
@run_bundle_audit = true
|
|
53
|
+
@auto_pipeline = nil
|
|
54
|
+
@gem_repositories = Repos.rubygems
|
|
55
|
+
@gem_publisher_class = GemPublisher
|
|
56
|
+
@gem_name = detect_gem_name
|
|
57
|
+
@gem_version = detect_gem_version
|
|
58
|
+
end
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
private
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
def detect_gem_name
|
|
63
|
+
gemspec = Dir.glob("*.gemspec").first
|
|
64
|
+
return nil unless gemspec
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
::Gem::Specification.load(gemspec).name
|
|
67
|
+
end
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
def detect_gem_version
|
|
70
|
+
gemspec = Dir.glob("*.gemspec").first
|
|
71
|
+
return nil unless gemspec
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
::Gem::Specification.load(gemspec).version.to_s
|
|
74
|
+
end
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
def repo_available?(repo)
|
|
77
|
+
url = repo[:url]
|
|
78
|
+
uri = URI.parse(url)
|
|
79
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
80
|
+
http.use_ssl = uri.scheme == "https"
|
|
81
|
+
http.open_timeout = 5
|
|
82
|
+
http.start { |h| h.head("/").code.to_i < 400 }
|
|
83
|
+
rescue StandardError
|
|
84
|
+
false
|
|
85
|
+
end
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
public
|
|
88
|
+
|
|
89
|
+
def define_tasks
|
|
90
|
+
define_top_level_task
|
|
91
|
+
define_info_tasks
|
|
92
|
+
define_prepare_version_task
|
|
93
|
+
define_auto_task
|
|
94
|
+
define_branch_task
|
|
95
|
+
define_gems_task
|
|
96
|
+
define_commit_task
|
|
97
|
+
define_push_task
|
|
98
|
+
define_renew_api_key_task
|
|
99
|
+
end
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
def define_info_tasks
|
|
102
|
+
task_instance = self
|
|
103
|
+
namespace name do
|
|
104
|
+
namespace :info do
|
|
105
|
+
define_info_repos_task(task_instance)
|
|
106
|
+
define_info_version_task(task_instance)
|
|
107
|
+
define_info_name_task(task_instance)
|
|
108
|
+
desc "Show all upgrade info"
|
|
109
|
+
task all: %i[name version repos]
|
|
110
|
+
end
|
|
107
111
|
desc "Show all upgrade info"
|
|
108
|
-
task
|
|
112
|
+
task info: "#{name}:info:all"
|
|
109
113
|
end
|
|
110
|
-
desc "Show all upgrade info"
|
|
111
|
-
task info: "#{name}:info:all"
|
|
112
114
|
end
|
|
113
|
-
end
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
116
|
+
def define_info_repos_task(task_instance)
|
|
117
|
+
desc "Show configured gem repositories"
|
|
118
|
+
task :repos do
|
|
119
|
+
puts "Gem repositories:"
|
|
120
|
+
task_instance.gem_repositories.each do |repo|
|
|
121
|
+
available = task_instance.send(:repo_available?, repo)
|
|
122
|
+
status = available ? "✓" : "✗"
|
|
123
|
+
avail_text = available ? "AVAILABLE" : "NOT AVAILABLE"
|
|
124
|
+
puts " - #{repo[:name]} (#{status}) - #{avail_text}: #{repo[:url]}"
|
|
125
|
+
end
|
|
124
126
|
end
|
|
125
127
|
end
|
|
126
|
-
end
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
def define_info_version_task(task_instance)
|
|
130
|
+
desc "Show current gem version"
|
|
131
|
+
task :version do
|
|
132
|
+
ver = task_instance.gem_version || "unknown"
|
|
133
|
+
puts "Current version: #{ver}"
|
|
134
|
+
end
|
|
133
135
|
end
|
|
134
|
-
end
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
def define_info_name_task(task_instance)
|
|
138
|
+
desc "Show current gem name"
|
|
139
|
+
task :name do
|
|
140
|
+
name = task_instance.gem_name || "unknown"
|
|
141
|
+
puts "Gem name: #{name}"
|
|
142
|
+
end
|
|
141
143
|
end
|
|
142
|
-
end
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
def define_top_level_task
|
|
146
|
+
desc "Alias for #{name}:auto"
|
|
147
|
+
task name => "#{name}:auto"
|
|
148
|
+
end
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
def define_prepare_version_task
|
|
151
|
+
task_instance = self
|
|
152
|
+
namespace name do
|
|
153
|
+
desc "Check version on all repositories before bumping"
|
|
154
|
+
task :prepare_version do
|
|
155
|
+
task_instance.send(:check_version_on_repositories)
|
|
156
|
+
end
|
|
155
157
|
end
|
|
156
158
|
end
|
|
157
|
-
end
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
def define_auto_task
|
|
161
|
+
task_instance = self
|
|
162
|
+
namespace name do
|
|
163
|
+
desc "Update gems automatically (branch to push and release)"
|
|
164
|
+
task auto: task_instance.send(:pipeline_tasks)
|
|
165
|
+
end
|
|
164
166
|
end
|
|
165
|
-
end
|
|
166
167
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
def define_branch_task
|
|
169
|
+
task_instance = self
|
|
170
|
+
namespace name do
|
|
171
|
+
desc "Create a branch for the upgrade"
|
|
172
|
+
task(:branch) { task_instance.send(:create_upgrade_branch) }
|
|
173
|
+
end
|
|
172
174
|
end
|
|
173
|
-
end
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
def define_gems_task
|
|
177
|
+
task_instance = self
|
|
178
|
+
namespace name do
|
|
179
|
+
desc "Upgrade gems, including bundler and gem"
|
|
180
|
+
task(:gems) { task_instance.send(:do_upgrade_gems) }
|
|
181
|
+
end
|
|
180
182
|
end
|
|
181
|
-
end
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
184
|
+
def define_commit_task
|
|
185
|
+
task_instance = self
|
|
186
|
+
namespace name do
|
|
187
|
+
desc "Commit the upgrade branch"
|
|
188
|
+
task(:commit) { task_instance.send(:commit_changes) }
|
|
189
|
+
end
|
|
188
190
|
end
|
|
189
|
-
end
|
|
190
191
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
def define_push_task
|
|
193
|
+
task_instance = self
|
|
194
|
+
namespace name do
|
|
195
|
+
desc "Push the upgrade"
|
|
196
|
+
task(:push) { task_instance.send(:push_branch) }
|
|
197
|
+
end
|
|
196
198
|
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def define_renew_api_key_task
|
|
200
|
-
renew_api_key_task_class.new(name)
|
|
201
|
-
end
|
|
202
199
|
|
|
203
|
-
|
|
204
|
-
|
|
200
|
+
def define_renew_api_key_task
|
|
201
|
+
renew_api_key_task_class.new(name)
|
|
202
|
+
end
|
|
205
203
|
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
def pipeline_tasks
|
|
205
|
+
return auto_pipeline if auto_pipeline
|
|
208
206
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
sh "git branch -D #{upgrade_branch}" unless `git branch --list #{upgrade_branch}`.chomp.empty?
|
|
213
|
-
sh "git checkout -b #{upgrade_branch}"
|
|
214
|
-
end
|
|
207
|
+
%i[branch
|
|
208
|
+
gems] + [verification_task, :commit, version_bump_task.to_sym, :prepare_version, release_task, :push]
|
|
209
|
+
end
|
|
215
210
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
end
|
|
211
|
+
def create_upgrade_branch
|
|
212
|
+
sh "git checkout #{main_branch}"
|
|
213
|
+
sh "git pull"
|
|
214
|
+
sh "git branch -D #{upgrade_branch}" unless `git branch --list #{upgrade_branch}`.chomp.empty?
|
|
215
|
+
sh "git checkout -b #{upgrade_branch}"
|
|
216
|
+
end
|
|
223
217
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
218
|
+
def do_upgrade_gems
|
|
219
|
+
sh "gem update --system" if update_rubygems
|
|
220
|
+
sh "gem update" if update_gems
|
|
221
|
+
sh "bundle update --bundler"
|
|
222
|
+
sh "bundle update --all"
|
|
223
|
+
sh "bundle audit" if run_bundle_audit
|
|
224
|
+
end
|
|
228
225
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
226
|
+
def commit_changes
|
|
227
|
+
sh "git add #{files_to_commit.join(' ')}"
|
|
228
|
+
sh "git commit -m '#{commit_message}'"
|
|
229
|
+
end
|
|
232
230
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
unless gem_name && gem_version
|
|
236
|
-
puts "[ERROR] No gemspec found - cannot check version/upgrade"
|
|
237
|
-
abort
|
|
231
|
+
def push_branch
|
|
232
|
+
sh "git push origin #{upgrade_branch}"
|
|
238
233
|
end
|
|
239
234
|
|
|
240
|
-
|
|
241
|
-
|
|
235
|
+
# rubocop:disable Metrics/AbcSize
|
|
236
|
+
def check_version_on_repositories
|
|
237
|
+
unless gem_name && gem_version
|
|
238
|
+
puts "[ERROR] No gemspec found - cannot check version/upgrade"
|
|
239
|
+
abort
|
|
240
|
+
end
|
|
242
241
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
abort
|
|
246
|
-
end
|
|
242
|
+
publisher = gem_publisher_class.new(gem_repositories)
|
|
243
|
+
gemspec_repos = gem_repositories.select { |repo| repo_available?(repo) }
|
|
247
244
|
|
|
248
|
-
|
|
245
|
+
if gemspec_repos.empty?
|
|
246
|
+
puts "[ERROR] No repositories available. Cannot check version."
|
|
247
|
+
abort
|
|
248
|
+
end
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
version = gem_version
|
|
252
|
-
next_ver = publisher.next_version(gem_name, version)
|
|
250
|
+
publisher.check_all_repositories(gem_name)
|
|
253
251
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
puts "[INFO] Version #{version} already published to all repositories"
|
|
258
|
-
puts "[INFO] Next available version: #{next_ver}"
|
|
259
|
-
end
|
|
252
|
+
print_failed_repository_warnings(publisher)
|
|
253
|
+
version = gem_version
|
|
254
|
+
next_ver = publisher.next_version(gem_name, version)
|
|
260
255
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
if next_ver == version
|
|
257
|
+
puts "[INFO] Version #{version} not found on any repository - will publish"
|
|
258
|
+
else
|
|
259
|
+
puts "[INFO] Version #{version} already published to all repositories"
|
|
260
|
+
puts "[INFO] Next available version: #{next_ver}"
|
|
261
|
+
end
|
|
264
262
|
|
|
265
|
-
|
|
266
|
-
|
|
263
|
+
handle_partial_publish_warning(publisher, version)
|
|
264
|
+
end
|
|
265
|
+
# rubocop:enable Metrics/AbcSize
|
|
267
266
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return unless published.size < total
|
|
267
|
+
def handle_partial_publish_warning(publisher, version)
|
|
268
|
+
return if publisher.successful_repos.empty?
|
|
271
269
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
270
|
+
published = publisher.successful_repos
|
|
271
|
+
total = gem_repositories.size
|
|
272
|
+
return unless published.size < total
|
|
275
273
|
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
puts "[WARN] Version #{version} was only published to #{published.size} of #{total} repositories"
|
|
275
|
+
puts "[WARN] Run 'rake upgrade:prepare_version' manually to check status"
|
|
276
|
+
end
|
|
278
277
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
end
|
|
278
|
+
def repos_available?(publisher)
|
|
279
|
+
return true if publisher.any_available?
|
|
282
280
|
|
|
283
|
-
|
|
284
|
-
|
|
281
|
+
puts "[ERROR] No repositories available. Cannot check version."
|
|
282
|
+
abort
|
|
283
|
+
end
|
|
285
284
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
def print_failed_repository_warnings(publisher)
|
|
286
|
+
return if publisher.failed_repositories.empty?
|
|
287
|
+
|
|
288
|
+
puts "[WARN] The following repositories were unavailable:"
|
|
289
|
+
publisher.failed_repositories.each do |repo_name|
|
|
290
|
+
puts " - #{repo_name}"
|
|
291
|
+
end
|
|
289
292
|
end
|
|
293
|
+
# rubocop:enable Metrics/ClassLength, Metrics/MethodLength
|
|
290
294
|
end
|
|
291
|
-
# rubocop:enable Metrics/ClassLength, Metrics/MethodLength
|
|
292
|
-
end
|
|
293
295
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
296
|
+
# Upgrades gems and publishes to cbp-org only (internal gems).
|
|
297
|
+
# Uses Repos.internal as default repositories.
|
|
298
|
+
class InternalUpgradeTask < UpgradeTask
|
|
299
|
+
def apply_default_configuration(name)
|
|
300
|
+
super
|
|
301
|
+
@gem_repositories = Repos.internal
|
|
302
|
+
end
|
|
300
303
|
end
|
|
301
|
-
end
|
|
302
304
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
305
|
+
# Upgrades gems and publishes to both rubygems.org and cbp-org.
|
|
306
|
+
# Uses Repos.all as default repositories.
|
|
307
|
+
class DualUpgradeTask < UpgradeTask
|
|
308
|
+
def apply_default_configuration(name)
|
|
309
|
+
super
|
|
310
|
+
@gem_repositories = Repos.all
|
|
311
|
+
end
|
|
309
312
|
end
|
|
310
|
-
end
|
|
311
313
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
314
|
+
# Upgrades gems and publishes to a local geminabox instance only.
|
|
315
|
+
# Uses Repos.geminabox as default repositories.
|
|
316
|
+
class GeminaboxUpgradeTask < UpgradeTask
|
|
317
|
+
def apply_default_configuration(name)
|
|
318
|
+
super
|
|
319
|
+
@gem_repositories = Repos.geminabox
|
|
320
|
+
end
|
|
318
321
|
end
|
|
319
322
|
end
|
|
320
323
|
end
|