kettle-family 1.2.65 → 1.2.67

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: 91689a2523f60872d8bf698271affca29c3a9149510543f87b3df956300527a2
4
- data.tar.gz: cab4664fe55f396532f46d0f808398d43880830baa824d41da9ffeb55a3d2659
3
+ metadata.gz: b6cce9c43c8db0ddb5984da9e0656dd3790a47dc718fa28195716a73d7b84a68
4
+ data.tar.gz: accd2cad6a1265c9c954e9908e6ab22ed02282bd0258c8116da295a9e6bf725f
5
5
  SHA512:
6
- metadata.gz: 1efb4bbb117cba0494d53461f06b3921eef031dec347f905ada977116335c1f65d980e0909906f8d6bcf78e42a4902332fa4f28ab57d0c8a5634f5310d5c3a93
7
- data.tar.gz: d3098d0dbe835bd746a7b0e93f524d22bf2007e19cb6968ef09a3a0f34486893edb84385fb6deaf8d8eb1984d0d8974ec273ea2f3b2d99fccb08e6e05f19497b
6
+ metadata.gz: f9bb56dbb8b43e418441588cd45c904d874086a7f9af349e097aa78a738779e86cef91dadf41e3f5ca0ae99737f717ccda23f0be250944646c247e24a0b3a99a
7
+ data.tar.gz: 45ea1c723af5437ec77d50961d3c0deca340358b2a904961addd8396ec47c28592faeaf68a94badbad8eb69da92b8124db7ad15fdf904fb91b29234830c6481e
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,34 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.2.67] - 2026-08-30
34
+
35
+ - TAG: [v1.2.67][1.2.67t]
36
+ - COVERAGE: 93.17% -- 5453/5853 lines in 33 files
37
+ - BRANCH COVERAGE: 76.13% -- 2210/2903 branches in 33 files
38
+ - 30.32% documented
39
+
40
+ ### Changed
41
+
42
+ - Add configurable aggregate release validation that runs shared changelog coverage and one monitored CI validation while preserving excluded independent members.
43
+
44
+ - [kc] release-recovery-docs: Clarify direct release recovery from later steps.
45
+
46
+ ### Fixed
47
+
48
+ - Emit member-scoped family release recovery commands from failed child release steps.
49
+
50
+ ## [1.2.66] - 2026-08-30
51
+
52
+ - TAG: [v1.2.66][1.2.66t]
53
+ - COVERAGE: 93.11% -- 5404/5804 lines in 33 files
54
+ - BRANCH COVERAGE: 76.06% -- 2179/2865 branches in 33 files
55
+ - 30.03% documented
56
+
57
+ ### Fixed
58
+
59
+ - Derive monorepo release lockfile path policy from family configuration and keep sibling tool paths opt-in.
60
+
33
61
  ## [1.2.65] - 2026-08-30
34
62
 
35
63
  - TAG: [v1.2.65][1.2.65t]
@@ -2279,7 +2307,11 @@ Please file a bug if you notice a violation of semantic versioning.
2279
2307
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
2280
2308
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
2281
2309
 
2282
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.65...HEAD
2310
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.67...HEAD
2311
+ [1.2.67]: https://github.com/kettle-dev/kettle-family/compare/v1.2.66...v1.2.67
2312
+ [1.2.67t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.67
2313
+ [1.2.66]: https://github.com/kettle-dev/kettle-family/compare/v1.2.65...v1.2.66
2314
+ [1.2.66t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.66
2283
2315
  [1.2.65]: https://github.com/kettle-dev/kettle-family/compare/v1.2.64...v1.2.65
2284
2316
  [1.2.65t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.65
2285
2317
  [1.2.64]: https://github.com/kettle-dev/kettle-family/compare/v1.2.63...v1.2.64
data/README.md CHANGED
@@ -249,24 +249,37 @@ release:
249
249
  family_changelog:
250
250
  enabled: true
251
251
  command: bundle exec kettle-changelog
252
+ aggregate_validation:
253
+ enabled: true
254
+ exclude_members:
255
+ - kettle-jem
252
256
  ```
253
257
 
254
258
  `family.local_path_env` names the environment variable that activates local
255
- path dependencies for this family. It is the family identity used by other
256
- families when they scan lockfile `PATH` remotes and need to disable local
257
- resolution before writing CI-facing release lockfiles. Configure it when the
258
- env name is not the default derived from `family.name`.
259
+ path dependencies for this family. For an explicitly configured
260
+ `family.mode: monorepo`, `kettle-family` derives a release-lock policy that
261
+ allows only `family.members_root` (or the configured member root) and passes
262
+ that policy to each `kettle-release` child. Those paths are valid because the
263
+ same monorepo checkout exists in CI. The tracked `Gemfile.lock` remains a
264
+ development lockfile and may retain those member `PATH` sources.
265
+
266
+ Sibling-repository families do not receive this exception. Their canonical
267
+ lockfiles must use released dependencies: a local Kettle Dev workspace, or
268
+ any other sibling workspace, is never allowed merely because its `*_DEV`
269
+ variable is set. Use `release.allowed_local_path_roots` together with
270
+ `release.allowed_local_path_env` only for an intentionally CI-available path
271
+ layout that cannot be inferred from a monorepo configuration.
259
272
 
260
273
  `release.disable_local_path_env` is different: it is an extra release-lockfile
261
274
  normalization shutoff list for path-injecting env vars that are not themselves
262
275
  discoverable as family roots, such as vendored parser/tooling paths.
263
276
 
264
- `release.local_path_strategy: waves` allows a release task to provide the
265
- family local-path environment as its default. Member release commands keep
266
- that path only while they have a selected sibling dependency that has not
267
- completed an earlier release wave; once the dependency is available, the
268
- member resolves it from the registry. Release lockfile normalization always
269
- disables local paths regardless of this strategy.
277
+ `release.local_path_strategy: waves` is for sibling families that explicitly
278
+ choose wave-local development resolution. An explicit monorepo instead keeps
279
+ its configured member-root path available throughout its waves, because its
280
+ canonical development lockfile and CI checkout share that path graph. In both
281
+ cases, release normalization rejects every path outside the configured allow
282
+ list.
270
283
 
271
284
  ### Release waves and dependency refresh
272
285
 
@@ -314,6 +327,29 @@ configured family name and `K_CHANGELOG_VERSION_FILE` when `version_file` is
314
327
  configured, so `kettle-changelog` can operate from a repository root without a
315
328
  root gemspec.
316
329
 
330
+ ### Aggregate release validation
331
+
332
+ Set `release.aggregate_validation.enabled: true` only for a shared-changelog
333
+ family whose root suite validates the release graph. `kettle-family` then:
334
+
335
+ 1. Runs `family_changelog` once, which runs the root coverage suite once.
336
+ 2. Marks the first selected non-excluded member's release-prep commit as the
337
+ aggregate CI validation and waits for that one CI run.
338
+ 3. Skips member changelog generation, the duplicate `bin/rake` test task, and
339
+ remote CI monitoring for the remaining aggregate members.
340
+
341
+ The marker is explicit (`[kettle-family:aggregate-ci]` for the validation
342
+ commit and `[kettle-family:aggregate-member]` for later members), allowing the
343
+ repository workflow to run one root suite for the validation commit and skip
344
+ redundant jobs for later release-only commits. Configure that workflow behavior
345
+ before enabling this policy.
346
+
347
+ Use `release.aggregate_validation.exclude_members` for a member that remains
348
+ independently releasable. Excluded members keep their own changelog coverage,
349
+ local test task, and monitored CI release flow. This is appropriate for a tool
350
+ such as `kettle-jem`, whose independent releases must not rely on a coordinated
351
+ family release having run first.
352
+
317
353
  For a flat repository that releases from multiple long-lived branches, list the
318
354
  release branches under `release.target_branches`. The branch list is processed
319
355
  in order. Each branch must be clean enough for `git checkout`, and each branch
@@ -346,6 +382,12 @@ metadata fix requires a new full release run from step 0:
346
382
  kettle-family release --publish --start-step 10 --local-ci
347
383
  ```
348
384
 
385
+ `--skip-changelog` skips only `kettle-changelog`; `--skip-appraisals` skips
386
+ only the Appraisal generation portion of release step 5 and still runs
387
+ `bin/rake yard`; and `--skip-ci` skips only remote CI monitoring. The generic
388
+ `--skip-steps LIST` remains available when a numbered release step is the
389
+ intended boundary.
390
+
349
391
  Executed publish runs skip versions that are already published. CI failures
350
392
  abort by default; pass `--continue-ci-failures` to set
351
393
  `K_RELEASE_CI_CONTINUE=true` for the underlying `kettle-release` process.
@@ -639,9 +681,12 @@ Executed family resets fail if the resulting lockfiles still contain sibling
639
681
  path remotes or checksum gaps.
640
682
 
641
683
  Resume a failed family publish after fixing the failure. Already published
642
- versions are skipped automatically; `start_step` is passed to `kettle-release`
643
- for unreleased members that still need work. Use `--start-step` only after the
644
- pre-release gate already passed or the failure was intentionally handled:
684
+ versions are skipped automatically. A release report gives a member-scoped
685
+ resume command for each failed child, including the recorded `--start-step`
686
+ when the child emitted a failed release-step event. Run those commands one at a
687
+ time. If any members were never started, the report also gives a separate
688
+ normal-flow command for exactly those members. Use `--start-step` only after
689
+ the pre-release gate already passed or the failure was intentionally handled:
645
690
 
646
691
  ```console
647
692
  kettle-family release --publish --execute --start-step 10
@@ -986,7 +1031,7 @@ Thanks for RTFM. ☺️
986
1031
  [📌gitmoji]: https://gitmoji.dev
987
1032
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
988
1033
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
989
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-5.782-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1034
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-5.853-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
990
1035
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
991
1036
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
992
1037
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -164,6 +164,7 @@ module Kettle
164
164
  release_fast_recovery_members: nil,
165
165
  release_skip_ci: false,
166
166
  release_skip_changelog: false,
167
+ release_skip_appraisals: false,
167
168
  release_local_ci: false,
168
169
  release_continue_ci_failures: false,
169
170
  release_ci_workflows: nil,
@@ -537,6 +538,7 @@ module Kettle
537
538
  option :fast_recovery_members, long: "--fast-recovery-members", value: {type: String, usage: "MEMBERS"}, desc: "Limit fast recovery to selected comma-separated members; defaults to all selected members"
538
539
  option :skip_ci, long: "--skip-ci", desc: "Run every selected member from step 0 while skipping only remote CI monitoring; requires --publish"
539
540
  option :skip_changelog, long: "--skip-changelog", desc: "Run release checks and child releases while skipping kettle-changelog"
541
+ option :skip_appraisals, long: "--skip-appraisals", desc: "Skip appraisal generation while retaining documentation generation"
540
542
  option :local_ci, long: "--local-ci", desc: "Pass --local-ci through to kettle-release commands"
541
543
  option :continue_ci_failures, long: "--continue-ci-failures", desc: "Set K_RELEASE_CI_CONTINUE=true for release commands"
542
544
  option :ci_workflows, long: "--ci-workflows", value: {type: String, usage: "LIST"}, desc: "Pass a comma-separated CI workflow monitor subset through to kettle-release commands"
@@ -566,6 +568,7 @@ module Kettle
566
568
  release_fast_recovery_members: options[:fast_recovery_members],
567
569
  release_skip_ci: truthy_option?(:skip_ci),
568
570
  release_skip_changelog: truthy_option?(:skip_changelog),
571
+ release_skip_appraisals: truthy_option?(:skip_appraisals),
569
572
  release_local_ci: truthy_option?(:local_ci),
570
573
  release_continue_ci_failures: truthy_option?(:continue_ci_failures),
571
574
  release_ci_workflows: options[:ci_workflows],
@@ -844,6 +847,7 @@ module Kettle
844
847
  fast_recovery_members: options[:release_fast_recovery_members],
845
848
  skip_ci: options[:release_skip_ci],
846
849
  skip_changelog: options[:release_skip_changelog],
850
+ skip_appraisals: options[:release_skip_appraisals],
847
851
  local_ci: options[:release_local_ci],
848
852
  continue_ci_failures: options[:release_continue_ci_failures],
849
853
  ci_workflows: options[:release_ci_workflows],
@@ -16,7 +16,8 @@ module Kettle
16
16
  :reason,
17
17
  :branch,
18
18
  :output_streamed,
19
- :log_path
19
+ :log_path,
20
+ :resume_step
20
21
  ) do
21
22
  def to_h
22
23
  {
@@ -33,7 +34,8 @@ module Kettle
33
34
  "skipped" => skipped,
34
35
  "reason" => reason,
35
36
  "output_streamed" => output_streamed?,
36
- "log_path" => log_path
37
+ "log_path" => log_path,
38
+ "resume_step" => resume_step
37
39
  }
38
40
  end
39
41
 
@@ -266,6 +266,23 @@ module Kettle
266
266
  fetch_path("release", "local_path_strategy").to_s
267
267
  end
268
268
 
269
+ def release_allowed_local_path_roots
270
+ inferred = configured_monorepo_release? ? [family_local_path_root] : []
271
+ configured = Array(fetch_path("release", "allowed_local_path_roots"))
272
+ .map { |path| expand_config_relative_path(path) }
273
+ (inferred + configured).uniq
274
+ end
275
+
276
+ def release_allowed_local_path_env_names
277
+ inferred = configured_monorepo_release? ? [family_local_path_env_name] : []
278
+ configured = Array(fetch_path("release", "allowed_local_path_env"))
279
+ (inferred + configured).compact.map(&:to_s).reject(&:empty?).uniq
280
+ end
281
+
282
+ def configured_monorepo_release?
283
+ fetch_path("family", "mode") == "monorepo"
284
+ end
285
+
269
286
  def release_secrets
270
287
  stringify_keys(fetch_path("release", "secrets") || {})
271
288
  end
@@ -296,6 +313,21 @@ module Kettle
296
313
  fetch_path("release", "family_changelog", "command") || "bundle exec kettle-changelog"
297
314
  end
298
315
 
316
+ # An aggregate validation release runs the shared changelog/coverage
317
+ # suite once locally, then validates that family state once in CI before
318
+ # publishing the remaining members. Excluded members retain their normal
319
+ # independent kettle-release test and CI lifecycle.
320
+ def release_aggregate_validation?
321
+ fetch_path("release", "aggregate_validation", "enabled") == true
322
+ end
323
+
324
+ def release_aggregate_validation_excluded_members
325
+ Array(fetch_path("release", "aggregate_validation", "exclude_members"))
326
+ .map(&:to_s)
327
+ .map(&:strip)
328
+ .reject(&:empty?)
329
+ end
330
+
299
331
  def release_tag_command
300
332
  fetch_path("release", "tag_command") || command_for("release_tag") || "git tag"
301
333
  end
@@ -67,7 +67,8 @@ module Kettle
67
67
  "command" => command,
68
68
  "results" => results.map(&:to_h),
69
69
  "summary" => summary,
70
- "resume_hint" => resume_hint
70
+ "resume_hint" => resume_hint,
71
+ "resume_hints" => resume_hints
71
72
  }
72
73
  end
73
74
 
@@ -145,7 +146,13 @@ module Kettle
145
146
  lines << " skipped: #{summary_list(data.fetch("skipped"))}"
146
147
  lines << " failed: #{summary_list(data.fetch("failed").map { |entry| summary_entry(entry) })}"
147
148
  lines << " pending: #{summary_list(data.fetch("pending").map { |entry| summary_entry(entry) })}"
148
- lines << " resume: #{data.fetch("resume_hint")}" if data.fetch("resume_hint")
149
+ hints = data.fetch("resume_hints")
150
+ if hints.one?
151
+ lines << " resume: #{hints.first}"
152
+ elsif hints.any?
153
+ lines << " resume:"
154
+ hints.each { |hint| lines << " #{hint}" }
155
+ end
149
156
  end
150
157
 
151
158
  def report_context_lines
@@ -170,7 +177,8 @@ module Kettle
170
177
  "failed" => summary_failed,
171
178
  "pending" => summary_pending,
172
179
  "release_log_dirs" => release_log_dirs,
173
- "resume_hint" => resume_hint
180
+ "resume_hint" => resume_hint,
181
+ "resume_hints" => resume_hints
174
182
  }
175
183
  end
176
184
 
@@ -661,21 +669,50 @@ module Kettle
661
669
  end
662
670
 
663
671
  def resume_hint
672
+ resume_hints.first
673
+ end
674
+
675
+ def resume_hints
676
+ return [] if success?
677
+ return non_release_resume_hints unless command == "release"
678
+
679
+ release_resume_hints
680
+ end
681
+
682
+ def non_release_resume_hints
664
683
  failed = results.find { |result| !result.ok? }
665
- resume_hint_for(failed) if failed
684
+ failed ? [resume_hint_for(failed)] : []
685
+ end
686
+
687
+ def release_resume_hints
688
+ failed_hints = visible_results.reject(&:ok?).map { |result| resume_hint_for(result) }.uniq
689
+ pending_names = summary_pending.map { |entry| entry.fetch("member") }
690
+ return failed_hints if pending_names.empty?
691
+
692
+ [*failed_hints, release_resume_hint_for_pending(pending_names)]
666
693
  end
667
694
 
668
695
  def resume_hint_for(result)
669
- return release_resume_hint if command == "release"
696
+ return release_resume_hint(result) if command == "release"
670
697
 
671
698
  "kettle-family #{command} --start-at #{result.member_name}"
672
699
  end
673
700
 
674
- def release_resume_hint
675
- hint = "kettle-family release --execute"
676
- hint = "#{hint} --publish" if release_mode == "publish"
701
+ def release_resume_hint(result)
702
+ hint = release_resume_base
703
+ hint = "#{hint} --only #{result.member_name}" if result.member_name
704
+ hint = "#{hint} --start-step #{result.resume_step}" if result.respond_to?(:resume_step) && result.resume_step
677
705
  hint
678
706
  end
707
+
708
+ def release_resume_hint_for_pending(member_names)
709
+ "#{release_resume_base} --only #{member_names.join(",")}"
710
+ end
711
+
712
+ def release_resume_base
713
+ hint = "kettle-family release --execute"
714
+ (release_mode == "publish") ? "#{hint} --publish" : hint
715
+ end
679
716
  end
680
717
  end
681
718
  end
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "1.2.65"
8
+ VERSION = "1.2.67"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
@@ -106,7 +106,7 @@ module Kettle
106
106
  REGISTRY_WAIT_ATTEMPTS = 15
107
107
  REGISTRY_WAIT_INTERVAL_SECONDS = 15
108
108
 
109
- def initialize(command:, config:, members:, family_members: nil, execute: false, accept: true, commit: true, allow_dirty: false, autostash: true, template_cleanup: true, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, fast_recovery: nil, fast_recovery_members: nil, skip_ci: false, skip_changelog: false, local_ci: false, continue_ci_failures: false, ci_workflows: nil, skip_bundle_audit: false, skip_remotes: nil, required_remotes: nil, auto_dependency_floors: nil, validate_ci_bundles: nil, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, gha_sha_pins_ttl_days: nil, env_overrides: {}, debug: false, verbose: false, gem_signing_password: nil, secrets_provider: nil, jobs: nil, progress_io: nil, reset_target: nil, bup_args: [], bex_args: [], start_member: nil, start_branch: nil, **options)
109
+ def initialize(command:, config:, members:, family_members: nil, execute: false, accept: true, commit: true, allow_dirty: false, autostash: true, template_cleanup: true, publish: false, push: false, tag: false, start_step: nil, skip_steps: nil, fast_recovery: nil, fast_recovery_members: nil, skip_ci: false, skip_changelog: false, skip_appraisals: false, local_ci: false, continue_ci_failures: false, ci_workflows: nil, skip_bundle_audit: false, skip_remotes: nil, required_remotes: nil, auto_dependency_floors: nil, validate_ci_bundles: nil, gha_sha_pins_upgrade: "patch", gha_sha_pins_check: false, gha_sha_pins_ttl_days: nil, env_overrides: {}, debug: false, verbose: false, gem_signing_password: nil, secrets_provider: nil, jobs: nil, progress_io: nil, reset_target: nil, bup_args: [], bex_args: [], start_member: nil, start_branch: nil, **options)
110
110
  @command = command
111
111
  @config = config
112
112
  @members = members
@@ -136,6 +136,7 @@ module Kettle
136
136
  @fast_recovery_members = normalize_fast_recovery_members(fast_recovery_members)
137
137
  @skip_ci = !!skip_ci
138
138
  @skip_changelog = !!skip_changelog
139
+ @skip_appraisals = !!skip_appraisals
139
140
  @local_ci = local_ci
140
141
  @continue_ci_failures = continue_ci_failures
141
142
  @ci_workflows = validate_ci_workflows(ci_workflows)
@@ -205,7 +206,7 @@ module Kettle
205
206
  execute && release_command_delegates_secrets_to_kettle_release?
206
207
  end
207
208
 
208
- attr_reader :command, :config, :members, :family_members, :execute, :accept, :commit, :allow_dirty, :autostash, :template_cleanup, :publish, :push, :tag, :start_step, :skip_steps, :fast_recovery, :fast_recovery_members, :skip_ci, :skip_changelog, :local_ci, :continue_ci_failures, :ci_workflows, :skip_bundle_audit, :skip_remotes, :required_remotes, :auto_dependency_floors, :validate_ci_bundles, :gha_sha_pins_upgrade, :gha_sha_pins_check, :gha_sha_pins_ttl_days, :env_overrides, :debug, :verbose, :jobs, :progress_io, :reset_target, :bup_args, :bex_args, :start_member, :start_branch
209
+ attr_reader :command, :config, :members, :family_members, :execute, :accept, :commit, :allow_dirty, :autostash, :template_cleanup, :publish, :push, :tag, :start_step, :skip_steps, :fast_recovery, :fast_recovery_members, :skip_ci, :skip_changelog, :skip_appraisals, :local_ci, :continue_ci_failures, :ci_workflows, :skip_bundle_audit, :skip_remotes, :required_remotes, :auto_dependency_floors, :validate_ci_bundles, :gha_sha_pins_upgrade, :gha_sha_pins_check, :gha_sha_pins_ttl_days, :env_overrides, :debug, :verbose, :jobs, :progress_io, :reset_target, :bup_args, :bex_args, :start_member, :start_branch
209
210
 
210
211
  def template_with_worktree_sync_results
211
212
  runner = command_runner
@@ -1219,6 +1220,7 @@ module Kettle
1219
1220
  runner = release_command_runner
1220
1221
  results = []
1221
1222
  @release_completed_member_names = []
1223
+ @aggregate_validation_member_name = aggregate_validation_members(release_members).first&.name
1222
1224
  append_family_changelog_result(runner: runner, memo: results) if include_family_changelog
1223
1225
  return results unless results.all?(&:ok?)
1224
1226
  return parallel_release_member_results(release_members, results) if parallel_release_members?(release_members)
@@ -1376,16 +1378,18 @@ module Kettle
1376
1378
  memo.last(2).each { |result| emit_member_result_progress(member, result, progress: progress) }
1377
1379
  return memo unless memo.last(2).all?(&:ok?)
1378
1380
 
1381
+ release_event_state = {}
1379
1382
  release_result = runner.call(
1380
1383
  member: member,
1381
1384
  phase: release_phase,
1382
1385
  command: release_command_for(member),
1383
1386
  env: release_env_for_member(member),
1384
1387
  interactive: release_command_interactive?,
1385
- stdout_line_handler: release_event_line_handler(member, progress: progress),
1388
+ stdout_line_handler: release_event_line_handler(member, progress: progress, state: release_event_state),
1386
1389
  log_path: release_command_log_path(member, release_phase),
1387
1390
  passthrough_output: release_command_passthrough_output?
1388
1391
  )
1392
+ release_result.resume_step = release_event_state[:failed_step] unless release_result.ok?
1389
1393
  memo << release_result
1390
1394
  emit_member_result_progress(member, memo.last, progress: progress)
1391
1395
  return memo unless memo.last.ok?
@@ -2222,7 +2226,8 @@ module Kettle
2222
2226
  effective_skip_steps = release_skip_steps_for(member)
2223
2227
  args << "start_step=#{effective_start_step}" if effective_start_step
2224
2228
  args << "skip_steps=#{effective_skip_steps}" unless effective_skip_steps.empty?
2225
- args << "--skip-changelog" if skip_changelog
2229
+ args << "--skip-changelog" if skip_changelog || aggregate_validation_member?(member)
2230
+ args << "--skip-appraisals" if skip_appraisals
2226
2231
  args << "--ci-workflows=#{ci_workflows}" if ci_workflows && !ci_workflows.to_s.empty?
2227
2232
  args << "--local-ci" if local_ci
2228
2233
  args << "--skip-bundle-audit" if skip_bundle_audit
@@ -2245,12 +2250,37 @@ module Kettle
2245
2250
  (fast_recovery == "retry-ci") ? 10 : 11
2246
2251
  end
2247
2252
 
2248
- def release_skip_steps_for(_member)
2253
+ def release_skip_steps_for(member)
2249
2254
  steps = Array(skip_steps).flat_map { |entry| entry.to_s.split(",") }.map(&:strip).reject(&:empty?)
2250
2255
  steps << "10" if skip_ci
2256
+ if aggregate_validation_member?(member)
2257
+ # The shared family changelog already ran the full local suite. The
2258
+ # validation member waits for the one aggregate CI run; all later
2259
+ # aggregate members publish without duplicate test/monitor phases.
2260
+ steps << "4"
2261
+ steps << "10" unless aggregate_validation_member_name?(member)
2262
+ end
2251
2263
  steps.uniq.join(",")
2252
2264
  end
2253
2265
 
2266
+ def aggregate_validation_members(release_members = members)
2267
+ return [] unless config.release_aggregate_validation?
2268
+ return [] unless config.shared_changelog?
2269
+
2270
+ excluded = config.release_aggregate_validation_excluded_members
2271
+ Array(release_members).reject { |member| excluded.include?(member.name) }
2272
+ end
2273
+
2274
+ def aggregate_validation_member?(member)
2275
+ return false unless member
2276
+
2277
+ aggregate_validation_members.include?(member)
2278
+ end
2279
+
2280
+ def aggregate_validation_member_name?(member)
2281
+ member && member.name == (@aggregate_validation_member_name || aggregate_validation_members.first&.name)
2282
+ end
2283
+
2254
2284
  def fast_recovery_for?(member)
2255
2285
  return false unless fast_recovery
2256
2286
  return false unless member
@@ -2349,6 +2379,7 @@ module Kettle
2349
2379
  def release_env_for_member(member)
2350
2380
  env = release_env
2351
2381
  env.merge!(release_wave_local_path_env_for(member))
2382
+ env.merge!(release_local_path_policy_env)
2352
2383
  # Family release performs one live pin review before member releases.
2353
2384
  # Child kettle-release must consume that reviewed cache instead of
2354
2385
  # repeating the GitHub API lookup for every member.
@@ -2376,6 +2407,9 @@ module Kettle
2376
2407
  # GitHub Release.
2377
2408
  env["KETTLE_RELEASE_SKIP_GITHUB_RELEASE"] = "true" if aggregate_monorepo_github_release?
2378
2409
  end
2410
+ if aggregate_validation_member?(member)
2411
+ env["KETTLE_RELEASE_FAMILY_CI_MODE"] = aggregate_validation_member_name?(member) ? "validation" : "member"
2412
+ end
2379
2413
  return env unless config.shared_changelog?
2380
2414
  if config.member_local_changelog?(member)
2381
2415
  return env.merge(
@@ -2412,6 +2446,14 @@ module Kettle
2412
2446
  # already treated as registry dependencies; this is what makes resume
2413
2447
  # runs work after an earlier wave has been published.
2414
2448
  def release_wave_local_path_env_for(member)
2449
+ # Monorepo members and their sibling dependencies share the same CI
2450
+ # checkout, so their canonical development lockfile may keep those
2451
+ # paths throughout every release wave.
2452
+ if config.configured_monorepo_release?
2453
+ env_name = config.family_local_path_env_name
2454
+ return env_name ? {env_name => config.family_local_path_root} : {}
2455
+ end
2456
+
2415
2457
  return {} unless config.release_local_path_strategy == "waves"
2416
2458
 
2417
2459
  env_name = config.family_local_path_env_name
@@ -3232,11 +3274,15 @@ module Kettle
3232
3274
  end
3233
3275
  end
3234
3276
 
3235
- def release_event_line_handler(member, progress: nil)
3277
+ def release_event_line_handler(member, progress: nil, state: nil)
3236
3278
  lambda do |line|
3237
3279
  event = parse_template_event(line)
3238
3280
  next false unless event
3239
3281
 
3282
+ if event["type"] == "command_step" && event["phase"] == "release" && event["status"] == "failed"
3283
+ state[:failed_step] = event["index"].to_i if state && event["index"].to_s.match?(/\A\d+\z/)
3284
+ end
3285
+
3240
3286
  if progress_io
3241
3287
  if suppress_release_secret_provider_event?(event, progress: progress)
3242
3288
  next true
@@ -4034,15 +4080,22 @@ module Kettle
4034
4080
  end
4035
4081
 
4036
4082
  def release_allowed_local_path_roots
4037
- # Release env disables are passed to Bundler normalization, but they
4038
- # must not hide the active workspace roots from readiness diagnostics.
4039
- release_local_path_env_detection_sources.filter_map do |key, value|
4040
- next unless key.end_with?("_LOCAL", "_DEV")
4041
- next unless local_path_env_value?(value)
4042
- next unless value.to_s.strip.start_with?("/", "./", "../", "~")
4083
+ config.release_allowed_local_path_roots
4084
+ end
4043
4085
 
4044
- value
4045
- end
4086
+ def release_allowed_local_path_env_names
4087
+ config.release_allowed_local_path_env_names
4088
+ end
4089
+
4090
+ def release_local_path_policy_env
4091
+ roots = release_allowed_local_path_roots
4092
+ env_names = release_allowed_local_path_env_names
4093
+ return {} if roots.empty? && env_names.empty?
4094
+
4095
+ {
4096
+ "KETTLE_RELEASE_ALLOWED_LOCAL_PATH_ROOTS" => roots.join(File::PATH_SEPARATOR),
4097
+ "KETTLE_RELEASE_ALLOWED_LOCAL_PATH_ENVS" => env_names.join(",")
4098
+ }
4046
4099
  end
4047
4100
 
4048
4101
  def release_lockfile_local_path_env_overrides(member = nil)
@@ -4051,11 +4104,18 @@ module Kettle
4051
4104
  key = key.to_s
4052
4105
  next unless key.end_with?("_LOCAL", "_DEV")
4053
4106
  next unless local_path_env_value?(value)
4107
+ next if release_allowed_local_path_env_names.include?(key)
4054
4108
 
4055
4109
  memo[key] = "false"
4056
4110
  end
4057
4111
  inferred = member ? release_lockfile_inferred_local_path_env(member).to_h { |key| [key, "false"] } : {}
4058
- explicit.merge(derived).merge(inferred)
4112
+ explicit.merge(derived).merge(inferred).merge(release_member_local_path_env(member))
4113
+ end
4114
+
4115
+ def release_member_local_path_env(member)
4116
+ return {} unless config.configured_monorepo_release?
4117
+
4118
+ release_wave_local_path_env_for(member)
4059
4119
  end
4060
4120
 
4061
4121
  def release_lockfile_inferred_local_path_env(member)
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-family
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.65
4
+ version: 1.2.67
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -74,7 +74,7 @@ dependencies:
74
74
  version: '1.0'
75
75
  - - ">="
76
76
  - !ruby/object:Gem::Version
77
- version: 1.0.6
77
+ version: 1.0.7
78
78
  type: :runtime
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
@@ -84,14 +84,14 @@ dependencies:
84
84
  version: '1.0'
85
85
  - - ">="
86
86
  - !ruby/object:Gem::Version
87
- version: 1.0.6
87
+ version: 1.0.7
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: kettle-dev
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - ">="
93
93
  - !ruby/object:Gem::Version
94
- version: 3.0.18
94
+ version: 3.0.22
95
95
  - - "<"
96
96
  - !ruby/object:Gem::Version
97
97
  version: 4.0.0
@@ -101,7 +101,7 @@ dependencies:
101
101
  requirements:
102
102
  - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 3.0.18
104
+ version: 3.0.22
105
105
  - - "<"
106
106
  - !ruby/object:Gem::Version
107
107
  version: 4.0.0
@@ -441,10 +441,10 @@ licenses:
441
441
  - AGPL-3.0-only
442
442
  metadata:
443
443
  homepage_uri: https://kettle-family.galtzo.com
444
- source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.65
445
- changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.65/CHANGELOG.md
444
+ source_code_uri: https://github.com/kettle-dev/kettle-family/tree/v1.2.67
445
+ changelog_uri: https://github.com/kettle-dev/kettle-family/blob/v1.2.67/CHANGELOG.md
446
446
  bug_tracker_uri: https://github.com/kettle-dev/kettle-family/issues
447
- documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.65
447
+ documentation_uri: https://www.rubydoc.info/gems/kettle-family/1.2.67
448
448
  funding_uri: https://github.com/sponsors/pboling
449
449
  wiki_uri: https://github.com/kettle-dev/kettle-family/wiki
450
450
  news_uri: https://www.railsbling.com/tags/kettle-family
metadata.gz.sig CHANGED
Binary file