kettle-dev 1.1.4 → 1.1.5

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.env.local.example +14 -1
  4. data/.envrc +2 -2
  5. data/.git-hooks/commit-msg +22 -16
  6. data/.git-hooks/prepare-commit-msg.example +19 -0
  7. data/.github/workflows/coverage.yml +2 -2
  8. data/.junie/guidelines.md +4 -0
  9. data/.opencollective.yml.example +3 -0
  10. data/CHANGELOG.md +31 -1
  11. data/CONTRIBUTING.md +29 -0
  12. data/FUNDING.md +2 -2
  13. data/README.md +148 -38
  14. data/README.md.example +7 -7
  15. data/Rakefile.example +1 -1
  16. data/exe/kettle-changelog +7 -2
  17. data/exe/kettle-commit-msg +20 -5
  18. data/exe/kettle-dev-setup +2 -1
  19. data/exe/kettle-dvcs +7 -2
  20. data/exe/kettle-pre-release +66 -0
  21. data/exe/kettle-readme-backers +7 -2
  22. data/exe/kettle-release +9 -2
  23. data/lib/kettle/dev/changelog_cli.rb +4 -5
  24. data/lib/kettle/dev/ci_helpers.rb +9 -5
  25. data/lib/kettle/dev/ci_monitor.rb +229 -8
  26. data/lib/kettle/dev/gem_spec_reader.rb +105 -39
  27. data/lib/kettle/dev/git_adapter.rb +6 -3
  28. data/lib/kettle/dev/git_commit_footer.rb +5 -2
  29. data/lib/kettle/dev/pre_release_cli.rb +248 -0
  30. data/lib/kettle/dev/readme_backers.rb +4 -2
  31. data/lib/kettle/dev/release_cli.rb +27 -17
  32. data/lib/kettle/dev/tasks/ci_task.rb +112 -22
  33. data/lib/kettle/dev/tasks/install_task.rb +23 -17
  34. data/lib/kettle/dev/tasks/template_task.rb +64 -23
  35. data/lib/kettle/dev/template_helpers.rb +44 -31
  36. data/lib/kettle/dev/version.rb +1 -1
  37. data/lib/kettle/dev.rb +5 -0
  38. data/sig/kettle/dev/ci_monitor.rbs +6 -0
  39. data/sig/kettle/dev/gem_spec_reader.rbs +8 -5
  40. data/sig/kettle/dev/pre_release_cli.rbs +20 -0
  41. data/sig/kettle/dev/template_helpers.rbs +2 -0
  42. data/sig/kettle/dev.rbs +1 -0
  43. data.tar.gz.sig +0 -0
  44. metadata +30 -4
  45. metadata.gz.sig +0 -0
@@ -5,8 +5,15 @@
5
5
 
6
6
  # Immediate, unbuffered output
7
7
  $stdout.sync = true
8
- # Depending library or project must be using bundler
9
- require "bundler/setup"
8
+ $stderr.sync = true
9
+
10
+ # Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
11
+ # Ensure RubyGems is available for 'require' lookups
12
+ begin
13
+ require "rubygems"
14
+ rescue LoadError
15
+ # Older Rubies always have rubygems; continue anyway
16
+ end
10
17
 
11
18
  script_basename = File.basename(__FILE__)
12
19
 
@@ -14,11 +21,18 @@ begin
14
21
  # Standard library
15
22
  require "erb"
16
23
 
17
- require "kettle/dev"
24
+ begin
25
+ require "kettle/dev"
26
+ rescue LoadError
27
+ # Fallback: allow running from a checkout without bundler by adding ./lib to the load path
28
+ repo_lib = File.expand_path("../lib", __dir__)
29
+ $LOAD_PATH.unshift(repo_lib) unless $LOAD_PATH.include?(repo_lib)
30
+ require "kettle/dev"
31
+ end
18
32
  puts "== #{script_basename} v#{Kettle::Dev::Version::VERSION} =="
19
33
  rescue LoadError => e
20
34
  warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
21
- warn("Hint: Ensure the host project has kettle-dev as a dependency and run bundle install.")
35
+ warn("Hint: Install the kettle-dev gem (`gem install kettle-dev`) or add it to your Gemfile. Bundler is not required for this script.")
22
36
  exit(1)
23
37
  end
24
38
 
@@ -28,7 +42,8 @@ end
28
42
  BRANCH_RULES = {
29
43
  "jira" => /^(?<story_type>(hotfix)|(bug)|(feature)|(candy))\/(?<story_id>\d{8,})-.+\Z/,
30
44
  }
31
- BRANCH_RULE_TYPE = (validate = ENV.fetch("GIT_HOOK_BRANCH_VALIDATE", "false")) && !validate.casecmp?("false") && validate
45
+ # Ruby 2.3 compatibility: String#casecmp? is not available; use casecmp.zero?
46
+ BRANCH_RULE_TYPE = (validate = ENV.fetch("GIT_HOOK_BRANCH_VALIDATE", "false")) && !(validate.respond_to?(:casecmp?) ? validate.casecmp?("false") : (validate.casecmp("false").zero?)) && validate
32
47
 
33
48
  # Called the "JIRA pattern", because of traditional use with JIRA.
34
49
  if (branch_rule = BRANCH_RULES[BRANCH_RULE_TYPE])
data/exe/kettle-dev-setup CHANGED
@@ -71,6 +71,7 @@ module Kettle
71
71
  opts.on("--allowed=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "allowed=#{v}" }
72
72
  opts.on("--force", "Pass through to kettle:dev:install") { @passthrough << "force=true" }
73
73
  opts.on("--hook_templates=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "hook_templates=#{v}" }
74
+ opts.on("--only=VAL", "Pass through to kettle:dev:install") { |v| @passthrough << "only=#{v}" }
74
75
  opts.on("-h", "--help", "Show help") do
75
76
  puts opts
76
77
  exit(0)
@@ -165,7 +166,7 @@ module Kettle
165
166
  modified = target.dup
166
167
  wanted.each do |gem_name, desired_line|
167
168
  # Replace existing add_development_dependency for this gem, else append near the end
168
- if modified.match?(/add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["']/)
169
+ if /add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["']/ =~ modified
169
170
  modified = modified.gsub(/^[^\n]*add_development_dependency\s*\(\s*["']#{Regexp.escape(gem_name)}["'][^\n]*\)$/) do |_|
170
171
  desired_line
171
172
  end
data/exe/kettle-dvcs CHANGED
@@ -11,8 +11,13 @@
11
11
  $stdout.sync = true
12
12
  $stderr.sync = true
13
13
 
14
- # Depending library or project must be using bundler
15
- require "bundler/setup"
14
+ # Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
15
+ # Ensure RubyGems is available for 'require' lookups
16
+ begin
17
+ require "rubygems"
18
+ rescue LoadError
19
+ # Older Rubies always have rubygems; continue anyway
20
+ end
16
21
 
17
22
  script_basename = File.basename(__FILE__)
18
23
 
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # vim: set syntax=ruby
5
+
6
+ # kettle-pre-release: Run pre-release checks to catch avoidable mistakes.
7
+ # - Structured as a sequence of checks that can be resumed via check_num
8
+ # - Initial check: validate all image URLs referenced by Markdown files resolve (HTTP HEAD)
9
+
10
+ require "optparse"
11
+
12
+ # Immediate, unbuffered output
13
+ $stdout.sync = true
14
+ $stderr.sync = true
15
+
16
+ # Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
17
+ # Ensure RubyGems is available for 'require' lookups
18
+ begin
19
+ require "rubygems"
20
+ rescue LoadError
21
+ # Older Rubies always have rubygems; continue anyway
22
+ end
23
+
24
+ script_basename = File.basename(__FILE__)
25
+
26
+ begin
27
+ require "kettle/dev"
28
+ puts "== #{script_basename} v#{Kettle::Dev::Version::VERSION} =="
29
+ rescue StandardError => e
30
+ warn("#{script_basename}: could not load dependency: #{e.class}: #{e.message}")
31
+ warn("Hint: Ensure the host project has kettle-dev as a dependency and run bundle install.")
32
+ exit(1)
33
+ end
34
+
35
+ # Option parsing
36
+ options = {
37
+ check_num: 1,
38
+ }
39
+
40
+ parser = OptionParser.new do |opts|
41
+ opts.banner = "Usage: kettle-pre-release [options]"
42
+ opts.on("--check-num N", Integer, "Start from check number N (1-based)") { |v| options[:check_num] = v }
43
+ opts.on("-cN", Integer, "Alias for --check-num N") { |v| options[:check_num] = v }
44
+ opts.on("-h", "--help", "Show this help") do
45
+ puts opts
46
+ puts
47
+ puts "Checks:"
48
+ puts " 1) Validate Markdown image links (HTTP HEAD)"
49
+ exit(0)
50
+ end
51
+ end
52
+
53
+ begin
54
+ parser.parse!(ARGV)
55
+ rescue OptionParser::ParseError => e
56
+ warn("#{script_basename}: #{e.message}")
57
+ puts parser
58
+ exit(2)
59
+ end
60
+
61
+ # Execute
62
+ # Build and run CLI in the same style as kettle-release
63
+ cli = Kettle::Dev::PreReleaseCLI.new(check_num: options[:check_num])
64
+ cli.run
65
+
66
+ puts "== #{script_basename} complete =="
@@ -5,8 +5,13 @@
5
5
 
6
6
  # Immediate, unbuffered output
7
7
  $stdout.sync = true
8
- # Depending library or project must be using bundler
9
- require "bundler/setup"
8
+ # Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
9
+ # Ensure RubyGems is available for 'require' lookups
10
+ begin
11
+ require "rubygems"
12
+ rescue LoadError
13
+ # Older Rubies always have rubygems; continue anyway
14
+ end
10
15
 
11
16
  script_basename = File.basename(__FILE__)
12
17
 
data/exe/kettle-release CHANGED
@@ -16,8 +16,15 @@
16
16
 
17
17
  # Immediate, unbuffered output
18
18
  $stdout.sync = true
19
- # Depending library or project must be using bundler
20
- require "bundler/setup"
19
+ $stderr.sync = true
20
+
21
+ # Do not rely on Bundler; allow running in repos that do not depend on kettle-dev
22
+ # Ensure RubyGems is available for 'require' lookups
23
+ begin
24
+ require "rubygems"
25
+ rescue LoadError
26
+ # Older Rubies always have rubygems; continue anyway
27
+ end
21
28
 
22
29
  script_basename = File.basename(__FILE__)
23
30
 
@@ -3,6 +3,7 @@
3
3
  module Kettle
4
4
  module Dev
5
5
  class ChangelogCLI
6
+ UNRELEASED_SECTION_HEADING = "[Unreleased]:"
6
7
  def initialize
7
8
  @root = Kettle::Dev::CIHelpers.project_root
8
9
  @changelog_path = File.join(@root, "CHANGELOG.md")
@@ -70,7 +71,7 @@ module Kettle
70
71
  if after && !after.empty?
71
72
  # Split 'after' by lines so we can locate the first link-ref to Unreleased
72
73
  after_lines = after.lines
73
- unreleased_ref_idx = after_lines.index { |l| l.start_with?("[Unreleased]:") }
74
+ unreleased_ref_idx = after_lines.index { |l| l.start_with?(UNRELEASED_SECTION_HEADING) }
74
75
  if unreleased_ref_idx
75
76
  # Keep all content prior to the link-ref (older releases and interspersed refs)
76
77
  preserved_body = after_lines[0...unreleased_ref_idx].join
@@ -96,8 +97,6 @@ module Kettle
96
97
 
97
98
  def abort(msg)
98
99
  Kettle::Dev::ExitAdapter.abort(msg)
99
- rescue NameError
100
- Kernel.abort(msg)
101
100
  end
102
101
 
103
102
  def detect_version
@@ -260,7 +259,7 @@ module Kettle
260
259
  # Identify the true start of the footer reference block: the line with the [Unreleased] link-ref.
261
260
  # Do NOT assume the first link-ref after the Unreleased heading starts the footer, because
262
261
  # some changelogs contain interspersed link-refs within section bodies.
263
- unreleased_ref_idx = lines.index { |l| l.start_with?("[Unreleased]:") }
262
+ unreleased_ref_idx = lines.index { |l| l.start_with?(UNRELEASED_SECTION_HEADING) }
264
263
  first_ref = if unreleased_ref_idx
265
264
  unreleased_ref_idx
266
265
  else
@@ -274,7 +273,7 @@ module Kettle
274
273
  # Update an existing Unreleased ref only if it appears after Unreleased heading; otherwise append
275
274
  idx = nil
276
275
  lines.each_with_index do |l, i|
277
- if l.start_with?("[Unreleased]:") && i >= first_ref
276
+ if l.start_with?(UNRELEASED_SECTION_HEADING) && i >= first_ref
278
277
  idx = i
279
278
  break
280
279
  end
@@ -115,7 +115,8 @@ module Kettle
115
115
  "html_url" => run["html_url"],
116
116
  "id" => run["id"],
117
117
  }
118
- rescue StandardError
118
+ rescue StandardError => e
119
+ Kettle::Dev.debug_error(e, __method__)
119
120
  nil
120
121
  end
121
122
 
@@ -185,8 +186,9 @@ module Kettle
185
186
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
186
187
  return unless res.is_a?(Net::HTTPSuccess)
187
188
  data = JSON.parse(res.body)
188
- pipe = data&.first
189
- return unless pipe
189
+ return unless data.is_a?(Array)
190
+ pipe = data.first
191
+ return unless pipe.is_a?(Hash)
190
192
  # Attempt to enrich with failure_reason by querying the single pipeline endpoint
191
193
  begin
192
194
  if pipe["id"]
@@ -202,7 +204,8 @@ module Kettle
202
204
  pipe["web_url"] = det["web_url"] if det["web_url"]
203
205
  end
204
206
  end
205
- rescue StandardError
207
+ rescue StandardError => e
208
+ Kettle::Dev.debug_error(e, __method__)
206
209
  # ignore enrichment errors; fall back to basic fields
207
210
  end
208
211
  {
@@ -211,7 +214,8 @@ module Kettle
211
214
  "id" => pipe["id"],
212
215
  "failure_reason" => pipe["failure_reason"],
213
216
  }
214
- rescue StandardError
217
+ rescue StandardError => e
218
+ Kettle::Dev.debug_error(e, __method__)
215
219
  nil
216
220
  end
217
221
 
@@ -21,8 +21,28 @@ module Kettle
21
21
  end
22
22
  module_function :abort
23
23
 
24
+ # Small helper to map CI run status/conclusion to an emoji.
25
+ # Reused by ci:act and release summary.
26
+ # @param status [String, nil]
27
+ # @param conclusion [String, nil]
28
+ # @return [String]
29
+ def status_emoji(status, conclusion)
30
+ case status.to_s
31
+ when "queued" then "⏳️"
32
+ when "in_progress", "running" then "👟"
33
+ when "completed"
34
+ (conclusion.to_s == "success") ? "✅" : "🍅"
35
+ else
36
+ # Some APIs report only a final state string like "success"/"failed"
37
+ return "✅" if conclusion.to_s == "success" || status.to_s == "success"
38
+ return "🍅" if conclusion.to_s == "failure" || status.to_s == "failed"
39
+ "⏳️"
40
+ end
41
+ end
42
+ module_function :status_emoji
43
+
24
44
  # Monitor both GitHub and GitLab CI for the current project/branch.
25
- # This mirrors ReleaseCLI behavior.
45
+ # This mirrors ReleaseCLI behavior and aborts on first failure.
26
46
  #
27
47
  # @param restart_hint [String] guidance command shown on failure
28
48
  # @return [void]
@@ -33,16 +53,217 @@ module Kettle
33
53
  abort("CI configuration not detected (GitHub or GitLab). Ensure CI is configured and remotes point to the correct hosts.") unless checks_any
34
54
  end
35
55
 
36
- # Monitor only the GitLab pipeline for current project/branch.
37
- # Used by ci:act after running 'act'.
38
- #
39
- # @param restart_hint [String] guidance command shown on failure
40
- # @return [Boolean] true if check performed (gitlab configured), false otherwise
41
- def monitor_gitlab!(restart_hint: "bundle exec rake ci:act")
56
+ # Public wrapper to monitor GitLab pipeline with abort-on-failure semantics.
57
+ # Matches RBS and call sites expecting ::monitor_gitlab!
58
+ # Returns false when GitLab is not configured for this repo/branch.
59
+ # @param restart_hint [String]
60
+ # @return [Boolean]
61
+ def monitor_gitlab!(restart_hint: "bundle exec kettle-release start_step=10")
42
62
  monitor_gitlab_internal!(restart_hint: restart_hint)
43
63
  end
64
+ module_function :monitor_gitlab!
65
+
66
+ # Non-aborting collection across GH and GL, returning a compact results hash.
67
+ # Results format:
68
+ # {
69
+ # github: [ {workflow: "file.yml", status: "completed", conclusion: "success"|"failure"|nil, url: String} ],
70
+ # gitlab: { status: "success"|"failed"|"blocked"|"unknown"|nil, url: String }
71
+ # }
72
+ # @return [Hash]
73
+ def collect_all
74
+ results = {github: [], gitlab: nil}
75
+ begin
76
+ gh = collect_github
77
+ results[:github] = gh if gh
78
+ rescue StandardError => e
79
+ Kettle::Dev.debug_error(e, __method__)
80
+ end
81
+ begin
82
+ gl = collect_gitlab
83
+ results[:gitlab] = gl if gl
84
+ rescue StandardError => e
85
+ Kettle::Dev.debug_error(e, __method__)
86
+ end
87
+ results
88
+ end
89
+ module_function :collect_all
90
+
91
+ # Print a concise summary like ci:act and return whether everything is green.
92
+ # @param results [Hash]
93
+ # @return [Boolean] true when all checks passed or were unknown, false when any failed
94
+ def summarize_results(results)
95
+ all_ok = true
96
+ gh_items = results[:github] || []
97
+ unless gh_items.empty?
98
+ puts "GitHub Actions:"
99
+ gh_items.each do |it|
100
+ emoji = status_emoji(it[:status], it[:conclusion])
101
+ details = [it[:status], it[:conclusion]].compact.join("/")
102
+ wf = it[:workflow]
103
+ puts " - #{wf}: #{emoji} (#{details}) #{it[:url] ? "-> #{it[:url]}" : ""}"
104
+ all_ok &&= (it[:conclusion] == "success")
105
+ end
106
+ end
107
+ gl = results[:gitlab]
108
+ if gl
109
+ status = if gl[:status] == "success"
110
+ "success"
111
+ else
112
+ ((gl[:status] == "failed") ? "failure" : nil)
113
+ end
114
+ emoji = status_emoji(gl[:status], status)
115
+ details = gl[:status].to_s
116
+ puts "GitLab Pipeline: #{emoji} (#{details}) #{gl[:url] ? "-> #{gl[:url]}" : ""}"
117
+ all_ok &&= (gl[:status] != "failed")
118
+ end
119
+ all_ok
120
+ end
121
+ module_function :summarize_results
122
+
123
+ # Prompt user to continue or quit when failures are present; otherwise return.
124
+ # Designed for kettle-release.
125
+ # @param restart_hint [String]
126
+ # @return [void]
127
+ def monitor_and_prompt_for_release!(restart_hint: "bundle exec kettle-release start_step=10")
128
+ results = collect_all
129
+ any_checks = !(results[:github].nil? || results[:github].empty?) || !!results[:gitlab]
130
+ abort("CI configuration not detected (GitHub or GitLab). Ensure CI is configured and remotes point to the correct hosts.") unless any_checks
131
+
132
+ ok = summarize_results(results)
133
+ return if ok
134
+
135
+ # Non-interactive environments default to quitting unless explicitly allowed
136
+ env_val = ENV.fetch("K_RELEASE_CI_CONTINUE", "false")
137
+ non_interactive_continue = !!(Kettle::Dev::ENV_TRUE_RE =~ env_val)
138
+ if !$stdin.tty?
139
+ abort("CI checks reported failures. Fix and restart from CI validation (#{restart_hint}).") unless non_interactive_continue
140
+ puts "CI checks reported failures, but continuing due to K_RELEASE_CI_CONTINUE=true."
141
+ return
142
+ end
143
+
144
+ # Prompt exactly once; avoid repeated printing in case of unexpected input buffering.
145
+ # Accept c/continue to proceed or q/quit to abort. Any other input defaults to quit with a message.
146
+ print("One or more CI checks failed. (c)ontinue or (q)uit? ")
147
+ ans = Kettle::Dev::InputAdapter.gets
148
+ if ans.nil?
149
+ abort("Aborting (no input available). Fix CI, then restart with: #{restart_hint}")
150
+ end
151
+ ans = ans.strip.downcase
152
+ if ans == "c" || ans == "continue"
153
+ puts "Continuing release despite CI failures."
154
+ elsif ans == "q" || ans == "quit"
155
+ abort("Aborting per user choice. Fix CI, then restart with: #{restart_hint}")
156
+ else
157
+ abort("Unrecognized input '#{ans}'. Aborting. Fix CI, then restart with: #{restart_hint}")
158
+ end
159
+ end
160
+ module_function :monitor_and_prompt_for_release!
161
+
162
+ # --- Collectors ---
163
+ def collect_github
164
+ root = Kettle::Dev::CIHelpers.project_root
165
+ workflows = Kettle::Dev::CIHelpers.workflows_list(root)
166
+ gh_remote = preferred_github_remote
167
+ return unless gh_remote && !workflows.empty?
168
+
169
+ branch = Kettle::Dev::CIHelpers.current_branch
170
+ abort("Could not determine current branch for CI checks.") unless branch
171
+
172
+ url = remote_url(gh_remote)
173
+ owner, repo = parse_github_owner_repo(url)
174
+ return unless owner && repo
175
+
176
+ total = workflows.size
177
+ return [] if total.zero?
178
+
179
+ puts "Checking GitHub Actions workflows on #{branch} (#{owner}/#{repo}) via remote '#{gh_remote}'"
180
+ pbar = if defined?(ProgressBar)
181
+ ProgressBar.create(title: "GHA", total: total, format: "%t %b %c/%C", length: 30)
182
+ end
183
+ # Initial sleep same as aborting path
184
+ begin
185
+ initial_sleep = Integer(ENV["K_RELEASE_CI_INITIAL_SLEEP"])
186
+ rescue
187
+ initial_sleep = nil
188
+ end
189
+ sleep((initial_sleep && initial_sleep >= 0) ? initial_sleep : 3)
190
+
191
+ results = {}
192
+ idx = 0
193
+ loop do
194
+ wf = workflows[idx]
195
+ run = Kettle::Dev::CIHelpers.latest_run(owner: owner, repo: repo, workflow_file: wf, branch: branch)
196
+ if run
197
+ if Kettle::Dev::CIHelpers.success?(run)
198
+ unless results[wf]
199
+ status = run["status"] || "completed"
200
+ conclusion = run["conclusion"] || "success"
201
+ results[wf] = {workflow: wf, status: status, conclusion: conclusion, url: run["html_url"]}
202
+ pbar&.increment
203
+ end
204
+ elsif Kettle::Dev::CIHelpers.failed?(run)
205
+ unless results[wf]
206
+ results[wf] = {workflow: wf, status: run["status"], conclusion: run["conclusion"] || "failure", url: run["html_url"] || "https://github.com/#{owner}/#{repo}/actions/workflows/#{wf}"}
207
+ pbar&.increment
208
+ end
209
+ end
210
+ end
211
+ break if results.size == total
212
+ idx = (idx + 1) % total
213
+ sleep(1)
214
+ end
215
+ pbar&.finish unless pbar&.finished?
216
+ results.values
217
+ end
218
+ module_function :collect_github
219
+
220
+ def collect_gitlab
221
+ root = Kettle::Dev::CIHelpers.project_root
222
+ gitlab_ci = File.exist?(File.join(root, ".gitlab-ci.yml"))
223
+ gl_remote = gitlab_remote_candidates.first
224
+ return unless gitlab_ci && gl_remote
225
+
226
+ branch = Kettle::Dev::CIHelpers.current_branch
227
+ abort("Could not determine current branch for CI checks.") unless branch
228
+
229
+ owner, repo = Kettle::Dev::CIHelpers.repo_info_gitlab
230
+ return unless owner && repo
231
+
232
+ puts "Checking GitLab pipeline on #{branch} (#{owner}/#{repo}) via remote '#{gl_remote}'"
233
+ pbar = if defined?(ProgressBar)
234
+ ProgressBar.create(title: "GL", total: 1, format: "%t %b %c/%C", length: 30)
235
+ end
236
+ result = {status: "unknown", url: nil}
237
+ loop do
238
+ pipe = Kettle::Dev::CIHelpers.gitlab_latest_pipeline(owner: owner, repo: repo, branch: branch)
239
+ if pipe
240
+ result[:url] ||= pipe["web_url"] || "https://gitlab.com/#{owner}/#{repo}/-/pipelines"
241
+ if Kettle::Dev::CIHelpers.gitlab_success?(pipe)
242
+ result[:status] = "success"
243
+ pbar&.increment unless pbar&.finished?
244
+ elsif Kettle::Dev::CIHelpers.gitlab_failed?(pipe)
245
+ reason = (pipe["failure_reason"] || "").to_s
246
+ if reason =~ /insufficient|quota|minute/i
247
+ result[:status] = "unknown"
248
+ pbar&.finish unless pbar&.finished?
249
+ else
250
+ result[:status] = "failed"
251
+ pbar&.increment unless pbar&.finished?
252
+ end
253
+ elsif pipe["status"] == "blocked"
254
+ result[:status] = "blocked"
255
+ pbar&.finish unless pbar&.finished?
256
+ end
257
+ break
258
+ end
259
+ sleep(1)
260
+ end
261
+ pbar&.finish unless pbar&.finished?
262
+ result
263
+ end
264
+ module_function :collect_gitlab
44
265
 
45
- # -- internals --
266
+ # -- internals (abort-on-failure legacy paths used elsewhere) --
46
267
 
47
268
  def monitor_github_internal!(restart_hint:)
48
269
  root = Kettle::Dev::CIHelpers.project_root