kettle-family 1.1.9 → 1.2.0

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: a7a1fef6a9376e6ea7f3d4f2ecd61bf5a610fdb1585037ce63631ef106ec6e24
4
- data.tar.gz: aa9c89f697651bf26926575dde900f6ab070b487191f2ca6436ce04a3ad231c6
3
+ metadata.gz: aefb679d0cc2bdabed07ed294dfe6a270a2c8739bcb11b80c1eb1a367a8c72c9
4
+ data.tar.gz: c77c800317e98d86444d28ef032956d57dcfa187cb79a39b622ef266502a5a34
5
5
  SHA512:
6
- metadata.gz: 11993355cf52d83e6bbbdae954983f7e10233968fece69a812c62ea5c1787112ba825e2eed207c12a06d44abb3af6199af5f3a11647733845abd8ba76ed25335
7
- data.tar.gz: 7abbd03f4c8f434c8a069dc92d6c4eb66b06b81adb73d2454cf5fc0be9dbcb5335201b248173c15d91fcff699761e408303be2c7887fdabd1a07b2dd63e38ec8
6
+ metadata.gz: 77f54999801132c906f45e29b377c088e6459fa4103649a06f4e1679fee57ffa99f43c38d5af5af9dae12c9352ef027e896775c897ba077a28057122f700aa5f
7
+ data.tar.gz: a213bee6bf3d05686cfe38957c47a8554190283b944037532426097a1d23285c5132a9aef20f71637305aba02421d9761ac5ae0605f8fcd59d43418d673150e8
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,97 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [1.2.0] - 2026-07-27
34
+
35
+ - TAG: [v1.2.0][1.2.0t]
36
+ - COVERAGE: 93.85% -- 3557/3790 lines in 27 files
37
+ - BRANCH COVERAGE: 76.15% -- 1338/1757 branches in 27 files
38
+ - 28.20% documented
39
+
40
+ ### Added
41
+
42
+ - `kettle-family reset Gemfile.lock` now resets selected member lockfiles with
43
+ local sibling paths disabled and repairs missing checksum entries by updating
44
+ the affected released gems.
45
+
46
+ - `kettle-family release` can now use an opt-in `1password` release secrets
47
+ provider to load the gem signing passphrase and RubyGems MFA OTP from the
48
+ local `op` CLI during executed publish flows.
49
+
50
+ - `kettle-family clean-unreleased` now scans installed versions of selected
51
+ family gems and can uninstall local versions newer than each gem's latest
52
+ released version, helping recover from failed local release attempts that
53
+ leave unpublished gems installed.
54
+
55
+ - kettle-jem-template-20260726-001 - Projects now include YARD lint
56
+ configuration and documentation dependencies so documentation issues fail
57
+ before generated docs are refreshed.
58
+
59
+ - kettle-jem-template-20260727-001 - Spec harness documentation now lists the
60
+ RSpec helpers provided by `kettle-test`.
61
+
62
+ ### Changed
63
+
64
+ - Templating and release progress rows now show a scheduled family-step counter
65
+ and per-member elapsed timer, making parallel family runs easier to scan.
66
+
67
+ - `kettle-family reset Gemfile.lock` now delegates to each member's
68
+ `kettle-reset release-lockfiles`, keeping `Gemfile.lock` and
69
+ `Appraisal.root.gemfile.lock` reset semantics aligned with `kettle-release`.
70
+
71
+ - Explicit `members.roots` lists now remain open to member discovery by default:
72
+ newly discovered gems are included in family operations and reported as
73
+ unlisted until the config is updated. Set `members.discover: false` to operate
74
+ only on configured members.
75
+ - The `kettle-family` executable startup header is now shown only when
76
+ `--verbose` is passed; `-v` and `--version` still print just the executable
77
+ version and exit.
78
+ - README release guidance now documents release secrets provider overrides,
79
+ release-state columns, default `--only` filters, JSON reports, and
80
+ unreleased-gem cleanup recovery.
81
+ - Release secrets now use the shared `kettle-dev` provider implementation;
82
+ family publish runs cache the signing passphrase once and let child
83
+ `kettle-release` processes fetch RubyGems OTP values directly.
84
+ - Family publish runs now pass `--yes` to child `kettle-release` commands by
85
+ default, so release-owned confirmation prompts are approved explicitly instead
86
+ of being answered by terminal prompt detection.
87
+
88
+ - GitHub workflows now use the setup-ruby-flash revision that supports
89
+ appraisal-only setup without installing the main Gemfile bundle.
90
+
91
+ - kettle-jem-template-20260728-001 - Generated Ruby workflows now use clearer
92
+ setup-ruby-flash planning and can prepare appraisal-only jobs without
93
+ installing the main Gemfile bundle.
94
+
95
+ ### Fixed
96
+
97
+ - RuboCop Gradual now ignores gems installed under `gemfiles/vendor/bundle`,
98
+ preventing vendored dependency source from being treated as project lint debt.
99
+
100
+ - Family releases now pass configured 1Password CLI paths through to child
101
+ `kettle-release` processes and derive direct secret handoff from normalized
102
+ provider config instead of parsing equivalent command-line spellings.
103
+
104
+ - Family release secrets now preserve `Kettle::Family::Error` failures when the
105
+ shared `kettle-dev` provider reports 1Password lookup errors.
106
+
107
+ - Template summaries now count unique changed files from kettle-jem event
108
+ streams instead of adding duplicate per-phase summary counts.
109
+ - Family release lockfile refreshes now disable the generated local-path
110
+ toggles used by member Gemfiles, reject lockfile refreshes that still write
111
+ path sources, and re-normalize lockfiles before family-managed release pushes.
112
+
113
+ - kettle-jem-template-20260726-002 - Generated version files now document their
114
+ version namespace and constants, reducing warning-only YARD lint output.
115
+
116
+ - kettle-jem-template-20260726-003 - Coverage upload steps now treat Coveralls,
117
+ QLTY, and Codecov as optional, so provider outages do not fail CI when local
118
+ coverage thresholds still pass.
119
+
120
+ - kettle-jem-template-20260728-002 - Generated RuboCop configs now ignore the
121
+ same `gemfiles/vendor/bundle` tree as `.gitignore`, so vendored dependency
122
+ installs are not reported as project lint debt.
123
+
33
124
  ## [1.1.9] - 2026-07-25
34
125
 
35
126
  - TAG: [v1.1.9][1.1.9t]
@@ -1072,7 +1163,9 @@ Please file a bug if you notice a violation of semantic versioning.
1072
1163
  - Fixed CI load failures on engines without compatible `pty` support by falling back to Open3 for interactive release commands.
1073
1164
  - Fixed Ruby 3.2 version-bump support by loading Prism lazily and wiring the Prism gem only for MRI versions that need it.
1074
1165
 
1075
- [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.1.9...HEAD
1166
+ [Unreleased]: https://github.com/kettle-dev/kettle-family/compare/v1.2.0...HEAD
1167
+ [1.2.0]: https://github.com/kettle-dev/kettle-family/compare/v1.1.9...v1.2.0
1168
+ [1.2.0t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.2.0
1076
1169
  [1.1.9]: https://github.com/kettle-dev/kettle-family/compare/v1.1.8...v1.1.9
1077
1170
  [1.1.9t]: https://github.com/kettle-dev/kettle-family/releases/tag/v1.1.9
1078
1171
  [1.1.8]: https://github.com/kettle-dev/kettle-family/compare/v1.1.7...v1.1.8
data/README.md CHANGED
@@ -38,7 +38,8 @@ is, avoiding duplicate release-prep commits after a failure/fix/retry cycle.
38
38
  `kettle-release` runs its own `kettle-pre-release` gate for full publish flows;
39
39
  options such as `start_step=N` and `--local-ci` pass through, CI failures still
40
40
  abort by default, and gem signing passphrases are cached once per family run
41
- while RubyGems MFA prompts remain interactive.
41
+ while RubyGems MFA prompts remain interactive unless an opt-in release secrets
42
+ provider supplies them.
42
43
 
43
44
  ## 💡 Info you can shake a stick at
44
45
 
@@ -139,17 +140,34 @@ gem install kettle-family
139
140
  `kettle-family` reads `.kettle-family.yml` from the family root by default.
140
141
  Use `--config PATH` to load a different file.
141
142
 
142
- Member discovery is recursive under the configured member roots. Discovery skips
143
- gemspecs ignored by git and any configured `members.exclude` glob patterns before
144
- loading gemspec metadata, so fixture or temporary gemspecs do not create duplicate
145
- members. Exclude patterns are matched relative to the family root and to each
146
- member root.
143
+ Member discovery is recursive under the family member search roots. Discovery
144
+ skips gemspecs ignored by git and any configured `members.exclude` glob patterns
145
+ before loading gemspec metadata, so fixture or temporary gemspecs do not create
146
+ duplicate members. Exclude patterns are matched relative to the family root and
147
+ to each member root.
148
+
149
+ Use `members.roots` as the normal configured member list. Each root is a member
150
+ directory with one primary gemspec. When `members.roots` is present,
151
+ `kettle-family` still discovers additional matching gems by default, includes
152
+ them in the operative member set, and reports warnings so the config can be
153
+ updated. Set `members.discover: false` when a family must operate only on the
154
+ configured roots.
155
+
156
+ Use `members.explicit` only when the member directory and gemspec cannot be
157
+ resolved by the one-gemspec rule. For example, use it when a member directory has
158
+ multiple gemspecs or the intended gemspec filename does not match the directory.
147
159
 
148
160
  ```yaml
149
161
  family:
150
162
  members_root: gems
151
163
 
152
164
  members:
165
+ roots:
166
+ - gems/alpha
167
+ - gems/beta
168
+ explicit:
169
+ - root: gems/compat
170
+ gemspec: compat-runtime.gemspec
153
171
  exclude:
154
172
  - "**/tmp/**"
155
173
  - "**/vendor/**"
@@ -191,6 +209,10 @@ Monorepo families whose member gems share release metadata from the repository
191
209
  root can configure readiness and changelog ownership explicitly:
192
210
 
193
211
  ```yaml
212
+ family:
213
+ name: structuredmerge-ruby
214
+ local_path_env: STRUCTUREDMERGE_DEV
215
+
194
216
  check:
195
217
  required_files:
196
218
  - Gemfile
@@ -212,11 +234,23 @@ changelog:
212
234
  release:
213
235
  env:
214
236
  KETTLE_DEV_DEV: false
237
+ disable_local_path_env:
238
+ - TSLP_DEV
215
239
  family_changelog:
216
240
  enabled: true
217
241
  command: bundle exec kettle-changelog
218
242
  ```
219
243
 
244
+ `family.local_path_env` names the environment variable that activates local
245
+ path dependencies for this family. It is the family identity used by other
246
+ families when they scan lockfile `PATH` remotes and need to disable local
247
+ resolution before writing CI-facing release lockfiles. Configure it when the
248
+ env name is not the default derived from `family.name`.
249
+
250
+ `release.disable_local_path_env` is different: it is an extra release-lockfile
251
+ normalization shutoff list for path-injecting env vars that are not themselves
252
+ discoverable as family roots, such as vendored parser/tooling paths.
253
+
220
254
  For `changelog.mode: root`, release commands pass `K_CHANGELOG_GEM_NAME` as the
221
255
  configured family name and `K_CHANGELOG_VERSION_FILE` when `version_file` is
222
256
  configured, so `kettle-changelog` can operate from a repository root without a
@@ -257,6 +291,30 @@ Executed publish runs skip versions that are already published. CI failures
257
291
  abort by default; pass `--continue-ci-failures` to set
258
292
  `K_RELEASE_CI_CONTINUE=true` for the underlying `kettle-release` process.
259
293
 
294
+ Executed publish runs can also use a release secrets provider. The default
295
+ provider is `interactive`. The `1password` provider is supplied by
296
+ `kettle-dev`, uses the local `op` CLI, and expects that `op` is installed,
297
+ unlocked, and signed in before the release starts.
298
+
299
+ ```yaml
300
+ release:
301
+ secrets:
302
+ provider: 1password
303
+ item: Rubygems
304
+ gem_signing_passphrase_field: GEM-SIGN-PASSPHRASE
305
+ rubygems_otp_field: one-time password
306
+ ```
307
+
308
+ Use `--secrets-provider 1password` to opt in explicitly for a release run, or
309
+ `--secrets-provider interactive` to force prompts even when the family config
310
+ has a provider configured. Family publish runs pass `--yes` to child
311
+ `kettle-release` commands by default, making release confirmation approval
312
+ explicit instead of relying on terminal prompt detection. Pass `--no-accept` to
313
+ leave child release confirmation prompts interactive. When publishing through
314
+ `kettle-release`, `kettle-family` loads the signing passphrase once and passes
315
+ the cached value to each child process; `kettle-release` still fetches RubyGems
316
+ OTP values directly from 1Password at prompt time.
317
+
260
318
  ## 🔧 Basic Usage
261
319
 
262
320
  Inspect discovery and release plans before executing them:
@@ -270,12 +328,17 @@ Audit changelog release state across the selected family members:
270
328
 
271
329
  ```console
272
330
  kettle-family release-state
331
+ kettle-family state
273
332
  ```
274
333
 
275
- The release-state report lists each gem's current `version.rb`, latest published
276
- release, latest versioned `CHANGELOG.md` section, whether pending changelog work
277
- exists in either `Unreleased` or an unpublished prepared release section, and
278
- how many commits the current branch is ahead of the latest release tag.
334
+ `state` is an alias for `release-state`. The release-state report lists each
335
+ gem's current `version.rb`, latest versioned `CHANGELOG.md` section, latest
336
+ published RubyGems release, latest GitHub release tag, transfer-changelog lag,
337
+ whether pending changelog work exists in either `Unreleased` or an unpublished
338
+ prepared release section, and how many commits the current branch is ahead of or
339
+ behind the latest release tag. The version columns are ordered as `V.rb`,
340
+ `V.ch.md`, `V.rel`, and `GH.rel`; `GH.rel` is prefixed with a red circle when it
341
+ does not match `V.rel`.
279
342
 
280
343
  Use release-state tokens with `--only` to select gems by state instead of by
281
344
  name. Multiple tokens are combined with logical AND, so this selects only gems
@@ -295,6 +358,14 @@ The supported release-state tokens are:
295
358
  | `unreleased` | The current `version.rb` is not the latest published gem version. |
296
359
  | `prepared` | The changelog already has a versioned section for the current `version.rb`. |
297
360
  | `pending` | The member has unreleased changelog work or an unpublished prepared release. |
361
+ | `bump` | The member has unreleased changelog work and still needs a version bump. |
362
+
363
+ When `--only` is omitted, `kettle-family bump` defaults to `--only bump` and
364
+ `kettle-family release` defaults to `--only pending`.
365
+
366
+ Most commands accept `--json` to print a machine-readable report instead of the
367
+ text report, and `--report PATH` to write that JSON report while still printing
368
+ the normal text report.
298
369
 
299
370
  Plan or update GitHub Actions workflow SHA pins across the selected family
300
371
  members:
@@ -322,12 +393,78 @@ kettle-family release --execute --publish --skip-remotes cb,mirror2
322
393
 
323
394
  Publish through `kettle-release`. A full publish first runs the
324
395
  `kettle-pre-release` gate, then prompts once for the gem signing key password
325
- and leaves RubyGems MFA prompts interactive:
396
+ and leaves RubyGems MFA prompts interactive by default:
326
397
 
327
398
  ```console
328
399
  kettle-family release --publish --execute
329
400
  ```
330
401
 
402
+ By default, family publish runs pass `--yes` to child `kettle-release`
403
+ commands. Use `--no-accept` when you want each child release command to ask its
404
+ own confirmation prompts.
405
+
406
+ Executed publish runs can opt in to the local 1Password CLI for unattended
407
+ release credentials. The gem signing passphrase is loaded once and cached only
408
+ in memory for the current `kettle-family` process. When the publish command is
409
+ `kettle-release`, that cached value is passed through an internal environment
410
+ integration so child releases do not query 1Password repeatedly for the same
411
+ passphrase. RubyGems OTP values are loaded by `kettle-release` when each MFA
412
+ prompt arrives, so long releases do not reuse a stale TOTP from release startup.
413
+
414
+ ```yaml
415
+ release:
416
+ secrets:
417
+ provider: 1password
418
+ item: Rubygems
419
+ gem_signing_passphrase_field: GEM-SIGN-PASSPHRASE
420
+ rubygems_otp_field: one-time password
421
+ ```
422
+
423
+ ```console
424
+ kettle-family release --publish --execute --secrets-provider 1password
425
+ ```
426
+
427
+ For vault-specific or field-specific references, configure explicit 1Password
428
+ secret references. `rubygems_otp_reference` is read with `op read`; otherwise
429
+ the OTP path uses `op item get ITEM --otp`.
430
+
431
+ ```yaml
432
+ release:
433
+ secrets:
434
+ provider: 1password
435
+ account: my.1password.com
436
+ gem_signing_passphrase_reference: op://Private/Rubygems/GEM-SIGN-PASSPHRASE
437
+ rubygems_otp_reference: op://Private/Rubygems/one-time password?attribute=otp
438
+ ```
439
+
440
+ If an old failed release left local unpublished versions installed and Bundler
441
+ keeps selecting them, clean selected family members before retrying:
442
+
443
+ ```console
444
+ kettle-family clean-unreleased --execute
445
+ ```
446
+
447
+ The cleanup command compares installed local gem versions with each member's
448
+ latest released version and uninstalls only versions newer than the release
449
+ state reports.
450
+
451
+ If a member lockfile has captured local sibling paths or checksum-less gem
452
+ versions from local development, reset the selected members' `Gemfile.lock`
453
+ files:
454
+
455
+ ```console
456
+ kettle-family reset Gemfile.lock
457
+ kettle-family reset Gemfile.lock --execute
458
+ ```
459
+
460
+ `reset Gemfile.lock` runs each member's `bundle exec kettle-reset release-lockfiles`
461
+ with configured, inferred, and path-like local `*_DEV`/`*_LOCAL` environment
462
+ variables disabled. The member-level reset rewrites local sibling paths and
463
+ checksum-less local gem versions back to released registry versions with
464
+ `sha256=` checksums in `Gemfile.lock` and `Appraisal.root.gemfile.lock`.
465
+ Executed family resets fail if the resulting lockfiles still contain sibling
466
+ path remotes or checksum gaps.
467
+
331
468
  Resume a failed family publish after fixing the failure. Already published
332
469
  versions are skipped automatically; `start_step` is passed to `kettle-release`
333
470
  for unreleased members that still need work. Use `--start-step` only after the
@@ -371,7 +508,9 @@ kettle-family template --execute --only kettle-family
371
508
 
372
509
  Templating uses `kettle-jem --events` as the default child interface, so
373
510
  executed runs stream member-prefixed progress while still keeping the final
374
- family report. Progress marks include:
511
+ family report. Each member line includes a scheduled family-step counter and a
512
+ per-member elapsed timer. Child `kettle-jem` events can still add progress marks,
513
+ but only completed family steps advance the counter. Progress marks include:
375
514
 
376
515
  | Mark | Meaning |
377
516
  |------|---------|
@@ -386,10 +525,12 @@ Example progress:
386
525
 
387
526
  ```text
388
527
  templating 1 member with 1 job:
389
- [kettle-family] > recipes
390
- [kettle-family] * Gemfile
391
- [kettle-family] * post_apply:git_hooks_executable
392
- [kettle-family] done 2 files changed
528
+ [kettle-family] (0/4) 00:00 > prepare_lockfiles
529
+ [kettle-family] (1/4) 00:01 . prepare_lockfiles
530
+ [kettle-family] (2/4) 00:05 . prepare_template_dependencies
531
+ [kettle-family] (3/4) 00:12 . template
532
+ [kettle-family] (4/4) 00:13 . normalize_lockfiles
533
+ [kettle-family] (4/4) 00:13 done 2 files changed
393
534
  .
394
535
  template summary: 1/1 members ok, 2 files changed
395
536
  ```
@@ -669,7 +810,7 @@ Thanks for RTFM. ☺️
669
810
  [📌gitmoji]: https://gitmoji.dev
670
811
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
671
812
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
672
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.357-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
813
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-3.790-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
673
814
  [🔐security]: https://github.com/kettle-dev/kettle-family/blob/main/SECURITY.md
674
815
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
675
816
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
data/exe/kettle-family CHANGED
@@ -13,6 +13,7 @@ end
13
13
 
14
14
  require "kettle/family"
15
15
 
16
- puts "== #{script_basename} v#{Kettle::Family::Version::VERSION} ==" unless ARGV.include?("--json")
16
+ verbose_header = ARGV.delete("--verbose")
17
+ puts "== #{script_basename} v#{Kettle::Family::Version::VERSION} ==" if verbose_header && !ARGV.include?("--json")
17
18
 
18
19
  exit(Kettle::Family::CLI.call(ARGV))
@@ -10,8 +10,8 @@ module Kettle
10
10
  class CLI < CommandKit::Command
11
11
  include CommandKit::Commands
12
12
 
13
- COMMANDS = %w[discover plan report metadata check test lint docs template gha-sha-pins bup bupb bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
14
- WORKFLOW_COMMANDS = %w[check test lint docs template gha-sha-pins bup bupb bex release push pull sync up].freeze
13
+ COMMANDS = %w[discover plan report metadata check clean-unreleased reset test lint docs template gha-sha-pins bup bupb bex install bump bump-version add-changelog release push pull sync up branch-lanes release-state state].freeze
14
+ WORKFLOW_COMMANDS = %w[check reset test lint docs template gha-sha-pins bup bupb bex release push pull sync up].freeze
15
15
 
16
16
  command_name "kettle-family"
17
17
  usage "[options] COMMAND [ARGS...]"
@@ -151,12 +151,14 @@ module Kettle
151
151
  release_ci_workflows: nil,
152
152
  release_skip_bundle_audit: false,
153
153
  release_skip_remotes: nil,
154
+ release_secrets_provider: nil,
154
155
  accept: true,
155
156
  tag: false,
156
157
  push: false,
157
158
  commit: !options.key?(:commit) || options[:commit],
158
159
  allow_dirty: truthy_option?(:allow_dirty),
159
160
  target_version: nil,
161
+ reset_target: nil,
160
162
  bup_args: [],
161
163
  bex_args: []
162
164
  }.merge(overrides)
@@ -281,6 +283,25 @@ module Kettle
281
283
  description "Run internal read-only readiness checks."
282
284
  end
283
285
 
286
+ class CleanUnreleased < WorkflowCommand
287
+ command_name "clean-unreleased"
288
+ usage "[options]"
289
+ description "Uninstall locally installed family gem versions newer than the latest released version."
290
+ end
291
+
292
+ class Reset < WorkflowCommand
293
+ command_name "reset"
294
+ usage "[options] TARGET"
295
+ description "Reset a managed family artifact. Initially supports Gemfile.lock."
296
+ argument :target, required: true, usage: "TARGET", desc: "Artifact to reset"
297
+
298
+ def run(target = nil)
299
+ raise Error, "reset requires TARGET" if target.to_s.empty?
300
+
301
+ run_family("reset", reset_target: target)
302
+ end
303
+ end
304
+
284
305
  class Test < WorkflowCommand
285
306
  command_name "test"
286
307
  usage "[options]"
@@ -443,6 +464,7 @@ module Kettle
443
464
  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"
444
465
  option :skip_bundle_audit, long: "--skip-bundle-audit", desc: "Skip bundle:audit/update during release rake checks"
445
466
  option :skip_remotes, long: "--skip-remotes", value: {type: String, usage: "LIST"}, desc: "Pass a comma-separated git remote skip list through to kettle-release commands"
467
+ option :secrets_provider, long: "--secrets-provider", value: {type: String, usage: "NAME"}, desc: "Release secrets provider: interactive, 1password"
446
468
  option :no_auto_floors, long: "--no-auto-floors", desc: "Do not raise family dependency floors between member releases" do
447
469
  options[:no_auto_floors] = true
448
470
  end
@@ -465,6 +487,7 @@ module Kettle
465
487
  release_ci_workflows: options[:ci_workflows],
466
488
  release_skip_bundle_audit: truthy_option?(:skip_bundle_audit),
467
489
  release_skip_remotes: options[:skip_remotes],
490
+ release_secrets_provider: options[:secrets_provider],
468
491
  release_auto_dependency_floors: !truthy_option?(:no_auto_floors),
469
492
  accept: !options.key?(:accept) || options[:accept],
470
493
  tag: truthy_option?(:tag),
@@ -502,6 +525,8 @@ module Kettle
502
525
  command "report", ReportCommand
503
526
  command Metadata
504
527
  command Check
528
+ command CleanUnreleased
529
+ command Reset
505
530
  command Test
506
531
  command Lint
507
532
  command Docs
@@ -552,7 +577,8 @@ module Kettle
552
577
  def build_report(command, options)
553
578
  config = Config.load(root: options[:root], path: options[:config])
554
579
  start_at = parse_start_at(options[:start_at])
555
- members = Discovery.new(config: config).members
580
+ discovery = Discovery.new(config: config)
581
+ members = discovery.members
556
582
  ordered = if command == "install"
557
583
  install_order(members, config)
558
584
  elsif %w[metadata release-state].include?(command)
@@ -577,7 +603,8 @@ module Kettle
577
603
  member_release_target_branches: member_release_target_branches(command: command, members: selected, config: config, start_at: start_at),
578
604
  release_mode: release_mode(command: command, options: options),
579
605
  command: command,
580
- results: results
606
+ results: results,
607
+ warnings: discovery.warnings
581
608
  )
582
609
  end
583
610
 
@@ -607,6 +634,7 @@ module Kettle
607
634
  def command_results_for_current_branch(command:, config:, members:, options:, start_at: StartAt.new(nil, nil))
608
635
  return bump_version_results(members: members, options: options, phase: command) if %w[bump bump-version].include?(command)
609
636
  return add_changelog_results(members: members, options: options) if command == "add-changelog"
637
+ return clean_unreleased_results(config: config, members: members, options: options) if command == "clean-unreleased"
610
638
  return branch_lane_results(config: config, members: members) if command == "branch-lanes"
611
639
  return release_state_results(config: config, members: members) if command == "release-state"
612
640
  return install_results(config: config, members: members, options: options) if command == "install"
@@ -630,6 +658,7 @@ module Kettle
630
658
  ci_workflows: options[:release_ci_workflows],
631
659
  skip_bundle_audit: options[:release_skip_bundle_audit],
632
660
  skip_remotes: options[:release_skip_remotes],
661
+ secrets_provider: release_secrets_provider(command: command, config: config, options: options),
633
662
  auto_dependency_floors: options[:release_auto_dependency_floors],
634
663
  gha_sha_pins_upgrade: options[:gha_sha_pins_upgrade],
635
664
  gha_sha_pins_check: options[:check],
@@ -638,6 +667,7 @@ module Kettle
638
667
  verbose: options[:verbose],
639
668
  jobs: options[:jobs],
640
669
  progress_io: progress_io(command, options),
670
+ reset_target: options[:reset_target],
641
671
  bup_args: options[:bup_args],
642
672
  bex_args: options[:bex_args],
643
673
  start_member: start_at.member,
@@ -653,6 +683,12 @@ module Kettle
653
683
  stdout
654
684
  end
655
685
 
686
+ def release_secrets_provider(command:, config:, options:)
687
+ return nil unless command == "release"
688
+
689
+ Secrets::Factory.build(config: config, override_provider: options[:release_secrets_provider])
690
+ end
691
+
656
692
  def branch_target_command?(command, config)
657
693
  return false if config.release_target_branches.empty?
658
694
  return false if command == "release-state"
@@ -727,6 +763,8 @@ module Kettle
727
763
  discovered = Discovery.new(config: config).members
728
764
  ordered = (command == "install") ? install_order(discovered, config) : Orderer.new(members: discovered, mode: config.order_mode, hints: config.order_hints).ordered
729
765
  ordered.select { |member| selected_names.include?(member.name) }
766
+ rescue Error
767
+ []
730
768
  end
731
769
 
732
770
  def family_member(config)
@@ -836,6 +874,10 @@ module Kettle
836
874
  BranchLaneAudit.new(config: config, members: members).results
837
875
  end
838
876
 
877
+ def clean_unreleased_results(config:, members:, options:)
878
+ UnreleasedGemCleanup.new(config: config, members: members, execute: options[:execute]).results
879
+ end
880
+
839
881
  def install_results(config:, members:, options:)
840
882
  LocalInstall.new(config: config, members: members, execute: options[:execute], jobs: options[:jobs]).results
841
883
  end
@@ -6,10 +6,15 @@ require "io/console"
6
6
  module Kettle
7
7
  module Family
8
8
  class CommandRunner
9
+ SENSITIVE_ENV_KEYS = [
10
+ "KETTLE_RELEASE_GEM_SIGNING_PASSPHRASE"
11
+ ].freeze
12
+
9
13
  class OtpCoordinator
10
- def initialize(input: $stdin, output: $stdout, queue_total: nil)
14
+ def initialize(input: $stdin, output: $stdout, queue_total: nil, secrets_provider: nil)
11
15
  @input = input
12
16
  @output = output
17
+ @secrets_provider = secrets_provider
13
18
  @mutex = Mutex.new
14
19
  @condition = ConditionVariable.new
15
20
  @prompting = false
@@ -61,6 +66,8 @@ module Kettle
61
66
 
62
67
  private
63
68
 
69
+ attr_reader :secrets_provider
70
+
64
71
  def wait_for_response(generation)
65
72
  @condition.wait(@mutex) while @prompting
66
73
  return @response if @completed_generation == generation
@@ -88,6 +95,12 @@ module Kettle
88
95
  end
89
96
 
90
97
  def read_response(chunk:)
98
+ provided = secrets_provider&.rubygems_otp.to_s
99
+ unless provided.empty?
100
+ @output.puts("RubyGems MFA code loaded from configured secrets provider.")
101
+ return provided
102
+ end
103
+
91
104
  @output.print("#{otp_prompt_label(chunk)} ")
92
105
  @output.flush if @output.respond_to?(:flush)
93
106
  if @input.respond_to?(:noecho) && @input.tty?
@@ -406,7 +419,8 @@ module Kettle
406
419
  argv = normalize_command(command)
407
420
  return argv unless mise_configured?(member)
408
421
 
409
- unset_env, set_env = env.partition { |_key, value| value.nil? }
422
+ command_env = nonsensitive_env(env)
423
+ unset_env, set_env = command_env.partition { |_key, value| value.nil? }
410
424
  injected_env = [
411
425
  *unset_env.flat_map { |key, _value| ["-u", key.to_s] },
412
426
  *set_env.map { |key, value| "#{key}=#{value}" }
@@ -421,7 +435,19 @@ module Kettle
421
435
  base_env = unbundled_process_env
422
436
  return base_env.merge(env) unless mise_configured?(member)
423
437
 
424
- base_env
438
+ base_env.merge(sensitive_env(env))
439
+ end
440
+
441
+ def nonsensitive_env(env)
442
+ env.reject { |key, _value| sensitive_env_key?(key) }
443
+ end
444
+
445
+ def sensitive_env(env)
446
+ env.select { |key, value| sensitive_env_key?(key) && !value.nil? }
447
+ end
448
+
449
+ def sensitive_env_key?(key)
450
+ SENSITIVE_ENV_KEYS.include?(key.to_s)
425
451
  end
426
452
 
427
453
  def unbundled_process_env