kettle-family 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 982720b995ca0f74423a382c1e46d8f8b9fe27570dcc2f4d5bf157f14622276c
4
- data.tar.gz: 942038041794d1744e68342280b457457de52c41f8ce2558bd568b8ebb429ca1
3
+ metadata.gz: 98d4c89b4fe8c5b8dc01b6b16624771da61982b4393117243b8888ca0c34358f
4
+ data.tar.gz: b6e7db140bf96d05083d2fc9239b1721528c6f587563ecfe6313b9f936354b47
5
5
  SHA512:
6
- metadata.gz: caeb1ffc7bd5bc0cddd161db05ec9b2c505373ee295c39531736adfd5f19bf014690590b6e890690b71b2445cf81e49585d67f206117a7d2c2770807fd87046f
7
- data.tar.gz: fbe08c43a39f538c45fb5918e6661f5d7d317721908710ddc29fd6b67f3269648874f43c6b98591b7c4a3ff06c347428c30efd2ae3067c34de449cc0983df257
6
+ metadata.gz: 2e181e19e17b26a281ec9787dac2fdfcba5811107b35a3bd0fb943411df3d7e2c9aee10903ed846939353d5c7c344992d8397e241e2497a959151bf2742e9f4d
7
+ data.tar.gz: 155c75272299e2a04c342bcadbb4a6d07d9b7a86154e4c4c177074678cc6c07ed7dc44042ce695d1baed49ea918b57215060bf98e073fc89397f081de5245427
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,33 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.1.6] - 2026-07-25
34
+
35
+ - TAG: [v1.1.6][1.1.6t]
36
+ - COVERAGE: 95.19% -- 3084/3240 lines in 25 files
37
+ - BRANCH COVERAGE: 76.80% -- 1132/1474 branches in 25 files
38
+ - 28.68% documented
39
+
40
+ ### Fixed
41
+
42
+ - Release summaries now count only members that reached a release terminal
43
+ phase as succeeded, so dependency-floor side effects do not appear as
44
+ published gems after an earlier failure.
45
+ - Dependency-floor lockfile refresh now uses checksum-aware Bundler locking
46
+ (`bundle lock --update ... --add-checksums`) before validating just-published
47
+ sibling floors, so missing or empty checksum entries still retry and fail
48
+ instead of being accepted.
49
+ - Interactive release commands now suppress consumed `kettle-release --events`
50
+ NDJSON lines from terminal output while keeping them captured for progress and
51
+ failure summaries.
52
+
53
+ ### Changed
54
+
55
+ - kettle-jem-template-20260725-001 - Generated JRuby and TruffleRuby workflow
56
+ files now run when pull request head branches start with `feature/release`,
57
+ so release CI monitoring does not report intentionally skipped engine
58
+ workflows as failures.
59
+
33
60
  ## [1.1.5] - 2026-07-25
34
61
 
35
62
  - TAG: [v1.1.5][1.1.5t]
@@ -986,7 +1013,9 @@ Please file a bug if you notice a violation of semantic versioning.
986
1013
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
987
1014
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
988
1015
 
989
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.1.5...HEAD
1016
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.1.6...HEAD
1017
+ [1.1.6]: https://github.com/kettle-dev/kettle-family/compare/v1.1.5...v1.1.6
1018
+ [1.1.6t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.1.6
990
1019
  [1.1.5]: https://github.com/kettle-dev/kettle-family/compare/v1.1.4...v1.1.5
991
1020
  [1.1.5t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.1.5
992
1021
  [1.1.4]: https://github.com/kettle-dev/kettle-family/compare/v1.1.3...v1.1.4
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-3.192-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
672
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.240-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
@@ -119,7 +119,14 @@ module Kettle
119
119
 
120
120
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
121
121
  stdout, stderr, status = if interactive
122
- run_interactive(env: process_env, argv: argv, chdir: member.root, member_name: member.name, process_options: spawn_options)
122
+ run_interactive(
123
+ env: process_env,
124
+ argv: argv,
125
+ chdir: member.root,
126
+ member_name: member.name,
127
+ process_options: spawn_options,
128
+ stdout_line_handler: stdout_line_handler
129
+ )
123
130
  elsif stdout_line_handler
124
131
  run_streaming(env: process_env, argv: argv, chdir: member.root, process_options: spawn_options, stdout_line_handler: stdout_line_handler)
125
132
  else
@@ -183,14 +190,31 @@ module Kettle
183
190
  remainder
184
191
  end
185
192
 
186
- def run_interactive(env:, argv:, chdir:, member_name:, process_options:)
187
- return run_interactive_pty(env: env, argv: argv, chdir: chdir, member_name: member_name, process_options: process_options) if pty_available?
193
+ def run_interactive(env:, argv:, chdir:, member_name:, process_options:, stdout_line_handler:)
194
+ if pty_available?
195
+ return run_interactive_pty(
196
+ env: env,
197
+ argv: argv,
198
+ chdir: chdir,
199
+ member_name: member_name,
200
+ process_options: process_options,
201
+ stdout_line_handler: stdout_line_handler
202
+ )
203
+ end
188
204
 
189
- run_interactive_open3(env: env, argv: argv, chdir: chdir, member_name: member_name, process_options: process_options)
205
+ run_interactive_open3(
206
+ env: env,
207
+ argv: argv,
208
+ chdir: chdir,
209
+ member_name: member_name,
210
+ process_options: process_options,
211
+ stdout_line_handler: stdout_line_handler
212
+ )
190
213
  end
191
214
 
192
- def run_interactive_pty(env:, argv:, chdir:, member_name:, process_options:)
215
+ def run_interactive_pty(env:, argv:, chdir:, member_name:, process_options:, stdout_line_handler:)
193
216
  stdout = +""
217
+ stdout_line_buffer = +""
194
218
  status = nil
195
219
  PTY.spawn(env, *argv, chdir: chdir, **process_options) do |output, input, pid|
196
220
  begin
@@ -202,7 +226,7 @@ module Kettle
202
226
  if reader.equal?(output)
203
227
  chunk = output.readpartial(1024)
204
228
  stdout << chunk
205
- $stdout.print(chunk)
229
+ stdout_line_buffer = print_interactive_stdout(stdout_line_buffer, chunk, stdout_line_handler)
206
230
  handle_interactive_prompt(input, chunk, member_name: member_name)
207
231
  else
208
232
  input.write($stdin.readpartial(1024))
@@ -212,14 +236,16 @@ module Kettle
212
236
  rescue Errno::EIO
213
237
  # PTY raises EIO when the child process exits after closing the slave.
214
238
  end
239
+ flush_interactive_stdout(stdout_line_buffer, stdout_line_handler)
215
240
  _, status = Process.wait2(pid)
216
241
  end
217
242
  [stdout, "", status]
218
243
  end
219
244
 
220
- def run_interactive_open3(env:, argv:, chdir:, member_name:, process_options:)
245
+ def run_interactive_open3(env:, argv:, chdir:, member_name:, process_options:, stdout_line_handler:)
221
246
  captured_stdout = +""
222
247
  captured_stderr = +""
248
+ stdout_line_buffer = +""
223
249
  status = nil
224
250
  Open3.popen3(env, *argv, chdir: chdir, **process_options) do |input, output, error, wait_thread|
225
251
  readers = [output, error]
@@ -230,27 +256,86 @@ module Kettle
230
256
  if reader.equal?($stdin)
231
257
  input.write($stdin.readpartial(1024))
232
258
  else
233
- read_interactive_stream(reader, output, input, captured_stdout, captured_stderr, readers, member_name: member_name)
259
+ stdout_line_buffer = read_interactive_stream(
260
+ reader,
261
+ output,
262
+ input,
263
+ captured_stdout,
264
+ captured_stderr,
265
+ readers,
266
+ member_name: member_name,
267
+ stdout_line_buffer: stdout_line_buffer,
268
+ stdout_line_handler: stdout_line_handler
269
+ )
234
270
  end
235
271
  end
236
272
  end
273
+ flush_interactive_stdout(stdout_line_buffer, stdout_line_handler)
237
274
  status = wait_thread.value
238
275
  end
239
276
  [captured_stdout, captured_stderr, status]
240
277
  end
241
278
 
242
- def read_interactive_stream(reader, output, input, captured_stdout, captured_stderr, readers, member_name:)
279
+ def read_interactive_stream(
280
+ reader,
281
+ output,
282
+ input,
283
+ captured_stdout,
284
+ captured_stderr,
285
+ readers,
286
+ member_name:,
287
+ stdout_line_buffer:,
288
+ stdout_line_handler:
289
+ )
243
290
  chunk = reader.readpartial(1024)
244
291
  if reader.equal?(output)
245
292
  captured_stdout << chunk
246
- $stdout.print(chunk)
293
+ stdout_line_buffer = print_interactive_stdout(stdout_line_buffer, chunk, stdout_line_handler)
247
294
  else
248
295
  captured_stderr << chunk
249
296
  $stderr.print(chunk)
250
297
  end
251
298
  handle_interactive_prompt(input, chunk, member_name: member_name)
299
+ stdout_line_buffer
252
300
  rescue EOFError
253
301
  readers.delete(reader)
302
+ stdout_line_buffer
303
+ end
304
+
305
+ def print_interactive_stdout(buffer, chunk, stdout_line_handler)
306
+ return print_interactive_chunk(chunk) unless stdout_line_handler
307
+
308
+ pending = buffer + chunk
309
+ lines = pending.lines
310
+ remainder = pending.end_with?("\n") ? +"" : lines.pop.to_s
311
+ lines.each { |line| print_interactive_line(line, stdout_line_handler) }
312
+ return +"" if remainder.empty?
313
+ unless possible_event_line?(remainder)
314
+ $stdout.print(remainder)
315
+ return +""
316
+ end
317
+
318
+ remainder
319
+ end
320
+
321
+ def flush_interactive_stdout(buffer, stdout_line_handler)
322
+ return if buffer.empty?
323
+
324
+ print_interactive_line(buffer, stdout_line_handler)
325
+ end
326
+
327
+ def print_interactive_chunk(chunk)
328
+ $stdout.print(chunk)
329
+ +""
330
+ end
331
+
332
+ def print_interactive_line(line, stdout_line_handler)
333
+ consumed = possible_event_line?(line) && stdout_line_handler.call(line.chomp)
334
+ $stdout.print(line) unless consumed
335
+ end
336
+
337
+ def possible_event_line?(line)
338
+ line.to_s.lstrip.start_with?("{")
254
339
  end
255
340
 
256
341
  def pty_available?
@@ -314,6 +314,8 @@ module Kettle
314
314
 
315
315
  def summary_succeeded
316
316
  selected_member_results.filter_map do |member_name, member_results|
317
+ next if member_results.empty?
318
+ member_results = summary_terminal_member_results(member_results)
317
319
  next if member_results.empty?
318
320
  next if member_results.any? { |result| !result.ok? }
319
321
  next if member_results.all?(&:skipped)
@@ -324,6 +326,8 @@ module Kettle
324
326
 
325
327
  def summary_skipped
326
328
  selected_member_results.filter_map do |member_name, member_results|
329
+ next if member_results.empty?
330
+ member_results = summary_terminal_member_results(member_results)
327
331
  next if member_results.empty?
328
332
  next unless member_results.all?(&:skipped)
329
333
 
@@ -345,7 +349,7 @@ module Kettle
345
349
  return [] unless member_result_command?
346
350
  return [] if visible_results.empty?
347
351
 
348
- ran = selected_member_results.keys
352
+ ran = summary_ran_member_names
349
353
  reason = pending_reason
350
354
  (selected_names - ran).map do |member_name|
351
355
  {
@@ -364,6 +368,28 @@ module Kettle
364
368
  end
365
369
  end
366
370
 
371
+ def summary_ran_member_names
372
+ return selected_member_results.keys unless command == "release"
373
+
374
+ failed = visible_results.reject(&:ok?).map(&:member_name)
375
+ terminal = selected_member_results.filter_map do |member_name, member_results|
376
+ member_name if member_results.any? { |result| release_terminal_result?(result) }
377
+ end
378
+ (failed + terminal).uniq
379
+ end
380
+
381
+ def summary_terminal_member_results(member_results)
382
+ return member_results unless command == "release"
383
+
384
+ member_results.select { |result| release_terminal_result?(result) }
385
+ end
386
+
387
+ def release_terminal_result?(result)
388
+ result.phase == "release_skip" ||
389
+ result.phase == "release_publish" ||
390
+ result.phase == "release_build"
391
+ end
392
+
367
393
  def summary_list(values)
368
394
  values.empty? ? "none" : values.join(", ")
369
395
  end
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Family
5
5
  module Version
6
- VERSION = "1.1.5"
6
+ VERSION = "1.1.6"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
@@ -623,7 +623,7 @@ module Kettle
623
623
  result = runner.call(
624
624
  member: member,
625
625
  phase: "dependency_floor_lockfiles",
626
- command: ["bundle", "update", *released_members.map(&:name)],
626
+ command: dependency_floor_lockfile_command(released_members),
627
627
  env: release_lockfile_env
628
628
  )
629
629
  validate_dependency_floor_lockfile_result(result: result, member: member, released_members: released_members) if result.ok?
@@ -659,6 +659,10 @@ module Kettle
659
659
  end
660
660
  end
661
661
 
662
+ def dependency_floor_lockfile_command(released_members)
663
+ ["bundle", "lock", "--update", *released_members.map(&:name), "--add-checksums"]
664
+ end
665
+
662
666
  def checksum_line_for(lockfile_source:, member:)
663
667
  in_checksums = false
664
668
  lockfile_source.each_line do |line|
@@ -1220,13 +1224,14 @@ module Kettle
1220
1224
 
1221
1225
  lambda do |line|
1222
1226
  event = parse_template_event(line)
1223
- next unless event
1227
+ next false unless event
1224
1228
 
1225
1229
  if verbose || debug
1226
1230
  emit_template_event_progress(member, event)
1227
1231
  else
1228
1232
  emit_template_event_status(member, event, progress: progress)
1229
1233
  end
1234
+ true
1230
1235
  end
1231
1236
  end
1232
1237
 
@@ -1236,13 +1241,14 @@ module Kettle
1236
1241
 
1237
1242
  lambda do |line|
1238
1243
  event = parse_template_event(line)
1239
- next unless event
1244
+ next false unless event
1240
1245
 
1241
1246
  if verbose || debug
1242
1247
  emit_release_event_progress(member, event)
1243
1248
  else
1244
1249
  emit_release_event_status(member, event, progress: progress)
1245
1250
  end
1251
+ true
1246
1252
  end
1247
1253
  end
1248
1254
 
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.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -319,10 +319,10 @@ licenses:
319
319
  - AGPL-3.0-only
320
320
  metadata:
321
321
  homepage_uri: https://kettle-family.galtzo.com
322
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.1.5
323
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.1.5/CHANGELOG.md
322
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.1.6
323
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.1.6/CHANGELOG.md
324
324
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
325
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.1.5
325
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.1.6
326
326
  funding_uri: https://github.com/sponsors/pboling
327
327
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
328
328
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file