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.
Semantic versioning

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

© Stacc 2024, all rights reserved