kettle-dev 1.0.10 → 1.0.11
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/.envrc +1 -1
- data/.github/workflows/coverage.yml +2 -2
- data/.github/workflows/coverage.yml.example +127 -0
- data/.github/workflows/discord-notifier.yml +2 -1
- data/.github/workflows/truffle.yml +0 -8
- data/Appraisals +3 -1
- data/Appraisals.example +102 -0
- data/CHANGELOG.md +63 -29
- data/CHANGELOG.md.example +4 -4
- data/CONTRIBUTING.md +37 -1
- data/Gemfile +3 -0
- data/README.md +47 -9
- data/README.md.example +515 -0
- data/{Rakefile → Rakefile.example} +13 -27
- data/exe/kettle-changelog +401 -0
- data/exe/kettle-commit-msg +2 -0
- data/exe/kettle-readme-backers +2 -0
- data/exe/kettle-release +2 -7
- data/gemfiles/modular/optional.gemfile +5 -0
- data/lib/kettle/dev/git_adapter.rb +98 -33
- data/lib/kettle/dev/git_commit_footer.rb +1 -1
- data/lib/kettle/dev/input_adapter.rb +40 -0
- data/lib/kettle/dev/release_cli.rb +24 -22
- data/lib/kettle/dev/tasks/ci_task.rb +4 -1
- data/lib/kettle/dev/tasks/install_task.rb +313 -95
- data/lib/kettle/dev/tasks/template_task.rb +175 -73
- data/lib/kettle/dev/template_helpers.rb +61 -8
- data/lib/kettle/dev/version.rb +1 -1
- data/lib/kettle/dev/versioning.rb +68 -0
- data/sig/kettle/dev/input_adapter.rbs +8 -0
- data/sig/kettle/dev/template_helpers.rbs +3 -1
- data.tar.gz.sig +0 -0
- metadata +21 -22
- metadata.gz.sig +0 -0
- data/.gitlab-ci.yml +0 -45
@@ -1,13 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "open3"
|
4
|
+
|
3
5
|
module Kettle
|
4
6
|
module Dev
|
5
7
|
# Minimal Git adapter used by kettle-dev to avoid invoking live shell commands
|
6
|
-
# directly from the library code. In tests, mock this adapter's
|
7
|
-
# prevent any real network or repository mutations.
|
8
|
+
# directly from the higher-level library code. In tests, mock this adapter's
|
9
|
+
# methods to prevent any real network or repository mutations.
|
8
10
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
+
# Behavior:
|
12
|
+
# - Prefer the 'git' gem when available.
|
13
|
+
# - If the 'git' gem is not present (LoadError), fall back to shelling out to
|
14
|
+
# the system `git` executable for the small set of operations we need.
|
11
15
|
#
|
12
16
|
# Public API is intentionally small and only includes what we need right now.
|
13
17
|
class GitAdapter
|
@@ -15,10 +19,21 @@ module Kettle
|
|
15
19
|
# @return [void]
|
16
20
|
def initialize
|
17
21
|
begin
|
18
|
-
|
19
|
-
|
22
|
+
# Allow users/CI to opt out of using the 'git' gem even when available.
|
23
|
+
# Set KETTLE_DEV_DISABLE_GIT_GEM to a truthy value ("1", "true", "yes") to force CLI backend.
|
24
|
+
env_val = ENV["KETTLE_DEV_DISABLE_GIT_GEM"]
|
25
|
+
# Ruby 2.3 compatibility: String#match? was added in 2.4; use Regexp#=== / =~ instead
|
26
|
+
disable_gem = env_val && !!(/\A(1|true|yes)\z/i =~ env_val)
|
27
|
+
if disable_gem
|
28
|
+
@backend = :cli
|
29
|
+
else
|
30
|
+
Kernel.require "git"
|
31
|
+
@backend = :gem
|
32
|
+
@git = ::Git.open(Dir.pwd)
|
33
|
+
end
|
20
34
|
rescue LoadError
|
21
|
-
|
35
|
+
# Optional dependency: fall back to CLI
|
36
|
+
@backend = :cli
|
22
37
|
rescue StandardError => e
|
23
38
|
raise Kettle::Dev::Error, "Failed to open git repository: #{e.message}"
|
24
39
|
end
|
@@ -30,42 +45,73 @@ module Kettle
|
|
30
45
|
# @param force [Boolean] whether to force push
|
31
46
|
# @return [Boolean] true when the push is reported successful
|
32
47
|
def push(remote, branch, force: false)
|
33
|
-
|
34
|
-
|
48
|
+
if @backend == :gem
|
49
|
+
begin
|
50
|
+
if remote
|
51
|
+
@git.push(remote, branch, force: force)
|
52
|
+
else
|
53
|
+
# Default remote according to repo config
|
54
|
+
@git.push(nil, branch, force: force)
|
55
|
+
end
|
56
|
+
true
|
57
|
+
rescue StandardError
|
58
|
+
false
|
59
|
+
end
|
60
|
+
else
|
61
|
+
args = ["git", "push"]
|
62
|
+
args << "--force" if force
|
35
63
|
if remote
|
36
|
-
|
37
|
-
else
|
38
|
-
# Default remote according to repo config
|
39
|
-
@git.push(nil, branch, force: force)
|
64
|
+
args << remote.to_s << branch.to_s
|
40
65
|
end
|
41
|
-
|
42
|
-
rescue StandardError
|
43
|
-
false
|
66
|
+
system(*args)
|
44
67
|
end
|
45
68
|
end
|
46
69
|
|
47
70
|
# @return [String, nil] current branch name, or nil on error
|
48
71
|
def current_branch
|
49
|
-
@
|
72
|
+
if @backend == :gem
|
73
|
+
@git.current_branch
|
74
|
+
else
|
75
|
+
out, status = Open3.capture2("git", "rev-parse", "--abbrev-ref", "HEAD")
|
76
|
+
status.success? ? out.strip : nil
|
77
|
+
end
|
50
78
|
rescue StandardError
|
51
79
|
nil
|
52
80
|
end
|
53
81
|
|
54
82
|
# @return [Array<String>] list of remote names
|
55
83
|
def remotes
|
56
|
-
@
|
84
|
+
if @backend == :gem
|
85
|
+
@git.remotes.map(&:name)
|
86
|
+
else
|
87
|
+
out, status = Open3.capture2("git", "remote")
|
88
|
+
status.success? ? out.split(/\r?\n/).map(&:strip).reject(&:empty?) : []
|
89
|
+
end
|
57
90
|
rescue StandardError
|
58
91
|
[]
|
59
92
|
end
|
60
93
|
|
61
94
|
# @return [Hash{String=>String}] remote name => fetch URL
|
62
95
|
def remotes_with_urls
|
63
|
-
@
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
96
|
+
if @backend == :gem
|
97
|
+
@git.remotes.each_with_object({}) do |r, h|
|
98
|
+
begin
|
99
|
+
h[r.name] = r.url
|
100
|
+
rescue StandardError
|
101
|
+
# ignore
|
102
|
+
end
|
103
|
+
end
|
104
|
+
else
|
105
|
+
out, status = Open3.capture2("git", "remote", "-v")
|
106
|
+
return {} unless status.success?
|
107
|
+
urls = {}
|
108
|
+
out.each_line do |line|
|
109
|
+
# Example: origin https://github.com/me/repo.git (fetch)
|
110
|
+
if line =~ /^(\S+)\s+(\S+)\s+\(fetch\)/
|
111
|
+
urls[Regexp.last_match(1)] = Regexp.last_match(2)
|
112
|
+
end
|
68
113
|
end
|
114
|
+
urls
|
69
115
|
end
|
70
116
|
rescue StandardError
|
71
117
|
{}
|
@@ -74,8 +120,13 @@ module Kettle
|
|
74
120
|
# @param name [String]
|
75
121
|
# @return [String, nil]
|
76
122
|
def remote_url(name)
|
77
|
-
|
78
|
-
|
123
|
+
if @backend == :gem
|
124
|
+
r = @git.remotes.find { |x| x.name == name }
|
125
|
+
r&.url
|
126
|
+
else
|
127
|
+
out, status = Open3.capture2("git", "config", "--get", "remote.#{name}.url")
|
128
|
+
status.success? ? out.strip : nil
|
129
|
+
end
|
79
130
|
rescue StandardError
|
80
131
|
nil
|
81
132
|
end
|
@@ -84,8 +135,12 @@ module Kettle
|
|
84
135
|
# @param branch [String]
|
85
136
|
# @return [Boolean]
|
86
137
|
def checkout(branch)
|
87
|
-
@
|
88
|
-
|
138
|
+
if @backend == :gem
|
139
|
+
@git.checkout(branch)
|
140
|
+
true
|
141
|
+
else
|
142
|
+
system("git", "checkout", branch.to_s)
|
143
|
+
end
|
89
144
|
rescue StandardError
|
90
145
|
false
|
91
146
|
end
|
@@ -95,8 +150,12 @@ module Kettle
|
|
95
150
|
# @param branch [String]
|
96
151
|
# @return [Boolean]
|
97
152
|
def pull(remote, branch)
|
98
|
-
@
|
99
|
-
|
153
|
+
if @backend == :gem
|
154
|
+
@git.pull(remote, branch)
|
155
|
+
true
|
156
|
+
else
|
157
|
+
system("git", "pull", remote.to_s, branch.to_s)
|
158
|
+
end
|
100
159
|
rescue StandardError
|
101
160
|
false
|
102
161
|
end
|
@@ -106,12 +165,18 @@ module Kettle
|
|
106
165
|
# @param ref [String, nil]
|
107
166
|
# @return [Boolean]
|
108
167
|
def fetch(remote, ref = nil)
|
109
|
-
if
|
110
|
-
|
168
|
+
if @backend == :gem
|
169
|
+
if ref
|
170
|
+
@git.fetch(remote, ref)
|
171
|
+
else
|
172
|
+
@git.fetch(remote)
|
173
|
+
end
|
174
|
+
true
|
175
|
+
elsif ref
|
176
|
+
system("git", "fetch", remote.to_s, ref.to_s)
|
111
177
|
else
|
112
|
-
|
178
|
+
system("git", "fetch", remote.to_s)
|
113
179
|
end
|
114
|
-
true
|
115
180
|
rescue StandardError
|
116
181
|
false
|
117
182
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kettle
|
4
|
+
module Dev
|
5
|
+
# Input indirection layer to make interactive prompts safe in tests.
|
6
|
+
#
|
7
|
+
# Production/default behavior delegates to $stdin.gets (or Kernel#gets)
|
8
|
+
# so application code does not read from STDIN directly. In specs, mock
|
9
|
+
# this adapter's methods to return deterministic answers without touching
|
10
|
+
# global IO.
|
11
|
+
#
|
12
|
+
# Example (RSpec):
|
13
|
+
# allow(Kettle::Dev::InputAdapter).to receive(:gets).and_return("y\n")
|
14
|
+
#
|
15
|
+
# This mirrors the "mockable adapter" approach used for GitAdapter and ExitAdapter.
|
16
|
+
module InputAdapter
|
17
|
+
module_function
|
18
|
+
|
19
|
+
# Read one line from the standard input, including the trailing newline if
|
20
|
+
# present. Returns nil on EOF, consistent with IO#gets.
|
21
|
+
#
|
22
|
+
# @param args [Array] any args are forwarded to $stdin.gets for compatibility
|
23
|
+
# @return [String, nil]
|
24
|
+
def gets(*args)
|
25
|
+
$stdin.gets(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Read one line from standard input, raising EOFError on end-of-file.
|
29
|
+
# Provided for convenience symmetry with IO#readline when needed.
|
30
|
+
#
|
31
|
+
# @param args [Array]
|
32
|
+
# @return [String]
|
33
|
+
def readline(*args)
|
34
|
+
line = gets(*args)
|
35
|
+
raise EOFError, "end of file reached" if line.nil?
|
36
|
+
line
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -16,6 +16,8 @@ require "ruby-progressbar"
|
|
16
16
|
# Internal
|
17
17
|
require "kettle/dev/git_adapter"
|
18
18
|
require "kettle/dev/exit_adapter"
|
19
|
+
require "kettle/dev/input_adapter"
|
20
|
+
require "kettle/dev/versioning"
|
19
21
|
|
20
22
|
module Kettle
|
21
23
|
module Dev
|
@@ -34,8 +36,6 @@ module Kettle
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def run
|
37
|
-
puts "== kettle-release =="
|
38
|
-
|
39
39
|
ensure_bundler_2_7_plus!
|
40
40
|
|
41
41
|
version = detect_version
|
@@ -68,11 +68,23 @@ module Kettle
|
|
68
68
|
else
|
69
69
|
latest_overall
|
70
70
|
end
|
71
|
-
if target
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
71
|
+
if target
|
72
|
+
bump = Kettle::Dev::Versioning.classify_bump(target, version)
|
73
|
+
case bump
|
74
|
+
when :same
|
75
|
+
series = cur_series.join(".")
|
76
|
+
warn("version.rb (#{version}) matches the latest released version for series #{series} (#{target}).")
|
77
|
+
abort("Aborting: version bump required. Bump PATCH/MINOR/MAJOR/EPIC.")
|
78
|
+
when :downgrade
|
79
|
+
series = cur_series.join(".")
|
80
|
+
warn("version.rb (#{version}) is lower than the latest released version for series #{series} (#{target}).")
|
81
|
+
abort("Aborting: version must be bumped above #{target}.")
|
82
|
+
else
|
83
|
+
label = {epic: "EPIC", major: "MAJOR", minor: "MINOR", patch: "PATCH"}[bump] || bump.to_s.upcase
|
84
|
+
puts "Proposed bump type: #{label} (from #{target} -> #{version})"
|
85
|
+
end
|
86
|
+
else
|
87
|
+
puts "Could not determine latest released version from RubyGems (offline?). Proceeding without sanity check."
|
76
88
|
end
|
77
89
|
else
|
78
90
|
puts "Could not determine latest released version from RubyGems (offline?). Proceeding without sanity check."
|
@@ -80,7 +92,7 @@ module Kettle
|
|
80
92
|
|
81
93
|
puts "Have you updated lib/**/version.rb and CHANGELOG.md for v#{version}? [y/N]"
|
82
94
|
print("> ")
|
83
|
-
ans =
|
95
|
+
ans = Kettle::Dev::InputAdapter.gets&.strip
|
84
96
|
abort("Aborted: please update version.rb and CHANGELOG.md, then re-run.") unless ans&.downcase&.start_with?("y")
|
85
97
|
|
86
98
|
run_cmd!("bin/setup")
|
@@ -119,7 +131,7 @@ module Kettle
|
|
119
131
|
# Prompt on CI to allow an explicit abort when signing would otherwise hang
|
120
132
|
if ENV.fetch("CI", "false").casecmp("true").zero?
|
121
133
|
print("Proceed with signing enabled? This may hang waiting for a PEM password. [y/N]: ")
|
122
|
-
ans =
|
134
|
+
ans = Kettle::Dev::InputAdapter.gets&.strip
|
123
135
|
unless ans&.downcase&.start_with?("y")
|
124
136
|
abort("Aborted. Re-run with SKIP_GEM_SIGNING=true bundle exec kettle-release (or set it in your environment).")
|
125
137
|
end
|
@@ -269,7 +281,7 @@ module Kettle
|
|
269
281
|
when "true", "1", "yes", "y" then true
|
270
282
|
when "ask"
|
271
283
|
print("Run local CI with 'act' before pushing? [Y/n] ")
|
272
|
-
ans =
|
284
|
+
ans = Kettle::Dev::InputAdapter.gets&.strip
|
273
285
|
ans.nil? || ans.empty? || ans =~ /\Ay(es)?\z/i
|
274
286
|
else
|
275
287
|
false
|
@@ -329,17 +341,7 @@ module Kettle
|
|
329
341
|
end
|
330
342
|
|
331
343
|
def detect_version
|
332
|
-
|
333
|
-
abort("Could not find version.rb under lib/**.") if candidates.empty?
|
334
|
-
versions = candidates.map do |path|
|
335
|
-
content = File.read(path)
|
336
|
-
m = content.match(/VERSION\s*=\s*(["'])([^"']+)\1/)
|
337
|
-
next unless m
|
338
|
-
m[2]
|
339
|
-
end.compact
|
340
|
-
abort("VERSION constant not found in #{@root}/lib/**/version.rb") if versions.none?
|
341
|
-
abort("Multiple VERSION constants found to be out of sync (#{versions.inspect}) in #{@root}/lib/**/version.rb") unless versions.uniq.length == 1
|
342
|
-
versions.first
|
344
|
+
Kettle::Dev::Versioning.detect_version(@root)
|
343
345
|
end
|
344
346
|
|
345
347
|
def detect_gem_name
|
@@ -571,7 +573,7 @@ module Kettle
|
|
571
573
|
puts " [m] Merge --no-ff #{gh_remote}/#{trunk} into #{trunk} (push to origin and #{gh_remote})"
|
572
574
|
puts " [a] Abort"
|
573
575
|
print("> ")
|
574
|
-
choice =
|
576
|
+
choice = Kettle::Dev::InputAdapter.gets&.strip&.downcase
|
575
577
|
case choice
|
576
578
|
when "r"
|
577
579
|
run_cmd!("git rebase #{Shellwords.escape("#{gh_remote}/#{trunk}")}")
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# External
|
4
4
|
require "kettle/dev/exit_adapter"
|
5
|
+
require "kettle/dev/input_adapter"
|
5
6
|
require "open3"
|
6
7
|
require "net/http"
|
7
8
|
require "json"
|
@@ -188,7 +189,7 @@ module Kettle
|
|
188
189
|
selected = nil
|
189
190
|
input_thread = Thread.new do
|
190
191
|
begin
|
191
|
-
selected =
|
192
|
+
selected = Kettle::Dev::InputAdapter.gets&.strip
|
192
193
|
rescue Exception => error
|
193
194
|
# Catch all exceptions in background thread, including SystemExit
|
194
195
|
# NOTE: look into refactoring to minimize potential SystemExit.
|
@@ -249,8 +250,10 @@ module Kettle
|
|
249
250
|
sleep(poll_interval)
|
250
251
|
end
|
251
252
|
rescue Exception
|
253
|
+
# :nocov:
|
252
254
|
# Catch all exceptions in the worker thread boundary, including SystemExit
|
253
255
|
status_q << [c, f, "err"]
|
256
|
+
# :nocov:
|
254
257
|
end
|
255
258
|
end
|
256
259
|
end
|