kettle-dev 3.1.12 → 3.1.13
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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +33 -1
- data/README.md +19 -5
- data/lib/kettle/dev/ci_helpers.rb +84 -12
- data/lib/kettle/dev/lockfile_reset.rb +31 -19
- data/lib/kettle/dev/release_cli.rb +26 -15
- data/lib/kettle/dev/tasks/ci_task.rb +4 -0
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +8 -8
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fee58bbb5088875b1370305934e2816bc7b1f7ad5103776d7877f7bfb8383bd2
|
|
4
|
+
data.tar.gz: 75dd9745eda2bd54c6dcf1a9a5ae9bf8b36fdfcb2edd2e8a3c26ab3bde366aa6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e21b6253a77ecf67f42b54a464405baa078615cc99585f1146f432135f347f5fc0e7c4044137841f3da5f589486fc3971cd8b5dbe6285bf1f194de9b7bbd8359
|
|
7
|
+
data.tar.gz: 0dd4e28b43e6d4ba4ae3481d0464fbeb8b1c7aa39ae1628f0fc9c2ce80fe9d850a738d62414ceda91f6daf43887ee4b400c6621df7b5b1c473bfaa934ade7941
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,36 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [3.1.13] - 2026-10-14
|
|
34
|
+
|
|
35
|
+
- TAG: [v3.1.13][3.1.13t]
|
|
36
|
+
- COVERAGE: 91.53% -- 6427/7022 lines in 49 files
|
|
37
|
+
- BRANCH COVERAGE: 75.69% -- 2456/3245 branches in 49 files
|
|
38
|
+
- 53.23% documented
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- GitHub API request budget is now enforced machine-wide through an flock'd XDG ledger (XDG_STATE_HOME/kettle-dev/ci-rate-budget.json, override with K_RELEASE_CI_RATE_STATE_FILE) instead of per-process memory: parallel family releases, wave jobs, ci:act, and kettle-release GitHub Releases calls all share one budget (default 90 req/min) matching GitHub's per-token quota; unavailable ledger degrades to in-process spacing
|
|
43
|
+
|
|
44
|
+
- [kc] kettle-jem/template: updated 80 project files:
|
|
45
|
+
- code and tests (5)
|
|
46
|
+
- configuration (2)
|
|
47
|
+
- dependencies (15)
|
|
48
|
+
- documentation (1)
|
|
49
|
+
- other (3)
|
|
50
|
+
- workflows (54)
|
|
51
|
+
- [kc] kettle-jem/prepare: updated 1 project file:
|
|
52
|
+
- dependencies (1)
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- release:guard_clean no longer aborts a --start-step 15 resume: the publish-boundary lockfile normalization added in 3.1.13 lived only inside the step 14 block, so resuming at step 15 skipped it and the parent's own boot (family local-path toggle + gemspec self-version bump) re-dirtied the canonical Gemfile.lock with sibling PATH remotes; normalization now runs once per run from BOTH the step 14 and step 15 entry points
|
|
57
|
+
- LockfileReset#rebuild_lockfile no longer crashes with Errno::ENOENT when a generated appraisal lockfile (e.g. Appraisal.root.gemfile.lock) vanishes between the entry guard and the backup copy (TOCTOU race with mid-process CI cleanup); the rebuild now yields without a backup and skips restore when the target was already gone
|
|
58
|
+
|
|
59
|
+
- Test suite no longer mutates the live repo checkout: appraisal rake task specs now run in a sandboxed CWD (the task's File.delete of the relative Appraisal.root.gemfile.lock path was really deleting the repo's lockfile mid-suite, racing parallel workers), and release_cli flow examples stub reset_release_lockfiles! instead of running real bundler resets against the live checkout — root cause of the ENOENT flake seen in ruby-3.4 CI
|
|
60
|
+
|
|
61
|
+
- CI rate limiter no longer sleeps while holding the machine-wide ledger flock: claims now reserve a future slot under the lock and sleep outside it (ticket limiter), so a wedged or slow claimer can no longer deadlock every other process sharing the ledger — the observed CI hang signature (spec-run worker held the flock for minutes while peers starved past the watchdog). The ledger path override is now read at call time, and the test suite isolates itself onto a per-run tmp ledger instead of the production file
|
|
62
|
+
|
|
33
63
|
## [3.1.12] - 2026-10-12
|
|
34
64
|
|
|
35
65
|
- TAG: [v3.1.12][3.1.12t]
|
|
@@ -4064,7 +4094,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
4064
4094
|
- Selecting will run the selected workflow via `act`
|
|
4065
4095
|
- This may move to its own gem in the future.
|
|
4066
4096
|
|
|
4067
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.
|
|
4097
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.13...HEAD
|
|
4098
|
+
[3.1.13]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.12...v3.1.13
|
|
4099
|
+
[3.1.13t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.1.13
|
|
4068
4100
|
[3.1.12]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.11...v3.1.12
|
|
4069
4101
|
[3.1.12t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.1.12
|
|
4070
4102
|
[3.1.11]: https://github.com/kettle-dev/kettle-dev/compare/v3.1.10...v3.1.11
|
data/README.md
CHANGED
|
@@ -93,7 +93,7 @@ out to `kettle-release` for each member that needs publishing.
|
|
|
93
93
|
| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
|
|
94
94
|
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
95
95
|
| Works with JRuby | [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎jruby-9.2-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎jruby-9.3-wf] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎jruby-9.4-wf] [![JRuby 10.0 Compat][💎jruby-10.0i]][🚎jruby-10.0-wf] [![JRuby current Compat][💎jruby-c-i]][🚎10-j-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf]|
|
|
96
|
-
| Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎truby-22.3-wf] [![Truffle Ruby 23.
|
|
96
|
+
| Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎truby-22.3-wf] [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎truby-23.1-wf] <br/> [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎truby-24.2-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎truby-25.0-wf] [![Truffle Ruby 33.0 Compat][💎truby-33.0i]][🚎truby-33.0-wf] [![Truffle Ruby current Compat][💎truby-c-i]][🚎9-t-wf]|
|
|
97
97
|
| Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf]|
|
|
98
98
|
| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎ruby-3.0-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎ruby-3.1-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎ruby-3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎ruby-3.3-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎ruby-3.4-wf]|
|
|
99
99
|
| Works with MRI Ruby 2 | [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎ruby-2.4-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎ruby-2.5-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎ruby-2.6-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎ruby-2.7-wf]|
|
|
@@ -132,6 +132,23 @@ The _amazing_ test matrix is powered by the kettle-dev stack.
|
|
|
132
132
|
|
|
133
133
|
</details>
|
|
134
134
|
|
|
135
|
+
### Runtime and Platform Support
|
|
136
|
+
|
|
137
|
+
Support tiers describe what this project's CI actually exercises, not what may
|
|
138
|
+
work in an untested environment:
|
|
139
|
+
|
|
140
|
+
- **Parallel support**: CI runs the test suite with multiple workers available.
|
|
141
|
+
- **Unsupported**: the engine/platform combination is not covered by CI and is
|
|
142
|
+
not a project compatibility claim.
|
|
143
|
+
|
|
144
|
+
| Ruby engine/version | Linux | macOS | Windows | Notes |
|
|
145
|
+
|---------------------|-------|-------|---------|-------|
|
|
146
|
+
| MRI 3.1-3.4 | Parallel support | Unsupported | Unsupported | Versioned Ruby workflows run on Linux. |
|
|
147
|
+
| MRI current | Parallel support | Parallel support | Unsupported | Current Ruby workflow covers the declared OS platforms. |
|
|
148
|
+
| MRI head | Parallel support | Unsupported | Unsupported | CI is allowed to fail while Ruby is in development. |
|
|
149
|
+
| JRuby (all CI lanes) | Parallel support | Unsupported | Unsupported | Engine workflows run on Linux; macOS and Windows are not exercised by CI. |
|
|
150
|
+
| TruffleRuby (all CI lanes) | Parallel support | Unsupported | Unsupported | Never shipped a Windows build (zero Windows assets across 66 releases scanned 2026-10-10); setup-ruby hard-fails ([source](https://github.com/ruby/setup-ruby/blob/master/README.md)) |
|
|
151
|
+
|
|
135
152
|
### Federated DVCS
|
|
136
153
|
|
|
137
154
|
<details markdown="1">
|
|
@@ -996,7 +1013,6 @@ Thanks for RTFM. ☺️
|
|
|
996
1013
|
[🚎jruby-9.4-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-9.4.yml
|
|
997
1014
|
[🚎jruby-10.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/jruby-10.0.yml
|
|
998
1015
|
[🚎truby-22.3-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-22.3.yml
|
|
999
|
-
[🚎truby-23.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.0.yml
|
|
1000
1016
|
[🚎truby-23.1-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-23.1.yml
|
|
1001
1017
|
[🚎truby-24.2-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-24.2.yml
|
|
1002
1018
|
[🚎truby-25.0-wf]: https://github.com/kettle-dev/kettle-dev/actions/workflows/truffleruby-25.0.yml
|
|
@@ -1031,13 +1047,11 @@ Thanks for RTFM. ☺️
|
|
|
1031
1047
|
[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
|
|
1032
1048
|
[💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
|
|
1033
1049
|
[💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
1034
|
-
[💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
1035
1050
|
[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
1036
1051
|
[💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
1037
1052
|
[💎truby-25.0i]: https://img.shields.io/badge/Truffle_Ruby-25.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
1038
1053
|
[💎truby-33.0i]: https://img.shields.io/badge/Truffle_Ruby-33.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
|
|
1039
1054
|
[💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
|
|
1040
|
-
[💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
|
|
1041
1055
|
[💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
|
|
1042
1056
|
[💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
|
|
1043
1057
|
[💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
|
|
@@ -1070,7 +1084,7 @@ Thanks for RTFM. ☺️
|
|
|
1070
1084
|
[📌gitmoji]: https://gitmoji.dev
|
|
1071
1085
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1072
1086
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1073
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
|
1087
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.022-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1074
1088
|
[🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
|
|
1075
1089
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1076
1090
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -5,6 +5,7 @@ require "open3"
|
|
|
5
5
|
require "net/http"
|
|
6
6
|
require "json"
|
|
7
7
|
require "uri"
|
|
8
|
+
require "fileutils"
|
|
8
9
|
|
|
9
10
|
module Kettle
|
|
10
11
|
module Dev
|
|
@@ -183,30 +184,101 @@ module Kettle
|
|
|
183
184
|
end
|
|
184
185
|
end
|
|
185
186
|
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
187
|
+
# Rate-limit budget shared by every CIHelpers caller. The budget is
|
|
188
|
+
# enforced MACHINE-WIDE: parallel family releases (kettle-family spawns
|
|
189
|
+
# one kettle-release process per member job, and every family can run
|
|
190
|
+
# concurrently) all share one GitHub per-token quota, so the limiter
|
|
191
|
+
# coordinates through an flock'd ledger file under XDG state rather than
|
|
192
|
+
# per-process memory. kettle-dev owns the file because it makes the
|
|
193
|
+
# requests; kettle-family never calls the API itself.
|
|
191
194
|
CI_RATE_BUDGET_PER_MINUTE = (ENV["K_RELEASE_CI_REQUESTS_PER_MINUTE"] || 90).to_f
|
|
192
195
|
CI_RATE_MIN_INTERVAL = CI_RATE_BUDGET_PER_MINUTE.positive? ? 60.0 / CI_RATE_BUDGET_PER_MINUTE : 0.0
|
|
193
|
-
@ci_rate_last_request = 0.0
|
|
194
196
|
@ci_rate_lock = Mutex.new
|
|
195
197
|
|
|
198
|
+
# Ledger location: XDG_STATE_HOME/kettle-dev/ci-rate-budget.json
|
|
199
|
+
# (XDG default is ~/.local/state). Overridable wholesale with
|
|
200
|
+
# K_RELEASE_CI_RATE_STATE_FILE. Read at CALL time (not frozen at load)
|
|
201
|
+
# so test suites can point the limiter at a per-run tmp ledger via
|
|
202
|
+
# stubbed_env: the shared ledger is effectively a local production
|
|
203
|
+
# system and specs must never consume or corrupt its state.
|
|
204
|
+
def ci_rate_state_path
|
|
205
|
+
override = ENV["K_RELEASE_CI_RATE_STATE_FILE"]
|
|
206
|
+
return override if override && !override.empty?
|
|
207
|
+
|
|
208
|
+
base = ENV["XDG_STATE_HOME"]
|
|
209
|
+
base = (base && !base.empty?) ? base : File.join(Dir.home, ".local", "state")
|
|
210
|
+
File.join(base, "kettle-dev", "ci-rate-budget.json")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Cross-process spacing, ticket-style: each caller claims the next
|
|
214
|
+
# request slot under an exclusive flock on the ledger file, RELEASES the
|
|
215
|
+
# lock, and only then sleeps out its wait. The critical section is a few
|
|
216
|
+
# microseconds regardless of budget, so hammering claimers queue briefly
|
|
217
|
+
# on the flock instead of each holding it for a whole interval — the old
|
|
218
|
+
# in-lock sleep serialized the whole machine behind one interval per
|
|
219
|
+
# claim, and a claimer wedged mid-sleep deadlocked every other process
|
|
220
|
+
# (observed in CI: a spec-run worker held the flock for minutes while
|
|
221
|
+
# its peers silently starved past the watchdog threshold).
|
|
222
|
+
# The flock is released automatically if a process dies. A missing or
|
|
223
|
+
# corrupt ledger starts fresh.
|
|
196
224
|
def github_rate_limit_wait!
|
|
197
225
|
return if CI_RATE_MIN_INTERVAL.zero?
|
|
198
226
|
|
|
227
|
+
wait = claim_ci_rate_slot
|
|
228
|
+
sleep(wait) if wait.positive?
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Returns how long the caller must wait before its request (0.0 when the
|
|
232
|
+
# budget is unspent). Never sleeps itself. Lock hold time is bounded by
|
|
233
|
+
# the file IO only.
|
|
234
|
+
def claim_ci_rate_slot
|
|
199
235
|
@ci_rate_lock.synchronize do
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
236
|
+
path = ci_rate_state_path
|
|
237
|
+
dir = File.dirname(path)
|
|
238
|
+
begin
|
|
239
|
+
FileUtils.mkdir_p(dir)
|
|
240
|
+
rescue SystemCallError
|
|
241
|
+
# No writable state dir: degrade to in-process throttling only.
|
|
242
|
+
return ci_rate_claim_in_process
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
begin
|
|
246
|
+
File.open(path, File::RDWR | File::CREAT, 0o644) do |f|
|
|
247
|
+
f.flock(File::LOCK_EX)
|
|
248
|
+
payload = f.size.zero? ? {} : JSON.parse(f.read)
|
|
249
|
+
last = payload["next_slot"] || payload["last_request"] || 0.0
|
|
250
|
+
now = Time.now.to_f
|
|
251
|
+
# Absurdly future (clock skew) or ancient entries start fresh.
|
|
252
|
+
last = 0.0 if !last.is_a?(Numeric) || (last - now).abs > 3600
|
|
253
|
+
slot = [last + CI_RATE_MIN_INTERVAL, now].max
|
|
254
|
+
f.rewind
|
|
255
|
+
f.truncate(0)
|
|
256
|
+
f.write(JSON.generate({"next_slot" => slot}))
|
|
257
|
+
f.flush
|
|
258
|
+
wait = slot - now
|
|
259
|
+
return wait.positive? ? wait : 0.0
|
|
260
|
+
end
|
|
261
|
+
rescue SystemCallError, JSON::ParserError
|
|
262
|
+
# Ledger unusable (read-only FS, broken JSON, no flock support):
|
|
263
|
+
# degrade to in-process throttling rather than failing releases.
|
|
264
|
+
return ci_rate_claim_in_process
|
|
205
265
|
end
|
|
206
|
-
@ci_rate_last_request = now
|
|
207
266
|
end
|
|
208
267
|
end
|
|
209
268
|
|
|
269
|
+
# Fallback when no shared ledger is available. In-process ticket claims,
|
|
270
|
+
# sized to the same budget; the caller sleeps outside this method.
|
|
271
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable -- callers hold @ci_rate_lock, so this state has no concurrent writers; the degradation path is intentionally module-scoped.
|
|
272
|
+
def ci_rate_claim_in_process
|
|
273
|
+
@ci_rate_inproc_last = 0.0 unless defined?(@ci_rate_inproc_last)
|
|
274
|
+
now = Time.now.to_f
|
|
275
|
+
slot = [@ci_rate_inproc_last + CI_RATE_MIN_INTERVAL, now].max
|
|
276
|
+
@ci_rate_inproc_last = slot
|
|
277
|
+
wait = slot - now
|
|
278
|
+
wait.positive? ? wait : 0.0
|
|
279
|
+
end
|
|
280
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
281
|
+
|
|
210
282
|
def github_get_json(url, token:)
|
|
211
283
|
github_rate_limit_wait!
|
|
212
284
|
uri = URI(url)
|
|
@@ -295,7 +295,7 @@ module Kettle
|
|
|
295
295
|
|
|
296
296
|
def lockfile_platforms(path)
|
|
297
297
|
in_platforms = false
|
|
298
|
-
|
|
298
|
+
safe_lockfile_lines(path).filter_map do |line|
|
|
299
299
|
stripped = line.strip
|
|
300
300
|
if stripped.match?(/\A[A-Z][A-Z ]*\z/)
|
|
301
301
|
in_platforms = stripped == "PLATFORMS"
|
|
@@ -448,7 +448,7 @@ module Kettle
|
|
|
448
448
|
end
|
|
449
449
|
|
|
450
450
|
def local_path_remote_lines(path)
|
|
451
|
-
self.class.local_path_remote_lines_from_source(
|
|
451
|
+
self.class.local_path_remote_lines_from_source(safe_lockfile_lines(path).join)
|
|
452
452
|
end
|
|
453
453
|
|
|
454
454
|
attr_reader :release_graph_contract
|
|
@@ -460,7 +460,7 @@ module Kettle
|
|
|
460
460
|
end
|
|
461
461
|
|
|
462
462
|
def local_path_remote_at(path, line_number)
|
|
463
|
-
|
|
463
|
+
safe_lockfile_lines(path)[line_number - 1].to_s.split("remote:", 2).last.to_s.strip
|
|
464
464
|
end
|
|
465
465
|
|
|
466
466
|
def allowed_local_path?(path)
|
|
@@ -477,7 +477,7 @@ module Kettle
|
|
|
477
477
|
def empty_registry_checksums(path)
|
|
478
478
|
non_registry_gems = non_registry_source_gems(path) | path_dependency_gems(path)
|
|
479
479
|
in_checksums = false
|
|
480
|
-
entries =
|
|
480
|
+
entries = safe_lockfile_lines(path).filter_map.with_index(1) do |line, index|
|
|
481
481
|
stripped = line.strip
|
|
482
482
|
if stripped == "CHECKSUMS"
|
|
483
483
|
in_checksums = true
|
|
@@ -517,7 +517,7 @@ module Kettle
|
|
|
517
517
|
|
|
518
518
|
def has_any_sha_checksum?(path)
|
|
519
519
|
in_checksums = false
|
|
520
|
-
|
|
520
|
+
safe_lockfile_lines(path).any? do |line|
|
|
521
521
|
stripped = line.strip
|
|
522
522
|
if stripped == "CHECKSUMS"
|
|
523
523
|
in_checksums = true
|
|
@@ -538,14 +538,14 @@ module Kettle
|
|
|
538
538
|
version = bundled_with_version(path)
|
|
539
539
|
return false if version.empty?
|
|
540
540
|
|
|
541
|
-
checksums = self.class.checksum_entries_from_source(
|
|
541
|
+
checksums = self.class.checksum_entries_from_source(safe_lockfile_lines(path).join)
|
|
542
542
|
return false unless checksums&.keys&.any? { |name, _checksum_version| name == "bundler" }
|
|
543
543
|
|
|
544
544
|
!checksums.key?(["bundler", version])
|
|
545
545
|
end
|
|
546
546
|
|
|
547
547
|
def bundled_with_version(path)
|
|
548
|
-
lines =
|
|
548
|
+
lines = safe_lockfile_lines(path)
|
|
549
549
|
marker = lines.index { |line| line.strip == "BUNDLED WITH" }
|
|
550
550
|
return "" unless marker
|
|
551
551
|
|
|
@@ -626,7 +626,8 @@ module Kettle
|
|
|
626
626
|
|
|
627
627
|
def gemfile_bootstrap_gems(gemfile)
|
|
628
628
|
gemfile_source_paths(gemfile).each_with_index.each_with_object({}) do |(path, index), gems|
|
|
629
|
-
|
|
629
|
+
source = File.file?(path) ? File.read(path) : ""
|
|
630
|
+
calls = require_calls_from_ripper(Ripper.sexp(source))
|
|
630
631
|
calls.filter_map { |method_name, argument| bootstrap_gem_name(method_name, argument) }.each do |name|
|
|
631
632
|
gems[name] = gems.fetch(name, false) || index.zero?
|
|
632
633
|
end
|
|
@@ -711,12 +712,7 @@ module Kettle
|
|
|
711
712
|
|
|
712
713
|
def lockfile_source_content(path)
|
|
713
714
|
in_checksums = false
|
|
714
|
-
|
|
715
|
-
# (CI workers clean up generated appraisal locks mid-process). Treat a
|
|
716
|
-
# missing lockfile as an empty graph: bootstrap version lookups return
|
|
717
|
-
# nil and the reset skips what no longer exists.
|
|
718
|
-
return [].freeze unless File.file?(path)
|
|
719
|
-
File.readlines(path).filter_map do |line|
|
|
715
|
+
safe_lockfile_lines(path).filter_map do |line|
|
|
720
716
|
stripped = line.strip
|
|
721
717
|
if stripped == "CHECKSUMS"
|
|
722
718
|
in_checksums = true
|
|
@@ -746,13 +742,18 @@ module Kettle
|
|
|
746
742
|
def rebuild_lockfile(path)
|
|
747
743
|
backup = backup_path_for(path)
|
|
748
744
|
FileUtils.mkdir_p(File.dirname(backup))
|
|
749
|
-
|
|
745
|
+
# TOCTOU: the target can vanish between the scan/entry guard and this
|
|
746
|
+
# copy (CI cleanup of generated appraisal lockfiles mid-process).
|
|
747
|
+
# Nothing to preserve in that case — yield so the rebuild writes a
|
|
748
|
+
# fresh lockfile, and skip the backup restore entirely.
|
|
749
|
+
existed = File.file?(path)
|
|
750
|
+
FileUtils.cp(path, backup) if existed
|
|
750
751
|
FileUtils.rm_f(path)
|
|
751
752
|
yield
|
|
752
|
-
FileUtils.mv(backup, path, force: true)
|
|
753
|
+
FileUtils.mv(backup, path, force: true) if existed && !File.file?(path)
|
|
753
754
|
FileUtils.rm_f(backup)
|
|
754
755
|
rescue
|
|
755
|
-
FileUtils.mv(backup, path, force: true) if File.file?(backup)
|
|
756
|
+
FileUtils.mv(backup, path, force: true) if existed && File.file?(backup)
|
|
756
757
|
raise
|
|
757
758
|
end
|
|
758
759
|
|
|
@@ -794,11 +795,22 @@ module Kettle
|
|
|
794
795
|
registry_specs_with_lines(path).map { |name, version, _line_number| [name, version] }
|
|
795
796
|
end
|
|
796
797
|
|
|
798
|
+
# Single read path for lockfile contents: a generated appraisal lock
|
|
799
|
+
# can vanish between the lockfile_paths scan and any read (CI workers
|
|
800
|
+
# create and clean up locks in the repo root mid-process). Every reader
|
|
801
|
+
# in this class goes through here and treats absence as an empty file
|
|
802
|
+
# so a deleted ghost lock degrades to "nothing to do" instead of
|
|
803
|
+
# crashing the release pipeline with Errno::ENOENT.
|
|
804
|
+
def safe_lockfile_lines(path)
|
|
805
|
+
return [].freeze unless File.file?(path)
|
|
806
|
+
File.readlines(path)
|
|
807
|
+
end
|
|
808
|
+
|
|
797
809
|
def registry_specs_with_lines(path)
|
|
798
810
|
specs = []
|
|
799
811
|
in_gem = false
|
|
800
812
|
in_specs = false
|
|
801
|
-
|
|
813
|
+
safe_lockfile_lines(path).each.with_index(1) do |line, line_number|
|
|
802
814
|
stripped = line.chomp
|
|
803
815
|
if stripped == "GEM"
|
|
804
816
|
in_gem = true
|
|
@@ -867,7 +879,7 @@ module Kettle
|
|
|
867
879
|
def registry_source_urls(path)
|
|
868
880
|
urls = []
|
|
869
881
|
in_gem = false
|
|
870
|
-
|
|
882
|
+
safe_lockfile_lines(path).each do |line|
|
|
871
883
|
stripped = line.strip
|
|
872
884
|
if stripped == "GEM"
|
|
873
885
|
in_gem = true
|
|
@@ -492,21 +492,7 @@ module Kettle
|
|
|
492
492
|
# 14. build
|
|
493
493
|
with_release_resume_step(14) do
|
|
494
494
|
if run_step?(14)
|
|
495
|
-
|
|
496
|
-
# skipped, but this process's own boot (`bundle exec
|
|
497
|
-
# kettle-release` with the family launcher's local-path toggle
|
|
498
|
-
# active) can silently rewrite the canonical Gemfile.lock with
|
|
499
|
-
# sibling PATH remotes — the gemspec self-version bump makes every
|
|
500
|
-
# boot re-resolve. `rake release` aborts on the dirty worktree at
|
|
501
|
-
# release:guard_clean. Normalize at the publish boundary too.
|
|
502
|
-
# Projects without any tracked release lockfile (bare scratch
|
|
503
|
-
# specs) have nothing to normalize; LockfileReset would abort.
|
|
504
|
-
unless lockfile_reset.lockfile_paths.empty?
|
|
505
|
-
reset_release_lockfiles!(stage: "before publish tasks")
|
|
506
|
-
validate_release_lockfiles!(stage: "before publish tasks")
|
|
507
|
-
prepare_release_lockfiles_for_release_tasks!
|
|
508
|
-
materialize_release_lockfiles_for_release_tasks!
|
|
509
|
-
end
|
|
495
|
+
normalize_release_lockfiles_before_publish_tasks!
|
|
510
496
|
ensure_release_secrets_ready_for_signing! if signing_enabled? && release_secrets_configured?
|
|
511
497
|
if signing_enabled? && release_secrets_configured?
|
|
512
498
|
puts "Running build with gem signing passphrase from configured secrets provider (#{release_secrets_provider_label})..."
|
|
@@ -520,6 +506,7 @@ module Kettle
|
|
|
520
506
|
# 15. release and tag
|
|
521
507
|
with_release_resume_step(15) do
|
|
522
508
|
if run_step?(15)
|
|
509
|
+
normalize_release_lockfiles_before_publish_tasks!
|
|
523
510
|
version ||= detect_version
|
|
524
511
|
gem_name = detect_gem_name
|
|
525
512
|
@release_candidate = build_release_candidate(gem_name, version)
|
|
@@ -778,6 +765,27 @@ module Kettle
|
|
|
778
765
|
reset_release_lockfiles!(stage: "before release task bundle installs")
|
|
779
766
|
end
|
|
780
767
|
|
|
768
|
+
# When resuming at step 14/15 the step 0-5 lockfile preflight is skipped,
|
|
769
|
+
# but this process's own boot (`bundle exec kettle-release` with the
|
|
770
|
+
# family launcher's local-path toggle active) can silently rewrite the
|
|
771
|
+
# canonical Gemfile.lock with sibling PATH remotes — the gemspec
|
|
772
|
+
# self-version bump makes every boot re-resolve. `rake release` aborts on
|
|
773
|
+
# the dirty worktree at release:guard_clean. Normalize at the publish
|
|
774
|
+
# boundary too: once per run, invoked from BOTH the step 14 and step 15
|
|
775
|
+
# entry points so a `--start-step 15` resume (which skips step 14) is
|
|
776
|
+
# covered as well. Projects without any tracked release lockfile (bare
|
|
777
|
+
# scratch specs) have nothing to normalize; LockfileReset would abort.
|
|
778
|
+
def normalize_release_lockfiles_before_publish_tasks!
|
|
779
|
+
return if @release_lockfiles_normalized_before_publish
|
|
780
|
+
return if lockfile_reset.lockfile_paths.empty?
|
|
781
|
+
|
|
782
|
+
reset_release_lockfiles!(stage: "before publish tasks")
|
|
783
|
+
validate_release_lockfiles!(stage: "before publish tasks")
|
|
784
|
+
prepare_release_lockfiles_for_release_tasks!
|
|
785
|
+
materialize_release_lockfiles_for_release_tasks!
|
|
786
|
+
@release_lockfiles_normalized_before_publish = true
|
|
787
|
+
end
|
|
788
|
+
|
|
781
789
|
# Resetting can select a platform-specific artifact that is not installed
|
|
782
790
|
# in the invoking development bundle. Materialize every canonical release
|
|
783
791
|
# bundle before a release gate runs `bundle exec`. Use a disposable copy:
|
|
@@ -3386,6 +3394,7 @@ module Kettle
|
|
|
3386
3394
|
prerelease: false
|
|
3387
3395
|
})
|
|
3388
3396
|
|
|
3397
|
+
Kettle::Dev::CIHelpers.github_rate_limit_wait!
|
|
3389
3398
|
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
3390
3399
|
http.request(req)
|
|
3391
3400
|
end
|
|
@@ -3456,6 +3465,7 @@ module Kettle
|
|
|
3456
3465
|
request["Accept"] = "application/vnd.github+json"
|
|
3457
3466
|
request["Authorization"] = "token #{token}"
|
|
3458
3467
|
request["User-Agent"] = "kettle-dev-release-cli"
|
|
3468
|
+
Kettle::Dev::CIHelpers.github_rate_limit_wait!
|
|
3459
3469
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
3460
3470
|
return [JSON.parse(response.body), nil] if response.is_a?(Net::HTTPSuccess)
|
|
3461
3471
|
|
|
@@ -3555,6 +3565,7 @@ module Kettle
|
|
|
3555
3565
|
request["Authorization"] = "token #{token}"
|
|
3556
3566
|
request["User-Agent"] = "kettle-dev-release-cli"
|
|
3557
3567
|
request.body = JSON.dump(name: title, body: body)
|
|
3568
|
+
Kettle::Dev::CIHelpers.github_rate_limit_wait!
|
|
3558
3569
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
|
|
3559
3570
|
response.is_a?(Net::HTTPSuccess) ? [true, "updated"] : [false, "HTTP #{response.code}: #{response.body}"]
|
|
3560
3571
|
rescue => e
|
|
@@ -68,6 +68,8 @@ module Kettle
|
|
|
68
68
|
token = Kettle::Dev::CIHelpers.default_token
|
|
69
69
|
req["Authorization"] = "token #{token}" if token && !token.empty?
|
|
70
70
|
begin
|
|
71
|
+
# Share the machine-wide GitHub API budget with release monitors.
|
|
72
|
+
Kettle::Dev::CIHelpers.github_rate_limit_wait!
|
|
71
73
|
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
|
|
72
74
|
if res.is_a?(Net::HTTPSuccess)
|
|
73
75
|
data = JSON.parse(res.body)
|
|
@@ -299,6 +301,8 @@ module Kettle
|
|
|
299
301
|
req = Net::HTTP::Get.new(uri)
|
|
300
302
|
req["User-Agent"] = "ci:act rake task"
|
|
301
303
|
req["Authorization"] = "token #{tk}" if tk && !tk.empty?
|
|
304
|
+
# Share the machine-wide GitHub API budget with release monitors.
|
|
305
|
+
Kettle::Dev::CIHelpers.github_rate_limit_wait!
|
|
302
306
|
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
|
|
303
307
|
if res.is_a?(Net::HTTPSuccess)
|
|
304
308
|
data = JSON.parse(res.body)
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "3.1.
|
|
8
|
+
VERSION = "3.1.13"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-dev
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.1.
|
|
4
|
+
version: 3.1.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -120,7 +120,7 @@ dependencies:
|
|
|
120
120
|
version: '2.0'
|
|
121
121
|
- - ">="
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: 2.0.
|
|
123
|
+
version: 2.0.25
|
|
124
124
|
type: :runtime
|
|
125
125
|
prerelease: false
|
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -130,7 +130,7 @@ dependencies:
|
|
|
130
130
|
version: '2.0'
|
|
131
131
|
- - ">="
|
|
132
132
|
- !ruby/object:Gem::Version
|
|
133
|
-
version: 2.0.
|
|
133
|
+
version: 2.0.25
|
|
134
134
|
- !ruby/object:Gem::Dependency
|
|
135
135
|
name: ruby-progressbar
|
|
136
136
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -276,7 +276,7 @@ dependencies:
|
|
|
276
276
|
version: '3.2'
|
|
277
277
|
- - ">="
|
|
278
278
|
- !ruby/object:Gem::Version
|
|
279
|
-
version: 3.2.
|
|
279
|
+
version: 3.2.17
|
|
280
280
|
type: :development
|
|
281
281
|
prerelease: false
|
|
282
282
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -286,7 +286,7 @@ dependencies:
|
|
|
286
286
|
version: '3.2'
|
|
287
287
|
- - ">="
|
|
288
288
|
- !ruby/object:Gem::Version
|
|
289
|
-
version: 3.2.
|
|
289
|
+
version: 3.2.17
|
|
290
290
|
- !ruby/object:Gem::Dependency
|
|
291
291
|
name: stone_checksums
|
|
292
292
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -422,10 +422,10 @@ licenses:
|
|
|
422
422
|
- AGPL-3.0-only
|
|
423
423
|
metadata:
|
|
424
424
|
homepage_uri: https://kettle-dev.galtzo.com
|
|
425
|
-
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.1.
|
|
426
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.1.
|
|
425
|
+
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.1.13
|
|
426
|
+
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.1.13/CHANGELOG.md
|
|
427
427
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
|
|
428
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.1.
|
|
428
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.1.13
|
|
429
429
|
funding_uri: https://github.com/sponsors/pboling
|
|
430
430
|
wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
|
|
431
431
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
|
Binary file
|