diff --git a/ci/bump_homebrew_formula.rb b/ci/bump_homebrew_formula.rb index 0113f94..a6d9e8d 100755 --- a/ci/bump_homebrew_formula.rb +++ b/ci/bump_homebrew_formula.rb @@ -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 @@ -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}/) - 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}/ + # 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 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" diff --git a/vendor/rubyists-homebrew-tap b/vendor/rubyists-homebrew-tap index 23d1237..ab0aae0 160000 --- a/vendor/rubyists-homebrew-tap +++ b/vendor/rubyists-homebrew-tap @@ -1 +1 @@ -Subproject commit 23d1237a1489a93c2671bd68ca7f6b9ec085ff32 +Subproject commit ab0aae00839b2abe2156a8e6ab5852eaed749bf5