Semantic Versioning in Azure DevOps: Automating Version Management with semantic-release-ado
July 09, 2026
Semantic Versioning in Azure DevOps: Automating Version Management with semantic-release-ado
Introduction
Managing software versions effectively is a critical aspect of the development lifecycle. It helps teams track changes, maintain backward compatibility, and communicate the impact of updates to users. In this article, we'll explore how to implement automated semantic versioning in Azure DevOps environments using semantic-release-ado, an adaptation of the popular semantic-release library originally designed for GitHub workflows.
Understanding Semantic Versioning
Semantic versioning (SemVer) is a versioning scheme that uses a 3-part version number: MAJOR.MINOR.PATCH (e.g., 2.4.1). Each component has a specific meaning:
- MAJOR: Increased when making incompatible API changes
- MINOR: Increased when adding functionality in a backward-compatible manner
- PATCH: Increased when making backward-compatible bug fixes
This standardized approach enables developers and users to understand the impact of version changes at a glance.
The Goals of semantic-release
The semantic-release library was created to automate the version management and package publishing workflow. Its core goals include:
- Fully automated version management: No manual intervention needed to determine the next version number
- Enforcing semantic versioning: Ensuring version numbers adhere to SemVer conventions
- Communicating changes effectively: Automatically generating changelogs
- Triggering deployment processes: Seamlessly integrating with CI/CD pipelines
- Reducing maintenance overhead: Eliminating version-related boilerplate and human error
However, semantic-release was primarily designed for GitHub environments and npm packages. For teams using Azure DevOps, this presented challenges in adopting these automation benefits.
Introducing semantic-release-ado
This is where semantic-release-ado comes into play. This library extends the functionality of semantic-release to work seamlessly within Azure DevOps environments. With semantic-release-ado, teams can enjoy all the benefits of semantic-release while working with Azure DevOps repositories, build pipelines, and release workflows.
Key features of semantic-release-ado include:
- Azure DevOps integration: Works natively with Azure DevOps repositories
- Automatic version calculation: Analyzes commit history to determine the next version
- Git tagging: Creates version tags in your repository
- Changelog generation: Produces comprehensive changelogs based on commit history
- Build pipeline integration: Seamlessly fits into your existing CI/CD pipeline
Benefits for Your Codebase and Releases
Implementing semantic-release-ado brings several tangible benefits:
Consistent Versioning
By automating the versioning process based on commit messages, your team ensures consistent application of semantic versioning principles. This eliminates debates about what the next version number should be and reduces the risk of incorrect versioning.
Enhanced Documentation
Automated changelog generation keeps your release notes up-to-date with minimal effort. Each release includes a comprehensive list of changes categorized by type (features, fixes, breaking changes) and linked to the relevant commits or work items.
Streamlined Workflow
With semantic-release-ado, version management becomes part of your CI/CD pipeline rather than a separate manual process. This streamlines your release workflow and eliminates version-related bottlenecks.
Better Communication
The semantic versioning scheme communicates the impact of changes to users through the version number itself. This helps maintainers and users make informed decisions about updating.
Implementation Guide
Let's walk through implementing semantic-release-ado in an Azure DevOps environment.
1. Install Dependencies
Your package.json should include these dependencies:
{
"devDependencies": {
"@commitlint/cli": "^18.5.0",
"@commitlint/config-conventional": "^18.5.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^14.0.3",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"husky": "^9.0.11",
"semantic-release": "^24.2.3",
"semantic-release-ado": "^1.4.0"
}
}
Important Note: These recent versions of semantic-release and its plugins require Node.js v20.8.1 or higher. We recommend using Node.js v22.15.1 (LTS) or newer.
2. Configure semantic-release
Create a .releaserc.json file in your project root with the following configuration:
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Semantic .NET Demo Changelog"
}
],
[
"@semantic-release/git",
{
"assets": [
"src/SemanticDotNetDemo.csproj",
"package.json",
"CHANGELOG.md"
],
"message": "chore(release): ${nextRelease.version} [skip ci] [skip hook]\n\n${nextRelease.notes}"
}
],
[
"semantic-release-ado",
{
"setOnlyOnRelease": false,
"isOutput": true
}
]
]
}
This configuration:
- Uses commit-analyzer to determine version increments
- Generates release notes
- Updates the CHANGELOG.md file with a custom title
- Commits changes to project files, package.json, and CHANGELOG.md
- Sets up semantic-release-ado with:
- setOnlyOnRelease: false — always set the version variable
- isOutput: true — make the version available as an output variable
Important Note: Setting isOutput: true in the semantic-release-ado configuration serves 2 purposes:
- Makes the version number available as $(stepName.nextRelease) rather than just $(nextRelease), which is important for accessing the version in subsequent steps or jobs in your pipeline
- Avoids the bracket issue documented in Issue #29 where isOutput: false can cause Azure DevOps to prepend a bracket to the version number (e.g., ]1.2.3)
3. Configure Your Azure Pipeline
Here's how to integrate semantic-release into your Azure DevOps pipeline:
trigger:
- main
pool:
vmImage: "ubuntu-latest"
variables:
buildConfiguration: "Release"
steps:
# Setup .NET Core SDK
- task: UseDotNet@2
inputs:
packageType: "sdk"
version: "9.0.x"
installationPath: $(Agent.ToolsDirectory)/dotnet
# Setup Node.js environment for semantic-release
- task: NodeTool@0
inputs:
versionSpec: "22.x" # Newer semantic-release requires Node.js v20.8.1+
displayName: "Install Node.js"
# Checkout with credentials persisted for semantic-release
- checkout: self
persistCredentials: true
# Install Node.js dependencies
- script: |
npm ci
displayName: "Install Node.js dependencies"
# Run semantic-release to determine version
- script: |
# Run semantic-release - the plugin will automatically set $(nextRelease)
npx semantic-release
displayName: "Run semantic-release" name: semanticStep
env:
GIT_CREDENTIALS: $(System.AccessToken)
GH_TOKEN: $(System.AccessToken)
# Note: By setting isOutput: true in the semantic-release-ado plugin configuration,
# the version will be available as $(semanticStep.nextRelease) in subsequent steps
# Restore and build the project
- script: |
dotnet restore src/SemanticDotNetDemo.csproj
dotnet build src/SemanticDotNetDemo.csproj --configuration $(buildConfiguration)
displayName: "Build .NET application"
# Create NuGet package
- script: |
dotnet pack src/SemanticDotNetDemo.csproj --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory) /p:Version=$(semanticStep.nextRelease)
displayName: "Create NuGet package"
condition: ne(variables['semanticStep.nextRelease'], '')
# Publish artifacts
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: "$(Build.ArtifactStagingDirectory)"
ArtifactName: "nuget-packages"
publishLocation: "Container"
condition: ne(variables['semanticStep.nextRelease'], '')
4. Set Up Commit Message Validation
To enforce the Conventional Commits format, set up commitlint with Husky:
- Create a commitlint.config.js file:
module.exports = { extends: ["@commitlint/config-conventional"], rules: { "body-max-line-length": [0, "always", Infinity], }, }; - Initialize Husky and add the commit-msg hook:
npx husky init npx husky add .husky/commit-msg "npx --no -- commitlint --edit \"$1\"" - Add Commitizen for an interactive commit message format helper:
// package.json "config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" } }
Testing Your Setup
Before deploying to production, test your semantic-release configuration locally:
# Verify the semantic-release configuration
npx semantic-release --dry-run --no-ci
This will analyze your commit history and show what version would be released without actually creating tags or releases.
Viewing Generated Artifacts
After a successful run of semantic-release in your Azure DevOps pipeline, you'll see:
- Updated version numbers in your project files
- A new Git tag with the version number
- An updated CHANGELOG.md with the latest changes
- Azure DevOps variables set for the build pipeline
Version Number Output
There are a few important considerations when working with version numbers in semantic-release-ado:
Output Variable Configuration
When using semantic-release-ado, you have 2 options for accessing the version number:
- Simple Variable (setOnlyOnRelease: false):
- Makes the version available as $(nextRelease)
- More straightforward, but can have issues with variable persistence
- May experience the leading bracket issue
- Output Variable (isOutput: true):
- Makes the version available as $(stepName.nextRelease)
- More reliable for multi-job or multi-stage pipelines
- Recommended approach for complex pipelines
Leading Bracket Issue
There is a known issue where semantic-release-ado can sometimes output version numbers with a leading bracket (e.g., ]1.2.3 instead of 1.2.3). This occurs due to how Azure DevOps handles logging commands when certain variable attributes are undefined or set to false.
We recommend:
- Using isOutput: true in your semantic-release-ado configuration
- Referencing the version through the step output variable ($(stepName.nextRelease))
- If needed, adding a cleaning step to remove any leading brackets:
- script: | CLEAN_VERSION=$(echo "$(nextRelease)" | sed 's/^]//') echo "##vso[task.setvariable variable=nextRelease]$CLEAN_VERSION"
For more information about this issue, see semantic-release-ado Issue #29.
Conclusion
Implementing semantic-release-ado in your Azure DevOps environment brings the power of automated semantic versioning to your development workflow. By combining it with Conventional Commits, you create a seamless process that:
- Automates version number determination
- Generates comprehensive changelogs
- Creates consistent Git tags
- Enhances your release management process
- Improves communication about changes
While semantic-release was originally designed for GitHub, semantic-release-ado now brings these same benefits to Azure DevOps users. By adopting this approach, your team can focus more on delivering value and less on managing the mechanics of releases and versioning.