A powerful GitHub Action that automates semantic versioning and release creation based on PR labels. No more manual version bumps or release notes!
- π·οΈ Label-based releases - Control versions with simple PR labels
- π¦ Semantic versioning - Automatic major/minor/patch version calculation
- π Protected-branch friendly - Validate
package.jsonin PRs and release on merge without pushing back tomain - π Optional PR preparation - Update
package.json, run checks, and push generated changes back to the PR branch before merge - π Prerelease support - Create beta/alpha/rc releases
- π Major version tracking - Automatic v1, v2, etc. release management
- π Auto-generated notes - Release notes from commit history
- π οΈ Multi-language support - Works with Node.js, Python, Go, and more
- β‘ Zero configuration - Works out of the box with sensible defaults
Create .github/workflows/release.yml:
name: Release
on:
pull_request:
types: [closed]
branches: [main]
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}For actions that need major version tags (v1, v2, etc.):
name: Release
on:
pull_request:
types: [closed]
branches: [main]
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
base_release: truemajor- Breaking changes (1.0.0 β 2.0.0)minor- New features (1.0.0 β 1.1.0)patch- Bug fixes (1.0.0 β 1.0.1)prerelease- Beta versions (1.0.0 β 1.1.0-beta.1)
When you merge a PR with labels, the action automatically:
- Calculates the new version
- Updates
package.jsonif configured to do so - Runs tests and builds your project
- Creates a git tag and GitHub release
- Generates release notes from commits
- With
base_release: true, updates major version tags (v1,v2, etc.) to the latest release without creating a second major-tag release entry
If main is protected, the safest pattern is:
- Validate the expected version on every PR.
- Require
package.jsonto already contain that version before merge. - Create the tag and GitHub release after merge without pushing a new commit back to
main.
This avoids branch-protection conflicts and prevents accidental double bumps. The version is always recalculated from the latest real tag plus the current PR labels, so changing labels ten times on the same PR does not increment ten times.
If you want a read-only PR check that calculates the planned version without changing the branch, use explicit validate mode.
name: Version Check
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
branches: [main]
jobs:
version-check:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: validate
package-json-mode: verifyname: Release
on:
pull_request:
types: [closed]
branches: [main]
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: release
package-json-mode: verify
commit-changes: false
base_release: trueWith this setup, the merged package.json version must already match the tag that gets created by the release job.
If you want the merged PR flow to create only the semantic version tag, the GitHub release, and the synced major version tag when base_release: true, use release-only. This mode skips package.json handling, install, test, build, and branch commit/push steps.
name: Release
on:
pull_request:
types: [closed]
branches: [main]
jobs:
release:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: release-only
base_release: trueFor same-repository pull requests, execution-mode: auto-detect now defaults to prepare. This mode updates files, runs install/test/build, commits the results, and pushes them back to the PR branch without creating a tag or release. Before pushing, the action fetches and rebases onto the latest PR branch tip so it is less likely to fail when another update lands mid-run. Fork PRs automatically fall back to validate because the action cannot push back to fork branches.
name: Prepare Release PR
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
branches: [main]
jobs:
prepare:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: prepare
package-json-mode: update
commit-changes: trueThis workflow is intended for same-repository PR branches. Fork PRs cannot be pushed back to by the action.
- uses: dnogu/semantic-release-action@v1
with:
# Required
github-token: ${{ secrets.GITHUB_TOKEN }}
# Version labels (customize as needed)
major-label: 'major'
minor-label: 'minor'
patch-label: 'patch'
prerelease-label: 'prerelease'
# Prerelease configuration
prerelease-suffix: 'beta' # beta, alpha, rc
prerelease-number: '1'
# Build configuration
node-version: '24'
package-manager: 'npm' # npm, yarn, pnpm
working-directory: '.'
# Custom commands (auto-detected if not specified)
install-command: 'npm ci'
test-command: 'npm test'
build-command: 'npm run build'
# Release options
create-major-release: true # Create full version releases (v1.2.3)
base_release: true # Update major version tags (v1, v2, etc.) to point to the latest stable release
copy-assets: true
auto-generate-notes: true
# Package.json handling
update-package-json: true # legacy toggle
package-json-mode: 'update' # update, verify, ignore
package-json-path: 'package.json'
# Git configuration
git-user-name: 'github-actions[bot]'
git-user-email: 'github-actions[bot]@users.noreply.github.com'
# Release execution
commit-changes: true # also controls branch commits in prepare mode
execution-mode: 'auto-detect' # prepare for same-repo PRs, validate for fork PRs, release after merge; or set release-only explicitlyThe action provides useful outputs for downstream jobs:
- uses: dnogu/semantic-release-action@v1
id: release
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Use release info
run: |
echo "Released: ${{ steps.release.outputs.released }}"
echo "Version: ${{ steps.release.outputs.version }}"
echo "Release URL: ${{ steps.release.outputs.release-url }}"| Output | Description | Example |
|---|---|---|
released |
Whether a release was created | true |
version |
The new version number | v1.2.3 |
previous-version |
The previous version | v1.2.2 |
release-type |
Type of release | minor |
is-prerelease |
Whether this is a prerelease | false |
release-url |
URL of the created release | https://github.com/... |
release-id |
ID of the created release | 12345 |
major-version |
Major version tag | v1 |
major-release-url |
URL of the release currently targeted by the major version tag | https://github.com/... |
tag-name |
Git tag that was created | v1.2.3 |
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
package-manager: 'npm'
build-command: 'npm run build'- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
package-json-mode: ignore
test-command: 'python -m pytest'
build-command: 'python setup.py build'- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
major-label: 'breaking'
minor-label: 'feature'
patch-label: 'bugfix'
prerelease-label: 'beta'name: Manual Release
on:
workflow_dispatch:
inputs:
release-type:
type: choice
options: [major, minor, patch]
is-prerelease:
type: boolean
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
release-type: ${{ github.event.inputs.release-type }}
is-prerelease: ${{ github.event.inputs.is-prerelease }}- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: release
package-json-mode: verify
commit-changes: false- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: release-only
base_release: true- uses: dnogu/semantic-release-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
execution-mode: prepare
package-json-mode: update
commit-changes: trueThe action automatically creates and maintains major version tags:
- When you release
v1.2.3, it updatesv1to point at that release commit - Any existing
v1release entry is removed so there is no duplicate major-tag release - Great for action consumers who want
uses: your-action@v1 - Prerelease versions don't update major version tags
The action intelligently detects your project type and commands:
- Package Manager: Detects from lock files (
package-lock.json,yarn.lock,pnpm-lock.yaml) - Test Command: Looks for a
testscript inpackage.json - Build Command: Looks for a
buildscript inpackage.json
- β Graceful failure if no labels found
- β Detailed logging for debugging
- β Validates version format and git state
- β
Warns instead of failing when
package.jsonverification is requested but nopackage.jsonexists
We welcome contributions! Please see our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.
Inspired by the need for simple, powerful release automation in the GitHub Actions ecosystem.
Made by dnogu
If this action helped you, please consider giving it a β star!