kettle-family 0.2.3 → 0.2.4

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: 1321ed38de6cb72dbb54f07eb9d64ad603d534ee0bde3986c69a12aea3209bd7
4
- data.tar.gz: fb828bcc85d5a373bfbaa19b9f409e4ccc061840943bb7605337106c346d1531
3
+ metadata.gz: 77de418bfa9adc07c6500af0e4231f28909e6480fb96b730e34ebb215add0f04
4
+ data.tar.gz: 3667086145e554a4d4d89e68a2c4b5b2b1b00f59c799040f69a918c236c725ec
5
5
  SHA512:
6
- metadata.gz: 93890dd533ed38d98f53ddf6c6c3a54e2be63e62d115e90e1c87caa3613eaeb0fe73c4fc76baf6de3f92c68591cbb73ac8dc6e869a817ff5ad7a8db200021ddb
7
- data.tar.gz: 36fe85d72cc06f7bae910e45161ebc65d4d1f847f9627c18625b402a401be8f2698bffb13562fbbd881b3c63b90cdcc03c412f6e9ea2ce4d35be307dfa25a8d1
6
+ metadata.gz: 30f2a3b90e0fc17022b97a384277dc004b78e4cee2a4a6d1bdf515425db3f2173a846ac00f7614031fda678ceacf6dcd7e7d75fbcb6bfc582ed29ea4debeddb7
7
+ data.tar.gz: c1569420377c2692180d7f04a8312de902efac20b84d390514e9599a6d7a5025761abe9a9e2b647ccb713f2dbdc8fa02ee142e52a9df0389f54c7393a0a726ef
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,19 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [0.2.4] - 2026-07-04
34
+
35
+ - TAG: [v0.2.4][0.2.4t]
36
+ - COVERAGE: 95.33% -- 2288/2400 lines in 22 files
37
+ - BRANCH COVERAGE: 76.42% -- 752/984 branches in 22 files
38
+ - 30.04% documented
39
+
40
+ ### Added
41
+
42
+ - Family releases now raise downstream family dependency floors after each
43
+ member release by default, with `--no-auto-floors` and
44
+ `release.auto_dependency_floors: false` opt-outs.
45
+
33
46
  ## [0.2.3] - 2026-07-03
34
47
 
35
48
  - TAG: [v0.2.3][0.2.3t]
@@ -630,7 +643,9 @@ Please file a bug if you notice a violation of semantic versioning.
630
643
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
631
644
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
632
645
 
633
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.2.3...HEAD
646
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v0.2.4...HEAD
647
+ [0.2.4]: https://github.com/kettle-dev/kettle-family/compare/v0.2.3...v0.2.4
648
+ [0.2.4t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.2.4
634
649
  [0.2.3]: https://github.com/kettle-dev/kettle-family/compare/v0.2.2...v0.2.3
635
650
  [0.2.3t]: https://github.com/kettle-dev/kettle-family/releases/tag/v0.2.3
636
651
  [0.2.2]: https://github.com/kettle-dev/kettle-family/compare/v0.2.1...v0.2.2
data/README.md CHANGED
@@ -592,7 +592,7 @@ Thanks for RTFM. ☺️
592
592
  [📌gitmoji]: https://gitmoji.dev
593
593
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
594
594
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
595
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.314-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
595
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-2.400-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
596
596
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
597
597
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
598
598
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -403,6 +403,9 @@ module Kettle
403
403
  option :skip_steps, long: "--skip-steps", value: {type: String, usage: "LIST"}, desc: "Pass skip_steps=LIST through to kettle-release commands"
404
404
  option :local_ci, long: "--local-ci", desc: "Pass --local-ci through to kettle-release commands"
405
405
  option :continue_ci_failures, long: "--continue-ci-failures", desc: "Set K_RELEASE_CI_CONTINUE=true for release commands"
406
+ option :no_auto_floors, long: "--no-auto-floors", desc: "Do not raise family dependency floors between member releases" do
407
+ options[:no_auto_floors] = true
408
+ end
406
409
  option :accept, desc: "Answer yes to confirmation prompts in interactive commands"
407
410
  option :no_accept, long: "--no-accept", desc: "Wait for user input at confirmation prompts" do
408
411
  options[:accept] = false
@@ -419,6 +422,7 @@ module Kettle
419
422
  release_skip_steps: options[:skip_steps],
420
423
  release_local_ci: truthy_option?(:local_ci),
421
424
  release_continue_ci_failures: truthy_option?(:continue_ci_failures),
425
+ release_auto_dependency_floors: !truthy_option?(:no_auto_floors),
422
426
  accept: !options.key?(:accept) || options[:accept],
423
427
  tag: truthy_option?(:tag),
424
428
  push: truthy_option?(:push)
@@ -555,6 +559,7 @@ module Kettle
555
559
  skip_steps: options[:release_skip_steps],
556
560
  local_ci: options[:release_local_ci],
557
561
  continue_ci_failures: options[:release_continue_ci_failures],
562
+ auto_dependency_floors: options[:release_auto_dependency_floors],
558
563
  gha_sha_pins_upgrade: options[:gha_sha_pins_upgrade],
559
564
  gha_sha_pins_check: options[:check],
560
565
  env_overrides: options[:workflow_env],
@@ -212,6 +212,10 @@ module Kettle
212
212
  fetch_path("release", "jobs")
213
213
  end
214
214
 
215
+ def release_auto_dependency_floors?
216
+ fetch_path("release", "auto_dependency_floors") != false
217
+ end
218
+
215
219
  def release_family_changelog?
216
220
  fetch_path("release", "family_changelog", "enabled") == true
217
221
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "kettle/dev"
4
+
5
+ module Kettle
6
+ module Family
7
+ class DependencyFloor
8
+ DEPENDENCY_METHODS = %i[add_dependency add_runtime_dependency].freeze
9
+
10
+ def initialize(released_members:, dependent_members:, mode: :dry_run)
11
+ @released_members = released_members
12
+ @dependent_members = dependent_members
13
+ @mode = mode
14
+ @released_versions = released_members.to_h { |member| [member.name, Gem::Version.new(member.version.to_s)] }
15
+ end
16
+
17
+ def results
18
+ dependent_members.filter_map { |member| result_for(member) }
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :released_members, :dependent_members, :mode, :released_versions
24
+
25
+ def result_for(member)
26
+ edits = collect_edits(member)
27
+ return if edits.empty?
28
+
29
+ write_edits(edits) if mode == :execute
30
+ CommandResult.new(
31
+ member_name: member.name,
32
+ phase: "dependency_floor",
33
+ command: ["internal", "dependency-floor", released_versions.keys.join(",")],
34
+ workdir: member.root,
35
+ status: 0,
36
+ success: true,
37
+ stdout: edit_summary(member: member, edits: edits),
38
+ stderr: "",
39
+ elapsed_seconds: 0.0,
40
+ skipped: mode != :execute,
41
+ reason: (mode == :execute) ? nil : "dry-run; pass --execute to write"
42
+ )
43
+ end
44
+
45
+ def collect_edits(member)
46
+ return [] unless member.gemspec_path && File.file?(member.gemspec_path)
47
+
48
+ source = File.read(member.gemspec_path)
49
+ parse_result = with_dev_errors { Kettle::Dev::VersionBump.parse_source(source, member.gemspec_path) }
50
+ Kettle::Dev::VersionBump.each_node(parse_result.value).filter_map do |node|
51
+ dependency_edit_for(member.gemspec_path, source, node)
52
+ end
53
+ end
54
+
55
+ def dependency_edit_for(path, source, node)
56
+ return unless node.is_a?(Prism::CallNode) && DEPENDENCY_METHODS.include?(node.name)
57
+
58
+ args = node.arguments&.arguments || []
59
+ name_node = args.first
60
+ return unless name_node.is_a?(Prism::StringNode)
61
+
62
+ dependency_name = name_node.unescaped
63
+ released_version = released_versions[dependency_name]
64
+ return unless released_version
65
+
66
+ floor_node = requirement_nodes(args.drop(1)).find do |requirement_node|
67
+ requirement_node.unescaped.start_with?(">= ")
68
+ end
69
+ return unless floor_node
70
+
71
+ current_floor = floor_node.unescaped.delete_prefix(">= ").strip
72
+ return unless floor_lower_than?(current_floor, released_version)
73
+
74
+ replacement = Kettle::Dev::VersionBump.quote_like(floor_node.location.slice, ">= #{released_version}")
75
+ Kettle::Dev::VersionBump.file_edit(path, source, floor_node.location.start_offset, floor_node.location.end_offset, replacement)
76
+ end
77
+
78
+ def requirement_nodes(nodes)
79
+ nodes.flat_map do |node|
80
+ case node
81
+ when Prism::StringNode
82
+ [node]
83
+ when Prism::ArrayNode
84
+ requirement_nodes(node.elements)
85
+ else
86
+ []
87
+ end
88
+ end
89
+ end
90
+
91
+ def floor_lower_than?(current_floor, released_version)
92
+ Gem::Version.new(current_floor) < released_version
93
+ rescue ArgumentError
94
+ false
95
+ end
96
+
97
+ def write_edits(edits)
98
+ Kettle::Dev::VersionBump.write_edits(edits)
99
+ end
100
+
101
+ def edit_summary(member:, edits:)
102
+ verb = (mode == :execute) ? "updated" : "would update"
103
+ ["#{verb} #{edits.length} family dependency floor(s) for #{member.name}", *edits.map { |edit| "#{verb} #{edit.fetch(:path)}" }.uniq].join("\n")
104
+ end
105
+
106
+ def with_dev_errors
107
+ yield
108
+ rescue Kettle::Dev::Error => error
109
+ raise Error, error.message
110
+ end
111
+ end
112
+ end
113
+ end
@@ -3,7 +3,7 @@
3
3
  module Kettle
4
4
  module Family
5
5
  module Version
6
- VERSION = "0.2.3"
6
+ VERSION = "0.2.4"
7
7
  end
8
8
  VERSION = Version::VERSION # Traditional Constant Location
9
9
  end
@@ -44,7 +44,7 @@ module Kettle
44
44
  "BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES" => "true"
45
45
  }.freeze
46
46
 
47
- def initialize(command:, config:, members:, execute: false, accept: true, commit: true, allow_dirty: false, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, local_ci: false, continue_ci_failures: false, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, env_overrides: {}, debug: false, gem_signing_password: nil, jobs: nil, progress_io: nil, bup_args: [], bex_args: [], start_member: nil, start_branch: nil)
47
+ def initialize(command:, config:, members:, execute: false, accept: true, commit: true, allow_dirty: false, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, local_ci: false, continue_ci_failures: false, auto_dependency_floors: nil, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, env_overrides: {}, debug: false, gem_signing_password: nil, jobs: nil, progress_io: nil, bup_args: [], bex_args: [], start_member: nil, start_branch: nil)
48
48
  @command = command
49
49
  @config = config
50
50
  @members = members
@@ -59,6 +59,7 @@ module Kettle
59
59
  @skip_steps = skip_steps
60
60
  @local_ci = local_ci
61
61
  @continue_ci_failures = continue_ci_failures
62
+ @auto_dependency_floors = auto_dependency_floors.nil? ? config.release_auto_dependency_floors? : auto_dependency_floors
62
63
  @gha_sha_pins_upgrade = gha_sha_pins_upgrade
63
64
  @gha_sha_pins_check = gha_sha_pins_check
64
65
  @env_overrides = env_overrides
@@ -85,7 +86,7 @@ module Kettle
85
86
 
86
87
  private
87
88
 
88
- attr_reader :command, :config, :members, :execute, :accept, :commit, :allow_dirty, :publish, :push, :tag, :start_step, :skip_steps, :local_ci, :continue_ci_failures, :gha_sha_pins_upgrade, :gha_sha_pins_check, :env_overrides, :debug, :jobs, :progress_io, :bup_args, :bex_args, :start_member, :start_branch
89
+ attr_reader :command, :config, :members, :execute, :accept, :commit, :allow_dirty, :publish, :push, :tag, :start_step, :skip_steps, :local_ci, :continue_ci_failures, :auto_dependency_floors, :gha_sha_pins_upgrade, :gha_sha_pins_check, :env_overrides, :debug, :jobs, :progress_io, :bup_args, :bex_args, :start_member, :start_branch
89
90
 
90
91
  def current_branch_results(workflow_members)
91
92
  return check_results(workflow_members) if command == "check"
@@ -286,6 +287,7 @@ module Kettle
286
287
  skip_steps: skip_steps,
287
288
  local_ci: local_ci,
288
289
  continue_ci_failures: continue_ci_failures,
290
+ auto_dependency_floors: auto_dependency_floors,
289
291
  gha_sha_pins_upgrade: gha_sha_pins_upgrade,
290
292
  gha_sha_pins_check: gha_sha_pins_check,
291
293
  env_overrides: env_overrides,
@@ -315,17 +317,27 @@ module Kettle
315
317
  release_members.each_with_object(results) do |member, memo|
316
318
  memo.concat(release_results_for_member(member, runner: runner))
317
319
  break memo unless memo.last.ok?
320
+
321
+ remaining_members = release_members.drop(release_members.index(member) + 1)
322
+ append_dependency_floor_results(released_members: [member], dependent_members: remaining_members, runner: runner, memo: memo)
323
+ break memo unless memo.last&.ok?
318
324
  end
319
325
  end
320
326
 
321
327
  def parallel_release_member_results(release_members, initial_results)
322
328
  results = initial_results.dup
323
329
  waves = release_waves(release_members)
330
+ completed_members = []
324
331
  waves.each_with_index do |wave, index|
325
332
  results << release_wave_result(wave, index: index, total: waves.length)
326
333
  wave_results = run_release_wave(wave)
327
334
  results.concat(wave_results.flatten)
328
335
  break unless wave_results.all? { |member_results| member_results.all?(&:ok?) }
336
+
337
+ completed_members.concat(wave)
338
+ remaining_members = release_members - completed_members
339
+ append_dependency_floor_results(released_members: wave, dependent_members: remaining_members, runner: release_command_runner, memo: results)
340
+ break unless results.last&.ok?
329
341
  end
330
342
  results
331
343
  end
@@ -477,6 +489,38 @@ module Kettle
477
489
  Array(member.dependencies).map(&:to_s).select { |dependency| by_name.key?(dependency) }
478
490
  end
479
491
 
492
+ def append_dependency_floor_results(released_members:, dependent_members:, runner:, memo:)
493
+ return unless auto_dependency_floors
494
+ return if dependent_members.empty?
495
+
496
+ floor_results = DependencyFloor.new(
497
+ released_members: released_members,
498
+ dependent_members: dependent_members,
499
+ mode: execute ? :execute : :dry_run
500
+ ).results
501
+ memo.concat(floor_results)
502
+ return unless floor_results.any? && floor_results.all?(&:ok?)
503
+
504
+ commit_dependency_floor_changes(dependent_members: floor_results.map(&:member_name), runner: runner, memo: memo) if execute && commit
505
+ end
506
+
507
+ def commit_dependency_floor_changes(dependent_members:, runner:, memo:)
508
+ by_name = members.to_h { |member| [member.name, member] }
509
+ dependent_members.each do |member_name|
510
+ member = by_name.fetch(member_name)
511
+ memo << runner.call(
512
+ member: member,
513
+ phase: "commit_dependency_floor",
514
+ command: [
515
+ "sh",
516
+ "-lc",
517
+ "if ! git diff --quiet -- '*.gemspec'; then git add -- '*.gemspec' && git commit -m '⬆️ Raise family dependency floors'; fi"
518
+ ]
519
+ )
520
+ break unless memo.last.ok?
521
+ end
522
+ end
523
+
480
524
  def distinct_git_roots?(release_members)
481
525
  roots = release_members.map { |member| git_root_for(member) }
482
526
  roots.uniq.length == roots.length
data/lib/kettle/family.rb CHANGED
@@ -11,6 +11,7 @@ require_relative "family/readiness_check"
11
11
  require_relative "family/changelog_check"
12
12
  require_relative "family/git_status"
13
13
  require_relative "family/version_bump"
14
+ require_relative "family/dependency_floor"
14
15
  require_relative "family/branch_lane_audit"
15
16
  require_relative "family/config"
16
17
  require_relative "family/branch_target_config"
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: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -319,6 +319,7 @@ files:
319
319
  - lib/kettle/family/command_result.rb
320
320
  - lib/kettle/family/command_runner.rb
321
321
  - lib/kettle/family/config.rb
322
+ - lib/kettle/family/dependency_floor.rb
322
323
  - lib/kettle/family/discovery.rb
323
324
  - lib/kettle/family/git_status.rb
324
325
  - lib/kettle/family/local_install.rb
@@ -339,10 +340,10 @@ licenses:
339
340
  - AGPL-3.0-only
340
341
  metadata:
341
342
  homepage_uri: https://kettle-family.galtzo.com
342
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.2.3
343
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.2.3/CHANGELOG.md
343
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v0.2.4
344
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v0.2.4/CHANGELOG.md
344
345
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
345
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.2.3
346
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/0.2.4
346
347
  funding_uri: https://github.com/sponsors/pboling
347
348
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
348
349
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file