Skip to content
16 changes: 14 additions & 2 deletions bake/gem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,22 @@ def files
@helper.gemspec.files
end

# Inspect the gem name, version, and version file in the current checkout.
def metadata
gemspec = @helper.gemspec
raise "No gemspec found." unless gemspec

return {name: gemspec.name, version: gemspec.version.to_s, version_path: @helper.version_path}
end

# Build the gem into the pkg directory.
# @parameter root [String] The root directory to build the gem into. Defaults to `pkg`.
# @parameter signing_key [Boolean] Whether to use a signing key.
# @parameter signing_key [String | Nil] A signing key path, "true" to require signing, or "false" to disable signing.
def build(root: "pkg", signing_key: nil)
# Accept boolean command line options while preserving signing key paths:
signing_key = true if signing_key == "true"
signing_key = false if signing_key == "false"

@helper.build_gem(root: root, signing_key: signing_key)
end

Expand Down Expand Up @@ -70,7 +82,7 @@ def release(tag: true)
raise
end

@helper.push_release(current_branch: current_branch)
@helper.push_release(current_branch: current_branch, tag: tag_name)
context["after_gem_release"]&.call(name: @helper.gemspec.name, version: version, tag: tag_name, path: path)

return {
Expand Down
10 changes: 10 additions & 0 deletions bake/gem/release.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,13 @@ def major(tag: true)
release_task = context.lookup("gem:release")
release_task.call(tag: tag)
end

# Regenerate release content from the base commit and compare it with the candidate.
# @parameter base [String] The current target commit (first parent when publishing).
# @parameter candidate [String] The proposed or merged release commit.
# @parameter optional [Boolean] Accept ordinary PRs with no version change.
def validate(base:, candidate: "HEAD", optional: false)
require_relative "../../lib/bake/gem/helper"
require_relative "../../lib/bake/gem/release"
Bake::Gem::Release.new(context.root).validate(base: base, candidate: candidate, optional: optional)
end
28 changes: 11 additions & 17 deletions bake/gem/release/branch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,15 @@ def major
def commit(bump, message: "Bump version.")
release = context.lookup("gem:release")
helper = release.instance.helper
gemspec = helper.gemspec

# helper.guard_clean

version_path = context.lookup("gem:release:version:increment").call(bump, message: message)

if version_path
branch_name = helper.create_release_branch(version_path, message: message)
else
raise "Could not find version number!"
end

return {
version: gemspec.version,
version_path: version_path,
branch: branch_name,
}
helper.guard_clean
helper.guard_last_commit_not_version_bump
path = helper.version_path or raise "Could not find version file!"
line = File.read(File.expand_path(path, helper.root))
version = nil
Bake::Gem::Version.update_version(line){|current| version = current.increment(bump)}
raise "Could not find version number!" unless version
branch_name = helper.create_release_branch(version: version.join)
result = context.lookup("gem:release:version:increment").call(bump, message: message)
helper.commit_version_changes(message: message)
return result.merge(branch: branch_name)
end
21 changes: 7 additions & 14 deletions bake/gem/release/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@ def increment(bump, message: "Bump version.")
helper = release.instance.helper
gemspec = helper.gemspec

helper.update_version(bump) do |version|
version_path = helper.update_version(bump) do |version|
Console.info(self, "Updated version:", version: version)

# Ensure that any subsequent tasks use the correct version!
gemspec.version = version.join

after_increment(version)
end
raise "Could not find version number!" unless version_path
helper.guard_release_changes

return {
version: gemspec.version,
version_path: helper.version_path,
version_path: version_path,
}
end

Expand All @@ -52,18 +54,9 @@ def commit(bump, message: "Bump version.")

helper.guard_clean

version_path = increment(bump, message: message)

if version_path
helper.commit_version_changes(message: message)
else
raise "Could not find version number!"
end

return {
version: helper.gemspec.version,
version_path: version_path,
}
result = increment(bump, message: message)
helper.commit_version_changes(message: message)
return result
end

protected
Expand Down
44 changes: 29 additions & 15 deletions context/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ end

## Usage

Run Bake tasks from the gem project's root directory. When using `Bake::Gem::Helper` directly, construct and use it with that directory as the process's working directory. Gemspec evaluation and packaging resolve relative paths there; the helper does not change the working directory.

Before using `bake-gem`, ensure you have:

1. A properly configured `gemspec` file in your project root
Expand Down Expand Up @@ -73,7 +75,7 @@ $ bake gem:release

### Automated CI/CD Pipeline

For releasing gems via automated pipelines, use a two-step process:
Use `bake-gem-github` for GitHub pull requests, native approval rules, Trusted Publishing and attestations. The provider-independent preparation tasks below work identically locally and in CI.

#### Step 1: Create Release Branch (Locally)

Expand All @@ -83,22 +85,25 @@ $ bake gem:release:branch:patch # or minor/major
```

This will:
- Create a new branch named `releases/v[new-version]`
- Require a clean checkout on a branch
- Create a new branch named `releases/v[new-version]` before modifying files
- Bump the gem version
- Commit the version change
- Push the branch to origin
- Run `after_gem_release_version_increment` and commit all changes, including added and deleted documentation

This task does not push, open a PR, create tags or publish. Select a current base before running it; the GitHub companion additionally fetches and checks the default branch. Failed hooks leave changes available for inspection.

#### Step 2: Release from CI (After Merge)

Once the release branch is merged into main:
The GitHub companion handles publishing the exact merged commit. To independently validate release content, supply the current target commit and proposed commit:

``` bash
$ export RUBYGEMS_HOST=https://rubygems.org
$ export GEM_HOST_API_KEY=your_api_key

$ bake gem:release
$ bundle exec bake gem:release:validate base=origin/main candidate=HEAD
```

Validation creates a temporary checkout of the base, applies the proposed patch/minor/major bump, runs the same hooks, and compares the complete generated tree with the candidate. It never bumps the candidate again or modifies your checkout. Stale notes and unexpected file additions/deletions fail with a diff. A rebase passes when the generated content still matches. Hooks must be repeatable for the same source and version.

For an ordinary PR check, add `optional=true` to accept candidates without a version change. After merge, use the merged commit's first parent as `base` and the merged commit as `candidate`; later changes on `main` do not affect that release boundary.

### Individual Commands

You can also run individual steps:
Expand All @@ -113,6 +118,9 @@ $ bake gem:install
# List files that will be included in the gem
$ bake gem:files

# Inspect the gem name, version, and version file as JSON
$ bake gem:metadata output format=json

# Build without signing
$ bake gem:build signing_key=false
```
Expand All @@ -127,6 +135,8 @@ The tool automatically prevents consecutive version bumps by checking the last c
### Clean Worktree Building
Gems are built in isolated git worktrees to ensure the build environment exactly matches your committed code, preventing issues with uncommitted changes affecting the build.

Worktree builds and release validation run Bake tasks in fresh Ruby processes launched with the checkout as their working directory, so version constants and hook state come from each checkout. The parent process's working directory is unchanged.

### Repository Cleanliness Check
Before any release operation, the tool ensures your repository has no uncommitted changes.

Expand All @@ -141,6 +151,12 @@ spec.signing_key = "path/to/private_key.pem"
spec.cert_chain = ["path/to/certificate.pem"]
```

To supply a signing key when building:

``` bash
$ bake gem:build signing_key=/path/to/private_key.pem
```

Or disable signing explicitly:

``` bash
Expand Down Expand Up @@ -186,11 +202,9 @@ $ bake gem:release:patch
# Create release branch
$ bake gem:release:branch:minor
# Creates branch: releases/v1.3.0
# Commits version bump
# Pushes branch
# Commits the version bump and release-hook output
# Leaves the branch local for inspection

# After code review and merge:
$ git checkout main
$ git pull
$ bake gem:release
# Validate before pushing or opening a PR:
$ bake gem:release:validate base=main
```
2 changes: 1 addition & 1 deletion gems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
end

group :test do
gem "sus"
gem "sus", "~> 0.38"
gem "covered"
gem "decode"

Expand Down
44 changes: 29 additions & 15 deletions guides/getting-started/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ end

## Usage

Run Bake tasks from the gem project's root directory. When using `Bake::Gem::Helper` directly, construct and use it with that directory as the process's working directory. Gemspec evaluation and packaging resolve relative paths there; the helper does not change the working directory.

Before using `bake-gem`, ensure you have:

1. A properly configured `gemspec` file in your project root
Expand Down Expand Up @@ -73,7 +75,7 @@ $ bake gem:release

### Automated CI/CD Pipeline

For releasing gems via automated pipelines, use a two-step process:
Use `bake-gem-github` for GitHub pull requests, native approval rules, Trusted Publishing and attestations. The provider-independent preparation tasks below work identically locally and in CI.

#### Step 1: Create Release Branch (Locally)

Expand All @@ -83,22 +85,25 @@ $ bake gem:release:branch:patch # or minor/major
```

This will:
- Create a new branch named `releases/v[new-version]`
- Require a clean checkout on a branch
- Create a new branch named `releases/v[new-version]` before modifying files
- Bump the gem version
- Commit the version change
- Push the branch to origin
- Run `after_gem_release_version_increment` and commit all changes, including added and deleted documentation

This task does not push, open a PR, create tags or publish. Select a current base before running it; the GitHub companion additionally fetches and checks the default branch. Failed hooks leave changes available for inspection.

#### Step 2: Release from CI (After Merge)

Once the release branch is merged into main:
The GitHub companion handles publishing the exact merged commit. To independently validate release content, supply the current target commit and proposed commit:

``` bash
$ export RUBYGEMS_HOST=https://rubygems.org
$ export GEM_HOST_API_KEY=your_api_key

$ bake gem:release
$ bundle exec bake gem:release:validate base=origin/main candidate=HEAD
```

Validation creates a temporary checkout of the base, applies the proposed patch/minor/major bump, runs the same hooks, and compares the complete generated tree with the candidate. It never bumps the candidate again or modifies your checkout. Stale notes and unexpected file additions/deletions fail with a diff. A rebase passes when the generated content still matches. Hooks must be repeatable for the same source and version.

For an ordinary PR check, add `optional=true` to accept candidates without a version change. After merge, use the merged commit's first parent as `base` and the merged commit as `candidate`; later changes on `main` do not affect that release boundary.

### Individual Commands

You can also run individual steps:
Expand All @@ -113,6 +118,9 @@ $ bake gem:install
# List files that will be included in the gem
$ bake gem:files

# Inspect the gem name, version, and version file as JSON
$ bake gem:metadata output format=json

# Build without signing
$ bake gem:build signing_key=false
```
Expand All @@ -127,6 +135,8 @@ The tool automatically prevents consecutive version bumps by checking the last c
### Clean Worktree Building
Gems are built in isolated git worktrees to ensure the build environment exactly matches your committed code, preventing issues with uncommitted changes affecting the build.

Worktree builds and release validation run Bake tasks in fresh Ruby processes launched with the checkout as their working directory, so version constants and hook state come from each checkout. The parent process's working directory is unchanged.

### Repository Cleanliness Check
Before any release operation, the tool ensures your repository has no uncommitted changes.

Expand All @@ -141,6 +151,12 @@ spec.signing_key = "path/to/private_key.pem"
spec.cert_chain = ["path/to/certificate.pem"]
```

To supply a signing key when building:

``` bash
$ bake gem:build signing_key=/path/to/private_key.pem
```

Or disable signing explicitly:

``` bash
Expand Down Expand Up @@ -186,11 +202,9 @@ $ bake gem:release:patch
# Create release branch
$ bake gem:release:branch:minor
# Creates branch: releases/v1.3.0
# Commits version bump
# Pushes branch
# Commits the version bump and release-hook output
# Leaves the branch local for inspection

# After code review and merge:
$ git checkout main
$ git pull
$ bake gem:release
# Validate before pushing or opening a PR:
$ bake gem:release:validate base=main
```
Loading
Loading