kettle-family 1.3.32 → 1.3.34

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: '08c77450947a59e4d899708d9ea06ac1f6a3ecdbca9f1875697a111221c8a513'
4
- data.tar.gz: 39bab3f859dffecd3c7c7bf218bf60a31d593d174f7bf35cc78ee00120796e77
3
+ metadata.gz: 0f998250246be593256749a248a7ae97b9e22eada3f522aeee55a760c7c69286
4
+ data.tar.gz: 5260947209c6e41c32d71daac0efc7aed14def50a68c6f26c9d7faf0934ce501
5
5
  SHA512:
6
- metadata.gz: e11ec34af3513c579f0de85e0ac3967736d9296edd3181c824661d0e77c958dba7fde98870a37b8ab0e030776f24ff8f90cee743e989b174ded6745f6c589363
7
- data.tar.gz: f3420d41b085ff447bc05e0f4976fa4ee12b1eba42b73047a1afb5cadf8966b82f7a17430a2e9a5c7923f66ef6498646bcb7225aa71677264ed57cbf8c10fda6
6
+ metadata.gz: 83195b27096a7dc07f64984d5c28db5e5bbfbe101662216478550c644f90eba7eacc0d6fb86251dc6b8a88a48d39d3519582a9f038448bfea899c2b3507116c6
7
+ data.tar.gz: 512e1cb879df1ceb8e2dc1913e915beef55f696f0c6e97c453c3800e9bccb66293c178d9a30ce0a6384a20d6d48b8a83d6991311f23aa985109abb391fed2654
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,48 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.3.34] - 2026-10-25
34
+
35
+ - TAG: [v1.3.34][1.3.34t]
36
+ - COVERAGE: 93.08% -- 7556/8118 lines in 45 files
37
+ - BRANCH COVERAGE: 77.48% -- 3017/3894 branches in 45 files
38
+ - 38.89% documented
39
+
40
+ ### Changed
41
+
42
+ - [kc] shared-dependency-floor-engine: Family workflows now emit and tape NDJSON through kettle-ndjson; the family runtime and local bundle declare kettle-ndjson directly. Dependency-floor updates use kettle-deps-floor with explicit sibling release targets and no registry lookups. Nomono bootstrap raises Gemfile and local activation-array floors.
43
+
44
+ - [kc] shared-ndjson-event-integration: Use kettle-ndjson for family command summaries and per-member event tape serialization; family child-event names and payloads remain compatible with downstream progress/report handling. Declare kettle-ndjson as a direct runtime and local development dependency.
45
+
46
+ - [kc] kettle-jem/prepare: updated 19 project files:
47
+ - dependencies (19)
48
+
49
+ - [kc] kettle-jem/template: updated 3 project files:
50
+ - code and tests (1)
51
+ - documentation (1)
52
+ - other (1)
53
+
54
+ ## [1.3.33] - 2026-10-24
55
+
56
+ - TAG: [v1.3.33][1.3.33t]
57
+ - COVERAGE: 93.11% -- 7579/8140 lines in 45 files
58
+ - BRANCH COVERAGE: 77.55% -- 3036/3915 branches in 45 files
59
+ - 38.81% documented
60
+
61
+ ### Changed
62
+
63
+ - [kc] local-template-bundles-and-family-sponsors: kettle-family: managed tasks generate local-template for current member bundles and unreleased-template for stale destinations using the family-root Gemfile; family sponsor configuration preserves target_href/image_src, sponsorship windows, and umbrella members when passed to kettle-jem.
64
+
65
+ - [kc] kettle-jem/prepare: updated 9 project files:
66
+ - dependencies (9)
67
+
68
+ - [kc] kettle-jem/template: updated 13 project files:
69
+ - code and tests (2)
70
+ - dependencies (7)
71
+ - documentation (1)
72
+ - other (1)
73
+ - workflows (2)
74
+
33
75
  ## [1.3.32] - 2026-10-23
34
76
 
35
77
  - TAG: [v1.3.32][1.3.32t]
@@ -3236,7 +3278,11 @@ Please file a bug if you notice a violation of semantic versioning.
3236
3278
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
3237
3279
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
3238
3280
 
3239
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.32...HEAD
3281
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.3.34...HEAD
3282
+ [1.3.34]: https://github.com/kettle-dev/kettle-family/compare/v1.3.33...v1.3.34
3283
+ [1.3.34t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.34
3284
+ [1.3.33]: https://github.com/kettle-dev/kettle-family/compare/v1.3.32...v1.3.33
3285
+ [1.3.33t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.33
3240
3286
  [1.3.32]: https://github.com/kettle-dev/kettle-family/compare/v1.3.31...v1.3.32
3241
3287
  [1.3.32t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.3.32
3242
3288
  [1.3.31]: https://github.com/kettle-dev/kettle-family/compare/v1.3.30...v1.3.31
data/README.md CHANGED
@@ -66,6 +66,7 @@ configuration, create their own GitHub Release through `kettle-release`.
66
66
 
67
67
  Compatible with MRI Ruby 4.0.0+.
68
68
  CI workflows and Appraisals are generated for MRI Ruby 4.0.0+.
69
+ The current MRI CI lane runs on Linux and macOS.
69
70
  This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
70
71
  may be higher than the gem's runtime compatibility floor when legacy Rubies are
71
72
  not practical for the current toolchain.
@@ -90,21 +91,6 @@ The _amazing_ test matrix is powered by the kettle-dev stack.
90
91
 
91
92
  </details>
92
93
 
93
- ### Runtime and Platform Support
94
-
95
- Support tiers describe what this project's CI actually exercises, not what may
96
- work in an untested environment:
97
-
98
- - **Parallel support**: CI runs the test suite with multiple workers available.
99
- - **Unsupported**: the engine/platform combination is not covered by CI and is
100
- not a project compatibility claim.
101
-
102
- | Ruby engine/version | Linux | macOS | Windows | Notes |
103
- |---------------------|-------|-------|---------|-------|
104
- | MRI 4.0-4.0 | Parallel support | Unsupported | Unsupported | Versioned Ruby workflows run on Linux. |
105
- | MRI current | Parallel support | Parallel support | Unsupported | Current Ruby workflow covers the declared OS platforms. |
106
- | MRI head | Parallel support | Unsupported | Unsupported | CI is allowed to fail while Ruby is in development. |
107
-
108
94
  ### Federated DVCS
109
95
 
110
96
  <details markdown="1">
@@ -120,29 +106,12 @@ work in an untested environment:
120
106
 
121
107
  ### Enterprise Support
122
108
 
123
- [![Tidelift](https://tidelift.com/badges/package/rubygems/kettle-family)](https://tidelift.com/subscription/pkg/rubygems-kettle-family?utm_source=rubygems-kettle-family&utm_medium=referral&utm_campaign=readme)
124
-
125
- Available as part of the Tidelift Subscription.
126
-
127
- <details markdown="1">
128
- <summary>Need enterprise-level guarantees?</summary>
129
-
130
- The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
131
-
132
- [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
109
+ For enterprise support from the maintainer, use [Upwork][👨🏼‍🏫expsup-upwork] or [CodeMentor][👨🏼‍🏫expsup-codementor].
133
110
 
134
- - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
135
- - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
136
- - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
137
-
138
- Alternatively:
111
+ Other support channels:
139
112
 
140
113
  - [![Ruby Users Forum][✉️ruby-forum-img]][✉️ruby-forum]
141
114
  - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
142
- - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
143
- - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
144
-
145
- </details>
146
115
 
147
116
  ## ✨ Installation
148
117
 
@@ -1154,9 +1123,6 @@ Thanks for RTFM. ☺️
1154
1123
  [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
1155
1124
  [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
1156
1125
  [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
1157
- [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-kettle-family?utm_source=rubygems-kettle-family&utm_medium=referral&utm_campaign=readme
1158
- [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
1159
- [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
1160
1126
  [💁🏼‍♂️peterboling]: http://www.peterboling.com
1161
1127
  [🚂railsbling]: http://www.railsbling.com
1162
1128
  [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
@@ -1225,7 +1191,7 @@ Thanks for RTFM. ☺️
1225
1191
  [📌gitmoji]: https://gitmoji.dev
1226
1192
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1227
1193
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1228
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-8.107-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1194
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-8.118-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1229
1195
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
1230
1196
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1231
1197
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -1255,5 +1221,5 @@ Thanks for RTFM. ☺️
1255
1221
  | Homepage | https://github.com/kettle-dev/kettle-family |
1256
1222
  | Source | https://github.com/kettle-dev/kettle-family |
1257
1223
  | License | `AGPL-3.0-only` |
1258
- | Funding | https://github.com/sponsors/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/kettle-family, https://www.buymeacoffee.com/pboling |
1224
+ | Funding | https://github.com/sponsors/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/kettle-dev, https://thanks.dev/u/gh/pboling, https://www.buymeacoffee.com/pboling |
1259
1225
  <!-- kettle-jem:metadata:end -->
@@ -3,6 +3,7 @@
3
3
  require "command_kit"
4
4
  require "command_kit/commands"
5
5
  require "fileutils"
6
+ require "kettle/ndjson"
6
7
  require "optparse"
7
8
 
8
9
  module Kettle
@@ -814,8 +815,11 @@ module Kettle
814
815
  def run_command(command, options)
815
816
  report = build_report(command, options)
816
817
  write_report(report, options)
817
- if options[:events] && command == "release-state"
818
- stdout.puts(JSON.generate("event_version" => 1, "type" => "summary", "report" => report.to_h))
818
+ if options[:events]
819
+ Kettle::Ndjson.emit_summary_event(
820
+ Kettle::Ndjson.event_stream(stdout, types: "summary"),
821
+ report: report.to_h
822
+ )
819
823
  else
820
824
  stdout.puts(options[:json] ? report.to_json : report.to_text)
821
825
  end
@@ -827,6 +831,33 @@ module Kettle
827
831
 
828
832
  private
829
833
 
834
+ def emit_command_result_events(event_tape, command, results)
835
+ return unless event_tape
836
+
837
+ results.each do |result|
838
+ event_tape.call(
839
+ "type" => "command_step",
840
+ "command" => command,
841
+ "name" => result.phase,
842
+ "member" => result.member_name,
843
+ "branch" => result.branch,
844
+ "phase" => result.phase,
845
+ "status" => command_result_event_status(result),
846
+ "status_code" => result.status,
847
+ "elapsed_seconds" => result.elapsed_seconds,
848
+ "reason" => result.reason,
849
+ "result" => result.to_h
850
+ )
851
+ end
852
+ end
853
+
854
+ def command_result_event_status(result)
855
+ return "skipped" if result.skipped
856
+ return "ok" if result.ok?
857
+
858
+ "failed"
859
+ end
860
+
830
861
  def build_report(command, options)
831
862
  config = Config.load(root: options[:root], path: options[:config])
832
863
  start_at = parse_start_at(options[:start_at])
@@ -888,6 +919,7 @@ module Kettle
888
919
  release_state_results: release_state_results
889
920
  )
890
921
  state_progress&.finish
922
+ emit_command_result_events(state_event_tape, command, results)
891
923
  elapsed_seconds = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
892
924
  Report.new(
893
925
  family_name: config.family_name,
@@ -1562,13 +1594,13 @@ module Kettle
1562
1594
  end
1563
1595
 
1564
1596
  def release_state_event_tape(command:, config:, options:)
1565
- return unless %w[release release-state].include?(command)
1597
+ return unless %w[release release-state].include?(command) || options[:events]
1566
1598
  # Release event taping requires execute mode: dry-run release has no
1567
1599
  # child event streams to tape.
1568
1600
  return if command == "release" && !options[:execute]
1569
1601
 
1570
1602
  tape = ReleaseStateEventTape.new(root: config.root, stream: options[:events] ? stdout : nil)
1571
- stderr.puts("release state event tapes: #{tape.directory}") unless options[:events]
1603
+ stderr.puts("family event tapes: #{tape.directory}") unless options[:events]
1572
1604
  tape
1573
1605
  end
1574
1606
 
@@ -199,7 +199,6 @@ module Kettle
199
199
  event_handler.call(
200
200
  member_name,
201
201
  {
202
- "event_version" => 1,
203
202
  "type" => "secret_provider",
204
203
  "action" => action,
205
204
  "label" => label,
@@ -208,6 +208,20 @@ module Kettle
208
208
  fetch_path("template", "jobs")
209
209
  end
210
210
 
211
+ # Additional local dependency selectors used by the generated
212
+ # `local-template` task. Values may use Mise's `{{config_root}}` token.
213
+ def template_local_env
214
+ configured = fetch_path("template", "local_env") || {}
215
+ raise Error, "template.local_env must be a mapping of ENV names to values" unless configured.is_a?(Hash)
216
+
217
+ configured.each_with_object({}) do |(name, value), env|
218
+ key = name.to_s
219
+ raise Error, "template.local_env cannot override #{key}" if %w[BUNDLE_GEMFILE RUBYOPT K_JEM_TEMPLATING].include?(key)
220
+
221
+ env[key] = value.to_s
222
+ end
223
+ end
224
+
211
225
  # A monorepo commonly has one root suite that covers every subgem. The
212
226
  # configured owner is optional; an --only invocation never expands its
213
227
  # own selection solely to reach that owner.
@@ -453,25 +467,63 @@ module Kettle
453
467
  def normalize_corporate_sponsors(raw)
454
468
  Array(raw).filter_map do |entry|
455
469
  unless entry.is_a?(Hash)
456
- raise ArgumentError, "corporate sponsor entries must be mappings with name, url, and img_src"
470
+ raise ArgumentError, "corporate sponsor entries must be mappings with name, target_href, and image_src (legacy url and img_src are also accepted)"
457
471
  end
458
472
 
459
473
  sponsor = stringify_keys(entry)
460
474
  name = sponsor["name"].to_s.strip
461
- url = sponsor["url"].to_s.strip
462
- img_src = sponsor["img_src"].to_s.strip
463
- if name.empty? || url.empty? || img_src.empty?
464
- raise ArgumentError, "corporate sponsor entries require name, url, and img_src"
475
+ target_href = (sponsor["target_href"] || sponsor["url"]).to_s.strip
476
+ image_src = (sponsor["image_src"] || sponsor["img_src"]).to_s.strip
477
+ if name.empty? || target_href.empty? || image_src.empty?
478
+ raise ArgumentError, "corporate sponsor entries require name, target_href, and image_src (legacy url and img_src are also accepted)"
479
+ end
480
+
481
+ sponsor.merge(
482
+ "name" => name,
483
+ "target_href" => target_href,
484
+ "image_src" => image_src,
485
+ "url" => target_href,
486
+ "img_src" => image_src,
487
+ "since" => normalize_sponsor_date(sponsor["since"]),
488
+ "until" => normalize_sponsor_date(sponsor["until"]),
489
+ "members" => normalize_corporate_sponsor_members(sponsor["members"], name)
490
+ ).compact
491
+ end
492
+ end
493
+
494
+ def normalize_corporate_sponsor_members(raw, sponsor_name)
495
+ return nil if raw.nil?
496
+
497
+ Array(raw).filter_map do |entry|
498
+ unless entry.is_a?(Hash)
499
+ raise ArgumentError, "corporate sponsor #{sponsor_name.inspect} members must be mappings with name and target_href"
465
500
  end
501
+ member = stringify_keys(entry)
502
+ raise ArgumentError, "corporate sponsor #{sponsor_name.inspect} members cannot themselves have members" if member.key?("members")
466
503
 
467
- {
504
+ name = member["name"].to_s.strip
505
+ target_href = (member["target_href"] || member["url"]).to_s.strip
506
+ raise ArgumentError, "corporate sponsor #{sponsor_name.inspect} members require name and target_href" if name.empty? || target_href.empty?
507
+
508
+ image_src = (member["image_src"] || member["img_src"]).to_s.strip
509
+ member.merge(
468
510
  "name" => name,
469
- "url" => url,
470
- "img_src" => img_src
471
- }
511
+ "target_href" => target_href,
512
+ "url" => target_href,
513
+ "image_src" => image_src.empty? ? nil : image_src,
514
+ "img_src" => image_src.empty? ? nil : image_src,
515
+ "since" => normalize_sponsor_date(member["since"]),
516
+ "until" => normalize_sponsor_date(member["until"])
517
+ ).compact
472
518
  end
473
519
  end
474
520
 
521
+ def normalize_sponsor_date(value)
522
+ return nil if value.nil? || value.to_s.strip.empty?
523
+
524
+ value.respond_to?(:iso8601) ? value.iso8601 : value.to_s.strip
525
+ end
526
+
475
527
  def fetch_path(*keys)
476
528
  keys.reduce(data) do |memo, key|
477
529
  break nil unless memo.is_a?(Hash)
@@ -1,27 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "kettle/dev"
3
+ require "kettle/deps/floor"
4
4
 
5
5
  module Kettle
6
6
  module Family
7
+ # Adapts family release versions and workflow results to the shared
8
+ # kettle-deps-floor updater. Parsing, floor eligibility, and AST edits live
9
+ # in the generic gem; this class supplies family context only.
7
10
  class DependencyFloor
8
- DEPENDENCY_METHODS = %i[add_dependency add_runtime_dependency add_development_dependency].freeze
9
-
10
- # Return only dependencies declared by the gemspec itself. Release
11
- # ordering also observes Gemfile-only tooling dependencies, but those
12
- # cannot have their published floor rewritten by this class.
13
11
  def self.gemspec_dependency_names(member)
14
12
  return [] unless member.gemspec_path && File.file?(member.gemspec_path)
15
13
 
16
- source = File.read(member.gemspec_path)
17
- parse_result = Kettle::Dev::VersionBump.parse_source(source, member.gemspec_path)
18
- Kettle::Dev::VersionBump.each_node(parse_result.value).filter_map do |node|
19
- next unless node.is_a?(Prism::CallNode) && DEPENDENCY_METHODS.include?(node.name)
20
-
21
- name_node = node.arguments&.arguments&.first
22
- name_node.unescaped if name_node.is_a?(Prism::StringNode)
23
- end.uniq
24
- rescue Kettle::Dev::Error => error
14
+ Kettle::Deps::Floor::Updater.dependency_names(
15
+ project_root: member.root,
16
+ sources: [File.basename(member.gemspec_path)]
17
+ )
18
+ rescue Kettle::Deps::Floor::Error => error
25
19
  raise Error, error.message
26
20
  end
27
21
 
@@ -29,7 +23,7 @@ module Kettle
29
23
  @released_members = released_members
30
24
  @dependent_members = dependent_members
31
25
  @mode = mode
32
- @released_versions = released_members.to_h { |member| [member.name, Gem::Version.new(member.version.to_s)] }
26
+ @released_versions = released_members.to_h { |member| [member.name, member.version.to_s] }
33
27
  end
34
28
 
35
29
  def results
@@ -41,10 +35,18 @@ module Kettle
41
35
  attr_reader :released_members, :dependent_members, :mode, :released_versions
42
36
 
43
37
  def result_for(member)
44
- edits = collect_edits(member)
45
- return if edits.empty?
38
+ return unless member.gemspec_path && File.file?(member.gemspec_path)
39
+
40
+ updater = Kettle::Deps::Floor::Updater.new(
41
+ project_root: member.root,
42
+ sources: [File.basename(member.gemspec_path)],
43
+ target_versions: released_versions,
44
+ registry_updates: false,
45
+ write: mode == :execute
46
+ )
47
+ changes = updater.run.fetch(:planned_changes)
48
+ return if changes.empty?
46
49
 
47
- write_edits(edits) if mode == :execute
48
50
  CommandResult.new(
49
51
  member_name: member.name,
50
52
  phase: "dependency_floor",
@@ -52,79 +54,20 @@ module Kettle
52
54
  workdir: member.root,
53
55
  status: 0,
54
56
  success: true,
55
- stdout: edit_summary(member: member, edits: edits),
57
+ stdout: edit_summary(member: member, changes: changes),
56
58
  stderr: "",
57
59
  elapsed_seconds: 0.0,
58
60
  skipped: mode != :execute,
59
61
  reason: (mode == :execute) ? nil : "dry-run; pass --execute to write"
60
62
  )
63
+ rescue Kettle::Deps::Floor::Error => error
64
+ raise Error, error.message
61
65
  end
62
66
 
63
- def collect_edits(member)
64
- return [] unless member.gemspec_path && File.file?(member.gemspec_path)
65
-
66
- source = File.read(member.gemspec_path)
67
- parse_result = with_dev_errors { Kettle::Dev::VersionBump.parse_source(source, member.gemspec_path) }
68
- Kettle::Dev::VersionBump.each_node(parse_result.value).filter_map do |node|
69
- dependency_edit_for(member.gemspec_path, source, node)
70
- end
71
- end
72
-
73
- def dependency_edit_for(path, source, node)
74
- return unless node.is_a?(Prism::CallNode) && DEPENDENCY_METHODS.include?(node.name)
75
-
76
- args = node.arguments&.arguments || []
77
- name_node = args.first
78
- return unless name_node.is_a?(Prism::StringNode)
79
-
80
- dependency_name = name_node.unescaped
81
- released_version = released_versions[dependency_name]
82
- return unless released_version
83
-
84
- floor_node = requirement_nodes(args.drop(1)).find do |requirement_node|
85
- requirement_node.unescaped.start_with?(">= ")
86
- end
87
- return unless floor_node
88
-
89
- current_floor = floor_node.unescaped.delete_prefix(">= ").strip
90
- return unless floor_lower_than?(current_floor, released_version)
91
-
92
- replacement = Kettle::Dev::VersionBump.quote_like(floor_node.location.slice, ">= #{released_version}")
93
- Kettle::Dev::VersionBump.file_edit(path, source, floor_node.location.start_offset, floor_node.location.end_offset, replacement)
94
- end
95
-
96
- def requirement_nodes(nodes)
97
- nodes.flat_map do |node|
98
- case node
99
- when Prism::StringNode
100
- [node]
101
- when Prism::ArrayNode
102
- requirement_nodes(node.elements)
103
- else
104
- []
105
- end
106
- end
107
- end
108
-
109
- def floor_lower_than?(current_floor, released_version)
110
- Gem::Version.new(current_floor) < released_version
111
- rescue ArgumentError
112
- false
113
- end
114
-
115
- def write_edits(edits)
116
- Kettle::Dev::VersionBump.write_edits(edits)
117
- end
118
-
119
- def edit_summary(member:, edits:)
67
+ def edit_summary(member:, changes:)
120
68
  verb = (mode == :execute) ? "updated" : "would update"
121
- ["#{verb} #{edits.length} family dependency floor(s) for #{member.name}", *edits.map { |edit| "#{verb} #{edit.fetch(:path)}" }.uniq].join("\n")
122
- end
123
-
124
- def with_dev_errors
125
- yield
126
- rescue Kettle::Dev::Error => error
127
- raise Error, error.message
69
+ paths = changes.map { |change| change.fetch(:relative_path) }.uniq
70
+ ["#{verb} #{changes.length} family dependency floor(s) for #{member.name}", *paths.map { |path| "#{verb} #{path}" }].join("\n")
128
71
  end
129
72
  end
130
73
  end
@@ -3,6 +3,8 @@
3
3
  require "ast/crispr"
4
4
  require "ast/crispr/toml"
5
5
  require "psych/merge"
6
+ require "pathname"
7
+ require "shellwords"
6
8
 
7
9
  module Kettle
8
10
  module Family
@@ -355,6 +357,8 @@ module Kettle
355
357
  "state" => "Report release state for every family member",
356
358
  "release-state" => "Report changelog release state for every family member",
357
359
  "template" => "Apply kettle-jem templates across family members",
360
+ "local-template" => "Apply templates using member-local Nomono and local workspace gems",
361
+ "unreleased-template" => "Apply templates to stale members using the family-root bundle",
358
362
  "bump" => "Bump member gem versions",
359
363
  "changelog" => "Add a structured entry to the family changelog",
360
364
  "bup" => "Update dependencies and commit lockfiles; pass gems or options after --",
@@ -393,6 +397,8 @@ module Kettle
393
397
  tasks["bup"] = "bundle exec kettle-family bup --execute --commit"
394
398
  tasks["bupb"] = "bundle exec kettle-family update-bundler --execute --commit"
395
399
  tasks["update-bundler"] = tasks["bupb"]
400
+ tasks["local-template"] = local_template_command(member_bundle: "Gemfile")
401
+ tasks["unreleased-template"] = local_template_command(member_bundle: "{{config_root}}/Gemfile")
396
402
 
397
403
  neutral = neutralize_assignments
398
404
  wiring = env_wiring_flags
@@ -407,6 +413,39 @@ module Kettle
407
413
 
408
414
  private
409
415
 
416
+ def local_template_command(member_bundle:)
417
+ local_env = config.template_local_env.dup
418
+ if (family_env = config.family_local_path_env_name)
419
+ relative_root = Pathname.new(config.family_local_path_root)
420
+ .relative_path_from(Pathname.new(config.root)).to_s
421
+ family_path = if relative_root == "."
422
+ "{{config_root}}"
423
+ else
424
+ "{{config_root}}/#{relative_root}"
425
+ end
426
+ local_env[family_env] = family_path
427
+ end
428
+ local_env["K_JEM_TEMPLATING"] = "true"
429
+
430
+ root_env = local_env.merge(
431
+ "RUBYOPT" => "-rbundler/setup",
432
+ "BUNDLE_GEMFILE" => "Gemfile"
433
+ )
434
+ child_env = local_env.merge(
435
+ "RUBYOPT" => "-rbundler/setup",
436
+ "BUNDLE_GEMFILE" => member_bundle
437
+ )
438
+ assignments = root_env.map { |key, value| "#{key}=#{escape_mise_value(value)}" }.join(" ")
439
+ forwarded = child_env.map { |key, value| "--env #{escape_mise_value("#{key}=#{value}")}" }.join(" ")
440
+ "#{assignments} bundle exec kettle-family template --execute #{forwarded}"
441
+ end
442
+
443
+ def escape_mise_value(value)
444
+ token = "{{config_root}}"
445
+ marker = "KETTLE_FAMILY_CONFIG_ROOT_TOKEN"
446
+ Shellwords.escape(value.to_s.gsub(token, marker)).gsub(marker, token)
447
+ end
448
+
410
449
  # Neutralize ambient local-path wiring so the command starts from the
411
450
  # family config, then hand kettle-family the intended overrides.
412
451
  def neutralize_assignments
@@ -89,9 +89,11 @@ module Kettle
89
89
  edits = if File.file?(gemfile)
90
90
  source = File.read(gemfile)
91
91
  parse_result = Kettle::Dev::VersionBump.parse_source(source, gemfile)
92
- Kettle::Dev::VersionBump.each_node(parse_result.value).filter_map do |node|
93
- nomono_floor_edit(gemfile, source, node)
94
- end
92
+ nodes = Kettle::Dev::VersionBump.each_node(parse_result.value)
93
+ [
94
+ *nodes.filter_map { |node| nomono_floor_edit(gemfile, source, node) },
95
+ *nodes.filter_map { |node| local_nomono_floor_edit(gemfile, source, node) }
96
+ ]
95
97
  else
96
98
  []
97
99
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
- require "json"
5
4
  require "time"
5
+ require "kettle/ndjson"
6
6
 
7
7
  module Kettle
8
8
  module Family
@@ -34,10 +34,8 @@ module Kettle
34
34
  @mutex.synchronize do
35
35
  @sequence_by_member[target] += 1
36
36
  payload["sequence"] = @sequence_by_member.fetch(target)
37
- line = JSON.generate(payload)
38
- File.open(path_for(target), "a") { |file| file.puts(line) }
39
- @stream&.puts(line)
40
- @stream.flush if @stream&.respond_to?(:flush)
37
+ File.open(path_for(target), "a") { |file| emit_ndjson(file, payload) }
38
+ emit_ndjson(@stream, payload) if @stream
41
39
  end
42
40
  payload
43
41
  rescue
@@ -46,6 +44,17 @@ module Kettle
46
44
 
47
45
  private
48
46
 
47
+ def emit_ndjson(io, payload)
48
+ type = payload.fetch("type").to_s
49
+ stream = Kettle::Ndjson.event_stream(io, types: type, event_types: [type], aliases: {})
50
+ attributes = payload.each_with_object({}) do |(key, value), result|
51
+ next if %w[type event_version].include?(key.to_s)
52
+
53
+ result[key.to_sym] = value
54
+ end
55
+ Kettle::Ndjson.emit_event(stream, type, attributes)
56
+ end
57
+
49
58
  def target_key(payload)
50
59
  member = payload.fetch("member", "family").to_s
51
60
  branch = payload["branch"].to_s
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "1.3.32"
8
+ VERSION = "1.3.34"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
@@ -4864,26 +4864,6 @@ module Kettle
4864
4864
  File.file?(candidate) ? candidate : nil
4865
4865
  end
4866
4866
 
4867
- def local_kettle_jem_gemfile
4868
- executable = local_kettle_jem_executable
4869
- return unless executable
4870
-
4871
- gemfile = File.expand_path("../Gemfile", File.dirname(executable))
4872
- gemfile if File.file?(gemfile)
4873
- end
4874
-
4875
- def local_nomono_path_locked?(member)
4876
- lockfile = File.join(member.root, "Gemfile.lock")
4877
- return false unless File.file?(lockfile)
4878
-
4879
- spec = Bundler::LockfileParser.new(Bundler.read_file(lockfile)).specs.find do |locked_spec|
4880
- locked_spec.name == NomonoBootstrap::GEM_NAME
4881
- end
4882
- spec&.source.is_a?(Bundler::Source::Path)
4883
- rescue Bundler::LockfileError, Errno::ENOENT
4884
- false
4885
- end
4886
-
4887
4867
  def template_local_kettle_jem_root
4888
4868
  values = [
4889
4869
  env_overrides["STRUCTUREDMERGE_DEV"],
@@ -5142,18 +5122,17 @@ module Kettle
5142
5122
  end
5143
5123
 
5144
5124
  def gha_sha_pins_event_line_handler(member)
5145
- return nil unless progress_io
5125
+ return nil unless progress_io || @event_tape
5146
5126
 
5147
5127
  lambda do |line|
5148
5128
  event = parse_template_event(line)
5149
5129
  next false unless event && event["type"].to_s.start_with?("gha_sha_pins")
5150
5130
 
5151
- if progress_io
5152
- if verbose || debug
5153
- emit_gha_sha_pins_event_progress(member, event)
5154
- elsif @gha_sha_pins_progress
5155
- emit_gha_sha_pins_event_status(member, event, progress: @gha_sha_pins_progress)
5156
- end
5131
+ @event_tape&.call(event.merge("member" => member.name))
5132
+ if progress_io && (verbose || debug)
5133
+ emit_gha_sha_pins_event_progress(member, event)
5134
+ elsif progress_io && @gha_sha_pins_progress
5135
+ emit_gha_sha_pins_event_status(member, event, progress: @gha_sha_pins_progress)
5157
5136
  end
5158
5137
  true
5159
5138
  end
@@ -5991,12 +5970,10 @@ module Kettle
5991
5970
  env["K_CHANGELOG_PATH"] = File.join(member.root, "CHANGELOG.md")
5992
5971
  end
5993
5972
  end
5994
- tool_gemfile = local_kettle_jem_gemfile if member && local_nomono_path_locked?(member)
5995
- # Nomono's existing generated bootstrap can activate an installed
5996
- # release before Bundler resolves a path-locked local Nomono. Start
5997
- # local Kettle Jem from its own bundle so prepare can repair that
5998
- # bootstrap before it loads the destination bundle.
5999
- env["BUNDLE_GEMFILE"] = tool_gemfile if tool_gemfile
5973
+ # Preserve the task-selected bundle exactly. Modern members use their
5974
+ # own Gemfile with local Nomono wiring; stale-member recovery selects
5975
+ # the family-root Gemfile. In both cases kettle-jem must not switch to
5976
+ # its own Gemfile during preflight.
6000
5977
  env
6001
5978
  end
6002
5979
 
@@ -6005,15 +5982,21 @@ module Kettle
6005
5982
  end
6006
5983
 
6007
5984
  def template_bundler_update_env(env, member)
6008
- return env unless member&.name == NomonoBootstrap::GEM_NAME
6009
-
6010
- # While nomono itself is being version-bumped, its Gemfile/gemspec can
6011
- # require the new version while Gemfile.lock still records the old
6012
- # self-version. Eager RUBYOPT setup activates that old lock before a
6013
- # Bundler update can reconcile it. Keep the setting for Kettle Jem:
6014
- # its standalone bootstrap needs it to activate local template gems.
6015
- # Bundler update initializes its own bundle, so remove only this token
6016
- # for nomono's lockfile-update commands and preserve all other options.
5985
+ return env unless member
5986
+
5987
+ # Bundler update/normalize/recovery commands operate on the MEMBER's
5988
+ # own bundle (its nomono floor and lockfile), so they must target the
5989
+ # member Gemfile — not the bundle the template task selected to launch
5990
+ # kettle-family. Under unreleased-template that launcher bundle is the
5991
+ # family-root Gemfile, which does not declare nomono, so
5992
+ # `bundle update nomono` fails with "Could not find gem 'nomono'".
5993
+ env["BUNDLE_GEMFILE"] = File.join(member.root, "Gemfile")
5994
+ # Eager RUBYOPT setup would activate the member's possibly-stale
5995
+ # lockfile before Bundler can reconcile it (and, while nomono itself is
5996
+ # being version-bumped, its Gemfile/gemspec can require the new version
5997
+ # while Gemfile.lock still records the old self-version). Bundler
5998
+ # update/lock/install commands initialize their own bundle, so drop
5999
+ # only this token and preserve all other options.
6017
6000
  options = Shellwords.split(env.fetch("RUBYOPT", ""))
6018
6001
  options.reject! { |option| option == "-rbundler/setup" }
6019
6002
  if options.empty?
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-family
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.32
4
+ version: 1.3.34
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -114,7 +114,7 @@ dependencies:
114
114
  version: '1.0'
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: 1.0.13
117
+ version: 1.0.14
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
@@ -124,7 +124,27 @@ dependencies:
124
124
  version: '1.0'
125
125
  - - ">="
126
126
  - !ruby/object:Gem::Version
127
- version: 1.0.13
127
+ version: 1.0.14
128
+ - !ruby/object:Gem::Dependency
129
+ name: kettle-deps-floor
130
+ requirement: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - "~>"
133
+ - !ruby/object:Gem::Version
134
+ version: '0.1'
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 0.1.1
138
+ type: :runtime
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '0.1'
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: 0.1.1
128
148
  - !ruby/object:Gem::Dependency
129
149
  name: kettle-dev
130
150
  requirement: !ruby/object:Gem::Requirement
@@ -134,7 +154,7 @@ dependencies:
134
154
  version: '3.1'
135
155
  - - ">="
136
156
  - !ruby/object:Gem::Version
137
- version: 3.1.23
157
+ version: 3.1.25
138
158
  type: :runtime
139
159
  prerelease: false
140
160
  version_requirements: !ruby/object:Gem::Requirement
@@ -144,7 +164,7 @@ dependencies:
144
164
  version: '3.1'
145
165
  - - ">="
146
166
  - !ruby/object:Gem::Version
147
- version: 3.1.23
167
+ version: 3.1.25
148
168
  - !ruby/object:Gem::Dependency
149
169
  name: kettle-gha-pins
150
170
  requirement: !ruby/object:Gem::Requirement
@@ -185,6 +205,26 @@ dependencies:
185
205
  - - ">="
186
206
  - !ruby/object:Gem::Version
187
207
  version: 7.1.28
208
+ - !ruby/object:Gem::Dependency
209
+ name: kettle-ndjson
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - "~>"
213
+ - !ruby/object:Gem::Version
214
+ version: '0.1'
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: 0.1.20
218
+ type: :runtime
219
+ prerelease: false
220
+ version_requirements: !ruby/object:Gem::Requirement
221
+ requirements:
222
+ - - "~>"
223
+ - !ruby/object:Gem::Version
224
+ version: '0.1'
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: 0.1.20
188
228
  - !ruby/object:Gem::Dependency
189
229
  name: kettle-test
190
230
  requirement: !ruby/object:Gem::Requirement
@@ -296,7 +336,7 @@ dependencies:
296
336
  version: '1.1'
297
337
  - - ">="
298
338
  - !ruby/object:Gem::Version
299
- version: 1.1.15
339
+ version: 1.1.16
300
340
  type: :runtime
301
341
  prerelease: false
302
342
  version_requirements: !ruby/object:Gem::Requirement
@@ -306,7 +346,7 @@ dependencies:
306
346
  version: '1.1'
307
347
  - - ">="
308
348
  - !ruby/object:Gem::Version
309
- version: 1.1.15
349
+ version: 1.1.16
310
350
  - !ruby/object:Gem::Dependency
311
351
  name: bundler-audit
312
352
  requirement: !ruby/object:Gem::Requirement
@@ -364,7 +404,7 @@ dependencies:
364
404
  version: '0.1'
365
405
  - - ">="
366
406
  - !ruby/object:Gem::Version
367
- version: 0.1.3
407
+ version: 0.1.4
368
408
  type: :development
369
409
  prerelease: false
370
410
  version_requirements: !ruby/object:Gem::Requirement
@@ -374,7 +414,7 @@ dependencies:
374
414
  version: '0.1'
375
415
  - - ">="
376
416
  - !ruby/object:Gem::Version
377
- version: 0.1.3
417
+ version: 0.1.4
378
418
  - !ruby/object:Gem::Dependency
379
419
  name: appraisal2
380
420
  requirement: !ruby/object:Gem::Requirement
@@ -384,7 +424,7 @@ dependencies:
384
424
  version: '3.2'
385
425
  - - ">="
386
426
  - !ruby/object:Gem::Version
387
- version: 3.2.5
427
+ version: 3.2.6
388
428
  type: :development
389
429
  prerelease: false
390
430
  version_requirements: !ruby/object:Gem::Requirement
@@ -394,7 +434,7 @@ dependencies:
394
434
  version: '3.2'
395
435
  - - ">="
396
436
  - !ruby/object:Gem::Version
397
- version: 3.2.5
437
+ version: 3.2.6
398
438
  - !ruby/object:Gem::Dependency
399
439
  name: turbo_tests2
400
440
  requirement: !ruby/object:Gem::Requirement
@@ -568,10 +608,10 @@ licenses:
568
608
  - AGPL-3.0-only
569
609
  metadata:
570
610
  homepage_uri: https://kettle-family.galtzo.com
571
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.32
572
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.32/CHANGELOG.md
611
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.3.34
612
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.3.34/CHANGELOG.md
573
613
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
574
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.32
614
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.3.34
575
615
  funding_uri: https://github.com/sponsors/pboling
576
616
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
577
617
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file