The semantic-release package (https://github.com/semantic-release/semantic-release) automates the version management and release process of a project. It determines the next version number based on the commit messages that adhere to the Conventional Commits specification, generates release notes, and publishes the release automatically.
However, some developers prefer more control over when to increment major and minor versions. Companies like JetBrains use the year as a major version and an auto-incrementing integer as a minor version as illustrated in the image below.
Since semantic-release offers various built-in tools, such as a release notes generator, it is interesting to keep using it and just change the bumping logic. That is what I will show in this post.
Azure DevOps pipeline for Manual bumping
My approach involves creating an Azure DevOps pipeline that runs the semantic-release with the following steps:
- The pipeline prompts the user to specify the bump type (major, minor, or patch) and the desired version number.
- Once the user confirms the choice, if the user opted for a major or minor version, the pipeline transitions to an approval state.
- Upon approval, the pipeline increments the version according to the chosen bump type.
- The pipeline verifies that the bump aligns with the desired version number.
The code snipet below shows the Azure Devops pipeline YAML with steps above. The parameters is used to request user bump type (default bump type set to patch) and wished version. Its values is set as environment variables that are used at semantic release configuration.
parameters:
- name: bumpType
type: string
default: "patch"
values:
- major
- minor
- patch
- name: bumpNumber
type: string
default: "0"
pool:
vmImage: ubuntu-latest
jobs:
- job: approval
pool: server
steps:
- task: ManualValidation@1
...
condition: ne('${{ parameters.bumpType }}', 'patch')
- job: create_tag
dependsOn: approval
steps:
...
- script: |
npx semantic-release
displayName: Run semantic-release setting ${{ parameters.bumpType }} version to ${{ parameters.bumpNumber }}
env:
SEMANTIC_RELEASE_BUMP_TYPE: ${{ parameters.bumpType }}
SEMANTIC_RELEASE_BUMP_NUMBER: ${{ parameters.bumpNumber }
That’s it! With this setup, you can manually bump the versions of your project without having to worry about commit messages.
The following code snipet, show the semantic release configuration plugin configuration at release.config.cjs. For the plugin @semantic-release/commit-analyzer (which bumps version according to commit messages) is set to always increase the version according to bump type options choosen by the user.
// file release.config.cjs
module.exports = {
...
plugins: [
...
['@semantic-release/commit-analyzer', { releaseRules: [
{ release: process.env.SEMANTIC_RELEASE_BUMP_TYPE }
] }],
'./verify-release.js'
]
};
The verify-release.js plugin verifies if the new version is incremented as expected. This ensures that if the pipeline is executed with the same input for the second time, it will fail because the bump will go to an undesired value (taking the JetBrains example, setting the major version to the next year). You can see the verify-release.js code in the next snippet.
// file verify-release.js
module.exports = {
verifyRelease: async (pluginConfig, context) => {
const { lastRelease = {}, nextRelease = {}, logger = console } = context;
const bumpType = process.env.SEMANTIC_RELEASE_BUMP_TYPE;
const bumpNumber = process.env.SEMANTIC_RELEASE_BUMP_NUMBER;
logger.log('Verifying expected release.');
if (!bumpType) {
logger.log('SEMANTIC_RELEASE_BUMP_TYPE not set — skipping version verification.');
return;
}
if (bumpType == 'patch') {
logger.log('Bump type set to patch. Nothing to verify.');
return;
}
const actual = nextRelease && nextRelease.version;
const match = actual.match(/^(\d+)\.(\d+)\.\d+$/);
if (!match) {
throw new Error(`Invalid tag format: ${lastRelease}`);
}
const actualMajor = Number(match[1]);
const actualMinor = Number(match[2]);
if (bumpType == 'major' && actualMajor != bumpNumber) {
logger.error(`Major version mismatch: expected ${bumpNumber} but will publish ${actualMajor}`);
throw new Error(`Version verification failed: expected major version ${bumpNumber}, got ${actualMajor}`);
}
if (bumpType == 'minor' && actualMinor != bumpNumber) {
logger.error(`Minor version mismatch: expected ${bumpNumber} but will publish ${actualMinor}`);
throw new Error(`Version verification failed: expected minor version ${bumpNumber}, got ${actualMinor}`);
}
logger.log(`Version verification passed: ${actual}`);
}
}
The picture below shows one example of pipeline execution that bumped major version.
Building new version
Once the release is ready, you can begin building your application. For this example, I’ve created a simple hello world CLI in Go.
The pipeline’s steps are as follows:
- Checkout the code with tags
- Gets a tag-based description of the current commit. If the commit lacks a tag, it creates a descriptive version based on the latest tag.
- Sets Azure DevOps build number to the description from previous step
- Generates cli executable and publishes it as build artifact.
Here’s a code snippet that shows the above pipeline:
variables:
appName: hello-world
buildDir: build
steps:
- checkout: self
fetchTags: true
fetchDepth: 0
- script: |
export VERSION=$(git describe --tags)
echo "##vso[build.updatebuildnumber]${VERSION}"
displayName: "Set build number"
- task: GoTool@0
inputs:
version: "1.25"
- script: |
mkdir -p $(buildDir)
go build -o $(buildDir)/$(appName) ./cmd
displayName: "Build Go binary"
- script: |
cd $(buildDir)
zip $(appName)-$(Build.BuildNumber).zip $(appName)
displayName: "Create ZIP with version"
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: "$(buildDir)"
ArtifactName: "release"
publishLocation: "Container"
The following image illustrates one successful build.
The code used in this post is available in the github repository https://github.com/dmo2000/semantic-release-manual



Top comments (0)