kettle-family 1.3.3 → 1.3.5
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 +59 -1
- data/README.md +7 -1
- data/lib/kettle/family/cli.rb +92 -8
- data/lib/kettle/family/release_state_check.rb +123 -25
- data/lib/kettle/family/report.rb +2 -0
- data/lib/kettle/family/selection.rb +27 -5
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +88 -24
- data.tar.gz.sig +0 -0
- metadata +26 -26
- 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: 3e18a444e22f9d52b19ef8396ce75bc80b85bb579fb353889fecb8547ddc8bec
|
|
4
|
+
data.tar.gz: b91bb8aa9018297be574e1eda7ddc26c6b8f314692cd4c4881c95fa4fe52062a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50d83077bd30c1c673772be79765f02d263cf499b382355f8b647889255abb9608155e8a0434d6c98d549ddad5188186f50e56b9608537e47b1cb5f2c1cfec5a
|
|
7
|
+
data.tar.gz: e81978953b9a9d94d2c518d770cd72e6b6efbcd5a3fd2c010da97f72cb9107d71624bd126cdf1799c1e17276f3546adaca3246089e151b6bccc7edc04db4cef2
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,60 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.3.5] - 2026-10-05
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.3.5][1.3.5t]
|
|
36
|
+
- COVERAGE: 93.16% -- 6636/7123 lines in 37 files
|
|
37
|
+
- BRANCH COVERAGE: 77.64% -- 2701/3479 branches in 37 files
|
|
38
|
+
- 33.64% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `Kettle::Family::ReleaseStateCheck` accepts a `branches:` scope, and `Kettle::Family::Selection.branches_matching` reports which of a member branch stack currently satisfies a release-state token. Both are the seams for per-branch release and bump decisions.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- A blocked release now reports phase `release_blocked` instead of `release_skip`, so a hard failure is never reported as a skipped member in the progress summary or report.
|
|
47
|
+
|
|
48
|
+
- [kc] kettle-jem/prepare: updated 12 project files:
|
|
49
|
+
- dependencies (12)
|
|
50
|
+
|
|
51
|
+
- [kc] kettle-jem/template: updated 6 project files:
|
|
52
|
+
- code and tests (1)
|
|
53
|
+
- dependencies (1)
|
|
54
|
+
- documentation (1)
|
|
55
|
+
- other (2)
|
|
56
|
+
- workflows (1)
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- Already-released detection for branch-stack members is now scoped to the branch being released instead of the whole stack, so a clean published branch is no longer reported as having unreleased changes and blocked with a release_skip failure.
|
|
61
|
+
|
|
62
|
+
- A published version whose release tag is followed only by post-release maintenance commits (checksums, dependency-floor lockfile reconciliation) is now skipped as already released; only changes to the gemspec, version file, or changelog after the tag make the published version stale.
|
|
63
|
+
|
|
64
|
+
- Release-state status-token selection (`--only unreleased|prepared|pending|bump`) no longer collapses a branch-stack member to whichever branch happened to be evaluated last; a member is selected when any of its branches matches, and multiple tokens are still ANDed within a single branch.
|
|
65
|
+
|
|
66
|
+
- `kettle-family bump` now bumps each branch of a member branch stack independently: branches with no pending version bump are reported as skipped instead of being bumped, so an already-released branch is no longer given version bumps it never needed. An explicit `--only MEMBER` remains an unconditional request and is not filtered.
|
|
67
|
+
|
|
68
|
+
- Release-state worktrees orphaned by a killed run are reaped on the next run. Directories are named `worktree-PID-RANDOM`, so only those whose owning PID is no longer running are removed; live worktrees from concurrent runs are left alone.
|
|
69
|
+
|
|
70
|
+
## [1.3.4] - 2026-10-02
|
|
71
|
+
|
|
72
|
+
- TAG: [v1.3.4][1.3.4t]
|
|
73
|
+
- COVERAGE: 93.26% -- 6571/7046 lines in 37 files
|
|
74
|
+
- BRANCH COVERAGE: 77.70% -- 2666/3431 branches in 37 files
|
|
75
|
+
- 33.33% documented
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
|
|
79
|
+
- kettle-family installs the latest stable Bundler and updates family lockfiles with `update-bundler`.
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
83
|
+
- Lock each repository's worktree registry across process setup, parallel branch probes, and cleanup to prevent intermittent Git worktree failures.
|
|
84
|
+
|
|
85
|
+
- Family reports now include results from the update-bundler command.
|
|
86
|
+
|
|
33
87
|
## [1.3.3] - 2026-09-28
|
|
34
88
|
|
|
35
89
|
- TAG: [v1.3.3][1.3.3t]
|
|
@@ -2830,7 +2884,11 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
2830
2884
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
2831
2885
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
2832
2886
|
|
|
2833
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.
|
|
2887
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.5...HEAD
|
|
2888
|
+
[1.3.5]: https://github.com/kettle-dev/kettle-family/compare/v1.3.4...v1.3.5
|
|
2889
|
+
[1.3.5t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.5
|
|
2890
|
+
[1.3.4]: https://github.com/kettle-dev/kettle-family/compare/v1.3.3...v1.3.4
|
|
2891
|
+
[1.3.4t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.4
|
|
2834
2892
|
[1.3.3]: https://github.com/kettle-dev/kettle-family/compare/v1.3.2...v1.3.3
|
|
2835
2893
|
[1.3.3t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.3
|
|
2836
2894
|
[1.3.2]: https://github.com/kettle-dev/kettle-family/compare/v1.3.1...v1.3.2
|
data/README.md
CHANGED
|
@@ -510,6 +510,7 @@ The command names below are available from `kettle-family --help`:
|
|
|
510
510
|
| `gha-sha-pins` | Inspect or update GitHub Actions SHA pins. |
|
|
511
511
|
| `bup` | Run `bundle update --all`, or update a named dependency, at the family root and per member. |
|
|
512
512
|
| `bupb` | Run `bundle update --bundler` at the family root and per member. |
|
|
513
|
+
| `update-bundler` | Install the latest stable Bundler from RubyGems, then update family lockfiles. |
|
|
513
514
|
| `bex` | Run an arbitrary `bundle exec` command per member. |
|
|
514
515
|
| `install` | Build and install selected local family gems. |
|
|
515
516
|
| `bump` | Check, plan, or execute family version alignment. |
|
|
@@ -614,6 +615,11 @@ root-first order with `bundle update --bundler`. A root that is itself a member
|
|
|
614
615
|
is updated only once. A gem name may be passed to `bup` to update only that
|
|
615
616
|
dependency, for example `kettle-family bup --execute kettle-test`.
|
|
616
617
|
|
|
618
|
+
`update-bundler` installs the latest stable Bundler from `https://rubygems.org`
|
|
619
|
+
without enabling prereleases, then runs the same root-first lockfile update as
|
|
620
|
+
`bupb`. Family mise tasks select an installed stable Bundler explicitly to
|
|
621
|
+
start the command, so an installed prerelease cannot intercept startup.
|
|
622
|
+
|
|
617
623
|
Executed member updates commit successful lockfile changes by default. The
|
|
618
624
|
family-root lockfile is also validated and committed for monorepos, where the
|
|
619
625
|
root and members share one Git repository. A sibling-repository family root is
|
|
@@ -1186,7 +1192,7 @@ Thanks for RTFM. ☺️
|
|
|
1186
1192
|
[📌gitmoji]: https://gitmoji.dev
|
|
1187
1193
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1188
1194
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1189
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.
|
|
1195
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-7.123-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1190
1196
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
1191
1197
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1192
1198
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/lib/kettle/family/cli.rb
CHANGED
|
@@ -4,14 +4,15 @@ require "command_kit"
|
|
|
4
4
|
require "command_kit/commands"
|
|
5
5
|
require "fileutils"
|
|
6
6
|
require "optparse"
|
|
7
|
+
require "rubygems/dependency_installer"
|
|
7
8
|
|
|
8
9
|
module Kettle
|
|
9
10
|
module Family
|
|
10
11
|
class CLI < CommandKit::Command
|
|
11
12
|
include CommandKit::Commands
|
|
12
13
|
|
|
13
|
-
COMMANDS = %w[version mise-trust discover plan report metadata check clean-unreleased reconcile-releases reset test lint docs template gha-sha-pins bup bupb bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
|
|
14
|
-
WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb bex release push pull sync up].freeze
|
|
14
|
+
COMMANDS = %w[version mise-trust discover plan report metadata check clean-unreleased reconcile-releases reset test lint docs template gha-sha-pins bup bupb update-bundler bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
|
|
15
|
+
WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb update-bundler bex release push pull sync up].freeze
|
|
15
16
|
|
|
16
17
|
command_name "kettle-family"
|
|
17
18
|
usage "[options] COMMAND [ARGS...]"
|
|
@@ -27,6 +28,16 @@ module Kettle
|
|
|
27
28
|
main(argv, stdout: out, stderr: err)
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
# Defined as a singleton method above the `private` keyword: a `private`
|
|
32
|
+
# instance modifier does not apply to `def self.` definitions.
|
|
33
|
+
def self.default_only_filter(command:, only:)
|
|
34
|
+
return only unless only.to_s.empty?
|
|
35
|
+
return "bump" if %w[bump bump-version].include?(command)
|
|
36
|
+
return "pending" if command == "release"
|
|
37
|
+
|
|
38
|
+
only
|
|
39
|
+
end
|
|
40
|
+
|
|
30
41
|
module SharedOptions
|
|
31
42
|
def self.included(base)
|
|
32
43
|
base.option :root, value: {type: String, usage: "PATH"}, desc: "Workspace or family root"
|
|
@@ -187,7 +198,12 @@ module Kettle
|
|
|
187
198
|
end
|
|
188
199
|
|
|
189
200
|
def run_family(command, overrides = {})
|
|
190
|
-
|
|
201
|
+
effective = family_options(overrides)
|
|
202
|
+
# Thread the *effective* --only (including the command's default
|
|
203
|
+
# release-state token) downstream so per-branch logic sees the same
|
|
204
|
+
# selection the member selector used.
|
|
205
|
+
effective[:only] = Kettle::Family::CLI.default_only_filter(command: command, only: effective[:only])
|
|
206
|
+
Kettle::Family::CLI.new(stdout: stdout, stderr: stderr).run_command(command, effective)
|
|
191
207
|
end
|
|
192
208
|
|
|
193
209
|
def truthy_option?(name)
|
|
@@ -434,6 +450,31 @@ module Kettle
|
|
|
434
450
|
description "Plan or execute bundle update --bundler."
|
|
435
451
|
end
|
|
436
452
|
|
|
453
|
+
class UpdateBundler < WorkflowCommand
|
|
454
|
+
command_name "update-bundler"
|
|
455
|
+
usage "[options]"
|
|
456
|
+
description "Install the latest stable Bundler, then update family lockfiles."
|
|
457
|
+
|
|
458
|
+
def run(*args)
|
|
459
|
+
unexpected_arguments!(args)
|
|
460
|
+
install_latest_stable_bundler if truthy_option?(:execute)
|
|
461
|
+
run_family("update-bundler")
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
private
|
|
465
|
+
|
|
466
|
+
def install_latest_stable_bundler
|
|
467
|
+
original_sources = Gem.sources.to_a
|
|
468
|
+
Gem.sources = ["https://rubygems.org"]
|
|
469
|
+
Gem::DependencyInstaller.new(domain: :remote, prerelease: false, document: []).install("bundler")
|
|
470
|
+
Gem::Specification.reset
|
|
471
|
+
rescue => error
|
|
472
|
+
raise Error, "could not install latest stable Bundler from RubyGems: #{error.message}"
|
|
473
|
+
ensure
|
|
474
|
+
Gem.sources = original_sources if original_sources
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
437
478
|
class Bex < WorkflowCommand
|
|
438
479
|
command_name "bex"
|
|
439
480
|
usage "[options] -- COMMAND [ARGS...]"
|
|
@@ -626,6 +667,7 @@ module Kettle
|
|
|
626
667
|
command GhaShaPins
|
|
627
668
|
command Bup
|
|
628
669
|
command Bupb
|
|
670
|
+
command UpdateBundler
|
|
629
671
|
command Bex
|
|
630
672
|
command Install
|
|
631
673
|
command Bump
|
|
@@ -795,11 +837,7 @@ module Kettle
|
|
|
795
837
|
end
|
|
796
838
|
|
|
797
839
|
def default_only_filter(command:, only:)
|
|
798
|
-
|
|
799
|
-
return "bump" if %w[bump bump-version].include?(command)
|
|
800
|
-
return "pending" if command == "release"
|
|
801
|
-
|
|
802
|
-
only
|
|
840
|
+
self.class.default_only_filter(command: command, only: only)
|
|
803
841
|
end
|
|
804
842
|
|
|
805
843
|
def direct_member_only_names(only)
|
|
@@ -1095,6 +1133,17 @@ module Kettle
|
|
|
1095
1133
|
memo.last.branch = branch
|
|
1096
1134
|
break unless memo.last.ok?
|
|
1097
1135
|
|
|
1136
|
+
if (skipped = branch_bump_skip_result(
|
|
1137
|
+
member: member,
|
|
1138
|
+
branch: branch,
|
|
1139
|
+
command: command,
|
|
1140
|
+
options: options,
|
|
1141
|
+
release_state_results: release_state_results
|
|
1142
|
+
))
|
|
1143
|
+
memo << skipped
|
|
1144
|
+
next
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1098
1147
|
branch_members = rediscovered_selected_members(config: member_config, selected_names: [member.name], command: command)
|
|
1099
1148
|
branch_members = [member] if branch_members.empty?
|
|
1100
1149
|
branch_results = command_results_for_current_branch(
|
|
@@ -1113,6 +1162,41 @@ module Kettle
|
|
|
1113
1162
|
end
|
|
1114
1163
|
end
|
|
1115
1164
|
|
|
1165
|
+
BUMP_COMMANDS = %w[bump bump-version].freeze
|
|
1166
|
+
|
|
1167
|
+
# Bumping a whole branch stack must be per-branch: a branch already
|
|
1168
|
+
# released with nothing pending has nothing to bump. `--only bump` is a
|
|
1169
|
+
# status-driven selection, so it is filtered by each branch's own
|
|
1170
|
+
# release-state. An explicit `--only MEMBER` is a direct request and is
|
|
1171
|
+
# honored as given.
|
|
1172
|
+
def branch_bump_skip_result(member:, branch:, command:, options:, release_state_results:)
|
|
1173
|
+
return unless BUMP_COMMANDS.include?(command)
|
|
1174
|
+
return unless status_token_only?(options[:only])
|
|
1175
|
+
|
|
1176
|
+
matching = Selection.branches_matching(release_state_results, member_name: member.name, tokens: %w[bump])
|
|
1177
|
+
return if matching.empty? || matching.include?(branch)
|
|
1178
|
+
|
|
1179
|
+
CommandResult.new(
|
|
1180
|
+
member_name: member.name,
|
|
1181
|
+
phase: command,
|
|
1182
|
+
command: ["internal", "branch-bump-skip", branch],
|
|
1183
|
+
workdir: member.root,
|
|
1184
|
+
status: 0,
|
|
1185
|
+
success: true,
|
|
1186
|
+
stdout: "#{member.name}@#{branch} has no unreleased entries at its released version; skipping bump on this branch",
|
|
1187
|
+
stderr: "",
|
|
1188
|
+
elapsed_seconds: 0.0,
|
|
1189
|
+
skipped: true,
|
|
1190
|
+
reason: "no version bump pending on #{branch}",
|
|
1191
|
+
branch: branch
|
|
1192
|
+
)
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
def status_token_only?(only)
|
|
1196
|
+
names = only.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
1197
|
+
!names.empty? && names.all? { |name| Selection.status_token?(name) }
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1116
1200
|
def rediscovered_selected_members(config:, selected_names:, command:)
|
|
1117
1201
|
discovered = Discovery.new(config: config).members
|
|
1118
1202
|
ordered = (command == "install") ? install_order(discovered, config) : Orderer.new(members: discovered, mode: config.order_mode, hints: config.order_hints).ordered
|
|
@@ -23,11 +23,18 @@ module Kettle
|
|
|
23
23
|
].freeze
|
|
24
24
|
TRANSFER_CHANGELOG_TOTAL_UNSET = Object.new.freeze
|
|
25
25
|
CACHE_MISS = Object.new.freeze
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
WORKTREE_ADMIN_MUTEX = Mutex.new
|
|
27
|
+
|
|
28
|
+
# +branches+ scopes the check to specific release target branches. A
|
|
29
|
+
# branch-stack member produces one result per branch; a per-branch
|
|
30
|
+
# decision (is this branch already published? does it need a bump?) must
|
|
31
|
+
# never consult the whole stack, or every branch inherits the state of
|
|
32
|
+
# the branch that happens to have unreleased entries.
|
|
33
|
+
def initialize(members:, config: nil, jobs: nil, branches: nil)
|
|
28
34
|
@members = members
|
|
29
35
|
@config = config
|
|
30
36
|
@jobs = jobs
|
|
37
|
+
@branches = branches&.map(&:to_s)&.reject(&:empty?)
|
|
31
38
|
@github_latest_release_by_repo = {}
|
|
32
39
|
@transfer_changelog_status_by_root = {}
|
|
33
40
|
@transfer_changelog_lag_by_key = {}
|
|
@@ -37,6 +44,7 @@ module Kettle
|
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
def results(event_handler: nil)
|
|
47
|
+
return scoped_checkout_results(event_handler: event_handler) if checked_out_scoped_results?
|
|
40
48
|
return branch_results(event_handler: event_handler) unless release_target_branches.empty?
|
|
41
49
|
if shared_changelog?
|
|
42
50
|
results = parallel_map(members) { |member| check_shared_changelog_member_or_local(member, event_handler: event_handler) }
|
|
@@ -52,26 +60,53 @@ module Kettle
|
|
|
52
60
|
|
|
53
61
|
private
|
|
54
62
|
|
|
55
|
-
attr_reader :members, :config, :jobs
|
|
63
|
+
attr_reader :members, :config, :jobs, :branches
|
|
64
|
+
|
|
65
|
+
# A single-branch scope whose checkout already sits on that branch needs
|
|
66
|
+
# no worktree: the primary checkout is the branch under test.
|
|
67
|
+
def checked_out_scoped_results?
|
|
68
|
+
return false unless branches&.one?
|
|
69
|
+
|
|
70
|
+
members.any? { |member| current_branch_for(member) == branches.first }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def scoped_checkout_results(event_handler: nil)
|
|
74
|
+
branch = branches.first
|
|
75
|
+
results = parallel_map(members) { |member| check_member(member, branch: branch, event_handler: event_handler) }
|
|
76
|
+
results.flatten
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def current_branch_for(member)
|
|
80
|
+
git_output(member.root, "branch", "--show-current")
|
|
81
|
+
end
|
|
56
82
|
|
|
57
83
|
def branch_results(event_handler: nil)
|
|
58
84
|
root = git_root
|
|
59
85
|
selected_names = members.map(&:name)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
with_branch_worktrees(root: root, branches: release_target_branches) do |entries|
|
|
87
|
+
parallel_map(entries) do |entry|
|
|
88
|
+
if entry[:error]
|
|
89
|
+
error_result(branch: entry[:branch], error: entry[:error])
|
|
90
|
+
else
|
|
91
|
+
branch_results_for_worktree(
|
|
92
|
+
branch: entry[:branch],
|
|
93
|
+
worktree_root: entry[:worktree_root],
|
|
94
|
+
selected_names: selected_names,
|
|
95
|
+
event_handler: event_handler
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end.flatten
|
|
99
|
+
end
|
|
63
100
|
end
|
|
64
101
|
|
|
65
|
-
def
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
next normalize_shared_version_bump(results)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
parallel_map(branch_members) { |member| check_member(member, branch: branch, event_handler: event_handler) }
|
|
102
|
+
def branch_results_for_worktree(branch:, worktree_root:, selected_names:, event_handler:)
|
|
103
|
+
branch_members = discover_branch_members(worktree_root: worktree_root, selected_names: selected_names)
|
|
104
|
+
if shared_changelog?
|
|
105
|
+
results = parallel_map(branch_members) { |member| check_shared_changelog_member_or_local(member, branch: branch, event_handler: event_handler) }
|
|
106
|
+
return normalize_shared_version_bump(results)
|
|
74
107
|
end
|
|
108
|
+
|
|
109
|
+
parallel_map(branch_members) { |member| check_member(member, branch: branch, event_handler: event_handler) }
|
|
75
110
|
rescue Error => error
|
|
76
111
|
[error_result(branch: branch, error: error)]
|
|
77
112
|
end
|
|
@@ -759,6 +794,7 @@ module Kettle
|
|
|
759
794
|
|
|
760
795
|
def release_target_branches
|
|
761
796
|
return [] unless config
|
|
797
|
+
return @branches if @branches
|
|
762
798
|
|
|
763
799
|
config.release_target_branches
|
|
764
800
|
end
|
|
@@ -826,25 +862,87 @@ module Kettle
|
|
|
826
862
|
File.realpath(stdout.strip)
|
|
827
863
|
end
|
|
828
864
|
|
|
829
|
-
def
|
|
865
|
+
def with_branch_worktrees(root:, branches:)
|
|
866
|
+
with_worktree_registry_lock(root: root) do
|
|
867
|
+
base = File.join(root, "tmp", "kettle-family-release-state")
|
|
868
|
+
FileUtils.mkdir_p(base)
|
|
869
|
+
prune_stale_worktrees(root: root)
|
|
870
|
+
entries = branches.map do |branch|
|
|
871
|
+
{branch: branch, worktree_root: File.join(base, "worktree-#{Process.pid}-#{SecureRandom.hex(8)}")}
|
|
872
|
+
end
|
|
873
|
+
entries.each do |entry|
|
|
874
|
+
add_branch_worktree(root: root, branch: entry[:branch], worktree_root: entry[:worktree_root])
|
|
875
|
+
rescue Error => error
|
|
876
|
+
entry[:error] = error
|
|
877
|
+
end
|
|
878
|
+
yield entries
|
|
879
|
+
ensure
|
|
880
|
+
entries&.reverse_each do |entry|
|
|
881
|
+
remove_branch_worktree(root: root, worktree_root: entry[:worktree_root])
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
def with_worktree_registry_lock(root:)
|
|
887
|
+
stdout, stderr, status = Open3.capture3("git", "rev-parse", "--git-common-dir", chdir: root)
|
|
888
|
+
raise Error, "could not determine git common directory for #{root}: #{stderr}" unless status.success?
|
|
889
|
+
|
|
890
|
+
lock_path = File.join(File.expand_path(stdout.strip, root), "kettle-family-worktree.lock")
|
|
891
|
+
File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock_file|
|
|
892
|
+
lock_file.flock(File::LOCK_EX)
|
|
893
|
+
yield
|
|
894
|
+
ensure
|
|
895
|
+
lock_file&.flock(File::LOCK_UN)
|
|
896
|
+
end
|
|
897
|
+
end
|
|
898
|
+
|
|
899
|
+
# A killed run (SIGKILL, terminal close) skips the ensure-block cleanup and
|
|
900
|
+
# leaves registered worktrees behind. Registered-but-ownerless worktrees
|
|
901
|
+
# are pinned refs that accumulate forever, so reap them on the way in.
|
|
902
|
+
def prune_stale_worktrees(root:)
|
|
830
903
|
base = File.join(root, "tmp", "kettle-family-release-state")
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
904
|
+
WORKTREE_ADMIN_MUTEX.synchronize do
|
|
905
|
+
Open3.capture3("git", "worktree", "prune", chdir: root)
|
|
906
|
+
orphaned_worktree_paths(base).each do |path|
|
|
907
|
+
Open3.capture3("git", "worktree", "remove", "--force", path, chdir: root)
|
|
908
|
+
FileUtils.rm_rf(path)
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# Worktree directories are named worktree-PID-RANDOM. A PID that is no
|
|
914
|
+
# longer running cannot still own a live worktree.
|
|
915
|
+
def orphaned_worktree_paths(base)
|
|
916
|
+
Dir[File.join(base, "worktree-*")].select do |path|
|
|
917
|
+
pid = File.basename(path)[/worktree-(\d+)-/, 1]
|
|
918
|
+
next false if pid.nil? || pid == Process.pid.to_s
|
|
919
|
+
|
|
920
|
+
!process_alive?(pid)
|
|
921
|
+
end
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
def process_alive?(pid)
|
|
925
|
+
Process.kill(0, pid)
|
|
926
|
+
true
|
|
927
|
+
rescue Errno::ESRCH
|
|
928
|
+
false
|
|
929
|
+
rescue Errno::EPERM
|
|
930
|
+
true
|
|
837
931
|
end
|
|
838
932
|
|
|
839
933
|
def add_branch_worktree(root:, branch:, worktree_root:)
|
|
840
|
-
|
|
841
|
-
|
|
934
|
+
WORKTREE_ADMIN_MUTEX.synchronize do
|
|
935
|
+
_stdout, stderr, status = Open3.capture3("git", "worktree", "add", "--detach", worktree_root, branch, chdir: root)
|
|
936
|
+
raise Error, "could not add worktree for #{branch}: #{stderr}" unless status.success?
|
|
937
|
+
end
|
|
842
938
|
end
|
|
843
939
|
|
|
844
940
|
def remove_branch_worktree(root:, worktree_root:)
|
|
845
941
|
return unless worktree_root && Dir.exist?(worktree_root)
|
|
846
942
|
|
|
847
|
-
|
|
943
|
+
WORKTREE_ADMIN_MUTEX.synchronize do
|
|
944
|
+
Open3.capture3("git", "worktree", "remove", "--force", worktree_root, chdir: root)
|
|
945
|
+
end
|
|
848
946
|
end
|
|
849
947
|
|
|
850
948
|
def discover_branch_members(worktree_root:, selected_names:)
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -29,6 +29,7 @@ module Kettle
|
|
|
29
29
|
template
|
|
30
30
|
test
|
|
31
31
|
up
|
|
32
|
+
update-bundler
|
|
32
33
|
].freeze
|
|
33
34
|
|
|
34
35
|
attr_reader :family_name, :family_mode, :order_mode, :members, :selected_members, :config_path, :command, :results, :branch_lanes, :release_target_branches, :member_release_target_branches, :release_mode, :release_resume_arguments, :warnings, :event_log_dirs
|
|
@@ -516,6 +517,7 @@ module Kettle
|
|
|
516
517
|
|
|
517
518
|
def release_terminal_result?(result)
|
|
518
519
|
result.phase == "release_skip" ||
|
|
520
|
+
result.phase == "release_blocked" ||
|
|
519
521
|
result.phase == "release_publish" ||
|
|
520
522
|
result.phase == "release_build"
|
|
521
523
|
end
|
|
@@ -21,6 +21,21 @@ module Kettle
|
|
|
21
21
|
STATUS_TOKEN_KEYS.key?(value.to_s)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Defined as a singleton method above the `private` keyword: a `private`
|
|
25
|
+
# instance modifier does not apply to `def self.` definitions.
|
|
26
|
+
#
|
|
27
|
+
# Branches of one member that currently satisfy +tokens+. Used to decide
|
|
28
|
+
# per-branch bump work, so a bump never touches a branch that has nothing
|
|
29
|
+
# to release.
|
|
30
|
+
def self.branches_matching(release_state_results, member_name:, tokens:)
|
|
31
|
+
keys = Array(tokens).map(&:to_s).filter_map { |token| STATUS_TOKEN_KEYS[token] }
|
|
32
|
+
return [] if keys.empty?
|
|
33
|
+
|
|
34
|
+
Array(release_state_results).select do |result|
|
|
35
|
+
result.member_name == member_name && keys.all? { |key| result.state[key] == true }
|
|
36
|
+
end.map(&:branch).compact.uniq
|
|
37
|
+
end
|
|
38
|
+
|
|
24
39
|
def self.validate_release_state_only_filter!(only)
|
|
25
40
|
names = only.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
26
41
|
status_tokens = names.select { |name| status_token?(name) }
|
|
@@ -83,22 +98,29 @@ module Kettle
|
|
|
83
98
|
selected.drop(index)
|
|
84
99
|
end
|
|
85
100
|
|
|
101
|
+
# A branch-stack member yields one release-state result per branch, all
|
|
102
|
+
# sharing a member_name. Collapsing them by member_name alone would let
|
|
103
|
+
# whichever branch came last decide selection for every branch, so a
|
|
104
|
+
# member qualifies when *any* of its branches matches the token.
|
|
86
105
|
def select_release_state_status(selected, status_tokens)
|
|
87
106
|
results_by_member = release_state_results_by_member
|
|
88
|
-
failed = results_by_member.values.select { |result| !result.ok? }
|
|
89
|
-
raise Error, "release-state check failed for: #{failed.map(&:member_name).join(", ")}" unless failed.empty?
|
|
107
|
+
failed = results_by_member.values.flatten.select { |result| !result.ok? }
|
|
108
|
+
raise Error, "release-state check failed for: #{failed.map(&:member_name).uniq.join(", ")}" unless failed.empty?
|
|
90
109
|
|
|
91
110
|
selected.select do |candidate|
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
Array(results_by_member[candidate.name]).any? do |result|
|
|
112
|
+
status_tokens.all? { |token| truthy_state?(result.state[STATUS_TOKEN_KEYS.fetch(token)]) }
|
|
113
|
+
end
|
|
94
114
|
end
|
|
95
115
|
end
|
|
96
116
|
|
|
117
|
+
# Branch-stack members report one result per branch, all sharing a member
|
|
118
|
+
# name, so results are grouped per member instead of collapsed to one.
|
|
97
119
|
def release_state_results_by_member
|
|
98
120
|
raise Error, "--only release-state tokens require release-state results" unless release_state_results
|
|
99
121
|
|
|
100
122
|
release_state_results.each_with_object({}) do |result, memo|
|
|
101
|
-
memo[result.member_name]
|
|
123
|
+
(memo[result.member_name] ||= []) << result
|
|
102
124
|
end
|
|
103
125
|
end
|
|
104
126
|
|
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "1.3.
|
|
8
|
+
VERSION = "1.3.5"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
|
@@ -574,7 +574,7 @@ module Kettle
|
|
|
574
574
|
|
|
575
575
|
runner = CommandRunner.new(execute: execute, accept: accept)
|
|
576
576
|
results = []
|
|
577
|
-
update_family_root_bundle(runner: runner, memo: results) if %w[bup bupb].include?(command)
|
|
577
|
+
update_family_root_bundle(runner: runner, memo: results) if %w[bup bupb update-bundler].include?(command)
|
|
578
578
|
return results unless results.all?(&:ok?)
|
|
579
579
|
|
|
580
580
|
if command == "gha-sha-pins" && execute
|
|
@@ -607,10 +607,10 @@ module Kettle
|
|
|
607
607
|
|
|
608
608
|
normalize_lockfiles(member: member, runner: runner, memo: memo, phase: "normalize_lockfiles") if command == "template"
|
|
609
609
|
commit_gha_sha_pins(member: member, runner: runner, memo: memo) if command == "gha-sha-pins"
|
|
610
|
-
if command
|
|
610
|
+
if %w[bupb update-bundler].include?(command)
|
|
611
611
|
bupb_appraisal_results(member: member, runner: runner, memo: memo)
|
|
612
612
|
end
|
|
613
|
-
if %w[bup bupb].include?(command) && memo.last&.ok? && validate_bundle_update_lockfile(member: member, memo: memo)
|
|
613
|
+
if %w[bup bupb update-bundler].include?(command) && memo.last&.ok? && validate_bundle_update_lockfile(member: member, memo: memo)
|
|
614
614
|
commit_bundle_update(member: member, runner: runner, memo: memo)
|
|
615
615
|
end
|
|
616
616
|
commit_bex_changes(member: member, runner: runner, memo: memo) if command == "bex"
|
|
@@ -3978,28 +3978,38 @@ module Kettle
|
|
|
3978
3978
|
@release_progress&.update(member, status: "ci bundle #{relative} #{attempt}/#{REGISTRY_WAIT_ATTEMPTS}", mark: ">")
|
|
3979
3979
|
end
|
|
3980
3980
|
|
|
3981
|
+
# A published version only means "already released" when nothing
|
|
3982
|
+
# releasable landed after its tag. kettle-release commits checksums
|
|
3983
|
+
# (and the family commits dependency-floor lockfiles) *after* tagging,
|
|
3984
|
+
# so comparing HEAD to the tag directly never matches; compare the
|
|
3985
|
+
# release-relevant paths instead.
|
|
3981
3986
|
def already_released_result(member)
|
|
3982
3987
|
tag = release_tag_name(member.version)
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3988
|
+
branch = current_member_branch(member)
|
|
3989
|
+
scope = branch ? "#{member.name}@#{branch}" : member.name
|
|
3990
|
+
if no_releasable_changes_since_tag?(member, tag)
|
|
3991
|
+
stdout = "#{scope} #{member.version} is already published#{branch_suffix(branch)} and nothing releasable " \
|
|
3992
|
+
"landed after #{tag}; skipping release"
|
|
3986
3993
|
reason = "already released"
|
|
3987
3994
|
skipped = true
|
|
3988
|
-
elsif unreleased_changes_pending?(member)
|
|
3989
|
-
stdout = "#{
|
|
3990
|
-
"
|
|
3995
|
+
elsif unreleased_changes_pending?(member, branch: branch)
|
|
3996
|
+
stdout = "#{scope} #{member.version} is already published#{branch_suffix(branch)}, but release-state reports " \
|
|
3997
|
+
"unreleased changes on this branch. Bump only this branch with " \
|
|
3998
|
+
"`kettle-family bump patch --execute --only #{member.name}` before releasing."
|
|
3991
3999
|
reason = "published version has unreleased changes"
|
|
3992
4000
|
skipped = false
|
|
3993
4001
|
else
|
|
3994
|
-
stdout = "#{
|
|
3995
|
-
"
|
|
4002
|
+
stdout = "#{scope} #{member.version} is already published#{branch_suffix(branch)}, and release-state reports " \
|
|
4003
|
+
"no unreleased changes on this branch; skipping release"
|
|
3996
4004
|
reason = "already released; no unreleased changes"
|
|
3997
4005
|
skipped = true
|
|
3998
4006
|
end
|
|
3999
4007
|
|
|
4000
4008
|
CommandResult.new(
|
|
4001
4009
|
member_name: member.name,
|
|
4002
|
-
phase:
|
|
4010
|
+
# A blocked release is a distinct terminal phase from a skip: it is a
|
|
4011
|
+
# hard failure and must never be reported as "skipped".
|
|
4012
|
+
phase: skipped ? "release_skip" : "release_blocked",
|
|
4003
4013
|
command: ["internal", "released-version-check", member.version],
|
|
4004
4014
|
workdir: member.root,
|
|
4005
4015
|
status: skipped ? 0 : 1,
|
|
@@ -4008,12 +4018,34 @@ module Kettle
|
|
|
4008
4018
|
stderr: "",
|
|
4009
4019
|
elapsed_seconds: 0.0,
|
|
4010
4020
|
skipped: skipped,
|
|
4011
|
-
reason: reason
|
|
4021
|
+
reason: reason,
|
|
4022
|
+
branch: branch
|
|
4012
4023
|
)
|
|
4013
4024
|
end
|
|
4014
4025
|
|
|
4015
|
-
def
|
|
4016
|
-
|
|
4026
|
+
def branch_suffix(branch)
|
|
4027
|
+
branch ? " on #{branch}" : ""
|
|
4028
|
+
end
|
|
4029
|
+
|
|
4030
|
+
def current_member_branch(member)
|
|
4031
|
+
return unless git_work_tree?(member.root)
|
|
4032
|
+
|
|
4033
|
+
stdout, _stderr, status = Open3.capture3("git", "branch", "--show-current", chdir: member.root)
|
|
4034
|
+
return unless status.success?
|
|
4035
|
+
|
|
4036
|
+
branch = stdout.strip
|
|
4037
|
+
branch.empty? ? nil : branch
|
|
4038
|
+
end
|
|
4039
|
+
|
|
4040
|
+
# Scope the check to the branch being released. Consulting the whole
|
|
4041
|
+
# branch stack here would report every already-published branch as
|
|
4042
|
+
# having unreleased changes whenever any other branch in the stack does.
|
|
4043
|
+
def unreleased_changes_pending?(member, branch: nil)
|
|
4044
|
+
results = ReleaseStateCheck.new(
|
|
4045
|
+
members: [member],
|
|
4046
|
+
config: config,
|
|
4047
|
+
branches: branch ? [branch] : nil
|
|
4048
|
+
).results
|
|
4017
4049
|
return true unless results.all?(&:ok?)
|
|
4018
4050
|
|
|
4019
4051
|
results.any? { |result| result.state.fetch("unreleased_entries", true) }
|
|
@@ -4023,12 +4055,42 @@ module Kettle
|
|
|
4023
4055
|
"v#{version}"
|
|
4024
4056
|
end
|
|
4025
4057
|
|
|
4026
|
-
|
|
4027
|
-
|
|
4058
|
+
# Post-release maintenance commits (checksums, lockfile reconciliation,
|
|
4059
|
+
# docs) land after the tag by design, so "HEAD != tag" cannot mean
|
|
4060
|
+
# "needs a release". Only a change to the gemspec, version file, or
|
|
4061
|
+
# changelog between the tag and HEAD makes the published version stale.
|
|
4062
|
+
def no_releasable_changes_since_tag?(member, tag)
|
|
4063
|
+
return false unless git_work_tree?(member.root)
|
|
4028
4064
|
|
|
4029
4065
|
tag_sha = git_rev_parse(member.root, "refs/tags/#{tag}^{}")
|
|
4030
|
-
|
|
4031
|
-
|
|
4066
|
+
return false if tag_sha.to_s.empty?
|
|
4067
|
+
return true if tag_sha == git_rev_parse(member.root, "HEAD")
|
|
4068
|
+
|
|
4069
|
+
releasable_changes_since_tag(member, tag).empty?
|
|
4070
|
+
end
|
|
4071
|
+
|
|
4072
|
+
def releasable_changes_since_tag(member, tag)
|
|
4073
|
+
paths = releasable_paths(member)
|
|
4074
|
+
return [] if paths.empty?
|
|
4075
|
+
|
|
4076
|
+
stdout, _stderr, status = Open3.capture3(
|
|
4077
|
+
"git", "diff", "--name-only", "#{tag}^{}", "HEAD", "--", *paths, chdir: member.root
|
|
4078
|
+
)
|
|
4079
|
+
# An unreadable diff is not evidence of "nothing to release".
|
|
4080
|
+
return [tag] unless status.success?
|
|
4081
|
+
|
|
4082
|
+
stdout.lines.map(&:strip).reject(&:empty?)
|
|
4083
|
+
end
|
|
4084
|
+
|
|
4085
|
+
def releasable_paths(member)
|
|
4086
|
+
paths = [member.gemspec_path, member.version_file, config.changelog_full_path(member)]
|
|
4087
|
+
paths.compact.filter_map do |path|
|
|
4088
|
+
next unless File.file?(path)
|
|
4089
|
+
|
|
4090
|
+
Pathname.new(path).relative_path_from(Pathname.new(member.root)).to_s
|
|
4091
|
+
rescue ArgumentError
|
|
4092
|
+
path.to_s
|
|
4093
|
+
end.uniq
|
|
4032
4094
|
end
|
|
4033
4095
|
|
|
4034
4096
|
def git_work_tree?(root)
|
|
@@ -4083,7 +4145,7 @@ module Kettle
|
|
|
4083
4145
|
return gha_sha_pins_command(command_text: command_text)
|
|
4084
4146
|
end
|
|
4085
4147
|
return bup_command if command == "bup"
|
|
4086
|
-
return bupb_command if command
|
|
4148
|
+
return bupb_command if %w[bupb update-bundler].include?(command)
|
|
4087
4149
|
return bex_command if command == "bex"
|
|
4088
4150
|
|
|
4089
4151
|
command_for(command)
|
|
@@ -4108,11 +4170,11 @@ module Kettle
|
|
|
4108
4170
|
|
|
4109
4171
|
def stable_bundler_version
|
|
4110
4172
|
active_version = Gem.loaded_specs["bundler"]&.version
|
|
4111
|
-
return active_version if active_version && !active_version.prerelease?
|
|
4112
|
-
|
|
4113
4173
|
Gem::Specification
|
|
4114
4174
|
.find_all_by_name("bundler")
|
|
4115
4175
|
.map(&:version)
|
|
4176
|
+
.push(active_version)
|
|
4177
|
+
.compact
|
|
4116
4178
|
.reject(&:prerelease?)
|
|
4117
4179
|
.max
|
|
4118
4180
|
end
|
|
@@ -4358,7 +4420,7 @@ module Kettle
|
|
|
4358
4420
|
end
|
|
4359
4421
|
|
|
4360
4422
|
def command_env
|
|
4361
|
-
return bundle_update_env if %w[bup bupb].include?(command)
|
|
4423
|
+
return bundle_update_env if %w[bup bupb update-bundler].include?(command)
|
|
4362
4424
|
|
|
4363
4425
|
workflow_env
|
|
4364
4426
|
end
|
|
@@ -4634,7 +4696,9 @@ module Kettle
|
|
|
4634
4696
|
def emit_release_progress_summary(results, progress:)
|
|
4635
4697
|
return unless progress
|
|
4636
4698
|
|
|
4637
|
-
release_results = results.select
|
|
4699
|
+
release_results = results.select do |result|
|
|
4700
|
+
result.phase == release_phase || result.phase == "release_skip" || result.phase == "release_blocked"
|
|
4701
|
+
end
|
|
4638
4702
|
progress.stop
|
|
4639
4703
|
progress.summary("release summary: #{release_results.count(&:ok?)}/#{release_results.length} members ok")
|
|
4640
4704
|
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kettle-family
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -94,7 +94,7 @@ dependencies:
|
|
|
94
94
|
version: '3.1'
|
|
95
95
|
- - ">="
|
|
96
96
|
- !ruby/object:Gem::Version
|
|
97
|
-
version: 3.1.
|
|
97
|
+
version: 3.1.7
|
|
98
98
|
type: :runtime
|
|
99
99
|
prerelease: false
|
|
100
100
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -104,7 +104,7 @@ dependencies:
|
|
|
104
104
|
version: '3.1'
|
|
105
105
|
- - ">="
|
|
106
106
|
- !ruby/object:Gem::Version
|
|
107
|
-
version: 3.1.
|
|
107
|
+
version: 3.1.7
|
|
108
108
|
- !ruby/object:Gem::Dependency
|
|
109
109
|
name: kettle-gha-pins
|
|
110
110
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -304,7 +304,7 @@ dependencies:
|
|
|
304
304
|
version: '3.2'
|
|
305
305
|
- - ">="
|
|
306
306
|
- !ruby/object:Gem::Version
|
|
307
|
-
version: 3.2.
|
|
307
|
+
version: 3.2.5
|
|
308
308
|
type: :development
|
|
309
309
|
prerelease: false
|
|
310
310
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -314,7 +314,7 @@ dependencies:
|
|
|
314
314
|
version: '3.2'
|
|
315
315
|
- - ">="
|
|
316
316
|
- !ruby/object:Gem::Version
|
|
317
|
-
version: 3.2.
|
|
317
|
+
version: 3.2.5
|
|
318
318
|
- !ruby/object:Gem::Dependency
|
|
319
319
|
name: turbo_tests2
|
|
320
320
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -324,7 +324,7 @@ dependencies:
|
|
|
324
324
|
version: '3.2'
|
|
325
325
|
- - ">="
|
|
326
326
|
- !ruby/object:Gem::Version
|
|
327
|
-
version: 3.2.
|
|
327
|
+
version: 3.2.13
|
|
328
328
|
type: :development
|
|
329
329
|
prerelease: false
|
|
330
330
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -334,21 +334,7 @@ dependencies:
|
|
|
334
334
|
version: '3.2'
|
|
335
335
|
- - ">="
|
|
336
336
|
- !ruby/object:Gem::Version
|
|
337
|
-
version: 3.2.
|
|
338
|
-
- !ruby/object:Gem::Dependency
|
|
339
|
-
name: webrick
|
|
340
|
-
requirement: !ruby/object:Gem::Requirement
|
|
341
|
-
requirements:
|
|
342
|
-
- - "~>"
|
|
343
|
-
- !ruby/object:Gem::Version
|
|
344
|
-
version: '1.9'
|
|
345
|
-
type: :development
|
|
346
|
-
prerelease: false
|
|
347
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
348
|
-
requirements:
|
|
349
|
-
- - "~>"
|
|
350
|
-
- !ruby/object:Gem::Version
|
|
351
|
-
version: '1.9'
|
|
337
|
+
version: 3.2.13
|
|
352
338
|
- !ruby/object:Gem::Dependency
|
|
353
339
|
name: ruby-progressbar
|
|
354
340
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -392,7 +378,7 @@ dependencies:
|
|
|
392
378
|
version: '2.0'
|
|
393
379
|
- - ">="
|
|
394
380
|
- !ruby/object:Gem::Version
|
|
395
|
-
version: 2.0.
|
|
381
|
+
version: 2.0.15
|
|
396
382
|
type: :development
|
|
397
383
|
prerelease: false
|
|
398
384
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -402,7 +388,21 @@ dependencies:
|
|
|
402
388
|
version: '2.0'
|
|
403
389
|
- - ">="
|
|
404
390
|
- !ruby/object:Gem::Version
|
|
405
|
-
version: 2.0.
|
|
391
|
+
version: 2.0.15
|
|
392
|
+
- !ruby/object:Gem::Dependency
|
|
393
|
+
name: webrick
|
|
394
|
+
requirement: !ruby/object:Gem::Requirement
|
|
395
|
+
requirements:
|
|
396
|
+
- - "~>"
|
|
397
|
+
- !ruby/object:Gem::Version
|
|
398
|
+
version: '1.9'
|
|
399
|
+
type: :development
|
|
400
|
+
prerelease: false
|
|
401
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
402
|
+
requirements:
|
|
403
|
+
- - "~>"
|
|
404
|
+
- !ruby/object:Gem::Version
|
|
405
|
+
version: '1.9'
|
|
406
406
|
description: "\U0001F469\U0001F469\U0001F467\U0001F467 Kettle::Family provides
|
|
407
407
|
scripts and conventions for coordinating related Ruby gems as one family."
|
|
408
408
|
email:
|
|
@@ -459,10 +459,10 @@ licenses:
|
|
|
459
459
|
- AGPL-3.0-only
|
|
460
460
|
metadata:
|
|
461
461
|
homepage_uri: https://kettle-family.galtzo.com
|
|
462
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.
|
|
463
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.
|
|
462
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.5
|
|
463
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.5/CHANGELOG.md
|
|
464
464
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
465
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.
|
|
465
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.5
|
|
466
466
|
funding_uri: https://github.com/sponsors/pboling
|
|
467
467
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
468
468
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|