kettle-dev 3.0.12 → 3.0.14

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: 778a9528095541a29e4c6a15022c1fe312cf32ccb8f336972c046b02134bae32
4
- data.tar.gz: bcc27b14726e3f17ad56b4e305e324c4ccb38df57df754f5f41b73e121f33a41
3
+ metadata.gz: '039cd8bb8df2cc898259f53fb65be82bca84a516067e02c61a31295475f92f79'
4
+ data.tar.gz: 6e0c274ac0c78a6e5960d076cd0cf63ad9639b806fb024a2712c4e81e2697856
5
5
  SHA512:
6
- metadata.gz: 4f4b916aeb6714071d76ab84ae57e08650e42fe0607df2e7b366855932c66019ad3f8a2ba37befa0089b9261eb38092ab3bc33cce61237b0d9880139da3399de
7
- data.tar.gz: 238a0802ae743f219f919a9fbdd2d1500aeea06ad8a756e5e377c4bc8c275e0d80bf07911f9a78b94e0095e9809afbb8eaeb9f98d9ee2e6db72242cfc2aa5fd9
6
+ metadata.gz: c756231572b2f1e2d446b2011942937032e089b05218560a20b73b5d1f087de688919eb7b8056b0638926b2e1ea9c80023eca85dd402a3ce99b20f9f90e45173
7
+ data.tar.gz: 87000a51e097e84a8d21ed9bf40e11792d2fd55937f728176c931df54715400ea5c07dfcdc76fce2979914dda7412b12b9287f68ec78e48f97cee255244f05b5
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,46 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [3.0.14] - 2026-08-28
34
+
35
+ - TAG: [v3.0.14][3.0.14t]
36
+ - COVERAGE: 91.46% -- 5818/6361 lines in 47 files
37
+ - BRANCH COVERAGE: 76.14% -- 2230/2929 branches in 47 files
38
+ - 50.86% documented
39
+
40
+ ### Changed
41
+
42
+ - [kc] kettle-jem/prepare: updated 9 project files:
43
+ - configuration (1)
44
+ - dependencies (8)
45
+
46
+ - [kc] kettle-jem/template: updated 14 project files:
47
+ - code and tests (2)
48
+ - configuration (1)
49
+ - dependencies (1)
50
+ - documentation (1)
51
+ - other (1)
52
+ - workflows (8)
53
+
54
+ ### Fixed
55
+
56
+ - Teach kettle-bump to update literal fallback versions in conditional gemspec loaders.
57
+
58
+ - Release lockfile resets now update the locked Bundler version before the release preparation commit, preventing the release's own Bundler upgrade from dirtying the working tree during publication.
59
+
60
+ - Standalone releases now commit Bundler lockfile updates separately before release preparation.
61
+
62
+ ## [3.0.13] - 2026-08-25
63
+
64
+ - TAG: [v3.0.13][3.0.13t]
65
+ - COVERAGE: 91.54% -- 5758/6290 lines in 47 files
66
+ - BRANCH COVERAGE: 76.44% -- 2210/2891 branches in 47 files
67
+ - 50.74% documented
68
+
69
+ ### Changed
70
+
71
+ - Add named standalone release aliases for CI bypass and numbered-step recovery.
72
+
33
73
  ## [3.0.12] - 2026-08-25
34
74
 
35
75
  - TAG: [v3.0.12][3.0.12t]
@@ -3557,7 +3597,11 @@ Please file a bug if you notice a violation of semantic versioning.
3557
3597
  - Selecting will run the selected workflow via `act`
3558
3598
  - This may move to its own gem in the future.
3559
3599
 
3560
- [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.12...HEAD
3600
+ [Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.14...HEAD
3601
+ [3.0.14]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.13...v3.0.14
3602
+ [3.0.14t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.14
3603
+ [3.0.13]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.12...v3.0.13
3604
+ [3.0.13t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.13
3561
3605
  [3.0.12]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.11...v3.0.12
3562
3606
  [3.0.12t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.12
3563
3607
  [3.0.11]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.10...v3.0.11
data/README.md CHANGED
@@ -145,7 +145,9 @@ The _amazing_ test matrix is powered by the kettle-dev stack.
145
145
 
146
146
  </details>
147
147
 
148
- ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/kettle-dev)](https://tidelift.com/subscription/pkg/rubygems-kettle-dev?utm_source=rubygems-kettle-dev&utm_medium=referral&utm_campaign=readme)
148
+ ### Enterprise Support
149
+
150
+ [![Tidelift](https://tidelift.com/badges/package/rubygems/kettle-dev)](https://tidelift.com/subscription/pkg/rubygems-kettle-dev?utm_source=rubygems-kettle-dev&utm_medium=referral&utm_campaign=readme)
149
151
 
150
152
  Available as part of the Tidelift Subscription.
151
153
 
@@ -412,7 +414,8 @@ What it does:
412
414
  - Validates Markdown reference definitions and local heading anchors across releasable documentation before network checks.
413
415
  Markdown fixtures under `spec/` and `test/`, along with `tmp/`, are intentionally excluded.
414
416
  - Skips pre-release checks for any `start_step` greater than `0`, so resumptions begin directly at the requested numbered step.
415
- - Runs sanity checks (`bin/setup`, `bin/rake`), confirms version/changelog, optionally updates Appraisals, regenerates docs via `bin/rake yard`, commits ā€œšŸ”– Prepare release vX.Y.Zā€.
417
+ - Verifies Bundler >= 2.7, updates the primary and appraisal bundles with `bundle update --bundler`, and commits those lockfiles separately as `šŸ”’ļø Update bundle` before release preparation.
418
+ - Runs sanity checks (`bin/setup`, `bin/rake`), confirms version/changelog, optionally updates Appraisals, regenerates docs via `bin/rake yard`, and commits ā€œšŸ”– Prepare release vX.Y.Zā€.
416
419
  - Optionally runs your CI locally with `act` before any push:
417
420
  - Enable with env: `K_RELEASE_LOCAL_CI="true"` (run automatically) or `K_RELEASE_LOCAL_CI="ask"` (prompt \[Y/n\]).
418
421
  - Select workflow with `K_RELEASE_LOCAL_CI_WORKFLOW` (with or without .yml/.yaml). Defaults to `locked_deps.yml` if present; otherwise the first workflow discovered.
@@ -422,7 +425,7 @@ What it does:
422
425
  - Options:
423
426
  - `start_step` map (skip directly to a phase):
424
427
  0. Run `kettle-pre-release` gates
425
- 1. Verify Bundler \>= 2.7 (start at 1 to skip pre-release checks)
428
+ 1. Verify Bundler \>= 2.7; update and commit the primary/appraisal Bundler lockfiles (start at 1 to skip pre-release checks)
426
429
  2. Detect version; RubyGems sanity check; confirm CHANGELOG/version; sync copyright years; update badges/headers
427
430
  3. Run bin/setup
428
431
  4. Run bin/rake (default task)
@@ -443,11 +446,11 @@ What it does:
443
446
  18. Create GitHub Release (requires `GITHUB_TOKEN`)
444
447
  19. Push tags to remotes (final)
445
448
  - Examples:
446
- - After a transient CI or provider failure with no code, version, changelog, or lockfile changes, restart from monitoring: `bundle exec kettle-release start_step=10`
449
+ - After a transient CI or provider failure with no code, version, changelog, or lockfile changes, restart from monitoring: `bundle exec kettle-release --start-step 10` (the legacy `start_step=10` spelling remains supported)
447
450
  - If the failure required a code or release-metadata change, rerun `bundle exec kettle-release` from step 0 so the release checks, prep metadata, commit, and CI all describe the new release commit.
448
- - For an intentional same-commit CI bypass, use `bundle exec kettle-release skip_steps=10` or `--skip-steps 10`; this skips remote CI monitoring only and does not claim that CI passed.
451
+ - For an intentional same-commit CI bypass, use `bundle exec kettle-release --skip-ci` (or `--skip-steps 10`); this skips remote CI monitoring only and does not claim that CI passed.
449
452
  - After fixing a failed pre-release gate, rerun from step 0: `bundle exec kettle-release`
450
- - After intentionally handling a failed pre-release gate, skip it and start numbered release steps: `bundle exec kettle-release start_step=1`
453
+ - After intentionally handling a failed pre-release gate, skip it and start numbered release steps: `bundle exec kettle-release --start-step 1`
451
454
  - Auto-approve release confirmation prompts: `bundle exec kettle-release --yes`
452
455
  - Run direct release with 1Password secrets: `bundle exec kettle-release --secrets-provider 1password`
453
456
  - Lockfile recovery:
@@ -463,10 +466,26 @@ What it does:
463
466
  variables, known local-template toggles, and writes checksums while
464
467
  updating path-sourced and checksum-gap gems back to released registry
465
468
  versions.
466
- - Release lockfile resets set `KETTLE_DEV_SKIP_CHANGELOG_DEPENDENCY=true`.
467
- This does not skip changelog generation; it keeps the development-only
468
- `kettle-changelog` gem out of the release bundle so unpublished family
469
- gems cannot enter Bundler's resolution graph.
469
+ - Release lockfile resets normally keep the complete development Gemfile
470
+ graph, including the development-only `kettle-changelog` dependency.
471
+ `kettle-changelog` depends on `kettle-dev`, so including it in
472
+ `kettle-dev`'s development bundle creates a downstream development-cycle
473
+ when sibling versions are unpublished. The
474
+ `KETTLE_DEV_SKIP_CHANGELOG_DEPENDENCY=true` switch remains available for
475
+ an explicit bootstrap/release graph that cannot resolve that downstream
476
+ tool, but it is not used while rewriting the tracked development
477
+ lockfiles.
478
+ - At release step 1, `kettle-release` runs the equivalent of
479
+ `kettle-family bupb`: it updates the primary lockfile and, when present,
480
+ `Appraisal.root.gemfile.lock`, resets appraisal locks, and commits only
481
+ those lockfiles as `šŸ”’ļø Update bundle`. This keeps Bundler maintenance in
482
+ its own atomic commit. `kettle-changelog` writes changelog content but
483
+ does not own this commit; the release helper commits release metadata
484
+ separately.
485
+ - Release-target resets also pass `bundle lock --bundler`, recording the
486
+ Bundler that performed the final reset in each committed release
487
+ lockfile. This keeps the normalized lockfile consistent with the
488
+ Bundler used by the release.
470
489
  - During `kettle-release`, release lockfiles are reset before the release
471
490
  prep commit. Before pushing, `kettle-release` validates those committed
472
491
  lockfiles again; if local paths or checksum gaps reappear, it prints the
@@ -1014,7 +1033,7 @@ Thanks for RTFM. ā˜ŗļø
1014
1033
  [šŸ“Œgitmoji]: https://gitmoji.dev
1015
1034
  [šŸ“Œgitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1016
1035
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1017
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-6.290-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1036
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-6.361-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1018
1037
  [šŸ”security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
1019
1038
  [šŸ”security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1020
1039
  [šŸ“„copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/exe/kettle-release CHANGED
@@ -49,16 +49,16 @@ end
49
49
  # Do not guard with __FILE__ == $PROGRAM_NAME because binstubs use Kernel.load.
50
50
  if ARGV.include?("-h") || ARGV.include?("--help")
51
51
  puts <<~USAGE
52
- Usage: kettle-release [--version VERSION] [--local-ci] [--yes] [--appraisal-update] [--skip-changelog] [--skip-bundle-audit] [--skip-steps STEPS] [--ci-workflows WORKFLOWS] [--skip-remotes REMOTES] [--required-remotes REMOTES] [--secrets-provider NAME] [--json|--events[=TYPES]] [--report PATH] [start_step=<0-19>]
52
+ Usage: kettle-release [--version VERSION] [--local-ci] [--yes] [--appraisal-update] [--skip-changelog] [--skip-bundle-audit] [--start-step STEP] [--skip-ci] [--skip-steps STEPS] [--ci-workflows WORKFLOWS] [--skip-remotes REMOTES] [--required-remotes REMOTES] [--secrets-provider NAME] [--json|--events[=TYPES]] [--report PATH] [start_step=<0-19>]
53
53
 
54
54
  Automates the release flow for a Ruby gem in the host project.
55
55
 
56
56
  Full releases start at step 0 and run `kettle-pre-release` plus
57
- `kettle-changelog` before the numbered release steps. Any start_step
57
+ `kettle-changelog` before the numbered release steps. Any start step
58
58
  greater than 0 skips pre-release prep and resumes directly at the
59
59
  requested numbered step.
60
60
 
61
- Start steps (use start_step=<n> to begin at that step):
61
+ Start steps (use --start-step <n>; start_step=<n> remains supported):
62
62
  0. Run kettle-pre-release gates and kettle-changelog
63
63
  1. Verify Bundler >= 2.7 (start at 1 to skip pre-release checks)
64
64
  2. Detect version; RubyGems sanity check; confirm CHANGELOG/version; sync copyright years; update badges/headers
@@ -82,7 +82,9 @@ if ARGV.include?("-h") || ARGV.include?("--help")
82
82
  19. Push tags to remotes (final)
83
83
 
84
84
  Options:
85
- start_step=<number> # Begin at the numbered step above; use 10 only for a transient CI failure on the same commit
85
+ --start-step NUMBER # Begin at the numbered step above; use 10 only for a transient CI failure on the same commit
86
+ start_step=<number> # Legacy alias for --start-step
87
+ --skip-ci # Alias for --skip-steps 10; skips remote CI monitoring while running the rest
86
88
  skip_steps=<numbers> # Comma-separated numbered steps to skip while running the rest; 10 skips remote CI monitoring
87
89
  --skip-steps NUMBERS # Alias for skip_steps=NUMBERS (e.g., --skip-steps 10 or --skip-steps 10,18)
88
90
  --version VERSION # Use this version instead of detecting VERSION from lib/**/version.rb
@@ -101,9 +103,11 @@ if ARGV.include?("-h") || ARGV.include?("--help")
101
103
  # create the git tag locally, then push commits and tags after publish
102
104
 
103
105
  Recovery guidance:
104
- start_step=10 # Retry only CI monitoring after a transient CI/provider failure; do not use after code or metadata changes
105
- start_step=11 # Continue after CI monitoring only when that same release commit was intentionally accepted
106
- skip_steps=10 # Skip remote CI monitoring for the same prepared release commit; this does not rerun or replace CI
106
+ --start-step 10 # Retry only CI monitoring after a transient CI/provider failure; do not use after code or metadata changes
107
+ --start-step 11 # Continue after CI monitoring only when that same release commit was intentionally accepted
108
+ --skip-ci # Skip remote CI monitoring for the same prepared release commit; this does not rerun or replace CI
109
+ start_step=10 # Legacy spelling of --start-step 10
110
+ skip_steps=10 # Legacy spelling of --skip-ci
107
111
  kettle-release # After code, version, changelog, or lockfile changes, rerun the complete flow from step 0
108
112
 
109
113
  Environment:
@@ -139,10 +143,11 @@ if ARGV.include?("-h") || ARGV.include?("--help")
139
143
  Examples:
140
144
  kettle-release # full flow from the top (start_step=0)
141
145
  kettle-release --local-ci # run local act CI, publish gem, then push commits/tags
142
- kettle-release start_step=1 # skip pre-release checks and start numbered release steps
143
- kettle-release start_step=10 # retry CI monitoring after a transient failure on the unchanged release commit
144
- kettle-release skip_steps=10 # intentionally skip remote CI monitoring for the unchanged release commit
145
- SKIP_GEM_SIGNING=true kettle-release start_step=14 # build+release without signing
146
+ kettle-release --start-step 1 # skip pre-release checks and start numbered release steps
147
+ kettle-release --start-step 10 # retry CI monitoring after a transient failure on the unchanged release commit
148
+ kettle-release --skip-ci # intentionally skip remote CI monitoring for the unchanged release commit
149
+ kettle-release start_step=10 # legacy spelling of --start-step 10
150
+ SKIP_GEM_SIGNING=true kettle-release --start-step 14 # build+release without signing
146
151
  USAGE
147
152
  exit 0
148
153
  end
@@ -184,6 +189,31 @@ def extract_skip_steps_arg!(argv)
184
189
  skip_steps
185
190
  end
186
191
 
192
+ def extract_start_step_arg!(argv)
193
+ start_step = nil
194
+ if (idx = argv.index("--start-step"))
195
+ start_step = argv[idx + 1]
196
+ if start_step.to_s.empty? || start_step.to_s.start_with?("-")
197
+ Kettle::Dev::ExitAdapter.abort("--start-step requires a numbered release step")
198
+ end
199
+ argv.slice!(idx, 2)
200
+ end
201
+ argv.delete_if do |arg|
202
+ if arg.start_with?("--start-step=", "start_step=")
203
+ start_step = arg.split("=", 2)[1]
204
+ Kettle::Dev::ExitAdapter.abort("--start-step requires a numbered release step") if start_step.to_s.empty?
205
+ true
206
+ else
207
+ false
208
+ end
209
+ end
210
+ start_step
211
+ end
212
+
213
+ def extract_skip_ci_arg!(argv)
214
+ !!argv.delete("--skip-ci")
215
+ end
216
+
187
217
  def extract_skip_changelog_arg!(argv)
188
218
  skip_changelog = !!argv.delete("--skip-changelog")
189
219
  argv.delete_if do |arg|
@@ -326,10 +356,11 @@ skip_changelog = extract_skip_changelog_arg!(ARGV)
326
356
  json_output = !!ARGV.delete("--json")
327
357
  events_output, event_types = extract_events_arg!(ARGV)
328
358
  Kettle::Dev::ExitAdapter.abort("--events cannot be combined with --json") if events_output && json_output
329
- start_step_arg = ARGV.find { |a| a.start_with?("start_step=") }
330
- start_step = start_step_arg ? start_step_arg.split("=", 2)[1].to_i : 0
359
+ start_step = extract_start_step_arg!(ARGV) || 0
360
+ skip_ci = extract_skip_ci_arg!(ARGV)
331
361
  version_override = extract_version_arg!(ARGV)
332
362
  skip_steps = extract_skip_steps_arg!(ARGV)
363
+ skip_steps = [skip_steps, "10"].compact.join(",") if skip_ci
333
364
  ci_workflows = extract_ci_workflows_arg!(ARGV)
334
365
  skip_remotes = extract_skip_remotes_arg!(ARGV)
335
366
  required_remotes = extract_required_remotes_arg!(ARGV)
@@ -49,7 +49,7 @@ module Kettle
49
49
  #
50
50
  # @param restart_hint [String] guidance command shown on failure
51
51
  # @return [Boolean] whether all configured CI providers passed
52
- def monitor_all!(restart_hint: "bundle exec kettle-release start_step=10", workflows: nil, keepalive: nil, event_recorder: nil, **options)
52
+ def monitor_all!(restart_hint: "bundle exec kettle-release --start-step 10", workflows: nil, keepalive: nil, event_recorder: nil, **options)
53
53
  results = [
54
54
  monitor_github_internal!(restart_hint: restart_hint, workflows: workflows, keepalive: keepalive, event_recorder: event_recorder),
55
55
  monitor_gitlab_internal!(restart_hint: restart_hint, keepalive: keepalive, event_recorder: event_recorder)
@@ -69,7 +69,7 @@ module Kettle
69
69
  # Returns false when GitLab is not configured for this repo/branch.
70
70
  # @param restart_hint [String]
71
71
  # @return [Boolean]
72
- def monitor_gitlab!(restart_hint: "bundle exec kettle-release start_step=10")
72
+ def monitor_gitlab!(restart_hint: "bundle exec kettle-release --start-step 10")
73
73
  result = monitor_gitlab_internal!(restart_hint: restart_hint)
74
74
  result.nil? ? false : result
75
75
  end
@@ -138,7 +138,7 @@ module Kettle
138
138
  # Designed for kettle-release.
139
139
  # @param restart_hint [String]
140
140
  # @return [void]
141
- def monitor_and_prompt_for_release!(restart_hint: "bundle exec kettle-release start_step=10")
141
+ def monitor_and_prompt_for_release!(restart_hint: "bundle exec kettle-release --start-step 10")
142
142
  results = collect_all
143
143
  any_checks = !(results[:github].nil? || results[:github].empty?) || !!results[:gitlab]
144
144
  abort("CI configuration not detected (GitHub or GitLab). Ensure CI is configured and remotes point to the correct hosts.") unless any_checks
@@ -82,6 +82,21 @@ module Kettle
82
82
  false
83
83
  end
84
84
 
85
+ # Commit only the changes already staged by the caller.
86
+ #
87
+ # This is intentionally different from +commit_all+: bundle maintenance
88
+ # must not sweep unrelated release-preparation changes into its own
89
+ # atomic commit.
90
+ #
91
+ # @param message [String]
92
+ # @return [Boolean]
93
+ def commit_staged(message)
94
+ git_system("commit", "-m", message.to_s)
95
+ rescue => e
96
+ Kettle::Dev.debug_error(e, __method__)
97
+ false
98
+ end
99
+
85
100
  # Amend the current commit without changing its message.
86
101
  #
87
102
  # @return [Boolean]
@@ -116,7 +116,6 @@ module Kettle
116
116
  BundlerEnvGuard.warn_unexpected_env!
117
117
  paths = lockfile_paths_for(target)
118
118
  force_full_update = release_lockfiles_target?(target)
119
- skip_changelog_dependency ||= force_full_update
120
119
  platforms = release_platforms_for(paths) if force_full_update
121
120
  uninstall_unreleased_local_gems(paths) if force_full_update
122
121
  paths.each do |path|
@@ -125,7 +124,8 @@ module Kettle
125
124
  path,
126
125
  full_update: force_full_update,
127
126
  platforms: platforms&.fetch(path),
128
- skip_changelog_dependency: skip_changelog_dependency
127
+ skip_changelog_dependency: skip_changelog_dependency,
128
+ update_bundler: force_full_update
129
129
  )
130
130
  end
131
131
  end
@@ -135,7 +135,8 @@ module Kettle
135
135
  path,
136
136
  full_update: true,
137
137
  platforms: platforms.fetch(path),
138
- skip_changelog_dependency: skip_changelog_dependency
138
+ skip_changelog_dependency: skip_changelog_dependency,
139
+ update_bundler: true
139
140
  )
140
141
  end
141
142
  end
@@ -145,7 +146,7 @@ module Kettle
145
146
  paths
146
147
  end
147
148
 
148
- def reset_lockfile!(path, full_update: false, platforms: nil, skip_changelog_dependency: false)
149
+ def reset_lockfile!(path, full_update: false, platforms: nil, skip_changelog_dependency: false, update_bundler: false)
149
150
  gemfile = gemfile_for_lockfile(path)
150
151
  unless gemfile && File.file?(gemfile)
151
152
  warn("Cannot reset #{display_path(path)} because its Gemfile was not found.")
@@ -159,6 +160,7 @@ module Kettle
159
160
  full_update: full_update,
160
161
  platforms: platforms,
161
162
  skip_changelog_dependency: skip_changelog_dependency,
163
+ update_bundler: update_bundler,
162
164
  isolated_gem_home: gem_home
163
165
  )
164
166
  if full_update || has_local_path_remote?(path)
@@ -169,7 +171,7 @@ module Kettle
169
171
  end
170
172
  end
171
173
 
172
- def reset_command(path:, gemfile:, full_update: false, platforms: nil, skip_changelog_dependency: false, isolated_gem_home: nil)
174
+ def reset_command(path:, gemfile:, full_update: false, platforms: nil, skip_changelog_dependency: false, update_bundler: false, isolated_gem_home: nil)
173
175
  update_gems = (full_update || has_local_path_remote?(path)) ? [] : reset_update_gems(path)
174
176
  platforms ||= reset_platforms(path)
175
177
  removed_platforms = full_update ? [] : lockfile_platforms(path) - platforms
@@ -198,6 +200,11 @@ module Kettle
198
200
  end
199
201
  command << " --update"
200
202
  command << " #{update_gems.map { |gem_name| Shellwords.escape(gem_name) }.join(" ")}" unless update_gems.empty?
203
+ # Bundler preserves BUNDLED WITH during a normal lockfile update.
204
+ # Release lockfiles must record the Bundler that performed the final
205
+ # reset, or the next release command can dirty them after the prep
206
+ # commit by reconciling to a newer installed Bundler.
207
+ command << " --bundler" if update_bundler
201
208
  command << " --add-checksums"
202
209
  command
203
210
  end
@@ -145,6 +145,7 @@ module Kettle
145
145
  words = effective_command_words(cmd)
146
146
  words == ["bin/setup"] ||
147
147
  words.first(1) == ["bin/rake"] ||
148
+ words.first(2) == ["bundle", "update"] ||
148
149
  words.first(3) == ["bundle", "exec", "rake"] ||
149
150
  words.first(3) == ["bundle", "exec", "kettle-changelog"]
150
151
  rescue ArgumentError
@@ -252,8 +253,12 @@ module Kettle
252
253
  def run_with_release_environment
253
254
  run_pre_release_checks! if run_step?(0)
254
255
 
255
- # 1. Ensure Bundler version āœ“
256
- ensure_bundler_2_7_plus! if run_step?(1)
256
+ # 1. Ensure Bundler version and record its current release in the
257
+ # development lockfiles before any release-preparation commit.
258
+ if run_step?(1)
259
+ ensure_bundler_2_7_plus!
260
+ update_bundler_and_commit!
261
+ end
257
262
 
258
263
  version = nil
259
264
  committed = nil
@@ -690,8 +695,7 @@ module Kettle
690
695
  puts "Resetting release lockfiles with local path dependencies disabled #{stage} (attempt #{attempt}/#{attempts})..."
691
696
  begin
692
697
  lockfile_reset.reset(
693
- Kettle::Dev::LockfileReset::RELEASE_LOCKFILES_TARGET,
694
- skip_changelog_dependency: true
698
+ Kettle::Dev::LockfileReset::RELEASE_LOCKFILES_TARGET
695
699
  )
696
700
  emit_release_lockfile_event(action: "reset", status: "ok", stage: stage, attempt: attempt, attempts: attempts)
697
701
  break
@@ -778,7 +782,7 @@ module Kettle
778
782
  end
779
783
 
780
784
  def normalize_release_lockfile!(path)
781
- lockfile_reset.reset_lockfile!(path, skip_changelog_dependency: true)
785
+ lockfile_reset.reset_lockfile!(path)
782
786
  end
783
787
 
784
788
  def release_gemfile_for_lockfile(path)
@@ -786,7 +790,7 @@ module Kettle
786
790
  end
787
791
 
788
792
  def release_lockfile_normalization_env
789
- lockfile_reset.normalization_env.merge("KETTLE_DEV_SKIP_CHANGELOG_DEPENDENCY" => "true")
793
+ lockfile_reset.normalization_env
790
794
  end
791
795
 
792
796
  def release_lockfile_diagnostics(path)
@@ -1253,7 +1257,7 @@ module Kettle
1253
1257
  def monitor_workflows_after_push!
1254
1258
  ensure_github_pull_request_for_ci!
1255
1259
  keep_release_secrets_alive!("CI monitoring")
1256
- restart_hint = "bundle exec kettle-release start_step=10"
1260
+ restart_hint = "bundle exec kettle-release --start-step 10"
1257
1261
  emit_ci_monitor_event(action: "start", status: "started", workflows: @ci_workflows, restart_hint: restart_hint)
1258
1262
  # The monitor preserves fail-fast behavior by default and returns false
1259
1263
  # when K_RELEASE_CI_CONTINUE explicitly allows failed CI checks.
@@ -1602,6 +1606,90 @@ module Kettle
1602
1606
  end
1603
1607
  end
1604
1608
 
1609
+ # Keep Bundler maintenance separate from the release metadata commit.
1610
+ # The project lockfiles are shared development state, so the update must
1611
+ # be real and committed rather than hidden behind a temporary lockfile.
1612
+ def update_bundler_and_commit!
1613
+ run_cmd!(release_project_command("bundle update --bundler"))
1614
+
1615
+ appraisal_gemfile = File.join(@root, "Appraisal.root.gemfile")
1616
+ if File.file?(appraisal_gemfile)
1617
+ appraisal_lockfile = File.join(@root, "Appraisal.root.gemfile.lock")
1618
+ appraisal_command = "BUNDLE_GEMFILE=#{Shellwords.escape(appraisal_gemfile)} " \
1619
+ "BUNDLE_LOCKFILE=#{Shellwords.escape(appraisal_lockfile)} bundle update --bundler"
1620
+ run_cmd!(release_project_command(appraisal_command))
1621
+ run_cmd!(release_project_command("bundle exec rake appraisal:reset"))
1622
+ end
1623
+
1624
+ commit_bundle_update!
1625
+ end
1626
+
1627
+ def commit_bundle_update!
1628
+ paths = changed_bundle_lockfile_paths
1629
+ return false if paths.empty?
1630
+
1631
+ unexpected = staged_paths - paths
1632
+ unless unexpected.empty?
1633
+ abort(<<~MSG)
1634
+ Cannot commit Bundler updates atomically because unrelated files are already staged:
1635
+ #{unexpected.map { |path| " - #{path}" }.join("\n")}
1636
+ Commit or unstage those files, then retry the release.
1637
+ MSG
1638
+ end
1639
+
1640
+ puts "Committing Bundler update in #{paths.length} lockfile(s)."
1641
+ abort("Failed to stage Bundler update lockfiles.") unless @git.add_paths(paths)
1642
+ abort("Failed to commit Bundler update lockfiles.") unless @git.commit_staged("šŸ”’ļø Update bundle")
1643
+ reconcile_bundle_update_commit!
1644
+ true
1645
+ end
1646
+
1647
+ def changed_bundle_lockfile_paths
1648
+ tracked, tracked_ok = git_output(["diff", "--name-only", "HEAD", "--"])
1649
+ untracked, untracked_ok = git_output(["ls-files", "--others", "--exclude-standard"])
1650
+ return [] unless tracked_ok && untracked_ok
1651
+
1652
+ (tracked.lines + untracked.lines).map(&:strip).reject(&:empty?).select { |path| bundle_lockfile_path?(path) }.uniq
1653
+ end
1654
+
1655
+ def staged_paths
1656
+ output, ok = git_output(["diff", "--cached", "--name-only"])
1657
+ return [] unless ok
1658
+
1659
+ output.lines.map(&:strip).reject(&:empty?).uniq
1660
+ end
1661
+
1662
+ def bundle_lockfile_path?(path)
1663
+ path == "Gemfile.lock" || File.basename(path).end_with?(".lock")
1664
+ end
1665
+
1666
+ def reconcile_bundle_update_commit!
1667
+ 3.times do
1668
+ paths = changed_bundle_lockfile_paths
1669
+ return if paths.empty?
1670
+
1671
+ puts "Bundler changed lockfiles while committing; amending the bundle update commit."
1672
+ abort("Failed to stage post-commit Bundler lockfile changes.") unless @git.add_paths(paths)
1673
+ abort("Failed to amend the bundle update commit.") unless @git.commit_amend_no_edit
1674
+ end
1675
+
1676
+ abort("Bundler continued changing lockfiles after the bundle update commit.") unless changed_bundle_lockfile_paths.empty?
1677
+ end
1678
+
1679
+ def reconcile_release_prep_commit!
1680
+ 3.times do
1681
+ output, = git_output(["status", "--porcelain"])
1682
+ return if output.empty?
1683
+
1684
+ puts "Release commit hooks changed tracked files; amending the release preparation commit."
1685
+ abort("Failed to stage post-commit release changes.") unless @git.add_all
1686
+ abort("Failed to amend the release preparation commit.") unless @git.commit_amend_no_edit
1687
+ end
1688
+
1689
+ output, = git_output(["status", "--porcelain"])
1690
+ abort("Release commit hooks continued changing files after the release preparation commit.") unless output.empty?
1691
+ end
1692
+
1605
1693
  def maybe_run_local_ci_before_push!(committed, force: false)
1606
1694
  mode = (ENV["K_RELEASE_LOCAL_CI"] || "").strip.downcase
1607
1695
  run_it =
@@ -1898,6 +1986,7 @@ module Kettle
1898
1986
  false
1899
1987
  else
1900
1988
  abort("Failed to commit release prep changes.") unless @git.commit_all(msg)
1989
+ reconcile_release_prep_commit!
1901
1990
  true
1902
1991
  end
1903
1992
  end
@@ -1982,6 +2071,7 @@ module Kettle
1982
2071
  return "gem_release" if joined == "bundle exec rake release"
1983
2072
  return "gem_push" if effective_words.first(2) == ["gem", "push"]
1984
2073
  return "gem_checksums" if effective_words.first == "bin/gem_checksums"
2074
+ return "bundle_update" if effective_words.first(2) == ["bundle", "update"]
1985
2075
  return "bundle_lock" if effective_words.first(2) == ["bundle", "lock"]
1986
2076
  return "bin_setup" if effective_words == ["bin/setup"]
1987
2077
  return "default_task" if effective_words == ["bin/rake"]
@@ -2023,6 +2113,7 @@ module Kettle
2023
2113
  joined = effective_words.join(" ")
2024
2114
 
2025
2115
  return "changelog" if joined.start_with?("bundle exec kettle-changelog")
2116
+ return "bundle update" if effective_words.first(2) == ["bundle", "update"]
2026
2117
  return command_event_bundle_lock_summary(words) if effective_words.first(2) == ["bundle", "lock"]
2027
2118
  return "setup" if effective_words == ["bin/setup"]
2028
2119
  return "default task" if effective_words == ["bin/rake"]
@@ -5,7 +5,7 @@ module Kettle
5
5
  # Version namespace for this gem.
6
6
  module Version
7
7
  # Current gem version.
8
- VERSION = "3.0.12"
8
+ VERSION = "3.0.14"
9
9
  end
10
10
  # Current gem version exposed at the traditional constant location.
11
11
  VERSION = Version::VERSION # Traditional Constant Location
@@ -189,11 +189,7 @@ module Kettle
189
189
 
190
190
  source = File.read(gemspec_path)
191
191
  parse_result = self.class.parse_source(source, gemspec_path)
192
- self.class.each_node(parse_result.value).filter_map do |node|
193
- next unless node.is_a?(Prism::CallNode) && node.name == :version=
194
-
195
- version_node = node.arguments&.arguments&.first
196
- next unless version_node.is_a?(Prism::StringNode)
192
+ gemspec_version_literal_nodes(parse_result).filter_map do |version_node|
197
193
  next unless version_node.unescaped == current_version
198
194
  next if version_node.unescaped == target_version
199
195
 
@@ -202,6 +198,34 @@ module Kettle
202
198
  end
203
199
  end
204
200
 
201
+ def gemspec_version_literal_nodes(parse_result)
202
+ version_assignments = self.class.each_node(parse_result.value).select do |node|
203
+ node.is_a?(Prism::CallNode) && node.name == :version=
204
+ end
205
+ local_version_assignments = self.class.each_node(parse_result.value).select do |node|
206
+ node.is_a?(Prism::LocalVariableWriteNode) && node.name == :gem_version
207
+ end
208
+
209
+ version_assignments.flat_map do |assignment|
210
+ value = assignment.arguments&.arguments&.first
211
+ if value.is_a?(Prism::StringNode)
212
+ [value]
213
+ elsif value.is_a?(Prism::LocalVariableReadNode) && value.name == :gem_version
214
+ local_version_assignments.flat_map { |node| version_literals(node.value) }
215
+ else
216
+ version_literals(value)
217
+ end
218
+ end.uniq
219
+ end
220
+
221
+ def version_literals(node)
222
+ return [] unless node
223
+
224
+ self.class.each_node(node).select do |candidate|
225
+ candidate.is_a?(Prism::StringNode)
226
+ end
227
+ end
228
+
205
229
  def gemspec_path_for_bump
206
230
  paths = Dir.glob(File.join(root, "*.gemspec")).sort
207
231
  return nil if paths.empty?
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kettle-dev
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.12
4
+ version: 3.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter H. Boling
@@ -60,7 +60,7 @@ dependencies:
60
60
  version: '0.3'
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
- version: 0.3.13
63
+ version: 0.3.14
64
64
  type: :runtime
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
@@ -70,7 +70,7 @@ dependencies:
70
70
  version: '0.3'
71
71
  - - ">="
72
72
  - !ruby/object:Gem::Version
73
- version: 0.3.13
73
+ version: 0.3.14
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: kettle-ndjson
76
76
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,7 @@ dependencies:
80
80
  version: '0.1'
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
- version: 0.1.12
83
+ version: 0.1.13
84
84
  type: :runtime
85
85
  prerelease: false
86
86
  version_requirements: !ruby/object:Gem::Requirement
@@ -90,7 +90,7 @@ dependencies:
90
90
  version: '0.1'
91
91
  - - ">="
92
92
  - !ruby/object:Gem::Version
93
- version: 0.1.12
93
+ version: 0.1.13
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: kettle-rb
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -100,7 +100,7 @@ dependencies:
100
100
  version: '0.1'
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: 0.1.10
103
+ version: 0.1.11
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
@@ -110,7 +110,7 @@ dependencies:
110
110
  version: '0.1'
111
111
  - - ">="
112
112
  - !ruby/object:Gem::Version
113
- version: 0.1.10
113
+ version: 0.1.11
114
114
  - !ruby/object:Gem::Dependency
115
115
  name: kettle-test
116
116
  requirement: !ruby/object:Gem::Requirement
@@ -256,7 +256,7 @@ dependencies:
256
256
  version: '3.2'
257
257
  - - ">="
258
258
  - !ruby/object:Gem::Version
259
- version: 3.2.5
259
+ version: 3.2.6
260
260
  type: :development
261
261
  prerelease: false
262
262
  version_requirements: !ruby/object:Gem::Requirement
@@ -266,7 +266,7 @@ dependencies:
266
266
  version: '3.2'
267
267
  - - ">="
268
268
  - !ruby/object:Gem::Version
269
- version: 3.2.5
269
+ version: 3.2.6
270
270
  - !ruby/object:Gem::Dependency
271
271
  name: stone_checksums
272
272
  requirement: !ruby/object:Gem::Requirement
@@ -296,7 +296,7 @@ dependencies:
296
296
  version: '2.0'
297
297
  - - ">="
298
298
  - !ruby/object:Gem::Version
299
- version: 2.0.11
299
+ version: 2.0.12
300
300
  type: :development
301
301
  prerelease: false
302
302
  version_requirements: !ruby/object:Gem::Requirement
@@ -306,7 +306,7 @@ dependencies:
306
306
  version: '2.0'
307
307
  - - ">="
308
308
  - !ruby/object:Gem::Version
309
- version: 2.0.11
309
+ version: 2.0.12
310
310
  description: "\U0001F372 Kettle::Dev is a meta tool from kettle-dev to streamline
311
311
  development and testing. Acts as a shim dependency, pulling in many other dependencies,
312
312
  to give you OOTB productivity with a RubyGem, or Ruby app project. Configures a
@@ -400,10 +400,10 @@ licenses:
400
400
  - AGPL-3.0-only
401
401
  metadata:
402
402
  homepage_uri: https://kettle-dev.galtzo.com
403
- source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.12
404
- changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.12/CHANGELOG.md
403
+ source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.14
404
+ changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.14/CHANGELOG.md
405
405
  bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
406
- documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.12
406
+ documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.14
407
407
  funding_uri: https://github.com/sponsors/pboling
408
408
  wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
409
409
  news_uri: https://www.railsbling.com/tags/kettle-dev
metadata.gz.sig CHANGED
Binary file