kettle-family 1.2.20 → 1.2.21
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 +1 -4
- data/CHANGELOG.md +32 -1
- data/README.md +1 -1
- data/lib/kettle/family/cli.rb +56 -1
- data/lib/kettle/family/debugger_bootstrap.rb +103 -0
- data/lib/kettle/family/release_reconciler.rb +149 -0
- data/lib/kettle/family/report.rb +1 -0
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +84 -16
- data/lib/kettle/family.rb +2 -0
- data.tar.gz.sig +0 -0
- metadata +8 -6
- 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: 351c882553083a30e5c52c10fb650195f5e00d5a77e452e19058da6e32c69c94
|
|
4
|
+
data.tar.gz: c3cf82b1ec51d6f9d043e579e3de5d2b09fd16f0d187d71b3abdfa9626db4576
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b2ff66410811aa28af2f578fd354cb9f9d7174a648f3863c7ad68e3e3098b129cb4a2bc9678a24a0fe8d478bba7a81bf61bae35013c48fae124ed0e113c56a2
|
|
7
|
+
data.tar.gz: e25a8ee6c06843662c0e2da1d9fa68d15b1aa6af30c5f5bbcdafe1690c0912e0d04e7f1b8bc1e814d4e5fef79850f1f4c430a69191d647679b393fa001547632
|
checksums.yaml.gz.sig
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
|
|
2
|
-
�'���z���ʊ��?��ޭ��۟�-��ȿ'g
|
|
3
|
-
�w*$
|
|
4
|
-
�}*'�ݠ��z��RU6�2Y�Zc���Q�ĸ�ԭY��]�zF�2>�f�&���i���gT�FO� ���,T�}���d@��q�r*���ʙ�3g5P����%���a/`[mR���W?���zw:�-�"{�����O����<?a�)�*��&
|
|
1
|
+
��OA�73�M����*h�C���6��&u;d��^D\.��:��t��E���'E�~����0{���pqmɸhDg�U�u�c]��1��lDq4q�@�ҋ%�
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,35 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.2.21] - 2026-08-01
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.2.21][1.2.21t]
|
|
36
|
+
- COVERAGE: 93.95% -- 4453/4740 lines in 30 files
|
|
37
|
+
- BRANCH COVERAGE: 76.28% -- 1733/2272 branches in 30 files
|
|
38
|
+
- 29.12% documented
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- Add version and mise-trust commands and run template members in dependency waves.
|
|
43
|
+
|
|
44
|
+
- Add reconcile-releases for verified RubyGems to GitHub Release backfills.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Template lockfile recovery preserves compound shell commands when appending lockfile bootstrap gems.
|
|
49
|
+
|
|
50
|
+
- Preserve the original template bootstrap failure when lockfile recovery finds no release lockfiles.
|
|
51
|
+
|
|
52
|
+
- Replace obsolete byebug debugger dependency stacks with debug before templating legacy members.
|
|
53
|
+
|
|
54
|
+
- Skip nomono-specific lockfile normalization until a legacy member declares nomono.
|
|
55
|
+
|
|
56
|
+
- Report actionable GitHub release reconciliation checks in family summaries.
|
|
57
|
+
|
|
58
|
+
- Honor KETTLE_DEV_DEV when release reconciliation invokes kettle-gh-release.
|
|
59
|
+
|
|
60
|
+
- Retry CI dependency installation while newly published family runtime floors propagate through gem.coop.
|
|
61
|
+
|
|
33
62
|
## [1.2.20] - 2026-07-31
|
|
34
63
|
|
|
35
64
|
- TAG: [v1.2.20][1.2.20t]
|
|
@@ -1579,7 +1608,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
1579
1608
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
1580
1609
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
1581
1610
|
|
|
1582
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.
|
|
1611
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.21...HEAD
|
|
1612
|
+
[1.2.21]: https://github.com/kettle-dev/kettle-family/compare/v1.2.20...v1.2.21
|
|
1613
|
+
[1.2.21t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.21
|
|
1583
1614
|
[1.2.20]: https://github.com/kettle-dev/kettle-family/compare/v1.2.19...v1.2.20
|
|
1584
1615
|
[1.2.20t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.20
|
|
1585
1616
|
[1.2.19]: https://github.com/kettle-dev/kettle-family/compare/v1.2.18...v1.2.19
|
data/README.md
CHANGED
|
@@ -819,7 +819,7 @@ Thanks for RTFM. ☺️
|
|
|
819
819
|
[📌gitmoji]: https://gitmoji.dev
|
|
820
820
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
821
821
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
822
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.
|
|
822
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-4.741-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
823
823
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
824
824
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
825
825
|
[📄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
|
@@ -10,7 +10,7 @@ module Kettle
|
|
|
10
10
|
class CLI < CommandKit::Command
|
|
11
11
|
include CommandKit::Commands
|
|
12
12
|
|
|
13
|
-
COMMANDS = %w[discover plan report metadata check clean-unreleased 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
|
|
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
14
|
WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb bex release push pull sync up].freeze
|
|
15
15
|
|
|
16
16
|
command_name "kettle-family"
|
|
@@ -204,6 +204,30 @@ module Kettle
|
|
|
204
204
|
end
|
|
205
205
|
end
|
|
206
206
|
|
|
207
|
+
class Version < CommandKit::Command
|
|
208
|
+
command_name "version"
|
|
209
|
+
usage ""
|
|
210
|
+
description "Print the kettle-family version."
|
|
211
|
+
|
|
212
|
+
def run(*args)
|
|
213
|
+
raise OptionParser::InvalidArgument, "unexpected argument(s): #{args.join(" ")}" unless args.empty?
|
|
214
|
+
|
|
215
|
+
stdout.puts(Kettle::Family::Version::VERSION)
|
|
216
|
+
0
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class MiseTrust < BaseCommand
|
|
221
|
+
command_name "mise-trust"
|
|
222
|
+
usage "[options]"
|
|
223
|
+
description "Trust mise.toml at the family root and selected member roots."
|
|
224
|
+
|
|
225
|
+
def run(*args)
|
|
226
|
+
unexpected_arguments!(args)
|
|
227
|
+
run_family("mise-trust", execute: true)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
207
231
|
class Plan < Discover
|
|
208
232
|
command_name "plan"
|
|
209
233
|
description "Alias for discover while execution workflows are built."
|
|
@@ -290,6 +314,19 @@ module Kettle
|
|
|
290
314
|
description "Uninstall locally installed family gem versions newer than the latest released version."
|
|
291
315
|
end
|
|
292
316
|
|
|
317
|
+
class ReconcileReleases < BaseCommand
|
|
318
|
+
include ExecutionOptions
|
|
319
|
+
|
|
320
|
+
command_name "reconcile-releases"
|
|
321
|
+
usage "[options]"
|
|
322
|
+
description "Check RubyGems releases for missing GitHub Releases; --execute creates only verified backfills."
|
|
323
|
+
|
|
324
|
+
def run(*args)
|
|
325
|
+
unexpected_arguments!(args)
|
|
326
|
+
run_family("reconcile-releases")
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
293
330
|
class Reset < WorkflowCommand
|
|
294
331
|
command_name "reset"
|
|
295
332
|
usage "[options] TARGET"
|
|
@@ -524,11 +561,14 @@ module Kettle
|
|
|
524
561
|
end
|
|
525
562
|
|
|
526
563
|
command Discover
|
|
564
|
+
command Version
|
|
565
|
+
command MiseTrust
|
|
527
566
|
command Plan
|
|
528
567
|
command "report", ReportCommand
|
|
529
568
|
command Metadata
|
|
530
569
|
command Check
|
|
531
570
|
command CleanUnreleased
|
|
571
|
+
command ReconcileReleases
|
|
532
572
|
command Reset
|
|
533
573
|
command Test
|
|
534
574
|
command Lint
|
|
@@ -639,9 +679,11 @@ module Kettle
|
|
|
639
679
|
end
|
|
640
680
|
|
|
641
681
|
def command_results_for_current_branch(command:, config:, members:, options:, start_at: StartAt.new(nil, nil))
|
|
682
|
+
return mise_trust_results(config: config, members: members) if command == "mise-trust"
|
|
642
683
|
return bump_version_results(members: members, options: options, phase: command) if %w[bump bump-version].include?(command)
|
|
643
684
|
return add_changelog_results(members: members, options: options) if command == "add-changelog"
|
|
644
685
|
return clean_unreleased_results(config: config, members: members, options: options) if command == "clean-unreleased"
|
|
686
|
+
return reconcile_release_results(config: config, members: members, options: options) if command == "reconcile-releases"
|
|
645
687
|
return branch_lane_results(config: config, members: members) if command == "branch-lanes"
|
|
646
688
|
return release_state_results(config: config, members: members) if command == "release-state"
|
|
647
689
|
return install_results(config: config, members: members, options: options) if command == "install"
|
|
@@ -683,6 +725,15 @@ module Kettle
|
|
|
683
725
|
).results
|
|
684
726
|
end
|
|
685
727
|
|
|
728
|
+
def mise_trust_results(config:, members:)
|
|
729
|
+
roots = [config.root, *members.map(&:root)].uniq
|
|
730
|
+
runner = CommandRunner.new(execute: true, accept: true)
|
|
731
|
+
roots.map.with_index do |root, index|
|
|
732
|
+
member = members.find { |candidate| candidate.root == root } || Member.new("family", root, nil, nil, nil, [], [], nil, [], [])
|
|
733
|
+
runner.call(member: member, phase: "mise_trust", command: ["mise", "trust", "-C", root], env: {})
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
|
|
686
737
|
def progress_io(command, options)
|
|
687
738
|
return nil unless %w[release template].include?(command)
|
|
688
739
|
return nil unless options[:execute]
|
|
@@ -928,6 +979,10 @@ module Kettle
|
|
|
928
979
|
UnreleasedGemCleanup.new(config: config, members: members, execute: options[:execute]).results
|
|
929
980
|
end
|
|
930
981
|
|
|
982
|
+
def reconcile_release_results(config:, members:, options:)
|
|
983
|
+
ReleaseReconciler.new(config: config, members: members, execute: options[:execute]).results
|
|
984
|
+
end
|
|
985
|
+
|
|
931
986
|
def install_results(config:, members:, options:)
|
|
932
987
|
LocalInstall.new(config: config, members: members, execute: options[:execute], jobs: options[:jobs]).results
|
|
933
988
|
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "kettle/dev"
|
|
4
|
+
|
|
5
|
+
module Kettle
|
|
6
|
+
module Family
|
|
7
|
+
# Removes the obsolete byebug debugger stack before Bundler evaluates a
|
|
8
|
+
# legacy member. This must happen before kettle-jem can template Gemfile.
|
|
9
|
+
class DebuggerBootstrap
|
|
10
|
+
DEBUG_GEM = "debug"
|
|
11
|
+
OBSOLETE_NAME_FRAGMENT = "byebug"
|
|
12
|
+
DECLARATION_METHODS = %i[gem add_dependency add_development_dependency].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(mode: :dry_run)
|
|
15
|
+
@mode = mode
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :mode
|
|
19
|
+
|
|
20
|
+
def member_needs_bootstrap?(member)
|
|
21
|
+
edits_for(member).any?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def bootstrap_member(member)
|
|
25
|
+
edits = edits_for(member)
|
|
26
|
+
write_edits(edits)
|
|
27
|
+
CommandResult.new(
|
|
28
|
+
member.name,
|
|
29
|
+
"template_bootstrap_debugger",
|
|
30
|
+
["internal", "debugger-bootstrap", DEBUG_GEM],
|
|
31
|
+
member.root,
|
|
32
|
+
0,
|
|
33
|
+
true,
|
|
34
|
+
"replaced obsolete byebug debugger dependencies with #{DEBUG_GEM} in #{edits.map { |edit| File.basename(edit.fetch(:path)) }.uniq.join(", ")}\n",
|
|
35
|
+
"",
|
|
36
|
+
0.0,
|
|
37
|
+
mode != :execute,
|
|
38
|
+
(mode == :execute) ? nil : "dry run"
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def write_edits(edits)
|
|
45
|
+
return if mode != :execute || edits.empty?
|
|
46
|
+
|
|
47
|
+
Kettle::Dev::VersionBump.write_edits(edits)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def edits_for(member)
|
|
51
|
+
[File.join(member.root, "Gemfile"), member.gemspec_path].filter_map { |path| file_edits(path) }.flatten
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def file_edits(path)
|
|
55
|
+
return [] unless File.file?(path)
|
|
56
|
+
|
|
57
|
+
source = File.read(path)
|
|
58
|
+
declarations = debugger_declarations(source, path)
|
|
59
|
+
return [] if declarations.empty?
|
|
60
|
+
|
|
61
|
+
declarations.filter_map.with_index do |declaration, index|
|
|
62
|
+
debugger_edit(path, source, declaration, replace: index.zero?)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def debugger_declarations(source, path)
|
|
67
|
+
parse_result = Kettle::Dev::VersionBump.parse_source(source, path)
|
|
68
|
+
Kettle::Dev::VersionBump.each_node(parse_result.value).filter_map do |node|
|
|
69
|
+
next unless node.is_a?(Prism::CallNode)
|
|
70
|
+
next unless DECLARATION_METHODS.include?(node.name)
|
|
71
|
+
|
|
72
|
+
name_node = node.arguments&.arguments&.first
|
|
73
|
+
next unless name_node.is_a?(Prism::StringNode)
|
|
74
|
+
next unless name_node.unescaped.include?(OBSOLETE_NAME_FRAGMENT)
|
|
75
|
+
|
|
76
|
+
{node: node, name_node: name_node}
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def debugger_edit(path, source, declaration, replace:)
|
|
81
|
+
node = declaration.fetch(:node)
|
|
82
|
+
if replace
|
|
83
|
+
replacement = debugger_replacement(node, declaration.fetch(:name_node))
|
|
84
|
+
return Kettle::Dev::VersionBump.file_edit(
|
|
85
|
+
path, source, node.location.start_offset, node.location.end_offset, replacement
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
line_start = source.rindex("\n", node.location.start_offset - 1).to_i + 1
|
|
90
|
+
line_end = source.index("\n", node.location.end_offset) || source.length
|
|
91
|
+
line_end += 1 if line_end < source.length
|
|
92
|
+
Kettle::Dev::VersionBump.file_edit(path, source, line_start, line_end, "")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def debugger_replacement(node, name_node)
|
|
96
|
+
receiver = node.receiver ? "#{node.receiver.location.slice}." : ""
|
|
97
|
+
quote = Kettle::Dev::VersionBump.quote_like(name_node.location.slice, DEBUG_GEM)
|
|
98
|
+
suffix = (node.name == :gem) ? ", require: false" : ""
|
|
99
|
+
"#{receiver}#{node.name} #{quote}#{suffix}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Kettle
|
|
7
|
+
module Family
|
|
8
|
+
# Reconciles RubyGems publications with GitHub Release metadata without
|
|
9
|
+
# creating tags or publishing gems. The child executable performs the
|
|
10
|
+
# authoritative prerequisite checks before an optional GitHub API call.
|
|
11
|
+
class ReleaseReconciler
|
|
12
|
+
CHECK_PHASE = "reconcile_github_release_check"
|
|
13
|
+
CREATE_PHASE = "reconcile_github_release"
|
|
14
|
+
|
|
15
|
+
def initialize(config:, members:, execute: false, runner: nil)
|
|
16
|
+
@config = config
|
|
17
|
+
@members = members
|
|
18
|
+
@execute = execute
|
|
19
|
+
@runner = runner || CommandRunner.new(execute: true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def results
|
|
23
|
+
release_states = release_state_by_member
|
|
24
|
+
members.flat_map do |member|
|
|
25
|
+
reconcile_member(member: member, release_state: release_states[member.name])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :config, :members, :execute, :runner
|
|
32
|
+
|
|
33
|
+
def release_state_by_member
|
|
34
|
+
ReleaseStateCheck.new(config: config, members: members).results.each_with_object({}) do |result, memo|
|
|
35
|
+
memo[result.member_name] = result
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reconcile_member(member:, release_state:)
|
|
40
|
+
return [state_failure(member: member, release_state: release_state)] unless release_state&.ok?
|
|
41
|
+
|
|
42
|
+
state = release_state.state || {}
|
|
43
|
+
version = published_version(state)
|
|
44
|
+
return [informational_result(member: member, message: "RubyGems release version is unknown; no reconciliation attempted")] unless version
|
|
45
|
+
return [informational_result(member: member, message: "GitHub release already matches v#{version}")] if github_release_matches?(state, version)
|
|
46
|
+
|
|
47
|
+
check = runner.call(member: member, phase: CHECK_PHASE, command: gh_release_command("--check", "--release-version", version, "--events"))
|
|
48
|
+
return [check] unless check.ok?
|
|
49
|
+
check = summarize_check(check: check, member: member, version: version)
|
|
50
|
+
return [check] unless execute
|
|
51
|
+
|
|
52
|
+
[check, runner.call(member: member, phase: CREATE_PHASE, command: gh_release_command("--release-version", version, "--events"))]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def published_version(state)
|
|
56
|
+
version = state.fetch("latest_released", nil).to_s.delete_prefix("v")
|
|
57
|
+
return nil if version.empty? || version == "unknown"
|
|
58
|
+
|
|
59
|
+
version
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def github_release_matches?(state, version)
|
|
63
|
+
state.fetch("github_latest_release", nil).to_s.delete_prefix("v") == version
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def gh_release_command(*args)
|
|
67
|
+
[RbConfig.ruby, kettle_gh_release_path, *args]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def kettle_gh_release_path
|
|
71
|
+
local_path = local_kettle_dev_executable
|
|
72
|
+
return local_path if local_path
|
|
73
|
+
|
|
74
|
+
specification = Gem.loaded_specs["kettle-dev"] || Gem::Specification.find_by_name("kettle-dev")
|
|
75
|
+
File.join(specification.full_gem_path, "exe", "kettle-gh-release")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def local_kettle_dev_executable
|
|
79
|
+
root = ENV.fetch("KETTLE_DEV_DEV", "").strip
|
|
80
|
+
return if root.empty? || %w[false 0 no off].include?(root.downcase)
|
|
81
|
+
|
|
82
|
+
candidates = [
|
|
83
|
+
File.join(root, "exe", "kettle-gh-release"),
|
|
84
|
+
File.join(root, "kettle-dev", "exe", "kettle-gh-release")
|
|
85
|
+
]
|
|
86
|
+
candidates.find { |path| File.file?(path) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def informational_result(member:, message:)
|
|
90
|
+
CommandResult.new(
|
|
91
|
+
member_name: member.name,
|
|
92
|
+
phase: CHECK_PHASE,
|
|
93
|
+
command: ["internal", CHECK_PHASE],
|
|
94
|
+
workdir: member.root,
|
|
95
|
+
status: 0,
|
|
96
|
+
success: true,
|
|
97
|
+
stdout: message,
|
|
98
|
+
stderr: "",
|
|
99
|
+
elapsed_seconds: 0.0,
|
|
100
|
+
skipped: false,
|
|
101
|
+
reason: nil
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def summarize_check(check:, member:, version:)
|
|
106
|
+
payload = check.stdout.to_s.each_line.filter_map do |line|
|
|
107
|
+
candidate = JSON.parse(line)
|
|
108
|
+
candidate if candidate["type"] == "github_release"
|
|
109
|
+
rescue JSON::ParserError
|
|
110
|
+
nil
|
|
111
|
+
end.last
|
|
112
|
+
message = payload&.fetch("message", nil) || "GitHub release v#{version} is eligible to be created"
|
|
113
|
+
|
|
114
|
+
CommandResult.new(
|
|
115
|
+
member_name: member.name,
|
|
116
|
+
phase: check.phase,
|
|
117
|
+
command: check.command,
|
|
118
|
+
workdir: check.workdir,
|
|
119
|
+
status: check.status,
|
|
120
|
+
success: check.success,
|
|
121
|
+
stdout: message,
|
|
122
|
+
stderr: check.stderr,
|
|
123
|
+
elapsed_seconds: check.elapsed_seconds,
|
|
124
|
+
skipped: check.skipped,
|
|
125
|
+
reason: check.reason,
|
|
126
|
+
branch: check.branch,
|
|
127
|
+
output_streamed: check.output_streamed,
|
|
128
|
+
log_path: check.log_path
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def state_failure(member:, release_state:)
|
|
133
|
+
CommandResult.new(
|
|
134
|
+
member_name: member.name,
|
|
135
|
+
phase: CHECK_PHASE,
|
|
136
|
+
command: ["internal", CHECK_PHASE],
|
|
137
|
+
workdir: member.root,
|
|
138
|
+
status: release_state&.status || 1,
|
|
139
|
+
success: false,
|
|
140
|
+
stdout: "",
|
|
141
|
+
stderr: release_state&.stderr.to_s,
|
|
142
|
+
elapsed_seconds: 0.0,
|
|
143
|
+
skipped: false,
|
|
144
|
+
reason: "release state unavailable"
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/kettle/family/report.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 = "1.2.
|
|
8
|
+
VERSION = "1.2.21"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
|
@@ -228,21 +228,43 @@ module Kettle
|
|
|
228
228
|
end
|
|
229
229
|
|
|
230
230
|
def template_member_workflow_results(workflow_members)
|
|
231
|
+
debugger_results = template_debugger_bootstrap_results(workflow_members)
|
|
232
|
+
return debugger_results unless debugger_results.all?(&:ok?)
|
|
233
|
+
|
|
231
234
|
bootstrap_results = template_bootstrap_dependency_results(workflow_members)
|
|
232
|
-
return bootstrap_results unless bootstrap_results.all?(&:ok?)
|
|
235
|
+
return debugger_results + bootstrap_results unless bootstrap_results.all?(&:ok?)
|
|
236
|
+
|
|
237
|
+
template_progress = start_template_progress(workflow_members)
|
|
238
|
+
results = template_dependency_waves(workflow_members).each_with_object([]) do |wave, memo|
|
|
239
|
+
wave_results = template_results_for_wave(wave, progress: template_progress)
|
|
240
|
+
memo.concat(wave_results)
|
|
241
|
+
break memo unless wave_results.all?(&:ok?)
|
|
242
|
+
end
|
|
243
|
+
emit_template_progress_summary(results, progress: template_progress)
|
|
244
|
+
debugger_results + bootstrap_results + results
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def template_debugger_bootstrap_results(workflow_members)
|
|
248
|
+
bootstrap = DebuggerBootstrap.new(mode: :execute)
|
|
249
|
+
workflow_members.filter_map do |member|
|
|
250
|
+
bootstrap.bootstrap_member(member) if bootstrap.member_needs_bootstrap?(member)
|
|
251
|
+
end
|
|
252
|
+
rescue => error
|
|
253
|
+
[template_bootstrap_failure_result(workflow_members.first, error.message)]
|
|
254
|
+
end
|
|
233
255
|
|
|
256
|
+
def template_results_for_wave(wave, progress:)
|
|
234
257
|
queue = Queue.new
|
|
235
|
-
|
|
236
|
-
ordered_results = Array.new(
|
|
258
|
+
wave.each_with_index { |member, index| queue << [index, member] }
|
|
259
|
+
ordered_results = Array.new(wave.length)
|
|
237
260
|
mutex = Mutex.new
|
|
238
261
|
stop = false
|
|
239
|
-
|
|
240
|
-
Array.new(template_jobs(workflow_members)) do
|
|
262
|
+
Array.new([template_jobs(wave), wave.length].min) do
|
|
241
263
|
Thread.new do # rubocop:disable ThreadSafety/NewThread -- family templating intentionally runs independent members concurrently.
|
|
242
264
|
loop do
|
|
243
265
|
break if mutex.synchronize { stop }
|
|
244
266
|
index, member = queue.pop(true)
|
|
245
|
-
member_results = template_results_for_member(member, progress:
|
|
267
|
+
member_results = template_results_for_member(member, progress: progress)
|
|
246
268
|
mutex.synchronize do
|
|
247
269
|
ordered_results[index] = member_results
|
|
248
270
|
stop = true unless member_results.all?(&:ok?)
|
|
@@ -252,9 +274,23 @@ module Kettle
|
|
|
252
274
|
end
|
|
253
275
|
end
|
|
254
276
|
end.each(&:join)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
277
|
+
ordered_results.compact.flatten
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def template_dependency_waves(workflow_members)
|
|
281
|
+
by_name = workflow_members.to_h { |member| [member.name, member] }
|
|
282
|
+
remaining = workflow_members.dup
|
|
283
|
+
completed = Set.new
|
|
284
|
+
[].tap do |waves|
|
|
285
|
+
until remaining.empty?
|
|
286
|
+
wave = remaining.select { |member| (member.dependencies & by_name.keys).all? { |name| completed.include?(name) } }
|
|
287
|
+
raise Error, "template dependency wave could not resolve: #{remaining.map(&:name).join(", ")}" if wave.empty?
|
|
288
|
+
|
|
289
|
+
waves << wave
|
|
290
|
+
completed.merge(wave.map(&:name))
|
|
291
|
+
remaining -= wave
|
|
292
|
+
end
|
|
293
|
+
end
|
|
258
294
|
end
|
|
259
295
|
|
|
260
296
|
def template_bootstrap_dependency_results(workflow_members)
|
|
@@ -2558,9 +2594,10 @@ module Kettle
|
|
|
2558
2594
|
return configured unless template_prepare_lockfiles_phase?(phase)
|
|
2559
2595
|
return %w[bundle install] if bundle_update_command?(configured) && !File.file?(File.join(member.root, "Gemfile.lock"))
|
|
2560
2596
|
|
|
2561
|
-
command = PRE_TEMPLATE_BOOTSTRAP_GEMS.select { |gem_name|
|
|
2597
|
+
command = PRE_TEMPLATE_BOOTSTRAP_GEMS.select { |gem_name| member_declares_or_locks_gem?(member, gem_name) }.reduce(configured) do |command_text, gem_name|
|
|
2562
2598
|
append_command_arg(command_text, gem_name)
|
|
2563
2599
|
end
|
|
2600
|
+
command = remove_command_arg(command, "nomono") unless member_declares_or_locks_gem?(member, "nomono")
|
|
2564
2601
|
skip_checksum_option ? remove_command_arg(command, "--add-checksums") : command
|
|
2565
2602
|
end
|
|
2566
2603
|
|
|
@@ -2575,7 +2612,19 @@ module Kettle
|
|
|
2575
2612
|
def append_command_arg(command_text, arg)
|
|
2576
2613
|
return command_text if normalize_command_includes_arg?(command_text, arg)
|
|
2577
2614
|
|
|
2578
|
-
|
|
2615
|
+
return append_command_arg_to_argv(command_text.map(&:to_s), arg) if command_text.is_a?(Array)
|
|
2616
|
+
|
|
2617
|
+
# CommandRunner executes String commands through `sh -lc`. Ruby has no
|
|
2618
|
+
# shell AST, so only parse the leading command segment and retain a
|
|
2619
|
+
# configured compound-command operator and its remaining segments.
|
|
2620
|
+
command, separator, remainder = command_text.to_s.partition(/\s(?:&&|\|\||;)\s/)
|
|
2621
|
+
argv = append_command_arg_to_argv(Shellwords.split(command), arg)
|
|
2622
|
+
[argv.shelljoin, separator, remainder].join
|
|
2623
|
+
rescue ArgumentError
|
|
2624
|
+
"#{command_text} #{Shellwords.escape(arg)}"
|
|
2625
|
+
end
|
|
2626
|
+
|
|
2627
|
+
def append_command_arg_to_argv(argv, arg)
|
|
2579
2628
|
update_index = argv.index("update")
|
|
2580
2629
|
if update_index
|
|
2581
2630
|
option_index = argv[(update_index + 1)..]&.index { |token| token.start_with?("-") }
|
|
@@ -2584,11 +2633,7 @@ module Kettle
|
|
|
2584
2633
|
else
|
|
2585
2634
|
argv << arg
|
|
2586
2635
|
end
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
argv.shelljoin
|
|
2590
|
-
rescue ArgumentError
|
|
2591
|
-
"#{command_text} #{Shellwords.escape(arg)}"
|
|
2636
|
+
argv
|
|
2592
2637
|
end
|
|
2593
2638
|
|
|
2594
2639
|
def remove_command_arg(command_text, arg)
|
|
@@ -2618,6 +2663,20 @@ module Kettle
|
|
|
2618
2663
|
File.readlines(lockfile).any? { |line| line.match?(/\A #{Regexp.escape(gem_name)} \(/) }
|
|
2619
2664
|
end
|
|
2620
2665
|
|
|
2666
|
+
def member_declares_or_locks_gem?(member, gem_name)
|
|
2667
|
+
return true if member_lockfile_contains_gem?(member, gem_name)
|
|
2668
|
+
|
|
2669
|
+
gemfile = File.join(member.root, "Gemfile")
|
|
2670
|
+
return false unless File.file?(gemfile)
|
|
2671
|
+
|
|
2672
|
+
parsed = Kettle::Dev::VersionBump.parse_source(File.read(gemfile), gemfile)
|
|
2673
|
+
Kettle::Dev::VersionBump.each_node(parsed.value).any? do |node|
|
|
2674
|
+
node.is_a?(Prism::CallNode) && node.name == :gem &&
|
|
2675
|
+
node.arguments&.arguments&.first.is_a?(Prism::StringNode) &&
|
|
2676
|
+
node.arguments.arguments.first.unescaped == gem_name
|
|
2677
|
+
end
|
|
2678
|
+
end
|
|
2679
|
+
|
|
2621
2680
|
def template_prepare_lockfiles_phase?(phase)
|
|
2622
2681
|
command == "template" && phase == "prepare_lockfiles"
|
|
2623
2682
|
end
|
|
@@ -2666,6 +2725,11 @@ module Kettle
|
|
|
2666
2725
|
memo: memo,
|
|
2667
2726
|
phase: "prepare_template_dependencies_recovery"
|
|
2668
2727
|
)
|
|
2728
|
+
if no_release_lockfiles_failure?(memo.last)
|
|
2729
|
+
memo.pop
|
|
2730
|
+
memo << result
|
|
2731
|
+
return false
|
|
2732
|
+
end
|
|
2669
2733
|
return false unless memo.last&.ok?
|
|
2670
2734
|
|
|
2671
2735
|
result = runner.call(
|
|
@@ -2679,6 +2743,10 @@ module Kettle
|
|
|
2679
2743
|
result.ok?
|
|
2680
2744
|
end
|
|
2681
2745
|
|
|
2746
|
+
def no_release_lockfiles_failure?(result)
|
|
2747
|
+
result && !result.ok? && [result.stdout, result.stderr].join("\n").include?("kettle-reset: no release lockfiles found")
|
|
2748
|
+
end
|
|
2749
|
+
|
|
2682
2750
|
def commit_template_changes(member:, runner:, memo:)
|
|
2683
2751
|
return unless deferred_monorepo_template_commit?(member)
|
|
2684
2752
|
return unless memo.all?(&:ok?)
|
data/lib/kettle/family.rb
CHANGED
|
@@ -13,9 +13,11 @@ require_relative "family/config"
|
|
|
13
13
|
require_relative "family/secrets"
|
|
14
14
|
require_relative "family/branch_target_config"
|
|
15
15
|
require_relative "family/release_state_check"
|
|
16
|
+
require_relative "family/release_reconciler"
|
|
16
17
|
require_relative "family/unreleased_gem_cleanup"
|
|
17
18
|
require_relative "family/local_install"
|
|
18
19
|
require_relative "family/nomono_bootstrap"
|
|
20
|
+
require_relative "family/debugger_bootstrap"
|
|
19
21
|
require_relative "family/release_waves"
|
|
20
22
|
require_relative "family/workflow"
|
|
21
23
|
require_relative "family/discovery"
|
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.2.
|
|
4
|
+
version: 1.2.21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -74,7 +74,7 @@ dependencies:
|
|
|
74
74
|
version: '2.3'
|
|
75
75
|
- - ">="
|
|
76
76
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: 2.5.
|
|
77
|
+
version: 2.5.15
|
|
78
78
|
type: :runtime
|
|
79
79
|
prerelease: false
|
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -84,7 +84,7 @@ dependencies:
|
|
|
84
84
|
version: '2.3'
|
|
85
85
|
- - ">="
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
|
-
version: 2.5.
|
|
87
|
+
version: 2.5.15
|
|
88
88
|
- !ruby/object:Gem::Dependency
|
|
89
89
|
name: kettle-test
|
|
90
90
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -316,6 +316,7 @@ files:
|
|
|
316
316
|
- lib/kettle/family/command_result.rb
|
|
317
317
|
- lib/kettle/family/command_runner.rb
|
|
318
318
|
- lib/kettle/family/config.rb
|
|
319
|
+
- lib/kettle/family/debugger_bootstrap.rb
|
|
319
320
|
- lib/kettle/family/dependency_floor.rb
|
|
320
321
|
- lib/kettle/family/discovery.rb
|
|
321
322
|
- lib/kettle/family/git_status.rb
|
|
@@ -324,6 +325,7 @@ files:
|
|
|
324
325
|
- lib/kettle/family/nomono_bootstrap.rb
|
|
325
326
|
- lib/kettle/family/orderer.rb
|
|
326
327
|
- lib/kettle/family/readiness_check.rb
|
|
328
|
+
- lib/kettle/family/release_reconciler.rb
|
|
327
329
|
- lib/kettle/family/release_state_check.rb
|
|
328
330
|
- lib/kettle/family/release_state_result.rb
|
|
329
331
|
- lib/kettle/family/release_waves.rb
|
|
@@ -342,10 +344,10 @@ licenses:
|
|
|
342
344
|
- AGPL-3.0-only
|
|
343
345
|
metadata:
|
|
344
346
|
homepage_uri: https://kettle-family.galtzo.com
|
|
345
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.
|
|
346
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.
|
|
347
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.21
|
|
348
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.21/CHANGELOG.md
|
|
347
349
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
348
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.
|
|
350
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.21
|
|
349
351
|
funding_uri: https://github.com/sponsors/pboling
|
|
350
352
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
351
353
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|