kettle-dev 2.5.27 → 3.0.1
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +50 -1
- data/README.md +1 -1
- data/lib/kettle/dev/interactive_release_command.rb +56 -27
- data/lib/kettle/dev/release_cli.rb +98 -4
- data/lib/kettle/dev/release_secrets.rb +22 -2
- data/lib/kettle/dev/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +8 -10
- metadata.gz.sig +0 -0
- data/exe/kettle-changelog +0 -244
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 335bd9651ff7bbe5381c8e050a26f070c6accd8054ab581226ba3355c8a4aa6e
|
|
4
|
+
data.tar.gz: 7f59238412adde48ba5f2b524b08e4ce77a5348c81d0984e44013e6ce905b2dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94e3d356b963078c8dd46d50d0f2b7ed92f656327bd3f766618617b56172ae0051dc32f6469cfd5c9faf539e00a4bbdf123947dd8febbd10c34172f51a2efc2d
|
|
7
|
+
data.tar.gz: d2eee5766a478c4846f05969ac4fcbf4614793740911c029219d85fb446fd308f9c376c5fa8d68edbdc71bd144dba09a1ad927c5915278c7933d0fc50ac74dca
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,49 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
30
30
|
|
|
31
31
|
### Security
|
|
32
32
|
|
|
33
|
+
## [3.0.1] - 2026-08-10
|
|
34
|
+
|
|
35
|
+
- TAG: [v3.0.1][3.0.1t]
|
|
36
|
+
- COVERAGE: 91.87% -- 5602/6098 lines in 47 files
|
|
37
|
+
- BRANCH COVERAGE: 76.29% -- 2117/2775 branches in 47 files
|
|
38
|
+
- 50.64% documented
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- Run 1Password authorization keepalives through the interactive terminal so release prompts remain visible.
|
|
43
|
+
|
|
44
|
+
- Re-normalize release lockfiles after release tasks before commit and CI validation.
|
|
45
|
+
|
|
46
|
+
## [3.0.0] - 2026-08-09
|
|
47
|
+
|
|
48
|
+
- TAG: [v3.0.0][3.0.0t]
|
|
49
|
+
- COVERAGE: 91.48% -- 5552/6069 lines in 47 files
|
|
50
|
+
- BRANCH COVERAGE: 76.26% -- 2107/2763 branches in 47 files
|
|
51
|
+
- 50.77% documented
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- Move the kettle-changelog executable to the standalone kettle-changelog gem while retaining kettle-dev support APIs for the transition.
|
|
56
|
+
|
|
57
|
+
- Resolve standalone kettle-changelog through the generated Ruby-4-only modular release-tool Gemfile, with local path wiring for development.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Use a dedicated standalone changelog bundle during bootstrap releases when the project bundle does not include kettle-changelog.
|
|
62
|
+
|
|
63
|
+
- Keep standalone changelog bootstrap paths out of target release tests and documentation.
|
|
64
|
+
|
|
65
|
+
## [2.5.28] - 2026-08-09
|
|
66
|
+
|
|
67
|
+
- TAG: [v2.5.28][2.5.28t]
|
|
68
|
+
- COVERAGE: 91.71% -- 5544/6045 lines in 47 files
|
|
69
|
+
- BRANCH COVERAGE: 76.48% -- 2104/2751 branches in 47 files
|
|
70
|
+
- 50.77% documented
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
|
|
74
|
+
- Retry an existing gem publication once with a fresh RubyGems MFA OTP when the release task reports an invalid OTP.
|
|
75
|
+
|
|
33
76
|
## [2.5.27] - 2026-08-09
|
|
34
77
|
|
|
35
78
|
- TAG: [v2.5.27][2.5.27t]
|
|
@@ -3371,7 +3414,13 @@ Please file a bug if you notice a violation of semantic versioning.
|
|
|
3371
3414
|
- Selecting will run the selected workflow via `act`
|
|
3372
3415
|
- This may move to its own gem in the future.
|
|
3373
3416
|
|
|
3374
|
-
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/
|
|
3417
|
+
[Unreleased]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.1...HEAD
|
|
3418
|
+
[3.0.1]: https://github.com/kettle-dev/kettle-dev/compare/v3.0.0...v3.0.1
|
|
3419
|
+
[3.0.1t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.1
|
|
3420
|
+
[3.0.0]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.28...v3.0.0
|
|
3421
|
+
[3.0.0t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v3.0.0
|
|
3422
|
+
[2.5.28]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.27...v2.5.28
|
|
3423
|
+
[2.5.28t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.28
|
|
3375
3424
|
[2.5.27]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.26...v2.5.27
|
|
3376
3425
|
[2.5.27t]: https://github.com/kettle-dev/kettle-dev/releases/tag/v2.5.27
|
|
3377
3426
|
[2.5.26]: https://github.com/kettle-dev/kettle-dev/compare/v2.5.25...v2.5.26
|
data/README.md
CHANGED
|
@@ -1007,7 +1007,7 @@ Thanks for RTFM. ☺️
|
|
|
1007
1007
|
[📌gitmoji]: https://gitmoji.dev
|
|
1008
1008
|
[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
|
|
1009
1009
|
[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
|
1010
|
-
[🧮kloc-img]: https://img.shields.io/badge/KLOC-6.
|
|
1010
|
+
[🧮kloc-img]: https://img.shields.io/badge/KLOC-6.098-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
|
|
1011
1011
|
[🔐security]: https://github.com/kettle-dev/kettle-dev/blob/main/SECURITY.md
|
|
1012
1012
|
[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
|
|
1013
1013
|
[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
|
|
@@ -1,39 +1,54 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require "shellwords"
|
|
4
5
|
|
|
5
6
|
module Kettle
|
|
6
7
|
module Dev
|
|
7
8
|
class InteractiveReleaseCommand
|
|
8
|
-
def initialize(secrets_provider:, input: $stdin, output: $stdout, error: $stderr, secret_event_handler: nil)
|
|
9
|
+
def initialize(secrets_provider:, input: $stdin, output: $stdout, error: $stderr, secret_event_handler: nil, forward_stdin: false)
|
|
9
10
|
@secrets_provider = secrets_provider
|
|
10
11
|
@input = input
|
|
11
12
|
@output = output
|
|
12
13
|
@error = error
|
|
13
14
|
@secret_event_handler = secret_event_handler
|
|
15
|
+
@forward_stdin = forward_stdin
|
|
14
16
|
@gem_signing_passphrase = nil
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def call(env, cmd)
|
|
18
|
-
|
|
20
|
+
call_argv(env, Shellwords.split(cmd.to_s))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call_argv(env, argv)
|
|
24
|
+
return call_with_pty(env, argv) if pty_available?
|
|
19
25
|
|
|
20
|
-
call_with_open3(env,
|
|
26
|
+
call_with_open3(env, argv)
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
private
|
|
24
30
|
|
|
25
31
|
attr_reader :secrets_provider, :secret_event_handler
|
|
26
32
|
|
|
27
|
-
def call_with_pty(env,
|
|
33
|
+
def call_with_pty(env, argv)
|
|
28
34
|
stdout_str = +""
|
|
35
|
+
prompt_buffer = +""
|
|
29
36
|
status = nil
|
|
30
|
-
PTY.spawn(env,
|
|
37
|
+
PTY.spawn(env, *argv) do |output, input, pid|
|
|
31
38
|
begin
|
|
32
39
|
loop do
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
readers = [output]
|
|
41
|
+
readers << @input if @forward_stdin && @input.tty?
|
|
42
|
+
IO.select(readers).first.each do |reader|
|
|
43
|
+
if reader.equal?(@input)
|
|
44
|
+
input.write(@input.readpartial(1024))
|
|
45
|
+
else
|
|
46
|
+
chunk = output.readpartial(1024)
|
|
47
|
+
stdout_str << chunk
|
|
48
|
+
@output.print(chunk)
|
|
49
|
+
prompt_buffer = handle_prompt(input, chunk, prompt_buffer: prompt_buffer)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
37
52
|
end
|
|
38
53
|
rescue Errno::EIO
|
|
39
54
|
# PTY raises EIO when the child process exits after closing the slave.
|
|
@@ -47,27 +62,33 @@ module Kettle
|
|
|
47
62
|
[stdout_str, "", status]
|
|
48
63
|
end
|
|
49
64
|
|
|
50
|
-
def call_with_open3(env,
|
|
65
|
+
def call_with_open3(env, argv)
|
|
51
66
|
stdout_str = +""
|
|
52
67
|
stderr_str = +""
|
|
68
|
+
prompt_buffer = +""
|
|
53
69
|
status = nil
|
|
54
|
-
Open3.popen3(env,
|
|
70
|
+
Open3.popen3(env, *argv) do |input, output, error, wait_thread|
|
|
55
71
|
readers = [output, error]
|
|
72
|
+
readers << @input if @forward_stdin && @input.tty?
|
|
56
73
|
until readers.empty?
|
|
57
74
|
ready = IO.select(readers)
|
|
58
75
|
ready.first.each do |reader|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
if reader.equal?(@input)
|
|
77
|
+
input.write(@input.readpartial(1024))
|
|
78
|
+
else
|
|
79
|
+
begin
|
|
80
|
+
chunk = reader.readpartial(1024)
|
|
81
|
+
if reader.equal?(output)
|
|
82
|
+
stdout_str << chunk
|
|
83
|
+
@output.print(chunk)
|
|
84
|
+
else
|
|
85
|
+
stderr_str << chunk
|
|
86
|
+
@error.print(chunk)
|
|
87
|
+
end
|
|
88
|
+
prompt_buffer = handle_prompt(input, chunk, prompt_buffer: prompt_buffer)
|
|
89
|
+
rescue EOFError
|
|
90
|
+
readers.delete(reader)
|
|
67
91
|
end
|
|
68
|
-
handle_prompt(input, chunk)
|
|
69
|
-
rescue EOFError
|
|
70
|
-
readers.delete(reader)
|
|
71
92
|
end
|
|
72
93
|
end
|
|
73
94
|
end
|
|
@@ -76,13 +97,21 @@ module Kettle
|
|
|
76
97
|
[stdout_str, stderr_str, status]
|
|
77
98
|
end
|
|
78
99
|
|
|
79
|
-
def handle_prompt(input, chunk)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
def handle_prompt(input, chunk, prompt_buffer: nil)
|
|
101
|
+
prompt_buffer = prompt_buffer.to_s.dup
|
|
102
|
+
prompt_buffer << chunk.to_s
|
|
103
|
+
prompt_buffer = prompt_buffer.byteslice([prompt_buffer.bytesize - 4096, 0].max, 4096).to_s
|
|
104
|
+
|
|
105
|
+
if otp_prompt?(prompt_buffer)
|
|
106
|
+
if otp_response_prompt?(prompt_buffer)
|
|
107
|
+
write_secret(input, label: "RubyGems MFA code", source: "rubygems_otp") { secrets_provider.rubygems_otp }
|
|
108
|
+
return +""
|
|
109
|
+
end
|
|
110
|
+
return prompt_buffer
|
|
83
111
|
end
|
|
84
112
|
|
|
85
|
-
write_secret(input, label: "gem signing passphrase", source: "gem_signing_passphrase") { gem_signing_passphrase } if signing_password_prompt?(
|
|
113
|
+
write_secret(input, label: "gem signing passphrase", source: "gem_signing_passphrase") { gem_signing_passphrase } if signing_password_prompt?(prompt_buffer)
|
|
114
|
+
prompt_buffer
|
|
86
115
|
end
|
|
87
116
|
|
|
88
117
|
def gem_signing_passphrase
|
|
@@ -25,6 +25,7 @@ require_relative "release_secrets"
|
|
|
25
25
|
module Kettle
|
|
26
26
|
module Dev
|
|
27
27
|
class ReleaseCLI
|
|
28
|
+
RUBYGEMS_INVALID_OTP = /Your OTP code is incorrect\. Please check it and retry\./.freeze
|
|
28
29
|
QUIET_ENV = {
|
|
29
30
|
"KETTLE_JEM_QUIET" => "true",
|
|
30
31
|
"KETTLE_JEM_DEBUG" => "false",
|
|
@@ -116,7 +117,8 @@ module Kettle
|
|
|
116
117
|
words = effective_command_words(cmd)
|
|
117
118
|
words == ["bin/setup"] ||
|
|
118
119
|
words.first(1) == ["bin/rake"] ||
|
|
119
|
-
words.first(3) == ["bundle", "exec", "rake"]
|
|
120
|
+
words.first(3) == ["bundle", "exec", "rake"] ||
|
|
121
|
+
words.first(3) == ["bundle", "exec", "kettle-changelog"]
|
|
120
122
|
rescue ArgumentError
|
|
121
123
|
false
|
|
122
124
|
end
|
|
@@ -620,7 +622,10 @@ module Kettle
|
|
|
620
622
|
end
|
|
621
623
|
|
|
622
624
|
def prepare_release_lockfiles_for_commit!
|
|
623
|
-
|
|
625
|
+
# Release setup and documentation tasks can re-materialize local PATH
|
|
626
|
+
# sources in the parent development bundle after the preflight reset.
|
|
627
|
+
# Always normalize again at the commit boundary.
|
|
628
|
+
reset_release_lockfiles!(stage: "before release prep commit")
|
|
624
629
|
validate_release_lockfiles!(stage: "before release prep commit")
|
|
625
630
|
end
|
|
626
631
|
|
|
@@ -827,7 +832,7 @@ module Kettle
|
|
|
827
832
|
end
|
|
828
833
|
|
|
829
834
|
def run_changelog!
|
|
830
|
-
cmd =
|
|
835
|
+
cmd = release_changelog_command
|
|
831
836
|
cmd = "#{cmd} --version #{Shellwords.escape(@version_override)}" if @version_override
|
|
832
837
|
cmd = "#{cmd} --yes" if @yes
|
|
833
838
|
cmd = "#{cmd} --events=changelog" if @event_stream
|
|
@@ -835,6 +840,51 @@ module Kettle
|
|
|
835
840
|
@changelog_generated_coverage = true
|
|
836
841
|
end
|
|
837
842
|
|
|
843
|
+
# The changelog executable is owned by kettle-changelog, not kettle-dev.
|
|
844
|
+
# During the bootstrap release of kettle-dev it is therefore intentionally
|
|
845
|
+
# absent from kettle-dev's bundle. When a local kettle-dev workspace is
|
|
846
|
+
# supplied, run the standalone tool through that project's bundle instead
|
|
847
|
+
# of relying on an ambient installed executable.
|
|
848
|
+
def release_changelog_command
|
|
849
|
+
gemfile = release_changelog_gemfile
|
|
850
|
+
return "bundle exec kettle-changelog" unless gemfile
|
|
851
|
+
|
|
852
|
+
escaped_gemfile = Shellwords.escape(gemfile)
|
|
853
|
+
command = +"env -u BUNDLE_GEMFILE -u BUNDLE_LOCKFILE"
|
|
854
|
+
if (local_root = release_changelog_local_root)
|
|
855
|
+
command << " KETTLE_DEV_DEV=false KETTLE_CHANGELOG_DEV_ROOT=#{Shellwords.escape(local_root)}"
|
|
856
|
+
end
|
|
857
|
+
command << " BUNDLE_GEMFILE=#{escaped_gemfile} bundle exec kettle-changelog"
|
|
858
|
+
command
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
def release_changelog_gemfile
|
|
862
|
+
configured = ENV["K_RELEASE_CHANGELOG_GEMFILE"].to_s.strip
|
|
863
|
+
unless configured.empty?
|
|
864
|
+
path = File.expand_path(configured)
|
|
865
|
+
return path if File.file?(path)
|
|
866
|
+
|
|
867
|
+
abort("Configured K_RELEASE_CHANGELOG_GEMFILE does not exist: #{path}")
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
local_root = release_changelog_local_root
|
|
871
|
+
return nil unless local_root
|
|
872
|
+
|
|
873
|
+
changelog_root = File.join(File.expand_path(local_root), "kettle-changelog")
|
|
874
|
+
candidates = [
|
|
875
|
+
File.join(changelog_root, "gemfiles", "release.gemfile"),
|
|
876
|
+
File.join(changelog_root, "Gemfile")
|
|
877
|
+
]
|
|
878
|
+
candidates.find { |path| File.file?(path) }
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
def release_changelog_local_root
|
|
882
|
+
local_root = ENV["KETTLE_DEV_DEV"].to_s.strip
|
|
883
|
+
return nil if local_root.empty? || %w[false 0 no off].include?(local_root.downcase)
|
|
884
|
+
|
|
885
|
+
File.expand_path(local_root)
|
|
886
|
+
end
|
|
887
|
+
|
|
838
888
|
# Monorepo subprojects commonly disable their local hard coverage gate
|
|
839
889
|
# because their narrow test suite cannot satisfy the aggregate root
|
|
840
890
|
# thresholds. Preserve an explicit changelog policy, otherwise derive
|
|
@@ -1237,7 +1287,7 @@ module Kettle
|
|
|
1237
1287
|
|
|
1238
1288
|
keep_release_secrets_alive!("prompt-bearing command")
|
|
1239
1289
|
puts "$ #{cmd}"
|
|
1240
|
-
|
|
1290
|
+
stdout_str, stderr_str, status = Kettle::Dev::InteractiveReleaseCommand.new(
|
|
1241
1291
|
secrets_provider: @secrets_provider,
|
|
1242
1292
|
secret_event_handler: ->(payload) { emit_secret_event(payload) }
|
|
1243
1293
|
).call(
|
|
@@ -1246,6 +1296,11 @@ module Kettle
|
|
|
1246
1296
|
)
|
|
1247
1297
|
return if status.success?
|
|
1248
1298
|
|
|
1299
|
+
if gem_release_command?(cmd) && rubygems_invalid_otp?(stdout_str, stderr_str)
|
|
1300
|
+
retry_gem_push_after_invalid_otp!
|
|
1301
|
+
return
|
|
1302
|
+
end
|
|
1303
|
+
|
|
1249
1304
|
exit_code = status.respond_to?(:exitstatus) ? status.exitstatus : 1
|
|
1250
1305
|
diag = stderr_str.to_s.empty? ? "" : "\n--- STDERR (last 20 lines) ---\n#{stderr_str.lines.last(20).join}".rstrip
|
|
1251
1306
|
abort("Command failed: #{cmd} (exit #{exit_code})#{diag}")
|
|
@@ -1261,6 +1316,45 @@ module Kettle
|
|
|
1261
1316
|
release_secrets_configured? ? @secrets_provider.class.name.to_s.split("::").last : "interactive"
|
|
1262
1317
|
end
|
|
1263
1318
|
|
|
1319
|
+
def gem_release_command?(cmd)
|
|
1320
|
+
effective_command_words(cmd).first(4) == ["bundle", "exec", "rake", "release"]
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
def rubygems_invalid_otp?(*output)
|
|
1324
|
+
output.any? { |chunk| chunk.to_s.match?(RUBYGEMS_INVALID_OTP) }
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
def retry_gem_push_after_invalid_otp!
|
|
1328
|
+
version = @release_candidate&.version || detect_version
|
|
1329
|
+
gem_path = gem_file_for_version(version)
|
|
1330
|
+
unless gem_path && File.file?(gem_path)
|
|
1331
|
+
abort("RubyGems rejected the OTP, but the built gem for version #{version} could not be found in pkg/.")
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
puts "RubyGems rejected the OTP after the release task completed; retrying the existing gem publication with a fresh OTP..."
|
|
1335
|
+
emit_secret_event(
|
|
1336
|
+
source: release_secrets_provider_label,
|
|
1337
|
+
action: "otp_retry",
|
|
1338
|
+
status: "started",
|
|
1339
|
+
reason: "RubyGems rejected the first OTP"
|
|
1340
|
+
)
|
|
1341
|
+
run_cmd!("gem push #{Shellwords.escape(gem_path)}")
|
|
1342
|
+
emit_secret_event(
|
|
1343
|
+
source: release_secrets_provider_label,
|
|
1344
|
+
action: "otp_retry",
|
|
1345
|
+
status: "ok",
|
|
1346
|
+
reason: "existing gem artifact published after OTP retry"
|
|
1347
|
+
)
|
|
1348
|
+
rescue SystemExit, Kettle::Dev::Error
|
|
1349
|
+
emit_secret_event(
|
|
1350
|
+
source: release_secrets_provider_label,
|
|
1351
|
+
action: "otp_retry",
|
|
1352
|
+
status: "failed",
|
|
1353
|
+
reason: "fresh OTP retry failed"
|
|
1354
|
+
)
|
|
1355
|
+
raise
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1264
1358
|
def signing_enabled?
|
|
1265
1359
|
ENV.fetch("SKIP_GEM_SIGNING", "false").casecmp("false").zero?
|
|
1266
1360
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "open3"
|
|
4
|
+
require "kettle/dev/interactive_release_command"
|
|
4
5
|
require "kettle/dev/release_notifier"
|
|
5
6
|
|
|
6
7
|
module Kettle
|
|
@@ -73,7 +74,7 @@ module Kettle
|
|
|
73
74
|
argv = [op_cli, "account", "get"]
|
|
74
75
|
account = string_config("account")
|
|
75
76
|
argv.concat(["--account", account]) unless account.empty?
|
|
76
|
-
run_op(argv, purpose: "authorization keepalive", elapsed: elapsed)
|
|
77
|
+
run_op(argv, purpose: "authorization keepalive", elapsed: elapsed, interactive: true)
|
|
77
78
|
true
|
|
78
79
|
end
|
|
79
80
|
|
|
@@ -112,9 +113,11 @@ module Kettle
|
|
|
112
113
|
required_config("cli")
|
|
113
114
|
end
|
|
114
115
|
|
|
115
|
-
def run_op(argv, purpose:, elapsed: nil)
|
|
116
|
+
def run_op(argv, purpose:, elapsed: nil, interactive: false)
|
|
116
117
|
elapsed_suffix = elapsed.to_s.empty? ? "" : " (elapsed #{elapsed})"
|
|
117
118
|
Kettle::Dev::ReleaseNotifier.alert("1Password #{purpose} lookup starting#{elapsed_suffix}; watch for authorization prompt.")
|
|
119
|
+
return run_interactive_op(argv, purpose: purpose) if interactive
|
|
120
|
+
|
|
118
121
|
stdout, stderr, status = Open3.capture3(*argv)
|
|
119
122
|
return stdout.to_s.strip if status.success? && !stdout.to_s.strip.empty?
|
|
120
123
|
|
|
@@ -125,6 +128,23 @@ module Kettle
|
|
|
125
128
|
raise Kettle::Dev::Error, "1Password CLI executable #{argv.first.inspect} was not found"
|
|
126
129
|
end
|
|
127
130
|
|
|
131
|
+
def run_interactive_op(argv, purpose:)
|
|
132
|
+
_stdout, stderr, status = Kettle::Dev::InteractiveReleaseCommand.new(
|
|
133
|
+
secrets_provider: Provider.new,
|
|
134
|
+
input: $stdin,
|
|
135
|
+
output: $stdout,
|
|
136
|
+
error: $stderr,
|
|
137
|
+
forward_stdin: true
|
|
138
|
+
).call_argv({}, argv)
|
|
139
|
+
return if status.success?
|
|
140
|
+
|
|
141
|
+
details = stderr.to_s.strip
|
|
142
|
+
details = "op exited #{status.exitstatus}" if details.empty?
|
|
143
|
+
raise Kettle::Dev::Error, "1Password #{purpose} lookup failed: #{details}"
|
|
144
|
+
rescue Errno::ENOENT
|
|
145
|
+
raise Kettle::Dev::Error, "1Password CLI executable #{argv.first.inspect} was not found"
|
|
146
|
+
end
|
|
147
|
+
|
|
128
148
|
def required_config(key)
|
|
129
149
|
value = string_config(key)
|
|
130
150
|
raise Kettle::Dev::Error, "1Password release secrets require #{key}" if value.empty?
|
data/lib/kettle/dev/version.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Kettle
|
|
|
5
5
|
# Version namespace for this gem.
|
|
6
6
|
module Version
|
|
7
7
|
# Current gem version.
|
|
8
|
-
VERSION = "
|
|
8
|
+
VERSION = "3.0.1"
|
|
9
9
|
end
|
|
10
10
|
# Current gem version exposed at the traditional constant location.
|
|
11
11
|
VERSION = Version::VERSION # Traditional Constant Location
|
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:
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter H. Boling
|
|
@@ -236,7 +236,7 @@ dependencies:
|
|
|
236
236
|
version: '3.2'
|
|
237
237
|
- - ">="
|
|
238
238
|
- !ruby/object:Gem::Version
|
|
239
|
-
version: 3.2.
|
|
239
|
+
version: 3.2.2
|
|
240
240
|
type: :development
|
|
241
241
|
prerelease: false
|
|
242
242
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -246,7 +246,7 @@ dependencies:
|
|
|
246
246
|
version: '3.2'
|
|
247
247
|
- - ">="
|
|
248
248
|
- !ruby/object:Gem::Version
|
|
249
|
-
version: 3.2.
|
|
249
|
+
version: 3.2.2
|
|
250
250
|
- !ruby/object:Gem::Dependency
|
|
251
251
|
name: turbo_tests2
|
|
252
252
|
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.
|
|
259
|
+
version: 3.2.5
|
|
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.
|
|
269
|
+
version: 3.2.5
|
|
270
270
|
- !ruby/object:Gem::Dependency
|
|
271
271
|
name: stone_checksums
|
|
272
272
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -317,7 +317,6 @@ email:
|
|
|
317
317
|
- floss@galtzo.com
|
|
318
318
|
executables:
|
|
319
319
|
- kettle-bump
|
|
320
|
-
- kettle-changelog
|
|
321
320
|
- kettle-check-eof
|
|
322
321
|
- kettle-commit-msg
|
|
323
322
|
- kettle-dev-setup
|
|
@@ -336,7 +335,6 @@ files:
|
|
|
336
335
|
- LICENSE.md
|
|
337
336
|
- README.md
|
|
338
337
|
- exe/kettle-bump
|
|
339
|
-
- exe/kettle-changelog
|
|
340
338
|
- exe/kettle-check-eof
|
|
341
339
|
- exe/kettle-check-eof.sh
|
|
342
340
|
- exe/kettle-commit-msg
|
|
@@ -402,10 +400,10 @@ licenses:
|
|
|
402
400
|
- AGPL-3.0-only
|
|
403
401
|
metadata:
|
|
404
402
|
homepage_uri: https://kettle-dev.galtzo.com
|
|
405
|
-
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/
|
|
406
|
-
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/
|
|
403
|
+
source_code_uri: https://github.com/kettle-dev/kettle-dev/tree/v3.0.1
|
|
404
|
+
changelog_uri: https://github.com/kettle-dev/kettle-dev/blob/v3.0.1/CHANGELOG.md
|
|
407
405
|
bug_tracker_uri: https://github.com/kettle-dev/kettle-dev/issues
|
|
408
|
-
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/
|
|
406
|
+
documentation_uri: https://www.rubydoc.info/gems/kettle-dev/3.0.1
|
|
409
407
|
funding_uri: https://github.com/sponsors/pboling
|
|
410
408
|
wiki_uri: https://github.com/kettle-dev/kettle-dev/wiki
|
|
411
409
|
news_uri: https://www.railsbling.com/tags/kettle-dev
|
metadata.gz.sig
CHANGED
|
Binary file
|
data/exe/kettle-changelog
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# vim: set syntax=ruby
|
|
5
|
-
|
|
6
|
-
# kettle-changelog: Generate a CHANGELOG.md entry for the current VERSION.
|
|
7
|
-
# - Reads VERSION from lib/**/version.rb (must be unique across files)
|
|
8
|
-
# - Set K_CHANGELOG_PATH to read/write a changelog outside the current gem root
|
|
9
|
-
# - Set K_CHANGELOG_COVERAGE_ROOT to collect coverage from another bundle root
|
|
10
|
-
# - Set K_CHANGELOG_VERSION_FILE to use a specific version.rb for monorepo roots
|
|
11
|
-
# - Set K_CHANGELOG_GEM_NAME to use a specific gem name for root changelogs
|
|
12
|
-
# - Moves entries from the "Unreleased" section into a new versioned section
|
|
13
|
-
# - Prepends 4 heading lines:
|
|
14
|
-
# - TAG
|
|
15
|
-
# - COVERAGE (line coverage)
|
|
16
|
-
# - BRANCH COVERAGE (branch coverage)
|
|
17
|
-
# - percent documented (parsed from `bin/rake yard` output)
|
|
18
|
-
# - Updates bottom link references to GitHub style, converts any existing
|
|
19
|
-
# GitLab links to GitHub links, and appends the new [X.Y.Z] and [X.Y.Zt] links.
|
|
20
|
-
#
|
|
21
|
-
# Notes:
|
|
22
|
-
# - Strict mode (default) removes stale coverage output, runs `bundle exec
|
|
23
|
-
# kettle-test` with JSON coverage enabled, and reads the freshly generated
|
|
24
|
-
# coverage/coverage.json.
|
|
25
|
-
# - Non-strict mode reads existing coverage JSON when present and leaves
|
|
26
|
-
# unavailable coverage or documentation stats blank with a warning.
|
|
27
|
-
# - Expects `bin/rake yard` or `bin/yard` to be available for documentation stats
|
|
28
|
-
# when strict mode is enabled.
|
|
29
|
-
|
|
30
|
-
$stdout.sync = true
|
|
31
|
-
|
|
32
|
-
# Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
|
|
33
|
-
# Ensure RubyGems is available for 'require' lookups
|
|
34
|
-
begin
|
|
35
|
-
require "rubygems"
|
|
36
|
-
rescue LoadError
|
|
37
|
-
# Older Rubies always have rubygems; continue anyway
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
script_basename = File.basename(__FILE__)
|
|
41
|
-
repo_lib = File.expand_path("../lib", __dir__)
|
|
42
|
-
$LOAD_PATH.unshift(repo_lib) if File.directory?(repo_lib) && !$LOAD_PATH.include?(repo_lib)
|
|
43
|
-
|
|
44
|
-
begin
|
|
45
|
-
# Standard library
|
|
46
|
-
require "json"
|
|
47
|
-
require "time"
|
|
48
|
-
require "open3"
|
|
49
|
-
require "shellwords"
|
|
50
|
-
|
|
51
|
-
# This library
|
|
52
|
-
require "kettle/dev"
|
|
53
|
-
require "kettle/ndjson"
|
|
54
|
-
require "kettle/dev/executable_version"
|
|
55
|
-
Kettle::Dev::ExecutableVersion.print_and_exit!(script_basename, ARGV, value_option: true)
|
|
56
|
-
machine_output = ARGV.any? { |arg| arg == "--json" || arg.start_with?("--events") }
|
|
57
|
-
Kettle::Dev::ExecutableVersion.print_header(script_basename) unless machine_output
|
|
58
|
-
rescue LoadError => e
|
|
59
|
-
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
|
60
|
-
warn("Hint: Ensure the host project has kettle-dev as a dependency and run bundle install.")
|
|
61
|
-
exit(1)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
if ARGV.include?("-h") || ARGV.include?("--help")
|
|
65
|
-
puts <<~USAGE
|
|
66
|
-
Usage: kettle-changelog [--version VERSION] [--yes] [--update-prep|--reformat|--backfill-released-version VERSION] [--pending-release] [--release-state] [--refresh-cache] [--add-unreleased-entry --section SECTION --entry TEXT] [--json|--events[=TYPES]] [--no-strict] [--no-coverage-threshold]
|
|
67
|
-
|
|
68
|
-
Detects the current version from lib/**/version.rb, the latest live release, and
|
|
69
|
-
the most recent CHANGELOG.md release section, then prompts to confirm the selected plan:
|
|
70
|
-
create a new release section, update the prepared release section in place, or reformat only.
|
|
71
|
-
|
|
72
|
-
Release plans add coverage and documentation stats, move entries from [Unreleased],
|
|
73
|
-
and update bottom link references to GitHub style.
|
|
74
|
-
|
|
75
|
-
Options:
|
|
76
|
-
--version VERSION Use this version instead of detecting VERSION from lib/**/version.rb
|
|
77
|
-
--yes Auto-approve the selected release plan
|
|
78
|
-
--update-prep Force updating the most recent prepared release section in place
|
|
79
|
-
--reformat Normalize CHANGELOG.md structure without release-state planning
|
|
80
|
-
--backfill-released-version VERSION
|
|
81
|
-
Add a tagged historical section without moving Unreleased entries
|
|
82
|
-
--pending-release Query whether the changelog has release work pending; exits 0 for yes, 1 for no
|
|
83
|
-
--release-state,
|
|
84
|
-
--release-status Print changelog release state, including latest published release and pending sources
|
|
85
|
-
--refresh-cache Bypass cached gem.coop version data for this invocation
|
|
86
|
-
--add-unreleased-entry Add one entry to an existing section under ## [Unreleased]
|
|
87
|
-
--section SECTION Unreleased section to receive the entry (Added, Changed, Deprecated, Removed, Fixed, Security)
|
|
88
|
-
--entry TEXT Changelog entry text; "- " is added when omitted
|
|
89
|
-
--json Print query output as JSON
|
|
90
|
-
--events[=TYPES] Print newline-delimited JSON changelog events
|
|
91
|
-
--no-strict Allow missing coverage and yard data (warnings only, no errors)
|
|
92
|
-
--no-coverage-threshold Generate coverage without hard-failing below configured thresholds
|
|
93
|
-
|
|
94
|
-
Environment:
|
|
95
|
-
K_CHANGELOG_STRICT=false Disable strict mode (equivalent to --no-strict flag)
|
|
96
|
-
K_CHANGELOG_COVERAGE_HARD=false Disable coverage threshold hard-failure
|
|
97
|
-
K_CHANGELOG_PATH=path Read and write this changelog instead of ./CHANGELOG.md
|
|
98
|
-
K_CHANGELOG_COVERAGE_ROOT=path Run/read coverage from this root instead of the gem root
|
|
99
|
-
K_CHANGELOG_VERSION_FILE=path Read VERSION from this file instead of lib/**/version.rb
|
|
100
|
-
K_CHANGELOG_GEM_NAME=name Read live release data as this gem instead of root .gemspec
|
|
101
|
-
KETTLE_GEM_COOP_REFRESH=true Bypass cached gem.coop version data
|
|
102
|
-
|
|
103
|
-
Data generation:
|
|
104
|
-
Strict mode is the default. Release plans remove stale coverage output,
|
|
105
|
-
run bundle exec kettle-test with JSON coverage enabled, read the resulting
|
|
106
|
-
coverage/coverage.json, and collect documentation stats from bin/rake yard
|
|
107
|
-
or bin/yard.
|
|
108
|
-
|
|
109
|
-
Strict mode fails if specs fail, coverage JSON is not produced, coverage
|
|
110
|
-
is below the configured project thresholds, or documentation stats cannot
|
|
111
|
-
be collected.
|
|
112
|
-
|
|
113
|
-
Non-strict mode does not generate missing coverage or documentation data;
|
|
114
|
-
it warns and leaves unavailable stats blank.
|
|
115
|
-
|
|
116
|
-
Runtime requirements:
|
|
117
|
-
- bundle exec kettle-test available for strict coverage generation
|
|
118
|
-
- bin/rake yard or bin/yard available for strict documentation stats
|
|
119
|
-
|
|
120
|
-
Use --no-strict or K_CHANGELOG_STRICT=false to allow missing data.
|
|
121
|
-
Use --no-coverage-threshold or K_CHANGELOG_COVERAGE_HARD=false to run fresh
|
|
122
|
-
coverage without hard-failing below configured thresholds.
|
|
123
|
-
USAGE
|
|
124
|
-
exit(0)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
begin
|
|
128
|
-
def extract_version_arg!(argv)
|
|
129
|
-
version = nil
|
|
130
|
-
if (idx = argv.index("--version"))
|
|
131
|
-
version = argv[idx + 1]
|
|
132
|
-
Kettle::Dev::ExitAdapter.abort("--version requires a VERSION") if version.to_s.empty?
|
|
133
|
-
argv.slice!(idx, 2)
|
|
134
|
-
end
|
|
135
|
-
argv.delete_if do |arg|
|
|
136
|
-
if arg.start_with?("--version=", "version=")
|
|
137
|
-
version = arg.split("=", 2)[1]
|
|
138
|
-
true
|
|
139
|
-
else
|
|
140
|
-
false
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
Kettle::Dev::Versioning.normalize_explicit_version(version)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def extract_events_arg!(argv)
|
|
147
|
-
events = false
|
|
148
|
-
event_types = nil
|
|
149
|
-
argv.delete_if do |arg|
|
|
150
|
-
if arg == "--events"
|
|
151
|
-
events = true
|
|
152
|
-
elsif arg.start_with?("--events=")
|
|
153
|
-
events = true
|
|
154
|
-
event_types = arg.split("=", 2)[1]
|
|
155
|
-
else
|
|
156
|
-
false
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
[events, event_types]
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Determine if strict mode is enabled (default: true)
|
|
163
|
-
strict_mode = !ARGV.include?("--no-strict") && ENV.fetch("K_CHANGELOG_STRICT", "true").downcase != "false"
|
|
164
|
-
update_prep = ARGV.delete("--update-prep")
|
|
165
|
-
reformat_only = ARGV.delete("--reformat")
|
|
166
|
-
yes = !!ARGV.delete("--yes")
|
|
167
|
-
pending_release_query = ARGV.delete("--pending-release")
|
|
168
|
-
release_state_query = ARGV.delete("--release-state") || ARGV.delete("--release-status")
|
|
169
|
-
refresh_cache = ARGV.delete("--refresh-cache")
|
|
170
|
-
add_unreleased_entry = ARGV.delete("--add-unreleased-entry") || ARGV.delete("--add-changelog-entry")
|
|
171
|
-
json_output = ARGV.delete("--json")
|
|
172
|
-
events_output, event_types = extract_events_arg!(ARGV)
|
|
173
|
-
Kettle::Dev::ExitAdapter.abort("--events cannot be combined with --json") if events_output && json_output
|
|
174
|
-
coverage_threshold_disabled = ARGV.include?("--no-coverage-threshold") || ARGV.include?("--no-coverage-thresholds")
|
|
175
|
-
coverage_hard = !coverage_threshold_disabled && ENV.fetch("K_CHANGELOG_COVERAGE_HARD", "true").downcase != "false"
|
|
176
|
-
version_override = extract_version_arg!(ARGV)
|
|
177
|
-
|
|
178
|
-
def extract_option_arg!(argv, name)
|
|
179
|
-
value = nil
|
|
180
|
-
if (idx = argv.index(name))
|
|
181
|
-
value = argv[idx + 1]
|
|
182
|
-
Kettle::Dev::ExitAdapter.abort("#{name} requires a value") if value.to_s.empty?
|
|
183
|
-
argv.slice!(idx, 2)
|
|
184
|
-
end
|
|
185
|
-
argv.delete_if do |arg|
|
|
186
|
-
if arg.start_with?("#{name}=")
|
|
187
|
-
value = arg.split("=", 2)[1]
|
|
188
|
-
true
|
|
189
|
-
else
|
|
190
|
-
false
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
value
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
historical_backfill_version = extract_option_arg!(ARGV, "--backfill-released-version")
|
|
197
|
-
|
|
198
|
-
if add_unreleased_entry
|
|
199
|
-
section = extract_option_arg!(ARGV, "--section")
|
|
200
|
-
entry = extract_option_arg!(ARGV, "--entry")
|
|
201
|
-
Kettle::Dev::ExitAdapter.abort("--section is required with --add-unreleased-entry") if section.to_s.empty?
|
|
202
|
-
Kettle::Dev::ExitAdapter.abort("--entry is required with --add-unreleased-entry") if entry.to_s.empty?
|
|
203
|
-
|
|
204
|
-
result = Kettle::Dev::ChangelogEntryAdder.new(section: section, entry: entry).run
|
|
205
|
-
message = (result == :changed) ? "CHANGELOG.md updated under Unreleased #{section}." : "CHANGELOG.md already contains that Unreleased #{section} entry."
|
|
206
|
-
puts(json_output ? JSON.pretty_generate({changed: result == :changed, section: section, entry: entry}) : message)
|
|
207
|
-
exit(0)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
event_stream = events_output ? Kettle::Ndjson.event_stream($stdout, types: event_types) : nil
|
|
211
|
-
cli = Kettle::Dev::ChangelogCLI.new(strict: strict_mode, enforce_coverage_thresholds: coverage_hard, update_prep: update_prep, reformat_only: reformat_only, historical_backfill_version: historical_backfill_version, version: version_override, refresh_cache: refresh_cache, yes: yes, event_stream: event_stream)
|
|
212
|
-
if release_state_query
|
|
213
|
-
state = cli.release_state
|
|
214
|
-
puts(json_output ? JSON.pretty_generate(state) : cli.release_state_table(state))
|
|
215
|
-
exit(0)
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
if pending_release_query
|
|
219
|
-
state = cli.pending_release_status
|
|
220
|
-
output = if json_output
|
|
221
|
-
JSON.pretty_generate(state)
|
|
222
|
-
elsif state.fetch(:pending_release)
|
|
223
|
-
"true"
|
|
224
|
-
else
|
|
225
|
-
"false"
|
|
226
|
-
end
|
|
227
|
-
puts(output)
|
|
228
|
-
exit(state.fetch(:pending_release) ? 0 : 1)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
cli.run
|
|
232
|
-
rescue LoadError => e
|
|
233
|
-
warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
|
|
234
|
-
warn(e.backtrace.join("\n")) if ENV["DEBUG"]
|
|
235
|
-
exit(1)
|
|
236
|
-
rescue SystemExit => e
|
|
237
|
-
# Preserve exit status, but ensure at least a newline so shells don't show an empty line only.
|
|
238
|
-
warn("#{script_basename}: exited (status=#{e.status}, msg=#{e.message})") if e.status != 0
|
|
239
|
-
raise
|
|
240
|
-
rescue => e
|
|
241
|
-
warn("#{script_basename}: unexpected error: #{e.class}: #{e.message}")
|
|
242
|
-
warn(e.backtrace.join("\n"))
|
|
243
|
-
exit(1)
|
|
244
|
-
end
|