Automating Semantic Versioning Releases - DotNet
This process was developed after the React/NodeJS and Python/Streamlit processes.
Use the Automation Scripts when Making a Release
You are "making a release" when you are at a good point to merge code from the
dev branch into master/main.
These merges should always be done via Pull Requests
When you're ready to make a release, follow these steps:
Commit and push all of the relevant code to the
devbranch.Decide what kind of version change this is: Major (large changes, like language version upgrades or version-breaking package upgrades), Minor (new features, non-breaking upgrades), Patch (changes, bug fixes, etc.)
Run the version bump script with the following command (specifying major/minor/patch):
./scripts/bump_version.cmd patchThis will:
- Update the version in all changed
.csprojfiles - Create a commit with the version bump
- Create a git tag for the new version
- Push the changes and tag to the remote repository
- Update the version in all changed
Go to GitHub and create a new Pull Request:
- Set the base branch to
master - Set the compare branch to
dev - Use a title like "Release vX.Y.Z" (where X.Y.Z is the new version)
- In the description, list the major changes in this release
- Request a review from a team member
- Set the base branch to
After the PR is accepted and merged, you (or the reviewer) can run the post-merge script with the command:
./scripts/post_merge.cmdThis will:
- Switch to the master branch
- Pull the latest changes
- Push the version tag to the remote repository
Add Automation to a new Dotnet Project
These instructions assume your project is starting at version 0.0.1. If you're starting at a different version number, adapt these instructions appropriately.
Ensure your solution's main project
.csprojfile has a version property. For example:<PropertyGroup>
<Version>0.0.1</Version>
</PropertyGroup>Under the repository's root directory, add a
scriptsfolder if one doesn't exist.Create a new file under the
scriptsdirectory calledbump_version.cmdwith the following content:@echo off
setlocal enabledelayedexpansion
REM =============================================
REM Input Validation
REM =============================================
REM Check if a version bump type is provided
if "%1"=="" (
echo Error: Please specify the version bump type: major, minor, or patch.
exit /b 1
)
REM Validate the version bump type
if not "%1"=="major" if not "%1"=="minor" if not "%1"=="patch" (
echo Error: Invalid version bump type. Use 'major', 'minor', or 'patch'.
exit /b 1
)
REM =============================================
REM Get Current Version
REM =============================================
REM Read the current version from the main project's .csproj file
for /f "tokens=*" %%a in ('powershell -Command "$csproj = Get-ChildItem -Path . -Filter *.csproj -Recurse -File | Select-Object -First 1; if ($csproj) { (Get-Content $csproj.FullName) -match '<Version>.*</Version>' | ForEach-Object { $_ -replace '<Version>|</Version>', '' } } else { Write-Error 'No .csproj file found in current directory or subdirectories' }"') do set CURRENT_VERSION=%%a
REM Verify we found a version
if "%CURRENT_VERSION%"=="" (
echo Error: Could not find version in .csproj file.
echo Please ensure you have a Version property in your .csproj file like this:
echo ^<PropertyGroup^>
echo ^<Version^>0.1.0^</Version^>
echo ^</PropertyGroup^>
exit /b 1
)
REM =============================================
REM Version Increment Logic
REM =============================================
REM Split the current version into its components (major.minor.patch)
for /f "tokens=1,2,3 delims=." %%a in ("%CURRENT_VERSION%") do (
set MAJOR=%%a
set MINOR=%%b
set PATCH=%%c
)
REM Increment the appropriate version component based on the bump type
if "%1"=="major" (
REM Major version bump: increment major, reset minor and patch
set /a MAJOR+=1
set MINOR=0
set PATCH=0
) else if "%1"=="minor" (
REM Minor version bump: increment minor, reset patch
set /a MINOR+=1
set PATCH=0
) else if "%1"=="patch" (
REM Patch version bump: increment patch only
set /a PATCH+=1
)
REM Construct the new version string
set NEW_VERSION=%MAJOR%.%MINOR%.%PATCH%
echo Bumping version from %CURRENT_VERSION% to %NEW_VERSION%
REM =============================================
REM Version Update Process
REM =============================================
REM Get the last version tag to check for changes
for /f "tokens=*" %%a in ('git describe --tags --abbrev=0 2^>nul') do set LAST_TAG=%%a
if "%LAST_TAG%"=="" (
REM If no previous tag exists, update all projects
echo No previous version tag found. Updating all project versions.
for /r %%f in (*.csproj) do (
echo Updating version in %%~nxf
powershell -Command "$file = '%%f'; Write-Host 'Processing file: ' $file; $content = Get-Content $file -Raw; Write-Host 'Current content contains version: ' ($content -match '<Version>.*?</Version>'); $newContent = $content -replace '<Version>.*?</Version>', '<Version>%NEW_VERSION%</Version>'; Write-Host 'New content contains version: ' ($newContent -match '<Version>.*?</Version>'); Set-Content -Path $file -Value $newContent -NoNewline"
)
) else (
REM Process each .csproj file in the solution
for /r %%f in (*.csproj) do (
REM Get the project's directory path
for %%d in ("%%~dpf.") do set PROJECT_DIR=%%~fd
REM Check if any files in this project have changed since the last tag
git diff --name-only %LAST_TAG% %PROJECT_DIR% > nul 2>&1
if !ERRORLEVEL! equ 0 (
REM Project has changes - update its version
echo Updating version in %%~nxf
powershell -Command "$file = '%%f'; Write-Host 'Processing file: ' $file; $content = Get-Content $file -Raw; Write-Host 'Current content contains version: ' ($content -match '<Version>.*?</Version>'); $newContent = $content -replace '<Version>.*?</Version>', '<Version>%NEW_VERSION%</Version>'; Write-Host 'New content contains version: ' ($newContent -match '<Version>.*?</Version>'); Set-Content -Path $file -Value $newContent -NoNewline"
) else (
REM Project unchanged - skip version update
echo Skipping unchanged project: %%~nxf
)
)
)
REM =============================================
REM Git Operations
REM =============================================
REM Stage all modified files
git add .
REM Create a commit with the version bump
git commit -m "chore(release): %NEW_VERSION%"
REM Create a new version tag
git tag v%NEW_VERSION%
REM Push changes and tags to remote
git push --follow-tags
echo Version bumped to %NEW_VERSION%.Create a new file under the
scriptsdirectory calledpost_merge.cmdwith the following content:@echo off
setlocal enabledelayedexpansion
REM =============================================
REM Git Operations - Current Branch
REM =============================================
REM Pull the latest changes from the current branch
git pull
if %ERRORLEVEL% neq 0 (
echo Error pulling the latest changes.
goto :cleanup
)
REM =============================================
REM Switch to Master Branch
REM =============================================
REM Switch to the master branch
git checkout master
if %ERRORLEVEL% neq 0 (
echo Error switching to master branch.
goto :cleanup
)
REM =============================================
REM Git Operations - Master Branch
REM =============================================
REM Pull the latest changes from master
git pull
if %ERRORLEVEL% neq 0 (
echo Error pulling the latest changes from master.
goto :cleanup
)
REM =============================================
REM Version Tag Operations
REM =============================================
REM Get the current version from the master project's .csproj file
for /f "tokens=*" %%a in ('powershell -Command "$csproj = Get-ChildItem -Path .\EpanetConsoleCore -Filter *.csproj -File | Select-Object -First 1; if ($csproj) { (Get-Content $csproj.FullName) -match '<Version>.*</Version>' | ForEach-Object { $_ -replace '<Version>|</Version>', '' } } else { Write-Error 'No .csproj file found in EpanetConsoleCore directory' }"') do set NEW_VERSION=%%a
REM Verify we found a version
if "%NEW_VERSION%"=="" (
echo Error reading version from .csproj file.
echo Please ensure you have a Version property in your .csproj file like this:
echo ^<PropertyGroup^>
echo ^<Version^>0.1.0^</Version^>
echo ^</PropertyGroup^>
goto :cleanup
)
REM Push the version tag to the remote repository
git push origin v%NEW_VERSION%
if %ERRORLEVEL% neq 0 (
echo Error pushing the tag v%NEW_VERSION%.
goto :cleanup
)
echo Successfully pushed tag v%NEW_VERSION% to origin.
:cleanup
REM Always switch back to dev branch
git checkout dev
if %ERRORLEVEL% neq 0 (
echo Warning: Failed to switch back to dev branch.
exit /b 1
)
exit /b 0