kettle-family 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +24 -1
- data/README.md +1 -1
- data/exe/kettle-family +12 -0
- data/lib/kettle/family/cli.rb +1 -1
- data/lib/kettle/family/release_state_check.rb +13 -2
- data/lib/kettle/family/report.rb +8 -2
- data/lib/kettle/family/selection.rb +5 -1
- data/lib/kettle/family/version.rb +1 -1
- data/lib/kettle/family/workflow.rb +187 -44
- data/lib/kettle/family/workflow_progress.rb +137 -0
- data.tar.gz.sig +0 -0
- metadata +19 -4
- 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: 2aaa5d7e3543091f4cc86ed298afe481609e4c57bd85a3b4d69e0e0ec587bc80
|
|
4
|
+
data.tar.gz: 36524c2f96f04ccd148048cbbaee7222e844f78e1fa5b5dd85dcd49c53975123
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9efc18d3efbcfa9e16aaa867a22a8b9e59fc337f9eb41f03f02829a88c6a4707eee5ff00c5c0b24931ea428d46841bf649af572a3a1fd84416b06aa46417d7d
|
|
7
|
+
data.tar.gz: fe0e3717d364977801a8272976d2e22ae41ae3b5fdf4d4066fe57b698a28ec45089b4b88fd0b1645d57402fd427a9b8bb457439c49352b8e39bb3aac62be660e
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,27 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [1.1.0] - 2026-07-23
|
|
34
|
+
|
|
35
|
+
- TAG: [v1.1.0][1.1.0t]
|
|
36
|
+
- COVERAGE: 94.37% -- 2934/3109 lines in 25 files
|
|
37
|
+
- BRANCH COVERAGE: 76.29% -- 1049/1375 branches in 25 files
|
|
38
|
+
- 28.68% documented
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- `kettle-family state` now includes a `bump` boolean column, documents the
|
|
43
|
+
boolean release-state columns above the table, and accepts `--only bump` to
|
|
44
|
+
select members with unreleased changes whose `V.rb` differs from `V.rel`.
|
|
45
|
+
- `--only` release-state filters now accept the short table column names
|
|
46
|
+
`unrel`, `prep`, and `pend` in addition to `unreleased`, `prepared`, and
|
|
47
|
+
`pending`.
|
|
48
|
+
- The `kettle-family` executable now supports `-v` / `--version` and prints a
|
|
49
|
+
standard startup header on normal runs.
|
|
50
|
+
- Template and release workflows now use `tty-progressbar`-backed multi-line
|
|
51
|
+
member progress in normal TTY output, with kettle-jem NDJSON marks rendered
|
|
52
|
+
as a fixed-width sliding event tape instead of dots or raw event dumps.
|
|
53
|
+
|
|
33
54
|
## [1.0.6] - 2026-07-23
|
|
34
55
|
|
|
35
56
|
- TAG: [v1.0.6][1.0.6t]
|
|
@@ -890,7 +911,9 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
890
911
|
- Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
|
|
891
912
|
- Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
|
|
892
913
|
|
|
893
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.0
|
|
914
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.1.0...HEAD
|
|
915
|
+
[1.1.0]: https://github.com/kettle-dev/kettle-family/compare/v1.0.6...v1.1.0
|
|
916
|
+
[1.1.0t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.1.0
|
|
894
917
|
[1.0.6]: https://github.com/kettle-dev/kettle-family/compare/v1.0.5...v1.0.6
|
|
895
918
|
[1.0.6t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.0.6
|
|
896
919
|
[1.0.5]: https://github.com/kettle-dev/kettle-family/compare/v1.0.4...v1.0.5
|
data/README.md
CHANGED
|
@@ -669,7 +669,7 @@ Thanks for RTFM. ☺️
|
|
|
669
669
|
[📌gitmoji]: https://gitmoji.dev
|
|
670
670
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
671
671
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
672
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-
|
|
672
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-3.109-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
673
673
|
[🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
|
|
674
674
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
675
675
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
data/exe/kettle-family
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
script_basename = File.basename(__FILE__)
|
|
5
|
+
repo_lib = File.expand_path("../lib", __dir__)
|
|
6
|
+
$LOAD_PATH.unshift(repo_lib) if File.directory?(repo_lib) && !$LOAD_PATH.include?(repo_lib)
|
|
7
|
+
|
|
8
|
+
require "kettle/family/version"
|
|
9
|
+
if ARGV.any? { |arg| arg == "-v" || arg == "--version" }
|
|
10
|
+
puts Kettle::Family::Version::VERSION
|
|
11
|
+
exit(0)
|
|
12
|
+
end
|
|
13
|
+
|
|
4
14
|
require "kettle/family"
|
|
5
15
|
|
|
16
|
+
puts "== #{script_basename} v#{Kettle::Family::Version::VERSION} ==" unless ARGV.include?("--json")
|
|
17
|
+
|
|
6
18
|
exit(Kettle::Family::CLI.call(ARGV))
|
data/lib/kettle/family/cli.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Kettle
|
|
|
37
37
|
|
|
38
38
|
module SelectionOptions
|
|
39
39
|
def self.included(base)
|
|
40
|
-
base.option :only, value: {type: String, usage: "MEMBERS"}, desc: "Select comma-separated members, or release-state tokens: unreleased, prepared, pending"
|
|
40
|
+
base.option :only, value: {type: String, usage: "MEMBERS"}, desc: "Select comma-separated members, or release-state tokens: unreleased/unrel, prepared/prep, pending/pend, bump"
|
|
41
41
|
base.option :exclude, value: {type: String, usage: "MEMBERS"}, desc: "Exclude comma-separated members"
|
|
42
42
|
base.option :start_at, long: "--start-at", value: {type: String, usage: "MEMBER[@BRANCH]"}, desc: "Select from member through the end of order"
|
|
43
43
|
end
|
|
@@ -59,6 +59,7 @@ module Kettle
|
|
|
59
59
|
success = status.success?
|
|
60
60
|
state = success ? JSON.parse(stdout) : {}
|
|
61
61
|
state = branch_filtered_state(member, state, branch) if success && branch
|
|
62
|
+
state = state_with_computed_booleans(state) if success
|
|
62
63
|
state = enrich_git_state(member.root, state) if success
|
|
63
64
|
result(member: member, command: command, stdout: stdout, stderr: stderr, status: status.exitstatus, elapsed: elapsed, success: success, state: state, branch: branch)
|
|
64
65
|
rescue JSON::ParserError => error
|
|
@@ -279,7 +280,7 @@ module Kettle
|
|
|
279
280
|
unreleased_entries = unreleased_entries?(content)
|
|
280
281
|
prepared_release_pending = !version.to_s.empty? && latest_changelog_version == version
|
|
281
282
|
ahead = commits_ahead_of_release(root, latest_changelog_version)
|
|
282
|
-
enrich_git_state(root, {
|
|
283
|
+
state_with_computed_booleans(enrich_git_state(root, {
|
|
283
284
|
"gem_name" => config.family_name,
|
|
284
285
|
"version" => version,
|
|
285
286
|
"latest_released" => nil,
|
|
@@ -288,7 +289,17 @@ module Kettle
|
|
|
288
289
|
"unreleased_entries" => unreleased_entries,
|
|
289
290
|
"prepared_release_pending" => prepared_release_pending,
|
|
290
291
|
"pending_release" => unreleased_entries || prepared_release_pending
|
|
291
|
-
})
|
|
292
|
+
}))
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def state_with_computed_booleans(state)
|
|
296
|
+
enriched = state.dup
|
|
297
|
+
enriched["bump_release_pending"] = bump_release_pending?(enriched)
|
|
298
|
+
enriched
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def bump_release_pending?(state)
|
|
302
|
+
state["unreleased_entries"] == true && state["version"].to_s != state["latest_released"].to_s
|
|
292
303
|
end
|
|
293
304
|
|
|
294
305
|
def root_changelog_version(root)
|
data/lib/kettle/family/report.rb
CHANGED
|
@@ -367,6 +367,11 @@ module Kettle
|
|
|
367
367
|
|
|
368
368
|
def append_release_state_results(lines)
|
|
369
369
|
lines << "release state:"
|
|
370
|
+
lines << " boolean columns:"
|
|
371
|
+
lines << " unrel: unreleased changelog entries are present"
|
|
372
|
+
lines << " prep: V.ch.md matches V.rb and is ready to publish"
|
|
373
|
+
lines << " pend: unrel or prep"
|
|
374
|
+
lines << " bump: unrel is yes and V.rb differs from V.rel"
|
|
370
375
|
rows = release_state_header
|
|
371
376
|
results.each do |result|
|
|
372
377
|
rows << release_state_row(result)
|
|
@@ -393,7 +398,8 @@ module Kettle
|
|
|
393
398
|
release_state_ahead_behind(state),
|
|
394
399
|
yes_no(state.fetch("unreleased_entries", nil)),
|
|
395
400
|
yes_no(state.fetch("prepared_release_pending", nil)),
|
|
396
|
-
yes_no(state.fetch("pending_release", nil))
|
|
401
|
+
yes_no(state.fetch("pending_release", nil)),
|
|
402
|
+
yes_no(state.fetch("bump_release_pending", nil))
|
|
397
403
|
]
|
|
398
404
|
return row unless release_state_has_branches?
|
|
399
405
|
|
|
@@ -401,7 +407,7 @@ module Kettle
|
|
|
401
407
|
end
|
|
402
408
|
|
|
403
409
|
def release_state_header
|
|
404
|
-
header = [["gem", "checkout", "V.rb", "V.rel", "V.ch.md", "🔼 / 🔽", "unrel", "prep", "pend"]]
|
|
410
|
+
header = [["gem", "checkout", "V.rb", "V.rel", "V.ch.md", "🔼 / 🔽", "unrel", "prep", "pend", "bump"]]
|
|
405
411
|
return header unless release_state_has_branches?
|
|
406
412
|
|
|
407
413
|
[["branch", *header.first]]
|
|
@@ -5,8 +5,12 @@ module Kettle
|
|
|
5
5
|
class Selection
|
|
6
6
|
STATUS_TOKEN_KEYS = {
|
|
7
7
|
"unreleased" => "unreleased_entries",
|
|
8
|
+
"unrel" => "unreleased_entries",
|
|
8
9
|
"prepared" => "prepared_release_pending",
|
|
9
|
-
"
|
|
10
|
+
"prep" => "prepared_release_pending",
|
|
11
|
+
"pending" => "pending_release",
|
|
12
|
+
"pend" => "pending_release",
|
|
13
|
+
"bump" => "bump_release_pending"
|
|
10
14
|
}.freeze
|
|
11
15
|
|
|
12
16
|
def self.status_tokens
|
|
@@ -7,6 +7,8 @@ require "etc"
|
|
|
7
7
|
require "open3"
|
|
8
8
|
require "uri"
|
|
9
9
|
|
|
10
|
+
require_relative "workflow_progress"
|
|
11
|
+
|
|
10
12
|
module Kettle
|
|
11
13
|
module Family
|
|
12
14
|
class Workflow
|
|
@@ -165,17 +167,16 @@ module Kettle
|
|
|
165
167
|
ordered_results = Array.new(workflow_members.length)
|
|
166
168
|
mutex = Mutex.new
|
|
167
169
|
stop = false
|
|
168
|
-
|
|
170
|
+
template_progress = start_template_progress(workflow_members)
|
|
169
171
|
Array.new(template_jobs(workflow_members)) do
|
|
170
172
|
Thread.new do # rubocop:disable ThreadSafety/NewThread -- family templating intentionally runs independent members concurrently.
|
|
171
173
|
loop do
|
|
172
174
|
break if mutex.synchronize { stop }
|
|
173
175
|
index, member = queue.pop(true)
|
|
174
|
-
member_results = template_results_for_member(member)
|
|
176
|
+
member_results = template_results_for_member(member, progress: template_progress)
|
|
175
177
|
mutex.synchronize do
|
|
176
178
|
ordered_results[index] = member_results
|
|
177
179
|
stop = true unless member_results.all?(&:ok?)
|
|
178
|
-
emit_template_progress_mark(member_results)
|
|
179
180
|
end
|
|
180
181
|
rescue ThreadError
|
|
181
182
|
break
|
|
@@ -183,19 +184,22 @@ module Kettle
|
|
|
183
184
|
end
|
|
184
185
|
end.each(&:join)
|
|
185
186
|
flattened = ordered_results.compact.flatten
|
|
186
|
-
emit_template_progress_summary(flattened)
|
|
187
|
+
emit_template_progress_summary(flattened, progress: template_progress)
|
|
187
188
|
flattened
|
|
188
189
|
end
|
|
189
190
|
|
|
190
|
-
def template_results_for_member(member)
|
|
191
|
+
def template_results_for_member(member, progress: nil)
|
|
192
|
+
progress&.start_member(member, total: template_phase_total, status: template_initial_status)
|
|
191
193
|
runner = CommandRunner.new(execute: execute, accept: accept)
|
|
192
194
|
[].tap do |memo|
|
|
193
195
|
if config.normalize_lockfiles?
|
|
194
196
|
normalize_lockfiles(member: member, runner: runner, memo: memo, phase: "prepare_lockfiles")
|
|
197
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
195
198
|
return memo unless memo.last.ok?
|
|
196
199
|
end
|
|
197
200
|
|
|
198
201
|
prepared = prepare_template_dependencies(member: member, runner: runner, memo: memo)
|
|
202
|
+
emit_member_result_progress(member, memo.last, progress: progress) if memo.last&.phase == "prepare_template_dependencies"
|
|
199
203
|
return memo if prepared == false
|
|
200
204
|
|
|
201
205
|
memo << runner.call(
|
|
@@ -203,11 +207,22 @@ module Kettle
|
|
|
203
207
|
phase: command,
|
|
204
208
|
command: workflow_command(member),
|
|
205
209
|
env: workflow_env,
|
|
206
|
-
stdout_line_handler: template_event_line_handler(member)
|
|
210
|
+
stdout_line_handler: template_event_line_handler(member, progress: progress)
|
|
207
211
|
)
|
|
212
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
208
213
|
return memo unless memo.last.ok?
|
|
209
214
|
|
|
210
215
|
normalize_lockfiles(member: member, runner: runner, memo: memo, phase: "normalize_lockfiles")
|
|
216
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
217
|
+
ensure
|
|
218
|
+
template_result = memo.find { |result| result.phase == "template" } || memo.last
|
|
219
|
+
if template_result
|
|
220
|
+
progress&.finish_member(
|
|
221
|
+
member,
|
|
222
|
+
success: memo.all?(&:ok?),
|
|
223
|
+
status: template_member_finish_status(template_result)
|
|
224
|
+
)
|
|
225
|
+
end
|
|
211
226
|
end
|
|
212
227
|
end
|
|
213
228
|
|
|
@@ -366,13 +381,20 @@ module Kettle
|
|
|
366
381
|
return results unless results.all?(&:ok?)
|
|
367
382
|
return parallel_release_member_results(release_members, results) if parallel_release_members?(release_members)
|
|
368
383
|
|
|
369
|
-
release_members
|
|
370
|
-
|
|
371
|
-
|
|
384
|
+
release_progress = start_release_progress(release_members)
|
|
385
|
+
@release_progress = release_progress
|
|
386
|
+
begin
|
|
387
|
+
release_members.each_with_object(results) do |member, memo|
|
|
388
|
+
memo.concat(release_results_for_member(member, runner: runner))
|
|
389
|
+
break memo unless memo.last.ok?
|
|
372
390
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
391
|
+
remaining_members = release_members.drop(release_members.index(member) + 1)
|
|
392
|
+
append_dependency_floor_results(released_members: [member], dependent_members: remaining_members, runner: runner, memo: memo)
|
|
393
|
+
break memo unless memo.last&.ok?
|
|
394
|
+
end
|
|
395
|
+
ensure
|
|
396
|
+
emit_release_progress_summary(results, progress: release_progress)
|
|
397
|
+
@release_progress = nil
|
|
376
398
|
end
|
|
377
399
|
end
|
|
378
400
|
|
|
@@ -380,16 +402,23 @@ module Kettle
|
|
|
380
402
|
results = initial_results.dup
|
|
381
403
|
waves = release_waves(release_members)
|
|
382
404
|
completed_members = []
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
405
|
+
release_progress = start_release_progress(release_members)
|
|
406
|
+
@release_progress = release_progress
|
|
407
|
+
begin
|
|
408
|
+
waves.each_with_index do |wave, index|
|
|
409
|
+
results << release_wave_result(wave, index: index, total: waves.length)
|
|
410
|
+
wave_results = run_release_wave(wave)
|
|
411
|
+
results.concat(wave_results.flatten)
|
|
412
|
+
break unless wave_results.all? { |member_results| member_results.all?(&:ok?) }
|
|
413
|
+
|
|
414
|
+
completed_members.concat(wave)
|
|
415
|
+
remaining_members = release_members - completed_members
|
|
416
|
+
append_dependency_floor_results(released_members: wave, dependent_members: remaining_members, runner: release_command_runner, memo: results)
|
|
417
|
+
break unless results.last&.ok?
|
|
418
|
+
end
|
|
419
|
+
ensure
|
|
420
|
+
emit_release_progress_summary(results, progress: release_progress)
|
|
421
|
+
@release_progress = nil
|
|
393
422
|
end
|
|
394
423
|
results
|
|
395
424
|
end
|
|
@@ -439,21 +468,27 @@ module Kettle
|
|
|
439
468
|
end
|
|
440
469
|
|
|
441
470
|
def release_results_for_member(member, runner:)
|
|
471
|
+
progress = @release_progress
|
|
472
|
+
progress&.start_member(member, total: release_phase_total, status: "check")
|
|
442
473
|
[].tap do |memo|
|
|
443
474
|
if skip_already_released?(member)
|
|
444
475
|
memo << already_released_result(member)
|
|
476
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
445
477
|
return memo
|
|
446
478
|
end
|
|
447
479
|
|
|
448
480
|
if config.release_normalize_lockfiles?
|
|
449
481
|
normalize_release_lockfiles(member: member, runner: runner, memo: memo)
|
|
482
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
450
483
|
return memo unless memo.last&.ok?
|
|
451
484
|
|
|
452
485
|
commit_normalized_lockfiles(branch_members: [member], runner: runner, memo: memo, reason: "release")
|
|
486
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
453
487
|
return memo unless memo.last&.ok?
|
|
454
488
|
end
|
|
455
489
|
|
|
456
490
|
append_release_internal_checks(member: member, memo: memo)
|
|
491
|
+
memo.last(2).each { |result| emit_member_result_progress(member, result, progress: progress) }
|
|
457
492
|
return memo unless memo.last(2).all?(&:ok?)
|
|
458
493
|
|
|
459
494
|
memo << runner.call(
|
|
@@ -463,9 +498,21 @@ module Kettle
|
|
|
463
498
|
env: release_env,
|
|
464
499
|
interactive: release_command_interactive?
|
|
465
500
|
)
|
|
501
|
+
emit_member_result_progress(member, memo.last, progress: progress)
|
|
466
502
|
return memo unless memo.last.ok?
|
|
467
503
|
|
|
504
|
+
before_git_phase_count = memo.length
|
|
468
505
|
append_release_git_phases(member: member, runner: runner, memo: memo)
|
|
506
|
+
memo.drop(before_git_phase_count).each { |result| emit_member_result_progress(member, result, progress: progress) }
|
|
507
|
+
ensure
|
|
508
|
+
finished_result = memo.find { |result| result.phase == release_phase } || memo.last
|
|
509
|
+
if finished_result
|
|
510
|
+
progress&.finish_member(
|
|
511
|
+
member,
|
|
512
|
+
success: memo.all?(&:ok?),
|
|
513
|
+
status: release_member_finish_status(finished_result)
|
|
514
|
+
)
|
|
515
|
+
end
|
|
469
516
|
end
|
|
470
517
|
end
|
|
471
518
|
|
|
@@ -1096,30 +1143,103 @@ module Kettle
|
|
|
1096
1143
|
append_command_args(command_text, args)
|
|
1097
1144
|
end
|
|
1098
1145
|
|
|
1099
|
-
def
|
|
1100
|
-
|
|
1146
|
+
def start_template_progress(workflow_members)
|
|
1147
|
+
progress = WorkflowProgress.new(
|
|
1148
|
+
io: progress_io,
|
|
1149
|
+
label: "templating",
|
|
1150
|
+
total: workflow_members.length,
|
|
1151
|
+
jobs: template_jobs(workflow_members)
|
|
1152
|
+
)
|
|
1153
|
+
progress.start
|
|
1154
|
+
progress
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
def start_release_progress(release_members)
|
|
1158
|
+
progress = WorkflowProgress.new(
|
|
1159
|
+
io: progress_io,
|
|
1160
|
+
label: release_progress_label,
|
|
1161
|
+
total: release_members.length,
|
|
1162
|
+
jobs: release_jobs(release_members)
|
|
1163
|
+
)
|
|
1164
|
+
progress.start
|
|
1165
|
+
progress
|
|
1166
|
+
end
|
|
1101
1167
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1168
|
+
def template_phase_total
|
|
1169
|
+
config.normalize_lockfiles? ? 4 : 2
|
|
1104
1170
|
end
|
|
1105
1171
|
|
|
1106
|
-
def
|
|
1107
|
-
|
|
1172
|
+
def release_phase_total
|
|
1173
|
+
total = 3
|
|
1174
|
+
total += 2 if config.release_normalize_lockfiles?
|
|
1175
|
+
total += 1 if tag
|
|
1176
|
+
total += 1 if push
|
|
1177
|
+
total
|
|
1178
|
+
end
|
|
1108
1179
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1180
|
+
def release_progress_label
|
|
1181
|
+
if release_phase == "release_publish"
|
|
1182
|
+
"publishing"
|
|
1183
|
+
else
|
|
1184
|
+
"releasing"
|
|
1185
|
+
end
|
|
1112
1186
|
end
|
|
1113
1187
|
|
|
1114
|
-
def
|
|
1188
|
+
def template_initial_status
|
|
1189
|
+
config.normalize_lockfiles? ? "prepare_lockfiles" : "prepare_template_dependencies"
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
def emit_member_result_progress(member, result, progress:)
|
|
1193
|
+
return unless result
|
|
1194
|
+
|
|
1195
|
+
progress&.advance(member, status: result.phase, success: result.ok?, mark: command_result_progress_mark(result))
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
def command_result_progress_mark(result)
|
|
1199
|
+
return "S" if result.skipped
|
|
1200
|
+
|
|
1201
|
+
result.ok? ? "." : "F"
|
|
1202
|
+
end
|
|
1203
|
+
|
|
1204
|
+
def template_member_finish_status(result)
|
|
1205
|
+
changed_files = template_changed_file_count(result)
|
|
1206
|
+
"#{changed_files} file#{"s" unless changed_files == 1} changed"
|
|
1207
|
+
end
|
|
1208
|
+
|
|
1209
|
+
def release_member_finish_status(result)
|
|
1210
|
+
result.skipped ? "skipped #{result.phase}" : result.phase
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1213
|
+
def emit_template_progress_summary(results, progress:)
|
|
1214
|
+
return unless progress
|
|
1215
|
+
|
|
1216
|
+
template_results = results.select { |result| result.phase == "template" }
|
|
1217
|
+
changed_files = template_results.sum { |result| template_changed_file_count(result) }
|
|
1218
|
+
progress.stop
|
|
1219
|
+
progress.summary("template summary: #{template_results.count(&:ok?)}/#{template_results.length} members ok, #{changed_files} file#{"s" unless changed_files == 1} changed")
|
|
1220
|
+
end
|
|
1221
|
+
|
|
1222
|
+
def emit_release_progress_summary(results, progress:)
|
|
1223
|
+
return unless progress
|
|
1224
|
+
|
|
1225
|
+
release_results = results.select { |result| result.phase == release_phase || result.phase == "release_skip" }
|
|
1226
|
+
progress.stop
|
|
1227
|
+
progress.summary("release summary: #{release_results.count(&:ok?)}/#{release_results.length} members ok")
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
def template_event_line_handler(member, progress: nil)
|
|
1115
1231
|
return nil unless progress_io
|
|
1116
|
-
return nil unless verbose || debug
|
|
1232
|
+
return nil unless verbose || debug || progress&.tty?
|
|
1117
1233
|
|
|
1118
1234
|
lambda do |line|
|
|
1119
1235
|
event = parse_template_event(line)
|
|
1120
1236
|
next unless event
|
|
1121
1237
|
|
|
1122
|
-
|
|
1238
|
+
if verbose || debug
|
|
1239
|
+
emit_template_event_progress(member, event)
|
|
1240
|
+
else
|
|
1241
|
+
emit_template_event_status(member, event, progress: progress)
|
|
1242
|
+
end
|
|
1123
1243
|
end
|
|
1124
1244
|
end
|
|
1125
1245
|
|
|
@@ -1151,6 +1271,39 @@ module Kettle
|
|
|
1151
1271
|
end
|
|
1152
1272
|
end
|
|
1153
1273
|
|
|
1274
|
+
def emit_template_event_status(member, event, progress:)
|
|
1275
|
+
status = case event["type"]
|
|
1276
|
+
when "phase_start", "phase_finish"
|
|
1277
|
+
event["phase"].to_s
|
|
1278
|
+
when "recipe"
|
|
1279
|
+
event["path"].to_s
|
|
1280
|
+
when "post_apply_step", "command_step"
|
|
1281
|
+
[event["phase"], event["name"]].map(&:to_s).reject(&:empty?).join(":")
|
|
1282
|
+
when "diagnostic"
|
|
1283
|
+
message = event["message"].to_s
|
|
1284
|
+
message.empty? ? event["kind"].to_s : message
|
|
1285
|
+
when "summary"
|
|
1286
|
+
"#{event["changed_count"].to_i} file#{"s" unless event["changed_count"].to_i == 1} changed"
|
|
1287
|
+
end
|
|
1288
|
+
mark = template_event_status_mark(event)
|
|
1289
|
+
progress&.update(member, status: status, mark: mark) if status && !status.empty?
|
|
1290
|
+
end
|
|
1291
|
+
|
|
1292
|
+
def template_event_status_mark(event)
|
|
1293
|
+
case event["type"]
|
|
1294
|
+
when "phase_start"
|
|
1295
|
+
">"
|
|
1296
|
+
when "phase_finish"
|
|
1297
|
+
phase_finish_event_mark(event)
|
|
1298
|
+
when "recipe"
|
|
1299
|
+
template_event_mark(event, changed_mark: "*")
|
|
1300
|
+
when "post_apply_step", "command_step"
|
|
1301
|
+
template_event_mark(event)
|
|
1302
|
+
when "diagnostic"
|
|
1303
|
+
"!"
|
|
1304
|
+
end
|
|
1305
|
+
end
|
|
1306
|
+
|
|
1154
1307
|
def emit_template_event_line(member, mark, label)
|
|
1155
1308
|
return if label.to_s.empty?
|
|
1156
1309
|
|
|
@@ -1178,16 +1331,6 @@ module Kettle
|
|
|
1178
1331
|
@template_progress_mutex.synchronize(&block)
|
|
1179
1332
|
end
|
|
1180
1333
|
|
|
1181
|
-
def emit_template_progress_summary(results)
|
|
1182
|
-
return unless progress_io
|
|
1183
|
-
|
|
1184
|
-
template_results = results.select { |result| result.phase == "template" }
|
|
1185
|
-
changed_files = template_results.sum { |result| template_changed_file_count(result) }
|
|
1186
|
-
progress_io.puts
|
|
1187
|
-
progress_io.puts("template summary: #{template_results.count(&:ok?)}/#{template_results.length} members ok, #{changed_files} file#{"s" unless changed_files == 1} changed")
|
|
1188
|
-
progress_io.flush if progress_io.respond_to?(:flush)
|
|
1189
|
-
end
|
|
1190
|
-
|
|
1191
1334
|
def template_changed_file_count(result)
|
|
1192
1335
|
event_count = template_changed_file_count_from_events(result.stdout)
|
|
1193
1336
|
return event_count if event_count
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tty-progressbar"
|
|
4
|
+
|
|
5
|
+
module Kettle
|
|
6
|
+
module Family
|
|
7
|
+
class WorkflowProgress
|
|
8
|
+
FORMAT = "%<member>-24s :events :status"
|
|
9
|
+
EVENT_WIDTH = 30
|
|
10
|
+
|
|
11
|
+
def initialize(io:, label:, total:, jobs:, enabled: true)
|
|
12
|
+
@io = io
|
|
13
|
+
@label = label
|
|
14
|
+
@total = total.to_i
|
|
15
|
+
@jobs = jobs.to_i
|
|
16
|
+
@enabled = enabled && !!io
|
|
17
|
+
@bars = {}
|
|
18
|
+
@member_totals = {}
|
|
19
|
+
@member_events = Hash.new("")
|
|
20
|
+
@mutex = Mutex.new
|
|
21
|
+
@tty = @enabled && io.respond_to?(:tty?) && io.tty?
|
|
22
|
+
@multibar = @tty ? TTY::ProgressBar::Multi.new(output: io) : nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def start
|
|
26
|
+
return unless @enabled
|
|
27
|
+
|
|
28
|
+
write_line("#{@label} #{@total} member#{plural(@total)} with #{@jobs} job#{plural(@jobs)}:")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def start_member(member, total:, status:)
|
|
32
|
+
return unless @enabled
|
|
33
|
+
|
|
34
|
+
synchronize do
|
|
35
|
+
@member_totals[member.name] = total.to_i
|
|
36
|
+
if @tty
|
|
37
|
+
render(member, status: status)
|
|
38
|
+
else
|
|
39
|
+
write_line("[#{member.name}] > #{status}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def advance(member, status:, success: true, mark: nil)
|
|
45
|
+
return unless @enabled
|
|
46
|
+
|
|
47
|
+
synchronize do
|
|
48
|
+
event_mark = mark || (success ? "." : "F")
|
|
49
|
+
if @tty
|
|
50
|
+
append_event(member, event_mark)
|
|
51
|
+
render(member, status: status)
|
|
52
|
+
else
|
|
53
|
+
write_line("[#{member.name}] #{event_mark} #{status}")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def update(member, status:, mark: nil)
|
|
59
|
+
return unless @enabled
|
|
60
|
+
return if status.to_s.empty?
|
|
61
|
+
|
|
62
|
+
synchronize do
|
|
63
|
+
if @tty
|
|
64
|
+
append_event(member, mark) if mark
|
|
65
|
+
render(member, status: status)
|
|
66
|
+
else
|
|
67
|
+
write_line("[#{member.name}] #{mark || ">"} #{status}")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def finish_member(member, success:, status:)
|
|
73
|
+
return unless @enabled
|
|
74
|
+
|
|
75
|
+
synchronize do
|
|
76
|
+
if @tty
|
|
77
|
+
render(member, status: status)
|
|
78
|
+
else
|
|
79
|
+
write_line("[#{member.name}] #{success ? "done" : "failed"} #{status}")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def stop
|
|
85
|
+
return unless @enabled
|
|
86
|
+
|
|
87
|
+
synchronize { @multibar&.stop }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def tty?
|
|
91
|
+
@tty
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def summary(message)
|
|
95
|
+
return unless @enabled
|
|
96
|
+
|
|
97
|
+
synchronize do
|
|
98
|
+
@io.puts unless @tty
|
|
99
|
+
write_line(message)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def render(member, status:)
|
|
106
|
+
bar_for(member).advance(0, events: event_tape(member), status: status)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def append_event(member, mark)
|
|
110
|
+
return if mark.to_s.empty?
|
|
111
|
+
|
|
112
|
+
@member_events[member.name] = (@member_events[member.name] + mark.to_s).chars.last(EVENT_WIDTH).join
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def event_tape(member)
|
|
116
|
+
@member_events[member.name].rjust(EVENT_WIDTH)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def bar_for(member)
|
|
120
|
+
@bars[member.name] ||= @multibar.register(Kernel.format(FORMAT, member: member.name), total: nil)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def plural(count)
|
|
124
|
+
"s" unless count == 1
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def write_line(line)
|
|
128
|
+
@io.puts(line)
|
|
129
|
+
@io.flush if @io.respond_to?(:flush)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def synchronize(&block)
|
|
133
|
+
@mutex.synchronize(&block)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
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.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -119,6 +119,20 @@ dependencies:
|
|
|
119
119
|
- - "~>"
|
|
120
120
|
- !ruby/object:Gem::Version
|
|
121
121
|
version: '0.2'
|
|
122
|
+
- !ruby/object:Gem::Dependency
|
|
123
|
+
name: tty-progressbar
|
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - "~>"
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '0.18'
|
|
129
|
+
type: :runtime
|
|
130
|
+
prerelease: false
|
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - "~>"
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '0.18'
|
|
122
136
|
- !ruby/object:Gem::Dependency
|
|
123
137
|
name: bundler-audit
|
|
124
138
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -298,16 +312,17 @@ files:
|
|
|
298
312
|
- lib/kettle/family/version_bump.rb
|
|
299
313
|
- lib/kettle/family/version_gem.rb
|
|
300
314
|
- lib/kettle/family/workflow.rb
|
|
315
|
+
- lib/kettle/family/workflow_progress.rb
|
|
301
316
|
- sig/kettle/family.rbs
|
|
302
317
|
homepage: https://github.com/kettle-dev/kettle-family
|
|
303
318
|
licenses:
|
|
304
319
|
- AGPL-3.0-only
|
|
305
320
|
metadata:
|
|
306
321
|
homepage_uri: https://kettle-family.galtzo.com
|
|
307
|
-
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.0
|
|
308
|
-
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.0
|
|
322
|
+
source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.1.0
|
|
323
|
+
changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.1.0/CHANGELOG.md
|
|
309
324
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
|
|
310
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.0
|
|
325
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.1.0
|
|
311
326
|
funding_uri: https://github.com/sponsors/pboling
|
|
312
327
|
wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
|
|
313
328
|
news_uri: https://www.railsbling.com/tags/kettle-family
|
metadata.gz.sig
CHANGED
|
Binary file
|