Advanced example with Semantic versioning, CHangelog auto generation and git commit message syntax enforcement

This example is slightly advanced and includes tools that are still being explored. Use with caution!

Dockerfile

FROM python:3.8

RUN useradd -u 1000 -M -d /tmp/jenkins jenkins && \
    mkdir -p /tmp/jenkins && \
    chown -R jenkins /tmp/jenkins && \
    ln -s /jenkins/.ssh /tmp/jenkins/.ssh

RUN apt-get update && apt-get install -y npm

RUN npm install -g semantic-release \
                   @semantic-release/commit-analyzer \
                   @semantic-release/release-notes-generator \
                   @semantic-release/changelog \
                   @semantic-release/git

RUN pip install commitizen

.releaserc

{
  "branches": ["stable", {"name": "master", "prerelease": true}],
  "plugins": [
    ["@semantic-release/commit-analyzer", {
      "releaseRules": [
        {"type": "docs", "release": "patch"},
        {"type": "style", "release": "patch"},
        {"scope": "no-release", "release": false}
      ],
      "parserOpts": {
        "noteKeywords": ["BREAKING"]
      }
    }],
    ["@semantic-release/release-notes-generator", {
      "preset": "angular",
      "parserOpts": {
        "noteKeywords": ["BREAKING"]
      },
      "writerOpts": {
        "commitsSort": ["subject", "scope"]
      }
    }],
    ["@semantic-release/changelog",
      {
        "changelogFile": "CHANGELOG.md"
      }
    ],
    ["@semantic-release/git", {
      "assets": ["CHANGELOG.md"],
      "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
    }]
  ]
}

.cz.toml

[tool.commitizen]
name = "cz_customize"

[tool.commitizen.customize]
message_template = "{{change_type}}{% if scope %}({{scope}}){% endif %}: {{message}}{% if ticket_no %} ({{ticket_no}}){% endif %}"
example = "feature(API): Adding a status endpoint for monitoring and service discovery (MDT-3495)"
schema = "<type>: <body>"
schema_pattern = "^(BREAKING|fix|feat|docs|style|refactor|perf|test|build|ci|chore)(\\(\\w+\\))?: [\\w -.]+(\\((MDT|OP)-[0-9]+\\))?(\\[skip ci\\](.|\\n)*)?$"

[[tool.commitizen.customize.questions]]
type = "list"
name = "change_type"
choices = [
    {value = "fix", name = "fix: A bug fix. Correlates with PATCH in SemVer"},
    {value = "feat", name = "feat: A new feature. Correlates with MINOR in SemVer"},
    {value = "docs", name = "docs: Documentation only changes"},
    {value = "style", name = "style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)"},
    {value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature"},
    {value = "chore", name = "chore: updating Makefile receipe or NPM tasks etc; no production related code change"},
    {value = "perf", name = "perf: A code change that improves performance"},
    {value = "test", name = "test: Adding missing or correcting existing tests"},
    {value = "build", name = "build: Changes that affect the build system or external dependencies (example scopes: pip, docker, npm)"},
    {value = "ci", name = "ci: Changes to our CI configuration files and scripts (example scopes: Jenkins, Docker)"},
    {value = "BREAKING", name = "BREAKING: A major change."}]
message = "Select the type of change you are committing"

[[tool.commitizen.customize.questions]]
type = "input"
name = "message"
message = "Commit subject"

[[tool.commitizen.customize.questions]]
type = "input"
name = "scope"
message = "Scope, i.e: API (Keep empty to make global)"

[[tool.commitizen.customize.questions]]
type = "input"
name = "ticket_no"
message = "Ticket number i.e: MDT-1234, OP-321 (Keep empty if no ticket)"

Makefile

.PHONY: check-git release

check-git:
	cz check --rev-range HEAD@{~100}

release:
	semantic-release

Jenkinsfile

#!/env/bin/env groovy

@Library("m2aJenkins") _

def builder = null
def suffix = "${env.BRANCH_NAME}".replaceAll(/[^a-zA-Z0-9-]/,"-").toLowerCase()

def isNoCICommit = false

pipeline {
    agent any

    triggers {
        bitbucketPush()
    }

    options {
        disableConcurrentBuilds()
        ansiColor('xterm')
    }

    environment {
        VERSION = "${artifacts.get_semantic_version()}~${currentBuild.number}~${GIT_COMMIT[0..7]}"
    }

    stages {
        stage("Prepare") {
            steps {
                script {
                    utils.build_notify(currentBuild, 'prepare', "Prepare"){
                        currentBuild.displayName = VERSION
                        builder = docker.build("m2a-jenkins-snippet:${suffix}")
                        isNoCICommit = sh(script: "git log --format=format:%s -1", returnStdout: true).trim() ==~ /.+\[skip ci\]$/
                    }
                }
            }
        }
        stage("Check") {
            steps {
                script {
                    builder.inside {
                        utilsLib.build_notify(currentBuild, "checkcommit", "Check commit"){
                            sh "make check-git"
                        }
                    }
                }
            }
        }
        stage("Release") {
            when {
                allOf {
                    anyOf { branch 'master'; branch 'stable' }
                    expression { return !isNoCICommit }
                }
            }
            steps {
                script {
                    builder.inside {
                        utilsLib.build_notify(currentBuild, "release", "Release"){
                            sshagent(['63351679-e80c-4fb8-8ca0-17d764f863d0']) {
                                sh "make release"
                                currentBuild.displayName = "${artifact.get_semantic_version()} - ${currentBuild.number} - ${GIT_COMMIT[0..7]}"
                            }
                            return "Version ${artifact.get_semantic_version()} has been released"
                        }
                    }
                }
            }
        }
    }

    post {
        cleanup {
            cleanWs()
        }
    }
}