This document describes the simplified and automated release process for our Open Source packages.
Our release process is designed to achieve the following:
- Automation: Automate as much as possible to reduce human error. The act of releasing should be a routine, low-risk event.
- Simplicity: The process should be simple enough for any team member to perform a standard release with confidence.
- Consistency: The process is the same for all packages, ensuring predictable and reliable releases.
- Clarity: Maintain a clear and useful
CHANGELOG.md
for our users, following the Keep a Changelog standard. - Adherence to Standards: Strictly follow Semantic Versioning (SemVer) to communicate the impact of our changes.
- The
main
branch is the source of truth. It always contains the code for the next potential release and should always be in a stable state. - Developers interact with the GitHub UI, not complex Git commands. Releases are triggered via a GitHub Actions workflow, not from a local machine.
- The
CHANGELOG.md
is our contract. Every user-facing change must be accompanied by an entry in the changelog. - Releases are marked by tags, not branches. The
main
branch is the only long-lived branch. All other branches (feature/*
,release/*
,hotfix/*
) are temporary and must be deleted after their purpose is served. - Main branch is protected - all merges must be via pull request. Only the GitHub release-bot may merge directly for changelog updates.
To achieve these goals, we will use the following tools and standards:
- Semantic Versioning
- Keep a Changelog
- Three manual-trigger GitHub Actions powered by standard command-line tools and actions:
Create Stable Release
: For production releases from main branchCreate Pre-Release
: For alpha/beta/rc releases from main branchCreate Branch Release
: For releases from release/* and hotfix/* branches
This entire automated process relies on one simple rule for every developer.
Every Pull Request (PR) that introduces a user-facing change MUST include an update to the
[Unreleased]
section of theCHANGELOG.md
file.
PR reviewers are responsible for enforcing this.
Example CHANGELOG.md
on the main
branch:
# Changelog
...
## [Unreleased]
### Added
- Feature to export user data to JSON.
- New CancellationToken parameter to `ProcessAsync` method.
### Fixed
- Resolved a null reference exception when the input string is empty.
CHANGELOG Integrity Rules
- CHANGELOG.md must only be manually edited under the [Unreleased] section.
- During merge conflicts, only resolve conflicts without adding or removing entries.
There are three release scenarios. The first path will cover the vast majority of all releases, while the other two handle more advanced, less frequent scenarios.
Use this path when you want to release all the new features and fixes that have been merged into the main
branch. This process is the same for stable (1.2.3
), release candidate (-rc.0
), beta, and alpha releases.
- Go to your repository's "Actions" tab.
- Select the appropriate workflow:
- For stable releases: Select "Create Stable Release"
- For pre-releases (alpha/beta/rc): Select "Create Pre-Release"
- Click the "Run workflow" button. Make sure the
main
branch is selected. - Fill out the inputs:
- For "Create Stable Release":
level
: Choosepatch
,minor
, ormajor
based on the changes in the "Unreleased" section.- Important: If the latest Git tag is a pre-release (e.g.,
v1.0.0-alpha.3
), all level options will result in stabilizing to the base version (v1.0.0
). This is the intended behavior for transitioning from pre-release to stable.
- Important: If the latest Git tag is a pre-release (e.g.,
- For "Create Pre-Release":
type
: Choosealpha
,beta
, orrc
for a pre-release.action
: Choosecontinue
to increment the current pre-release,transition
to switch to a different pre-release type (e.g., from alpha.1 to beta.0), ornew
to start a new pre-release from stable.level
: Required whenaction
isnew
- choosepatch
,minor
, ormajor
. Ignored fortransition
.
- For "Create Stable Release":
- Click "Run workflow".
That's it. The automation handles everything else:
- It calculates the new version number based on your input.
- It reads the contents of the
[Unreleased]
section to use for the release notes. - For
stable
releases only:- It updates
CHANGELOG.md
: The[Unreleased]
content is moved under a new version heading (e.g.,[1.3.0] - 2023-10-27
). - It creates a new, empty
[Unreleased]
section. - It commits this updated file back to the
main
branch.
- It updates
- It creates a new Git tag (e.g.,
v1.3.0
orv1.3.0-rc.0
). - It creates a formal GitHub Release, using the changelog content for the release notes.
- It builds, packages, and publishes the new version to NuGet.
Use this path when you need to prepare a major or minor release (e.g., 2.0.0
) while allowing new, unrelated feature development to continue on the main
branch. This involves creating a temporary release/*
branch to act as a feature-frozen staging area.
-
Create a Release Branch from
main
.- Once all features for the upcoming release are on
main
, create the stabilization branch.
# On your local machine, ensure main is up-to-date git checkout main git pull # Create the release branch and push it git checkout -b release/v2.0.0 git push --set-upstream origin release/v2.0.0
From this point, the
release/v2.0.0
branch is feature-frozen. Only fixes for this specific release are allowed.main
is now free to accept features for the next version (e.g.,2.1.0
).Critical: The release branch must be preserved until the stable release is complete. Do not delete the release branch before producing the final stable release, as this could make it impossible to continue isolated stabilization work.
Note: The 'Create Branch Release' workflow automatically detects whether it's running from a
release/*
orhotfix/*
branch and adjusts version detection accordingly.- Publish Pre-Releases (e.g., RCs) from the Release Branch.
- Go to Actions, select the "Create Branch Release" workflow.
- Crucially, use the "Branch" dropdown to select your
release/v2.0.0
branch. - For the inputs, set the
type
toalpha
,beta
, orrc
. The version will be automatically parsed from the branch name and the appropriate suffix appended (e.g.,2.0.0-rc.0
).
- Once all features for the upcoming release are on
-
Apply Bug Fixes to the Release Branch.
-
If a bug is found during testing, commit the fix to the
release/v2.0.0
branch first. -
Note: Bug fixes can be cherry-picked into
main
at any point during the stabilization process, but the release branch itself cannot be merged intomain
until after the stable release is complete.
-
-
Publish the Final Stable Release.
- Run the "Create Branch Release" workflow one last time from the
release/v2.0.0
branch. - Set the
type
tostable
. The version will be automatically parsed from the branch name.
- Run the "Create Branch Release" workflow one last time from the
-
Merge Release Branch Back to
main
.- Create a pull request from the
release/v2.0.0
branch tomain
and merge it to ensure all fixes are included in future development. - Resolve any CHANGELOG.md merge conflicts carefully, preserving both release history and ongoing development entries.
- Create a pull request from the
-
Clean up. The
release/v2.0.0
branch can now be safely deleted.
Use this path ONLY when you need to patch an older stable or pre-release version without including all the new features from main
. For example, fixing a critical bug in v1.2.3
when main
is already on its way to v2.0.0
.
This process involves a few manual Git commands because it is an exceptional event that requires deliberate, careful action.
-
Create a Hotfix Branch from the Old Version's Tag.
# On your local machine, check out the specific tag you need to patch git checkout -b hotfix/v1.2.3 v1.2.3
The version number is automatically detected from the latest Git tag in the branch history using release-it. The branch name must start with the
hotfix/
prefix.Note: The 'Create Branch Release' workflow automatically detects whether it's running from a
release/*
orhotfix/*
branch and adjusts version detection accordingly.- Apply the Fix. There are two ways to do this:
- A) The fix is already on
main
: Cherry-pick the specific commit.git cherry-pick <commit-hash-of-the-fix-from-main>
- B) The fix is new: Make the code changes directly on this hotfix branch and commit them.
-
Update the Changelog. On the
hotfix/v1.2.3
branch, ensure the fix is documented in the[Unreleased]
section ofCHANGELOG.md
. The workflow will automatically move it to a new version heading. -
Push the Hotfix Branch.
git push --set-upstream origin hotfix/v1.2.3
-
Run the Release Workflow from the Hotfix Branch.
- Go to the "Actions" tab and select the "Create Branch Release" workflow.
- Crucially, use the "Branch" dropdown to select your
hotfix/v1.2.3
branch. - For the inputs, set the
type
tostable
. The version will be automatically calculated from the latest Git tag in the branch history.
-
Merge the Hotfix Back into
main
. This is a critical final step to ensure the fix is not lost in future releases.- Since
main
is protected, create a pull request (PR) from thehotfix/v1.2.3
branch tomain
and merge it through the GitHub UI. - You may need to resolve a small merge conflict in
CHANGELOG.md
. This is expected. Simply ensure the fix is noted in the[Unreleased]
section ofmain
's changelog. During merge conflict resolution, do not add or remove entries; only resolve the conflict to maintain integrity.
- Since
-
Clean up. The
hotfix/v1.2.3
branch can now be safely deleted from GitHub and your local machine.
All workflows use release-it with the keep-a-changelog plugin to automate versioning, changelog management, and GitHub releases.
- Pre-releases use standard SemVer pre-release identifiers (alpha, beta, rc)
- The
action
parameter controls how the pre-release version is calculated:continue
: Increments the current pre-release version (e.g., 1.0.0-alpha.1 → 1.0.0-alpha.2)transition
: Switches to a different pre-release type while maintaining the base version (e.g., 1.0.0-alpha.1 → 1.0.0-beta.0)new
: Starts a new pre-release from the stable version, requiring alevel
parameter to determine the base version increment
- For stable releases, the
[Unreleased]
section is moved to a new version heading with the current date - A new empty
[Unreleased]
section is created and committed back to the main branch - This ensures the changelog remains current for future development
- Stable and Pre-Release workflows: Version detection based on existing Git tags and the specified level/action parameters
- Branch releases:
- For
release/*
branches: Automatic detection from branch name (e.g.,release/v2.0.0
→2.0.0
) - For
hotfix/*
branches: Automatic detection from Git tag history using release-it's built-in logic
- For
- Pre-releases: Complex logic combining action type, existing tags, and user inputs to determine the appropriate version number
To create the very first release of a package (e.g., 0.1.0
):
- Ensure your
CHANGELOG.md
file is created and has an[Unreleased]
section with an entry like "Initial release 🎉". - Follow Path 1: The Standard Release.
- Select the "Create Stable Release" workflow.
- Set the
level
input tominor
. This will create the0.1.0
release as the first version.