Module: Kettle::Dev::CIHelpers

Defined in:
lib/kettle/dev/ci_helpers.rb

Overview

CI-related helper functions used by Rake tasks and release tooling.

This module only exposes module-functions (no instance state) and is intentionally small so it can be required by both Rake tasks and the kettle-release executable.

Class Method Summary collapse

Class Method Details

.current_branchString?

Current git branch name, or nil when not in a repository.

Returns:

  • (String, nil)


54
55
56
57
# File 'lib/kettle/dev/ci_helpers.rb', line 54

def current_branch
  out, status = Open3.capture2("git", "rev-parse", "--abbrev-ref", "HEAD")
  status.success? ? out.strip : nil
end

.default_tokenString?

Default GitHub token sourced from environment.

Returns:

  • (String, nil)


166
167
168
# File 'lib/kettle/dev/ci_helpers.rb', line 166

def default_token
  ENV["GITHUB_TOKEN"] || ENV["GH_TOKEN"]
end

.exclusionsArray<String>

List of workflow files to exclude from interactive menus and checks.

For reference…

A list of all worlflows,

with each marked relative to if they exist in this repo,
or at the top of the README marked.

- ancient                 (+)
- auto-assign.yml         (-)
- codeql-analysis.yml     (-)
- coverage.yml            (+)
- current.yml             (+)
- danger.yml              (x)
- dependency-review.yml   (-)
- discord-notifier.yml    (-)
- heads.yml               (+)
- jruby.yml               (+)
- legacy.yml              (+)
- locked_deps.yml         (+)
- opencollective.yml      (-)
- style.yml               (+)
- supported.yml           (+)
- truffle.yml             (+)
- unlocked_deps.yml       (+)
- unsupported.yml         (+)

All those marked as (-) or (x) are excluded from interactive menus and checks. The (x) exist because they may be common in other repos.

Returns:

  • (Array<String>)


108
109
110
111
112
113
114
115
116
117
# File 'lib/kettle/dev/ci_helpers.rb', line 108

def exclusions
  %w[
    auto-assign.yml
    codeql-analysis.yml
    danger.yml
    dependency-review.yml
    discord-notifier.yml
    opencollective.yml
  ]
end

.failed?(run) ⇒ Boolean

Whether a run has completed with a non-success conclusion.

Parameters:

  • run (Hash, nil)

Returns:

  • (Boolean)


160
161
162
# File 'lib/kettle/dev/ci_helpers.rb', line 160

def failed?(run)
  run && run["status"] == "completed" && run["conclusion"] && run["conclusion"] != "success"
end

.latest_run(owner:, repo:, workflow_file:, branch: nil, token: default_token) ⇒ Hash{String=>String,Integer}?

Fetch latest workflow run info for a given workflow and branch via GitHub API.

Parameters:

  • owner (String)
  • repo (String)
  • workflow_file (String)

    the workflow basename (e.g., “ci.yml”)

  • branch (String, nil) (defaults to: nil)

    branch to query; defaults to #current_branch

  • token (String, nil) (defaults to: default_token)

    OAuth token for higher rate limits; defaults to #default_token

Returns:

  • (Hash{String=>String,Integer}, nil)

    minimal run info or nil on error/none



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/kettle/dev/ci_helpers.rb', line 127

def latest_run(owner:, repo:, workflow_file:, branch: nil, token: default_token)
  return unless owner && repo
  b = branch || current_branch
  return unless b
  uri = URI("https://api.github.com/repos/#{owner}/#{repo}/actions/workflows/#{workflow_file}/runs?branch=#{URI.encode_www_form_component(b)}&per_page=1")
  req = Net::HTTP::Get.new(uri)
  req["User-Agent"] = "kettle-dev/ci-helpers"
  req["Authorization"] = "token #{token}" if token && !token.empty?
  res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
  return unless res.is_a?(Net::HTTPSuccess)
  data = JSON.parse(res.body)
  run = data["workflow_runs"]&.first
  return unless run
  {
    "status" => run["status"],
    "conclusion" => run["conclusion"],
    "html_url" => run["html_url"],
    "id" => run["id"],
  }
rescue StandardError
  nil
end

.project_rootString

Determine the project root directory.

Prefers the directory Rake was invoked from (Rake.application.original_dir) so that tasks shipped with this gem operate relative to the host project. Falls back to the current working directory when Rake context is absent.

Returns:

  • (String)

    absolute path to the project root



25
26
27
28
29
30
31
32
33
# File 'lib/kettle/dev/ci_helpers.rb', line 25

def project_root
  # Too difficult to test every possible branch here, so ignoring
  # :nocov:
  dir = if defined?(Rake) && Rake&.application&.respond_to?(:original_dir)
    Rake.application.original_dir
  end
  # :nocov:
  dir || Dir.pwd
end

.repo_infoArray(String, String)?

Parse the GitHub owner/repo from the configured origin remote.

Supports SSH (git@github.com:owner/repo(.git)) and HTTPS (github.com/owner/repo(.git)) forms.

Returns:

  • (Array(String, String), nil)
    owner, repo

    or nil when unavailable



41
42
43
44
45
46
47
48
49
50
# File 'lib/kettle/dev/ci_helpers.rb', line 41

def repo_info
  out, status = Open3.capture2("git", "config", "--get", "remote.origin.url")
  return unless status.success?
  url = out.strip
  if url =~ %r{git@github.com:(.+?)/(.+?)(\.git)?$}
    [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")]
  elsif url =~ %r{https://github.com/(.+?)/(.+?)(\.git)?$}
    [Regexp.last_match(1), Regexp.last_match(2).sub(/\.git\z/, "")]
  end
end

.success?(run) ⇒ Boolean

Whether a run has completed successfully.

Parameters:

  • run (Hash, nil)

Returns:

  • (Boolean)


153
154
155
# File 'lib/kettle/dev/ci_helpers.rb', line 153

def success?(run)
  run && run["status"] == "completed" && run["conclusion"] == "success"
end

.workflows_list(root = project_root) ⇒ Array<String>

List workflow YAML basenames under .github/workflows at the given root.

Excludes maintenance workflows defined by #exclusions.

Parameters:

  • root (String) (defaults to: project_root)

    project root (defaults to #project_root)

Returns:

  • (Array<String>)

    sorted list of basenames (e.g., “ci.yml”)



65
66
67
68
69
70
71
72
73
74
75
# File 'lib/kettle/dev/ci_helpers.rb', line 65

def workflows_list(root = project_root)
  workflows_dir = File.join(root, ".github", "workflows")
  files = if Dir.exist?(workflows_dir)
    Dir[File.join(workflows_dir, "*.yml")] + Dir[File.join(workflows_dir, "*.yaml")]
  else
    []
  end
  basenames = files.map { |p| File.basename(p) }
  basenames = basenames.uniq - exclusions
  basenames.sort
end