rake-gem-maintenance 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56b2317bfd8c0682d6ec738cec517684acbc61618a5cdabbae392f6b3af1585a
4
- data.tar.gz: 0bc94e5f08ec7f816a23ad7d01afc947df12b14518f5a59c50984725aa3a4e67
3
+ metadata.gz: 6dc9985cb348ee5f79a3170eb6ef534b5fe8bc1aad6361be15178f883ba22767
4
+ data.tar.gz: e819c2d3f58a936c59fb8caf620f84e92dc68bf59cee8f592bb852c03d02de62
5
5
  SHA512:
6
- metadata.gz: b7a7178dcae40451f051de63bb8cd64ad9559d5fa91a47cb1cf65cbd188db088e7cd1c88b6e06ac6b95947b5460d2d8a3f38ff6f778a9d327a418171cf5782e4
7
- data.tar.gz: f714225b46c59edc8882db10fe26afded7eae11f89c06573b4a0fe5ac8effa9b312f945efe3df706989d3c9a1d3ee6513347b38b177052138b418c9ea355596f
6
+ metadata.gz: 43b4d7dec762bcefd6cd816f4c7a8c05a5e091929937f6e329801a6d4ff211fabdac4631f449d39501807b408f0f1411dc97637519857da49911cafa5bd108f6
7
+ data.tar.gz: 81d529473f1404940f0cc45d1011af5d1930506f972b2485cbe190f184cd53a820cbfaa683201ec0b8ef85e63f2d50714cf337780087a94704071a5ef82d735c
@@ -0,0 +1,52 @@
1
+ # publish.yml — build and publish rake-gem-maintenance to rubygems.org
2
+ #
3
+ # Triggers:
4
+ # - push to main (auto-publish on every merge)
5
+ # - manual (ad-hoc publish on demand)
6
+ #
7
+ # Required Woodpecker secrets (org-level cbp-org):
8
+ # rubygems_api_key — rubygems.org API key (set at https://ci.cbp-org.internal)
9
+ # rubygems_otp_seed — base32 TOTP seed for rubygems.org 2FA (already registered)
10
+ # badge_service_token — write token for badge.cbp-org.internal
11
+
12
+ when:
13
+ event: [push, manual]
14
+ branch: main
15
+
16
+ labels:
17
+ platform: linux
18
+
19
+ clone:
20
+ git:
21
+ image: woodpeckerci/plugin-git:2.8.0
22
+ settings:
23
+ skip_verify: true
24
+
25
+ steps:
26
+ - name: publish-gem
27
+ image: ruby:4.0.2-alpine
28
+ backend_options:
29
+ docker:
30
+ network_mode: host
31
+ environment:
32
+ GEM_HOST_API_KEY:
33
+ from_secret: rubygems_api_key
34
+ RUBYGEMS_OTP_SEED:
35
+ from_secret: rubygems_otp_seed
36
+ commands:
37
+ - apk add --no-cache build-base
38
+ - bundle install --jobs 4 --retry 3
39
+ - ruby scripts/ci_publish_rubygems.rb
40
+
41
+ - name: badge
42
+ image: docker-registry.cbp-org.internal/badge-reporter:latest
43
+ when:
44
+ status: [success, failure]
45
+ backend_options:
46
+ docker:
47
+ network_mode: host
48
+ environment:
49
+ BADGE_SECRET:
50
+ from_secret: badge_service_token
51
+ commands:
52
+ - badge report publish
@@ -0,0 +1,47 @@
1
+ when:
2
+ - event: cron
3
+ cron: monthly-renew-api-key
4
+ - event: manual
5
+
6
+ labels:
7
+ platform: linux
8
+
9
+ clone:
10
+ git:
11
+ image: woodpeckerci/plugin-git:2.8.0
12
+ settings:
13
+ skip_verify: true
14
+
15
+ steps:
16
+ - name: renew-api-key
17
+ image: ruby:4.0.2-alpine
18
+ backend_options:
19
+ docker:
20
+ network_mode: host
21
+ environment:
22
+ RUBYGEMS_USERNAME:
23
+ from_secret: rubygems_username
24
+ RUBYGEMS_PASSWORD:
25
+ from_secret: rubygems_password
26
+ RUBYGEMS_OTP_SEED:
27
+ from_secret: rubygems_otp_seed
28
+ WOODPECKER_TOKEN:
29
+ from_secret: woodpecker_api_token
30
+ WOODPECKER_SERVER: "https://ci.cbp-org.internal"
31
+ commands:
32
+ - apk add --no-cache build-base
33
+ - bundle install --jobs 4 --retry 3
34
+ - bundle exec rake upgrade:renew_api_key
35
+
36
+ - name: badge
37
+ image: docker-registry.cbp-org.internal/badge-reporter:latest
38
+ when:
39
+ status: [success, failure]
40
+ backend_options:
41
+ docker:
42
+ network_mode: host
43
+ environment:
44
+ BADGE_SECRET:
45
+ from_secret: badge_service_token
46
+ commands:
47
+ - badge report renew-api-key
@@ -0,0 +1,38 @@
1
+ when:
2
+ event: [push, pull_request, manual]
3
+
4
+ labels:
5
+ platform: linux
6
+
7
+ clone:
8
+ git:
9
+ image: woodpeckerci/plugin-git:2.8.0
10
+ settings:
11
+ skip_verify: true
12
+
13
+ steps:
14
+ - name: verify
15
+ image: ruby:4.0.2-alpine
16
+ backend_options:
17
+ docker:
18
+ network_mode: host
19
+ environment:
20
+ CUCUMBER_PUBLISH_QUIET: "true"
21
+ commands:
22
+ - apk add --no-cache build-base
23
+ - mkdir -p /root/.local/share/ruby-advisory-db/gems
24
+ - bundle install --jobs 4 --retry 3
25
+ - bundle exec rake verify
26
+
27
+ - name: badge
28
+ image: docker-registry.cbp-org.internal/badge-reporter:latest
29
+ when:
30
+ status: [success, failure]
31
+ backend_options:
32
+ docker:
33
+ network_mode: host
34
+ environment:
35
+ BADGE_SECRET:
36
+ from_secret: badge_service_token
37
+ commands:
38
+ - badge report verify
data/CLAUDE.md CHANGED
@@ -54,6 +54,20 @@ Rake::GemMaintenance::UpgradeTask.new do |t|
54
54
  end
55
55
  ```
56
56
 
57
+ ## Repository & CI Workflow
58
+
59
+ **GitHub** (`github.com/cbroult/rake-gem-maintenance`) is the canonical source.
60
+ **Forgejo** (`git.cbp-org.internal/forgejo-admin/rake-gem-maintenance`) is a read-only pull mirror.
61
+
62
+ - All pull requests go on **GitHub only**. Never open PRs on Forgejo.
63
+ - Forgejo mirrors GitHub automatically every 10 minutes — never push to the `forgejo` remote manually.
64
+ - **Woodpecker CI** (`ci.cbp-org.internal`) watches Forgejo and runs verify/publish/renew pipelines.
65
+ - Local clone only needs the `origin` (GitHub) remote. Remove `forgejo` if present: `git remote remove forgejo`.
66
+
67
+ ## Code Style
68
+
69
+ - Never add `# rubocop:disable` comments without explicit user permission.
70
+
57
71
  ## Commit Conventions
58
72
 
59
73
  - Use conventional commit format: `type(scope): subject`
data/Gemfile CHANGED
@@ -12,6 +12,7 @@ gem "guard-cucumber"
12
12
  gem "guard-rspec"
13
13
  gem "guard-rubocop"
14
14
  gem "ostruct"
15
+ gem "rdoc"
15
16
  gem "rspec"
16
17
  gem "rubocop"
17
18
  gem "rubocop-rake", require: false
data/Gemfile.lock CHANGED
@@ -1,18 +1,20 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rake-gem-maintenance (0.1.4)
4
+ rake-gem-maintenance (0.1.6)
5
5
  bundler-audit
6
- gem-release (~> 2.2)
7
- rake (>= 13.0)
6
+ gem-release
7
+ rake
8
+ rotp
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
- aruba (2.3.3)
13
+ aruba (2.4.0)
13
14
  bundler (>= 1.17)
14
15
  contracts (>= 0.16.0, < 0.18.0)
15
- cucumber (>= 8.0, < 11.0)
16
+ cucumber (>= 8.0, < 12.0)
17
+ irb (~> 1.16)
16
18
  rspec-expectations (>= 3.4, < 5.0)
17
19
  thor (~> 1.0)
18
20
  ast (2.4.3)
@@ -24,18 +26,18 @@ GEM
24
26
  thor (~> 1.0)
25
27
  coderay (1.1.3)
26
28
  contracts (0.17.3)
27
- cucumber (10.2.0)
29
+ cucumber (11.0.0)
28
30
  base64 (~> 0.2)
29
31
  builder (~> 3.2)
30
32
  cucumber-ci-environment (> 9, < 12)
31
- cucumber-core (> 15, < 17)
33
+ cucumber-core (>= 16.2.0, < 17)
32
34
  cucumber-cucumber-expressions (> 17, < 20)
33
- cucumber-html-formatter (> 21, < 23)
35
+ cucumber-html-formatter (> 21, < 24)
34
36
  diff-lcs (~> 1.5)
35
37
  logger (~> 1.6)
36
38
  mini_mime (~> 1.1)
37
39
  multi_test (~> 1.1)
38
- sys-uname (~> 1.3)
40
+ sys-uname (~> 1.5)
39
41
  cucumber-ci-environment (11.0.0)
40
42
  cucumber-core (16.2.0)
41
43
  cucumber-gherkin (> 36, < 40)
@@ -43,14 +45,15 @@ GEM
43
45
  cucumber-tag-expressions (> 6, < 9)
44
46
  cucumber-cucumber-expressions (19.0.0)
45
47
  bigdecimal
46
- cucumber-gherkin (39.0.0)
48
+ cucumber-gherkin (39.1.0)
47
49
  cucumber-messages (>= 31, < 33)
48
- cucumber-html-formatter (22.3.0)
50
+ cucumber-html-formatter (23.1.0)
49
51
  cucumber-messages (> 23, < 33)
50
52
  cucumber-messages (32.3.1)
51
53
  cucumber-tag-expressions (8.1.0)
54
+ date (3.5.1)
52
55
  diff-lcs (1.6.2)
53
- ffi (1.17.4-x64-mingw-ucrt)
56
+ erb (6.0.4)
54
57
  ffi (1.17.4-x86_64-linux-gnu)
55
58
  formatador (1.2.3)
56
59
  reline
@@ -81,7 +84,12 @@ GEM
81
84
  guard (~> 2.0)
82
85
  rubocop (< 2.0)
83
86
  io-console (0.8.2)
84
- json (2.19.4)
87
+ irb (1.18.0)
88
+ pp (>= 0.6.0)
89
+ prism (>= 1.3.0)
90
+ rdoc (>= 4.0.0)
91
+ reline (>= 0.4.2)
92
+ json (2.19.5)
85
93
  language_server-protocol (3.17.0.5)
86
94
  lint_roller (1.1.0)
87
95
  listen (3.10.0)
@@ -103,20 +111,31 @@ GEM
103
111
  parser (3.3.11.1)
104
112
  ast (~> 2.4.1)
105
113
  racc
114
+ pp (0.6.3)
115
+ prettyprint
116
+ prettyprint (0.2.0)
106
117
  prism (1.9.0)
107
118
  pry (0.16.0)
108
119
  coderay (~> 1.1)
109
120
  method_source (~> 1.0)
110
121
  reline (>= 0.6.0)
122
+ psych (5.3.1)
123
+ date
124
+ stringio
111
125
  racc (1.8.1)
112
126
  rainbow (3.1.1)
113
127
  rake (13.4.2)
114
128
  rb-fsevent (0.11.2)
115
129
  rb-inotify (0.11.1)
116
130
  ffi (~> 1.0)
131
+ rdoc (7.2.0)
132
+ erb
133
+ psych (>= 4.0.0)
134
+ tsort
117
135
  regexp_parser (2.12.0)
118
136
  reline (0.6.3)
119
137
  io-console (~> 0.5)
138
+ rotp (6.3.0)
120
139
  rspec (3.13.2)
121
140
  rspec-core (~> 3.13.0)
122
141
  rspec-expectations (~> 3.13.0)
@@ -130,7 +149,7 @@ GEM
130
149
  diff-lcs (>= 1.2.0, < 2.0)
131
150
  rspec-support (~> 3.13.0)
132
151
  rspec-support (3.13.7)
133
- rubocop (1.86.1)
152
+ rubocop (1.86.2)
134
153
  json (~> 2.3)
135
154
  language_server-protocol (~> 3.17.0.2)
136
155
  lint_roller (~> 1.1.0)
@@ -152,21 +171,17 @@ GEM
152
171
  rubocop (~> 1.81)
153
172
  ruby-progressbar (1.13.0)
154
173
  shellany (0.0.1)
174
+ stringio (3.2.0)
155
175
  sys-uname (1.5.1)
156
176
  ffi (~> 1.1)
157
177
  memoist3 (~> 1.0.0)
158
- sys-uname (1.5.1-universal-mingw32)
159
- ffi (~> 1.1)
160
- memoist3 (~> 1.0.0)
161
- win32ole
162
178
  thor (1.5.0)
179
+ tsort (0.2.0)
163
180
  unicode-display_width (3.2.0)
164
181
  unicode-emoji (~> 4.1)
165
182
  unicode-emoji (4.2.0)
166
- win32ole (1.9.3)
167
183
 
168
184
  PLATFORMS
169
- x64-mingw-ucrt
170
185
  x86_64-linux
171
186
 
172
187
  DEPENDENCIES
@@ -179,13 +194,14 @@ DEPENDENCIES
179
194
  guard-rubocop
180
195
  ostruct
181
196
  rake-gem-maintenance!
197
+ rdoc
182
198
  rspec
183
199
  rubocop
184
200
  rubocop-rake
185
201
  rubocop-rspec
186
202
 
187
203
  CHECKSUMS
188
- aruba (2.3.3) sha256=837a2f023368a75a38ad9be227e9738ab9af7df3b3f35afd8fb5fc5f7a93f1d4
204
+ aruba (2.4.0) sha256=92bb696efb06b1aa1dc3ff8b13ca71cd727e59d27572f9264216b9722b558299
189
205
  ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
190
206
  base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
191
207
  bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
@@ -193,16 +209,17 @@ CHECKSUMS
193
209
  bundler-audit (0.9.3) sha256=81c8766c71e47d0d28a0f98c7eed028539f21a6ea3cd8f685eb6f42333c9b4e9
194
210
  coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
195
211
  contracts (0.17.3) sha256=e72e626413ea47099becb7b5683beb1c2ea902c69f5bad55c9258fe2b48314d7
196
- cucumber (10.2.0) sha256=fdedbd31ecf40858b60f04853f2aa15c44f5c30bbac29c6a227fa1e7005a8158
212
+ cucumber (11.0.0) sha256=14bb964cc172999e9010fece2fad9f104044b055b3199230091894637a2a784c
197
213
  cucumber-ci-environment (11.0.0) sha256=0df79a9e1d0b015b3d9def680f989200d96fef206f4d19ccf86a338c4f71d1e2
198
214
  cucumber-core (16.2.0) sha256=592b58a95cf42feef8e5a349f68e363784ba3b6568ffbcf6776e38e136cf970b
199
215
  cucumber-cucumber-expressions (19.0.0) sha256=33208ff204732ac9bed42b46993a0a243054f71ece08579d57e53df6a1c9d93a
200
- cucumber-gherkin (39.0.0) sha256=46f51d87e910f41c3c5cee3b500028ca2b2e7149a413a8280b9a58cee2593e55
201
- cucumber-html-formatter (22.3.0) sha256=f9768ed05588dbd73a5f3824c2cc648bd86b00206e6972d743af8051281d0729
216
+ cucumber-gherkin (39.1.0) sha256=aed12a0c955d8563d80a012633c1a72075525f4d64d4cc983001df2181b379ed
217
+ cucumber-html-formatter (23.1.0) sha256=7789b4a792c876394b9604aeb66aa5cf4c61514473b7e712c76d5eaedcdd8cdf
202
218
  cucumber-messages (32.3.1) sha256=ddc88e4c1cf7afb96c06005b92a4a6f221a2fa435a8b4ca04677d215fd82771c
203
219
  cucumber-tag-expressions (8.1.0) sha256=9bd8c4b6654f8e5bf2a9c99329b6f32136a75e50cd39d4cfb3927d0fa9f52e21
220
+ date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
204
221
  diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
205
- ffi (1.17.4-x64-mingw-ucrt) sha256=f6ff9618cfccc494138bddade27aa06c74c6c7bc367a1ea1103d80c2fcb9ed35
222
+ erb (6.0.4) sha256=38e3803694be357fe2bfe312487c74beaf9fb4e5beb3e22498952fe1645b95d9
206
223
  ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
207
224
  formatador (1.2.3) sha256=19fa898133c2c26cdbb5d09f6998c1e137ad9427a046663e55adfe18b950d894
208
225
  gem-release (2.2.4) sha256=2f11124c1580c811507c3b47e875e420cf3ed792a98105b49df11971e6e94db3
@@ -213,7 +230,8 @@ CHECKSUMS
213
230
  guard-rspec (4.7.3) sha256=a47ba03cbd1e3c71e6ae8645cea97e203098a248aede507461a43e906e2f75ca
214
231
  guard-rubocop (1.5.0) sha256=3041d796dcb5ee31e352de74732250826f5f235b4ff48df9dbf424a6dc736251
215
232
  io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
216
- json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
233
+ irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
234
+ json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
217
235
  language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
218
236
  lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
219
237
  listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2
@@ -228,33 +246,38 @@ CHECKSUMS
228
246
  ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
229
247
  parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
230
248
  parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
249
+ pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
250
+ prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
231
251
  prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
232
252
  pry (0.16.0) sha256=d76c69065698ed1f85e717bd33d7942c38a50868f6b0673c636192b3d1b6054e
253
+ psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
233
254
  racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
234
255
  rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
235
256
  rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
236
- rake-gem-maintenance (0.1.4)
257
+ rake-gem-maintenance (0.1.6)
237
258
  rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe
238
259
  rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e
260
+ rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
239
261
  regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
240
262
  reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
263
+ rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854
241
264
  rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
242
265
  rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
243
266
  rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
244
267
  rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
245
268
  rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
246
- rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
269
+ rubocop (1.86.2) sha256=bb2e97f635eda42c448f2588f4a6ff78f221b8bdfdf65b1e9b07fbd57521b45d
247
270
  rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
248
271
  rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
249
272
  rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
250
273
  ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
251
274
  shellany (0.0.1) sha256=0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7
275
+ stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
252
276
  sys-uname (1.5.1) sha256=784d7e6491b0393c25cbbe5ac38324ac7be9fda083a6094832648af669386d7b
253
- sys-uname (1.5.1-universal-mingw32) sha256=aceb618e3276da5eae0ce368e9f6fae8c1f3e9ef23a0595cb88db7b6ecd45f62
254
277
  thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
278
+ tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
255
279
  unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
256
280
  unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
257
- win32ole (1.9.3) sha256=01f43dc5dc13806e6e58204f538b4a28f3d85968ea89074abc9a3cd118e94d96
258
281
 
259
282
  BUNDLED WITH
260
283
  4.0.4
data/README.md CHANGED
@@ -46,6 +46,81 @@ Rake::GemMaintenance::VersionBumpTask.new do |t|
46
46
  end
47
47
  ```
48
48
 
49
+ ## Automated Publishing to rubygems.org
50
+
51
+ Set two environment variables and `gem push` runs fully unattended — including TOTP 2FA code
52
+ generation if your rubygems.org account has MFA enabled.
53
+
54
+ | Env var | Purpose |
55
+ |---|---|
56
+ | `GEM_HOST_API_KEY` | rubygems.org API key (scoped to push) |
57
+ | `RUBYGEMS_OTP_SEED` | Base32 TOTP seed — auto-generates the 2FA code; omit if MFA is disabled |
58
+
59
+ ### Quick setup
60
+
61
+ `require "rake/gem_maintenance/install_tasks"` pre-configures both env var names automatically —
62
+ no extra Ruby needed. See [features/install_tasks.feature](features/install_tasks.feature) for
63
+ the full workflow.
64
+
65
+ ### Custom env var names
66
+
67
+ ```ruby
68
+ require "rake/gem/maintenance"
69
+
70
+ Rake::GemMaintenance::Repos.rubygems_api_key_env_var = "MY_RUBYGEMS_KEY"
71
+ Rake::GemMaintenance::Repos.rubygems_otp_seed_env_var = "MY_OTP_SEED"
72
+
73
+ Rake::GemMaintenance::UpgradeTask.new
74
+ ```
75
+
76
+ See [features/upgrade_task/repos_configuration.feature](features/upgrade_task/repos_configuration.feature)
77
+ for all configuration options including geminabox and dual publishing.
78
+
79
+ ### Local credential store
80
+
81
+ After the first successful `upgrade:renew_api_key` run, the API key and OTP seed are saved to:
82
+
83
+ ```
84
+ ~/.config/rake-gem-maintenance/credentials.yml # Linux / Mac (respects $XDG_CONFIG_HOME)
85
+ %APPDATA%\rake-gem-maintenance\credentials.yml # Windows
86
+ ```
87
+
88
+ The file is created with `0600` permissions (owner-read-only on Unix). It stores `username`,
89
+ `gem_host_api_key`, and `rubygems_otp_seed` — **never the password**. Any project using
90
+ `require "rake/gem_maintenance/install_tasks"` automatically loads the key and OTP seed from
91
+ this file at startup, so `gem push` works without any manual env-var setup.
92
+
93
+ See [features/upgrade_task/credential_store.feature](features/upgrade_task/credential_store.feature)
94
+ for the full behaviour specification.
95
+
96
+ ### API key renewal
97
+
98
+ API keys can be rotated in two ways:
99
+
100
+ **Automatic** — when `gem push` returns a 401/403, the publisher transparently obtains a new
101
+ key using `RUBYGEMS_USERNAME` + `RUBYGEMS_PASSWORD` (+ TOTP from `RUBYGEMS_OTP_SEED` if MFA is
102
+ enabled), then retries the push once. No intervention needed.
103
+
104
+ **On-demand** — run the task explicitly to rotate ahead of expiry:
105
+
106
+ ```bash
107
+ rake upgrade:renew_api_key
108
+ ```
109
+
110
+ Locally this prompts for credentials interactively. In CI, supply all three env vars for
111
+ unattended operation:
112
+
113
+ | Env var | Purpose |
114
+ |---|---|
115
+ | `RUBYGEMS_USERNAME` | rubygems.org account username or email |
116
+ | `RUBYGEMS_PASSWORD` | rubygems.org account password |
117
+ | `RUBYGEMS_OTP_SEED` | Same TOTP seed as above — reused here to authenticate the key-creation request |
118
+
119
+ The new key is written back to the `GEM_HOST_API_KEY` CI secret automatically (requires
120
+ `WOODPECKER_TOKEN` and `WOODPECKER_SERVER` when running under Woodpecker CI).
121
+
122
+ See [features/upgrade_task/renew_api_key.feature](features/upgrade_task/renew_api_key.feature).
123
+
49
124
  ## License
50
125
 
51
126
  The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rake
4
+ module GemMaintenance
5
+ # Renews a rubygems.org API key using credentials from env vars and
6
+ # persists the new key to Woodpecker CI when server details are available.
7
+ class ApiKeyRenewer
8
+ def initialize(otp_provider:,
9
+ username_env_var: "RUBYGEMS_USERNAME",
10
+ password_env_var: "RUBYGEMS_PASSWORD")
11
+ @otp_provider = otp_provider
12
+ @username_env_var = username_env_var
13
+ @password_env_var = password_env_var
14
+ end
15
+
16
+ def renew(repository)
17
+ username = env_credential(@username_env_var)
18
+ password = env_credential(@password_env_var)
19
+ return nil if username.nil? || password.nil?
20
+
21
+ otp = @otp_provider.otp_for(repository[:name], otp_seed_env_var: repository[:otp_seed_env_var])
22
+ new_key = RubyGemsApiKeyCreator.new(host: repository.fetch(:url, "https://rubygems.org"))
23
+ .create(username, password, otp: otp)
24
+ persist_to_woodpecker(new_key)
25
+ new_key
26
+ rescue StandardError
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ def env_credential(var)
33
+ value = ENV.fetch(var, nil)
34
+ value&.empty? ? nil : value
35
+ end
36
+
37
+ def persist_to_woodpecker(new_key)
38
+ server = ENV.fetch("WOODPECKER_SERVER", nil)
39
+ token = ENV.fetch("WOODPECKER_TOKEN", nil)
40
+ return unless server && token
41
+
42
+ org = ENV.fetch("WOODPECKER_ORG", "cbp-org")
43
+ WoodpeckerSecretStore.new(server: server, org: org, token: token)
44
+ .store("rubygems_api_key", new_key)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rake
4
+ module GemMaintenance
5
+ # Detects whether the current process is running inside a CI environment.
6
+ module CIEnvironment
7
+ def self.ci?
8
+ ENV["CI"].to_s != ""
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+ require "rubygems"
6
+
7
+ module Rake
8
+ module GemMaintenance
9
+ # Persists rubygems.org credentials (username, API key, OTP seed) to a local config file.
10
+ class CredentialStore
11
+ def self.default_path
12
+ base = if Gem.win_platform?
13
+ ENV.fetch("APPDATA", File.expand_path("~"))
14
+ else
15
+ ENV.fetch("XDG_CONFIG_HOME", File.join(Dir.home, ".config"))
16
+ end
17
+ File.join(base, "rake-gem-maintenance", "credentials.yml")
18
+ end
19
+
20
+ def initialize(path: self.class.default_path)
21
+ @path = path
22
+ end
23
+
24
+ attr_reader :path
25
+
26
+ def read
27
+ return {} unless File.exist?(@path)
28
+
29
+ YAML.safe_load_file(@path, symbolize_names: true) || {}
30
+ rescue StandardError
31
+ {}
32
+ end
33
+
34
+ def write(credentials)
35
+ FileUtils.mkdir_p(File.dirname(@path))
36
+ File.write(@path, credentials.transform_keys(&:to_s).to_yaml)
37
+ File.chmod(0o600, @path) unless Gem.win_platform?
38
+ end
39
+
40
+ def apply_to_env(username_env_var:, api_key_env_var:)
41
+ creds = read
42
+ set_env_if_absent(username_env_var, creds[:username])
43
+ set_env_if_absent("RUBYGEMS_OTP_SEED", creds[:rubygems_otp_seed])
44
+ set_env_if_absent(api_key_env_var, creds[:gem_host_api_key])
45
+ end
46
+
47
+ def update(username:, api_key:, api_key_env_var:)
48
+ otp_seed = ENV.fetch("RUBYGEMS_OTP_SEED", nil)
49
+ updated = read.merge(username: username, gem_host_api_key: api_key)
50
+ updated[:rubygems_otp_seed] = otp_seed if otp_seed && !otp_seed.empty?
51
+ write(updated)
52
+ ENV[api_key_env_var] = api_key
53
+ end
54
+
55
+ private
56
+
57
+ def set_env_if_absent(env_var, value)
58
+ return unless value && !value.empty?
59
+ return if (existing = ENV.fetch(env_var, nil)) && !existing.empty?
60
+
61
+ ENV[env_var] = value
62
+ end
63
+ end
64
+ end
65
+ end
@@ -9,8 +9,12 @@ module Rake
9
9
  class GemPublisher
10
10
  attr_reader :repositories, :warnings, :failed_repositories, :successful_repos
11
11
 
12
- def initialize(repositories = default_repositories)
12
+ def initialize(repositories = default_repositories,
13
+ otp_provider: OtpProvider.new,
14
+ ci_environment: CIEnvironment)
13
15
  @repositories = repositories
16
+ @otp_provider = otp_provider
17
+ @ci_environment = ci_environment
14
18
  @warnings = []
15
19
  @failed_pushes = []
16
20
  @failed_repositories = []
@@ -76,16 +80,23 @@ module Rake
76
80
  end
77
81
 
78
82
  def push(gem_file, repository:)
79
- cmd = "gem push #{gem_file} --host #{repository[:url]}"
80
- result = system(cmd)
81
- if result
82
- @published_files << gem_file
83
- @successful_repos << repository[:name]
84
- end
83
+ result = GemPush.new(gem_file, repository, @otp_provider).attempt
84
+ return record_success(gem_file, repository) if result.success
85
+
86
+ record_push_failure(repository, result.error)
85
87
  rescue StandardError => e
86
88
  @failed_pushes << { repository: repository[:name], error: e.message }
87
89
  end
88
90
 
91
+ def record_success(gem_file, repository)
92
+ @published_files << gem_file
93
+ @successful_repos << repository[:name]
94
+ end
95
+
96
+ def record_push_failure(repository, message)
97
+ @failed_pushes << { repository: repository[:name], error: message.strip }
98
+ end
99
+
89
100
  def version_exists_on_all_repos?(gem_name, version)
90
101
  repositories.all? do |repo|
91
102
  versions_on_repository(gem_name, repo).include?(version.to_s)