Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions ci/bump_homebrew_formula.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#!/usr/bin/env ruby
# Bumps a Homebrew formula's per-platform `url`/`sha256` pairs to a new
# rubyists/linear-cli release - the `.github/workflows/main.yaml`
# `homebrew-tap-bump` job's only real logic, kept out of the workflow YAML
# so it's runnable/testable locally without a real CI run.
# Bumps a Homebrew formula to a new rubyists/linear-cli release: writes
# `.version` (the formula's own single source of truth - it interpolates
# `#{version}` into each `url` itself, see the formula file, so the url
# lines never need touching here) and updates each platform's `sha256`.
# The `.github/workflows/main.yaml` `homebrew-tap-bump` job's only real
# logic, kept out of the workflow YAML so it's runnable/testable locally
# without a real CI run.
#
# Usage: bump_homebrew_formula.rb FORMULA_PATH TAG SHA256SUMS_PATH
#
# FORMULA_PATH path to the formula file (e.g. Formula/linear-cli/linear-cli.rb)
# TAG the release tag, e.g. "v1.3.0"
# TAG the release tag, e.g. "v1.4.0"
# SHA256SUMS_PATH that release's own SHA256SUMS asset, as downloaded

formula_path, tag, sha256sums_path = ARGV
Expand All @@ -16,33 +19,50 @@
abort "Usage: #{$PROGRAM_NAME} FORMULA_PATH TAG SHA256SUMS_PATH"
end

# The formula's url lines hardcode a literal "v" before the interpolated
# version (v#{version}/<asset>) - a TAG without that same prefix would
# silently write a .version that doesn't match what the url actually
# requests. Fail loudly instead.
unless tag.start_with?("v")
abort "TAG must start with 'v' (e.g. v1.4.0), got: #{tag}"
end

sha256sums =
File.readlines(sha256sums_path).each_with_object({}) do |line, acc|
sha, name = line.split(/\s+/, 2)
acc[name.strip] = sha if name
end

version = tag.delete_prefix("v")
version_file = File.join(File.dirname(formula_path), ".version")
content = File.read(formula_path)

%w[macos_aarch64 linux_x86_64].each do |target|
asset = "lc_#{target}.tar.gz"
sha = sha256sums.fetch(asset) { abort "No checksum found for #{asset} in #{sha256sums_path}" }

# The url line is a constant - it always reads v#{version}/<asset>
# verbatim in the formula's own source, never a literal version - only
# the sha256 that follows it actually changes per release. sha256 is
# always exactly 64 hex chars - matching that exactly (rather than
# [a-f0-9]+, any length) rejects a malformed/truncated checksum instead
# of silently writing one.
pattern = /
url\ "https:\/\/github\.com\/rubyists\/linear-cli\/releases\/download\/v[\d.]+\/#{Regexp.escape(asset)}"
\n(\s*)
sha256\ "[a-f0-9]+"
(url\ "https:\/\/github\.com\/rubyists\/linear-cli\/releases\/download\/v\#\{version\}\/#{Regexp.escape(asset)}"
\n\s*sha256\ ")[a-fA-F0-9]{64}(")
/x
Comment thread
bougyman marked this conversation as resolved.

unless content.match?(pattern)
abort "Could not find a url/sha256 pair for #{asset} in #{formula_path}"
end

content = content.sub(pattern) do
indent = ::Regexp.last_match(1)
%(url "https://github.com/rubyists/linear-cli/releases/download/#{tag}/#{asset}"\n#{indent}sha256 "#{sha}")
end
content = content.sub(pattern, "\\1#{sha}\\2")
end

# Both writes deferred until every asset's checked and every pattern
# matched - an abort above now never leaves .version bumped while the
# formula's actual sha256/url pairs are still on the old release (or
# vice versa).
File.write(version_file, "#{version}\n")
File.write(formula_path, content)
puts "Bumped #{formula_path} to #{tag}"
puts "Wrote #{version_file} (#{version}) and updated #{formula_path}'s sha256 pairs to match"