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
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
# Release Champion

A GitHub Action that automates semantic versioning and releases
using <a href="https://www.conventionalcommits.org/">conventional commits</a>. Label a PR, merge it, and the action
handles the
rest — version bumps, changelogs, tags, and GitHub releases.
using <a href="https://www.conventionalcommits.org/">conventional commits</a>. Merge a PR and the action
handles the rest — version bumps, changelogs, tags, and GitHub releases. Optionally gate releases behind a label.

## Quick start

1. Add the workflow to your repo (see [Usage](#usage) below)
2. Create a label called `change-release` in your repo (or configure a custom label via the `release-label` input)
3. When you're ready to release, add the `change-release` label to a PR
4. Merge the PR — the action creates a release PR with the version bump and changelog
5. Review and merge the release PR — the action creates the tag and GitHub release
2. Merge a PR — the action creates a release PR with the version bump and changelog
3. Review and merge the release PR — the action creates the tag and GitHub release

That's it. The label is what tells the action "this merge should trigger a release."
By default, every merged PR triggers a release. If you'd rather control when releases happen, set `require-release-label: true` and add the configured label (default: `change-release`) to PRs that should trigger a release.

## How it works

The action operates in two phases, both triggered by `pull_request` `closed` events:

### Phase 1: Create a release PR

When a PR with the release label (default: `change-release`) is merged:
When a PR is merged (and the release label requirement is met, if enabled):

1. Finds the latest version tag
2. Analyzes all commits since that tag using conventional commit prefixes
Expand Down Expand Up @@ -69,15 +66,16 @@ jobs:

## Inputs

| Input | Description | Required | Default |
|------------------|-------------------------------------------------------|----------|------------------|
| `github-token` | GitHub token for creating PRs, tags, and releases | Yes | — |
| `release-label` | Label that marks a PR for release | No | `change-release` |
| `version-prefix` | Prefix for version tags (e.g., `v` produces `v1.2.3`) | No | `v` |
| `create-tag` | Create a git tag when the release PR is merged | No | `true` |
| `create-release` | Create a GitHub release when the release PR is merged | No | `true` |
| `draft-release` | Create the GitHub release as a draft (unpublished) | No | `true` |
| `changelog` | Generate or update `CHANGELOG.md` in the release PR | No | `true` |
| Input | Description | Required | Default |
|-------------------------|--------------------------------------------------------------------------------------------------|----------|------------------|
| `github-token` | GitHub token for creating PRs, tags, and releases | Yes | — |
| `release-label` | Label that marks a PR for release (only used when `require-release-label` is `true`) | No | `change-release` |
| `require-release-label` | Require the release label to trigger a release PR. When `false`, every merged PR triggers a release | No | `false` |
| `version-prefix` | Prefix for version tags (e.g., `v` produces `v1.2.3`) | No | `v` |
| `create-tag` | Create a git tag when the release PR is merged | No | `true` |
| `create-release` | Create a GitHub release when the release PR is merged | No | `true` |
| `draft-release` | Create the GitHub release as a draft (unpublished) | No | `true` |
| `changelog` | Generate or update `CHANGELOG.md` in the release PR | No | `true` |

## Outputs

Expand Down Expand Up @@ -145,12 +143,22 @@ Commits that can't be linked to a PR will reference the commit SHA instead.
changelog: false
```

### Require a label to release

```yaml
- uses: offload-project/release-champion@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
require-release-label: true
```

### Custom label and no version prefix

```yaml
- uses: offload-project/release-champion@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
require-release-label: true
release-label: 'ready-to-release'
version-prefix: ''
```
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ inputs:
description: 'Label that marks a PR for release'
required: false
default: 'change-release'
require-release-label:
description: 'Require the release label to trigger a release PR. When false, every merged PR triggers a release.'
required: false
default: 'false'
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require-release-label defaults to false, which changes the action’s default behavior from “only labeled PRs create a release PR” to “every merged PR creates a release PR”. To avoid a breaking change for existing users on @v1, consider defaulting this input to true (preserve prior behavior) and let users opt out, or publish this under a new major version and call out the breaking change prominently.

Suggested change
default: 'false'
default: 'true'

Copilot uses AI. Check for mistakes.
version-prefix:
description: 'Prefix for version tags (e.g., "v" produces "v1.2.3")'
required: false
Expand Down Expand Up @@ -56,6 +60,7 @@ runs:
env:
GH_TOKEN: ${{ inputs.github-token }}
INPUT_RELEASE_LABEL: ${{ inputs.release-label }}
INPUT_REQUIRE_RELEASE_LABEL: ${{ inputs.require-release-label }}
INPUT_VERSION_PREFIX: ${{ inputs.version-prefix }}
INPUT_CREATE_TAG: ${{ inputs.create-tag }}
INPUT_CREATE_RELEASE: ${{ inputs.create-release }}
Expand Down
5 changes: 5 additions & 0 deletions scripts/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ if [[ "$PR_HEAD_REF" == release/* ]]; then
echo "Detected release PR merge (branch: ${PR_HEAD_REF})"
bash "$SCRIPT_DIR/finalize-release.sh"

elif [[ "${INPUT_REQUIRE_RELEASE_LABEL:-false}" != "true" ]]; then
# Phase 1: Label not required — every merged PR triggers a release
echo "Release label not required — creating release PR"
bash "$SCRIPT_DIR/create-release-pr.sh"
Comment on lines +20 to +23
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new require-release-label branching changes the phase-detection logic but there are no automated tests covering scripts/main.sh behavior. Since the repo already uses Bats, consider adding a small Bats test suite for main.sh to assert the action triggers/releases correctly when require-release-label is true vs false and when the PR is a release/* branch.

Copilot uses AI. Check for mistakes.

elif echo "$PR_LABELS" | tr ',' '\n' | grep -qx "$INPUT_RELEASE_LABEL"; then
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Label matching uses grep -qx, which treats INPUT_RELEASE_LABEL as a regex. If a label name contains regex metacharacters, this can produce incorrect matches. Use fixed-string matching (e.g., grep -F) so the label is matched literally.

Suggested change
elif echo "$PR_LABELS" | tr ',' '\n' | grep -qx "$INPUT_RELEASE_LABEL"; then
elif echo "$PR_LABELS" | tr ',' '\n' | grep -Fqx "$INPUT_RELEASE_LABEL"; then

Copilot uses AI. Check for mistakes.
# Phase 1: Labeled PR was merged → create release PR
echo "Detected labeled PR merge (label: ${INPUT_RELEASE_LABEL})"
Expand Down
Loading