Semantic versioning #
TLDR: By using conventional commit messages, we can automatically generate semantic version numbers
Rationale: Assessing versioning of artifacts is a key part of the software supply chain. By using conventional commit messages, we can automatically generate semantic version numbers and avoid version conflicts. If your code is on Github, use the resuable workflow detailed below.
Background #
We use conventional commits to automatically generate semantic version numbers. This allows us to automatically generate version numbers, and avoid version conflicts.
Commit message #
Structure #
When we commit changes we follow the following structure
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
although most of you will use: <type>[optional scope]: <description>
Types #
Types can be found here in conventional commits own documentation, but we will list them here for convenience.
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
- chore: Other changes that don’t modify src or test files
A feat
will amount to a minor version bump,
A fix
will amount to a patch version bump
A BREAKING CHANGE
will amount to a major version bump. This will have to be in the footer.
Examples #
feat: add feature TICKET-01
feat(api): add endpoint /y to api TICKET-02
fix: fix bug TICKET-03
fix(api): fix bug in endpoint /x TICKET-04
chore: cleaning up unused imports
build: add step to pipeline
The type defines the “weight” of the change. It boils down this:
- If the change adds anything new it’s a feat.
- If it fixes something that already exists, it’s a fix.
- If it’s related to tests, it’s a test.
- If it’s related to the build system, it’s a build.
- If it’s anything else, it’s a chore.
Advantages #
- Automatically generated version numbers
- Avoid version conflicts
- Easy to understand
- Tooling support
Github Reusable Workflow #
This is language agnostic and will not produce an artifact (unless you want NPM). It will only produce a release in Github.
If you want to produce an image as a side effect, you can use the reusable workflow here
name: Release new version of my thing
on:
push:
branches:
- main
# This keeps versioning from happening on subsequent merges
concurrency:
group: release-my-ting${{ github.ref }}-1
cancel-in-progress: true
jobs:
version:
uses: stacc/github-workflow-actions/.github/workflows/version.yaml@main
secrets:
pat_token: ${{ secrets.PAT_TOKEN }}
npm_token: ${{ secrets.NPM_TOKEN }}
npm_release: false # set true if you want to publish to NPM
slack_release_bot_webhook: ${{ secrets.SLACK_RELEASE_BOT_WEBHOOK }}
with:
# name of your thing
name: 'my-thing'
# optional as this main is the default
branches: |
['main']
# optional
slack-channel: "my-teams-release-channel"
NPM #
Please use the above method if you can.
For projects using NPM, these are the packages needed to automatically generate version numbers
We can use this for .NET(using dotnet nuget push
though exec
) and Java as well, but we will not go into detail here
Stacc maintains a dynamic config here: (link to config)[https://www.npmjs.com/package/@stacc/semantic-release-config]
"@stacc/semantic-release-config": "^1.2.0"
Although exec
is not needed, it is useful for running scripts during a release (such as updating the version number of a helm chart)
git
is needed to commit the version number to the repository alongside the changelog
In addition we need a release config to tell semantic release what to do
{
"extends": "@stacc/semantic-release-config",
"branches": [
"main",
{"name": "next", "channel": "next"}
]
}
or inside package.json
{
"extends": "@stacc/semantic-release-config",
}
NPM Packages #
Note in the above config there are two branches, main
and next
. main
is the default branch, and next
is a branch that is used for testing new features. The next
branch is not released to the public, but is used for testing new features. This is useful for testing new features before releasing them to the public.
Release on next
channel with be published to npm with the next
tag (e.g. npm install @stacc/<mypackage>@next
)
Semantic release will now evaluate the commit messages, and generate a new version number. It will then update the version number in the package.json
and package-lock.json
files, and commit them to the repository. It will also generate a changelog, and commit that to the repository as well.
Example output:
[10:41:51 AM] [semantic-release] › ℹ Running semantic-release version 19.0.2
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "verifyConditions" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "verifyConditions" from "@semantic-release/changelog"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "verifyConditions" from "@semantic-release/github"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "verifyConditions" from "@semantic-release/git"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "analyzeCommits" from "@semantic-release/commit-analyzer"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "analyzeCommits" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "verifyRelease" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "generateNotes" from "@semantic-release/release-notes-generator"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "generateNotes" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "prepare" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "prepare" from "@semantic-release/changelog"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "prepare" from "@semantic-release/git"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "publish" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "publish" from "@semantic-release/github"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "addChannel" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "addChannel" from "@semantic-release/github"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "success" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "success" from "@semantic-release/github"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "fail" from "@semantic-release/exec"
[10:41:51 AM] [semantic-release] › ✔ Loaded plugin "fail" from "@semantic-release/github"
[10:41:56 AM] [semantic-release] › ✔ Run automated release from branch main on repository https://github.com/stacc/the-services
[10:41:56 AM] [semantic-release] › ✔ Allowed to push to the Git repository
[10:41:56 AM] [semantic-release] › ℹ Start step "verifyConditions" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ✔ Completed step "verifyConditions" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ℹ Start step "verifyConditions" of plugin "@semantic-release/changelog"
[10:41:56 AM] [semantic-release] › ✔ Completed step "verifyConditions" of plugin "@semantic-release/changelog"
[10:41:56 AM] [semantic-release] › ℹ Start step "verifyConditions" of plugin "@semantic-release/github"
[10:41:56 AM] [semantic-release] [@semantic-release/github] › ℹ Verify GitHub authentication (https://api.github.com)
[10:41:56 AM] [semantic-release] › ✔ Completed step "verifyConditions" of plugin "@semantic-release/github"
[10:41:56 AM] [semantic-release] › ℹ Start step "verifyConditions" of plugin "@semantic-release/git"
[10:41:56 AM] [semantic-release] › ✔ Completed step "verifyConditions" of plugin "@semantic-release/git"
[10:41:56 AM] [semantic-release] › ℹ Found git tag v3.6.0 associated with version 3.6.0 on branch main
[10:41:56 AM] [semantic-release] › ℹ Found 1 commits since last release
[10:41:56 AM] [semantic-release] › ℹ Start step "analyzeCommits" of plugin "@semantic-release/commit-analyzer"
[10:41:56 AM] [semantic-release] [@semantic-release/commit-analyzer] › ℹ Analyzing commit: fix(skatteetaten): fix ignore code for income data
[10:41:56 AM] [semantic-release] [@semantic-release/commit-analyzer] › ℹ The release type for the commit is patch
[10:41:56 AM] [semantic-release] [@semantic-release/commit-analyzer] › ℹ Analysis of 1 commits complete: patch release
[10:41:56 AM] [semantic-release] › ✔ Completed step "analyzeCommits" of plugin "@semantic-release/commit-analyzer"
[10:41:56 AM] [semantic-release] › ℹ Start step "analyzeCommits" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ✔ Completed step "analyzeCommits" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ℹ The next release version is 3.6.1
[10:41:56 AM] [semantic-release] › ℹ Start step "verifyRelease" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ✔ Completed step "verifyRelease" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ℹ Start step "generateNotes" of plugin "@semantic-release/release-notes-generator"
[10:41:56 AM] [semantic-release] › ✔ Completed step "generateNotes" of plugin "@semantic-release/release-notes-generator"
[10:41:56 AM] [semantic-release] › ℹ Start step "generateNotes" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ✔ Completed step "generateNotes" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] › ℹ Start step "prepare" of plugin "@semantic-release/exec"
[10:41:56 AM] [semantic-release] [@semantic-release/exec] › ℹ Call script ./updateVersions.sh 3.6.1
[10:41:57 AM] [semantic-release] › ✔ Completed step "prepare" of plugin "@semantic-release/exec"
[10:41:57 AM] [semantic-release] › ℹ Start step "prepare" of plugin "@semantic-release/changelog"
[10:41:57 AM] [semantic-release] [@semantic-release/changelog] › ℹ Update /home/runner/work/services-sbl/services-sbl/CHANGELOG.md
[10:41:57 AM] [semantic-release] › ✔ Completed step "prepare" of plugin "@semantic-release/changelog"
[10:41:57 AM] [semantic-release] › ℹ Start step "prepare" of plugin "@semantic-release/git"
[10:41:58 AM] [semantic-release] [@semantic-release/git] › ℹ Found 4 file(s) to commit
[10:41:59 AM] [semantic-release] [@semantic-release/git] › ℹ Prepared Git release: v3.6.1
[10:41:59 AM] [semantic-release] › ✔ Completed step "prepare" of plugin "@semantic-release/git"
[10:41:59 AM] [semantic-release] › ℹ Start step "generateNotes" of plugin "@semantic-release/release-notes-generator"
[10:41:59 AM] [semantic-release] › ✔ Completed step "generateNotes" of plugin "@semantic-release/release-notes-generator"
[10:41:59 AM] [semantic-release] › ℹ Start step "generateNotes" of plugin "@semantic-release/exec"
[10:41:59 AM] [semantic-release] › ✔ Completed step "generateNotes" of plugin "@semantic-release/exec"
[10:42:01 AM] [semantic-release] › ✔ Created tag v3.6.1
[10:42:01 AM] [semantic-release] › ℹ Start step "publish" of plugin "@semantic-release/exec"
[10:42:01 AM] [semantic-release] › ✔ Completed step "publish" of plugin "@semantic-release/exec"
[10:42:01 AM] [semantic-release] › ℹ Start step "publish" of plugin "@semantic-release/github"
[10:42:01 AM] [semantic-release] [@semantic-release/github] › ℹ Published GitHub release: https://github.com/stacc/the-services/releases/tag/v3.6.1
[10:42:01 AM] [semantic-release] › ✔ Completed step "publish" of plugin "@semantic-release/github"
[10:42:01 AM] [semantic-release] › ℹ Start step "success" of plugin "@semantic-release/exec"
[10:42:01 AM] [semantic-release] › ✔ Completed step "success" of plugin "@semantic-release/exec"
[10:42:01 AM] [semantic-release] › ℹ Start step "success" of plugin "@semantic-release/github"
[10:42:04 AM] [semantic-release] › ✔ Completed step "success" of plugin "@semantic-release/github"
[10:42:04 AM] [semantic-release] › ✔ Published release 3.6.1 on default channel
Important notes #
- The example in this article has both versioning and publishing in the same workflow.
- You can use semantic versioning without automatic deployment, but it is recommended.
- If you choose to not deploy or publish an artifact as part of a version bump, make sure no deployment can be done with delta or unstaged changes