class Helper
Helper class for performing gem-related operations like building, installing, and publishing gems.
Definitions
def initialize(root = Dir.pwd, gemspec: nil)
Initialize a new helper with the specified root directory and optional gemspec.
Signature
-
parameter
root
String
The root directory of the gem project.
-
parameter
gemspec
Gem::Specification | Nil
The gemspec to use, or nil to find it automatically.
Implementation
def initialize(root = Dir.pwd, gemspec: nil)
@root = root
@gemspec = gemspec || find_gemspec
end
attr :root
Signature
-
attribute
String
The root directory of the gem project.
attr :gemspec
Signature
-
attribute
Gem::Specification
The gemspec for the gem.
def version_path
Find the path to the version.rb file in the gem.
Signature
-
returns
String | Nil
The path to the version file, or nil if not found.
Implementation
def version_path
if @gemspec
@gemspec.files.grep(/lib(.*?)\/version.rb/).first
end
end
def update_version(bump, version_path = self.version_path)
Update the version number in the version file according to the bump specification.
Signature
-
parameter
bump
Array(Integer)
Array specifying how to increment each version part.
-
parameter
version_path
String
The path to the version file.
-
returns
String | Boolean
The path to the version file if updated, or false if no version file found.
Implementation
def update_version(bump, version_path = self.version_path)
return false unless version_path
# Guard against consecutive version bumps
guard_last_commit_not_version_bump
lines = File.readlines(version_path)
new_version = nil
lines.each do |line|
Version.update_version(line) do |version|
new_version = version.increment(bump)
end
end
if new_version
File.write(version_path, lines.join)
if block_given?
yield new_version
end
return version_path
end
end
def guard_clean
Verify that the repository has no uncommitted changes.
Signature
-
returns
Boolean
True if the repository is clean.
-
raises
RuntimeError
If there are uncommitted changes in the repository.
Implementation
def guard_clean
lines = readlines("git", "status", "--porcelain", chdir: @root)
if lines.any?
raise "Repository has uncommited changes!\n#{lines.join('')}"
end
return true
end
def guard_last_commit_not_version_bump
Verify that the last commit was not a version bump.
Signature
-
returns
Boolean
True if the last commit was not a version bump.
-
raises
RuntimeError
If the last commit was a version bump.
Implementation
def guard_last_commit_not_version_bump
# Get the last commit message:
begin
last_commit_message = readlines("git", "log", "-1", "--pretty=format:%s", chdir: @root).first&.strip
rescue CommandExecutionError => error
# If git log fails (e.g., no commits yet), skip the check:
if error.exit_code == 128
return true
else
raise
end
end
if last_commit_message && last_commit_message.match?(/^Bump (patch|minor|major|version)( version)?\.?$/i)
raise "Last commit appears to be a version bump: #{last_commit_message.inspect}. Cannot bump version consecutively."
end
return true
end
def build_gem(root: "pkg", signing_key: nil)
Signature
-
parameter
root
String
The root path for package files.
-
parameter
signing_key
String | Nil
The signing key to use for signing the package.
-
returns
String
The path to the built gem package.
Implementation
def build_gem(root: "pkg", signing_key: nil)
# Ensure the output directory exists:
FileUtils.mkdir_p(root)
output_path = File.join(root, @gemspec.file_name)
if signing_key == false
@gemspec.signing_key = nil
elsif signing_key.is_a?(String)
@gemspec.signing_key = signing_key
elsif signing_key == true and @gemspec.signing_key.nil?
raise ArgumentError, "Signing key is required for signing the gem, but none was specified by the gemspec."
end
::Gem::Package.build(@gemspec, false, false, output_path)
end
def install_gem(*arguments, path: @gemspec.file_name)
Install the gem using the gem install
command.
Signature
-
parameter
arguments
Array
Additional arguments to pass to
gem install
.-
parameter
path
String
The path to the gem file to install.
Implementation
def install_gem(*arguments, path: @gemspec.file_name)
system("gem", "install", path, *arguments)
end
def push_gem(*arguments, path: @gemspec.file_name)
Push the gem to a gem repository using the gem push
command.
Signature
-
parameter
arguments
Array
Additional arguments to pass to
gem push
.-
parameter
path
String
The path to the gem file to push.
Implementation
def push_gem(*arguments, path: @gemspec.file_name)
system("gem", "push", path, *arguments)
end
def build_gem_in_worktree(root: "pkg", signing_key: nil)
Build the gem in a clean worktree for better isolation
Signature
-
parameter
root
String
The root path for package files.
-
parameter
signing_key
String | Nil
The signing key to use for signing the package.
-
returns
String
The path to the built gem package.
Implementation
def build_gem_in_worktree(root: "pkg", signing_key: nil)
original_pkg_path = File.join(@root, root)
# Create a unique temporary path for the worktree
timestamp = Time.now.strftime("%Y%m%d-%H%M%S-%N")
worktree_path = File.join(Dir.tmpdir, "bake-gem-build-#{timestamp}")
begin
# Create worktree from current HEAD
unless system("git", "worktree", "add", worktree_path, "HEAD", chdir: @root)
raise "Failed to create git worktree. Make sure you have at least one commit in the repository."
end
# Create helper for the worktree
worktree_helper = self.class.new(worktree_path)
# Build gem directly into the target pkg directory
output_path = worktree_helper.build_gem(root: original_pkg_path, signing_key: signing_key)
output_path
ensure
# Clean up the worktree
system("git", "worktree", "remove", worktree_path, "--force", chdir: @root)
end
end
def create_release_branch(version_path, message: "Bump version.")
Create a release branch, add the version file, and commit the changes.
Signature
-
parameter
version_path
String
The path to the version file that was updated.
-
parameter
message
String
The commit message to use.
-
returns
String
The name of the created branch.
Implementation
def create_release_branch(version_path, message: "Bump version.")
branch_name = "release-v#{@gemspec.version}"
system("git", "checkout", "-b", branch_name, chdir: @root)
system("git", "add", version_path, chdir: @root)
system("git", "commit", "-m", message, chdir: @root)
return branch_name
end
def commit_version_changes(message: "Bump version.")
Commit version changes to the current branch.
Signature
-
parameter
message
String
The commit message to use.
Implementation
def commit_version_changes(message: "Bump version.")
system("git", "add", "--all", chdir: @root)
system("git", "commit", "-m", message, chdir: @root)
end
def create_release_tag(tag: true, version:)
Fetch remote tags and create a release tag for the specified version.
Signature
-
parameter
tag
Boolean
Whether to tag the release.
-
parameter
version
String
The version to tag.
-
returns
String | Nil
The tag name if created, nil otherwise.
Implementation
def create_release_tag(tag: true, version:)
tag_name = nil
if tag
tag_name = "v#{version}"
system("git", "fetch", "--all", "--tags", chdir: @root)
system("git", "tag", tag_name, chdir: @root)
end
return tag_name
end
def delete_git_tag(tag_name)
Delete a git tag.
Signature
-
parameter
tag_name
String
The name of the tag to delete.
Implementation
def delete_git_tag(tag_name)
system("git", "tag", "--delete", tag_name, chdir: @root)
end
def push_release(current_branch: nil)
Push changes and tags to the remote repository.
Signature
-
parameter
current_branch
String | Nil
The current branch name, or nil if not on a branch.
Implementation
def push_release(current_branch: nil)
# If we are on a branch, push, otherwise just push the tags (assuming shallow checkout):
if current_branch
system("git", "push", chdir: @root)
end
system("git", "push", "--tags", chdir: @root)
end
def current_branch
Figure out if there is a current branch, if not, return nil
.
Signature
-
returns
String | Nil
The current branch name, or nil if not on a branch.
Implementation
def current_branch
# We originally used this but it is not supported by older versions of git.
# readlines("git", "branch", "--show-current").first&.chomp
readlines("git", "symbolic-ref", "--short", "--quiet", "HEAD", chdir: @root).first&.chomp
rescue CommandExecutionError
nil
end
def find_gemspec(glob = "*.gemspec")
Find a gemspec file in the root directory.
Signature
-
parameter
glob
String
The glob pattern to use for finding gemspec files.
-
returns
Gem::Specification | Nil
The loaded gemspec, or nil if none found.
-
raises
RuntimeError
If multiple gemspec files are found.
Implementation
def find_gemspec(glob = "*.gemspec")
paths = Dir.glob(glob, base: @root).sort
if paths.size > 1
raise "Multiple gemspecs found: #{paths}, please specify one!"
end
if path = paths.first
return ::Gem::Specification.load(File.expand_path(path, @root))
end
end