# Semantic Release

# What is a Release

A release is the distribution of a version of an application.
A software release may be either public or private and generally constitutes the initial generation of a new or upgraded application.

# What is Semantic Release

Semantic Release is an Open-Source Software tool for automatically versioning your software with Semantic Versions (opens new window) based on your Git commit messages.
It then releases/deploys the new version to the channel(s) you specify, for example, GitHub Release, NPM, PyPI, etc.

By default, Semantic Release expects commits to be in the Conventional Commit format (opens new window) .

# Why Semantic Release

semantic-release (opens new window) automates the whole package release workflow including:

  • determining the next version number
  • generating the release notes
  • publishing the package

This removes the immediate connection between human emotions and version numbers, strictly following the Semantic Versioning specification.

semantic-release (opens new window) is using Conventional Commits (opens new window) and Semantic Versioning (opens new window)

# CI flow proposal

Before releasing a version and via github action:

  • You need to check that your commit messages meet the Conventional Commits format
  • You need to lint your commited code
  • You need to run some unit testing
  • You may also publish the released version
  • You may also use the released version on Sentry
  • You may also deploy the released version

# Setup

You need to

# Git tags

Semantic Versioning does not cover libraries tagged 0.*.*. The first stable version is 1.0.0.
If tag v1.0.0 has already been created make sure that it is tagged to a commit that belongs to the release branch.

For most cases Semantic release will create v1.0.0 tag automatically on last git commit (on first CI Action run)
You may create manually a v1.0.0 tag (release notes - changelog will note generated from commits that exist before of this tag)

# Examples

git-tag tutorial (opens new window)

# Create tag - git command

git tag <tagname>

# Create tag on last commit

git tag -a v1.0.0 -m "initial release"

# Create tag on specific commit

git tag -a v1.0.0 -m "initial release" 15027957951b64cf874c3557a0f3547bd83b3ff6

# Create tag without description

git tag -a v1.0.0

# Push tag - git command

git push origin <tag_name>

# Push tag to origin

git push origin v1.0.0

# GitHub repo Encrypted Secrets

GITHUB_TOKEN does not have the required permissions to operate on protected branches.
If you are using semantic-release for protected branches, replace GITHUB_TOKEN with Personal Access Token.

# Creating Encrypted secrets for a repository

# create personal access token

  • Verify your email address, if it hasn't been verified yet
  • Ιn the upper-right corner of GitHub page, click your profile photo, then click Settings
  • In the left sidebar, click Developer settings
  • In the left sidebar, click Personal access tokens
  • Click Generate new token
  • Set Note as ADMIN_GH_TOKEN
  • Select scope repo - Full control of private repositories
  • Click Generate token
  • Copy generated token

# creating encrypted secret

  • On GitHub, visit the main repository page
  • Under your repository name, click Settings
  • In the left sidebar, click Secrets
  • Click New repository secret
  • Type ADMIN_GH_TOKEN for your secret in the Name input box
  • Enter the previously generated token for your secret
  • Click Add secret

# Bump Version but not publish

Just set private: true into package.json

package.json

{
  "name": "pitcher-project-name",
  "private": true,

# Configuration File

configuration guide (opens new window)

  • Create release.config.js
  • Use @semantic-release/github
    • Disable successComment, failComment, labels to avoid sending too much emails
    • Set releaseLabels as 'released<%= nextRelease.channel ? ` on ${nextRelease.channel}@${nextRelease.version}` : "" %> from <%= branch.name %>'
  • Use @semantic-release/release-notes-generator plugin to generate release notes
  • Use @semantic-release/changelog plugin to create changelog
  • Use @semantic-release/git plugin to commit release assets to the project's git repository

# Main branch | publish - commit - no npm version

  • The release branch is main
  • The release is published
  • A commit is created with release assets
    • npm version is NOT commited on git repository
    • changelog is commited
    • build files are commited

release.config.js

module.exports = {
  branches: [{ name: 'main' }],
  plugins: [
    '@semantic-release/commit-analyzer',
    '@semantic-release/release-notes-generator',
    ['@semantic-release/changelog', { changelogFile: 'CHANGELOG.md' }],
    ['@semantic-release/npm', { npmPublish: true, pkgRoot: '.' }],
    [
      '@semantic-release/github',
      {
        successComment: false,
        failComment: false,
        releasedLabels: [
          // eslint-disable-next-line max-len
          'released<%= nextRelease.channel ? ` on ${nextRelease.channel}@${nextRelease.version}` : "" %> from <%= branch.name %>',
        ],
      },
    ],
    [
      '@semantic-release/git',
      {
        assets: ['build/**/*.js', 'CHANGELOG.md'],
        message: 'chore(release): set `package.json` to ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
      },
    ],
  ],
}

package.json

{
  "private": false,

# Dev branch | publish - commit

  • The release branch is dev
  • The release is NOT published
  • A commit is created with release assets
    • npm version is commited on git repository
    • changelog is commited

release.config.js

module.exports = {
  branches: [{ name: 'dev' }],
  plugins: [
    '@semantic-release/commit-analyzer',
    '@semantic-release/release-notes-generator',
    [
      '@semantic-release/changelog',
      {
        changelogFile: 'docs/CHANGELOG.md',
      },
    ],
    ['@semantic-release/npm', { npmPublish: false }],
    [
      '@semantic-release/github',
      {
        successComment: false,
        failComment: false,
        labels: false,
        releasedLabels: [
          'released<%= nextRelease.channel ? ` on ${nextRelease.channel}@${nextRelease.version}` : "" %> from <%= branch.name %>',
        ],
      },
    ],
    [
      '@semantic-release/git',
      {
        assets: ['docs/CHANGELOG.md', 'package.json'],
        message: 'chore(release): set `package.json` to ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
      },
    ],
  ],
}

package.json

{
  "private": true,

# Maintenance branches

  • The release branch is dev
  • The maintenance branch is firstVersion/dev
    • The major version of the maintenance branch will always be 1
    • A common commit for both release and maintenance branch should be tagged as v1.0.0
  • The release is NOT published
  • A commit is created with release assets
    • npm version is commited on git repository
    • changelog is commited

release.config.js

module.exports = {
  branches: [{ name: 'dev' }, { name: 'firstVersion/dev', range: '1.x' }],
  plugins: [
    '@semantic-release/commit-analyzer',
    '@semantic-release/release-notes-generator',
    [
      '@semantic-release/changelog',
      {
        changelogFile: 'docs/CHANGELOG.md',
      },
    ],
    '@semantic-release/npm',
    [
      '@semantic-release/github',
      {
        successComment: false,
        failComment: false,
        labels: false,
        releasedLabels: [
          'released<%= nextRelease.channel ? ` on ${nextRelease.channel}@${nextRelease.version}` : "" %> from <%= branch.name %>',
        ],
      },
    ],
    [
      '@semantic-release/git',
      {
        assets: ['docs/CHANGELOG.md', 'package.json'],
        message: 'chore(release): set `package.json` to ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
      },
    ],
  ],
}

package.json

{
  "private": true,

# Github Actions

# Release using cycjimmy/semantic-release-action

Using cycjimmy/semantic-release-action (opens new window)

If using the @semantic-release/git plugin for protected branches, avoid persisting credentials as part of actions/checkout@v2 by setting the parameter persist-credentials: false.
This credential does not have the required permission to operate on protected branches

Note: No need to install semantic release or semantic release plugins as dev dependencies on your project

.github/workflows/release.yml

name: Release
on:
  push:
    branches:
      - main
jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          persist-credentials: false
      - name: Install dependencies
        run: npm install
      - name: Semantic release
        uses: cycjimmy/semantic-release-action@v2
        id: semantic_release
        with:
          semantic_version: 17.4.2
          extra_plugins: |
            @semantic-release/changelog@5.0.1
            @semantic-release/git@9.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.ADMIN_GH_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

# Release and publish - minimal configuration with a build running on Node 12

.github/workflows/release-and-publish.yml

name: Release and publish
on:
  push:
    branches:
      - main
jobs:
  release-and-publish:
    name: Release and publish
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 12
      - name: Install dependencies
        run: npm ci
      - name: Semantic release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release

# Build (commit_lint - lint - unit_test - release - sentry - deploy -no publish)

Note: kudu -u $KUDU_USER -p $KUDU_PASSWORD push -f 999999 set your own document id (instead of 999999)

.github/workflows/build.yml

name: CI
on:
  push:
    branches: [dev]
  pull_request:
    branches: [dev]
jobs:
  commit_lint:
    name: Commit Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: wagoid/commitlint-github-action@v2
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: |
            **/node_modules
          key: node-modules
      - run: npm install
      - name: Lint
        run: npm run lint
  unit_tests:
    name: Unit tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: |
            **/node_modules
          key: node-modules
      - run: npm install
      - name: Unit testing with coverage report
        run: npm run test:unit-coverage
      - name: Coveralls GitHub Action
        uses: coverallsapp/github-action@v1.1.2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
  deployment:
    name: Deploy dev branch
    runs-on: ubuntu-latest
    needs: [commit_lint, lint, unit_tests]
    if: contains('refs/heads/dev', github.ref)
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          lfs: true
          persist-credentials: false
      - name: Semantic release
        uses: cycjimmy/semantic-release-action@v2
        id: semantic_release
        with:
          semantic_version: 17.4.2
          extra_plugins: |
            @semantic-release/changelog@5.0.1
            @semantic-release/git@9.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.ADMIN_GH_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
      - name: Set up Python 2.7
        uses: actions/setup-python@v1
        with:
          python-version: 2.7
      - name: Install Kudu
        run: |
          python -m pip install --upgrade pip
          pip install kudu
      - name: Cache Node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: |
            **/node_modules
          key: node-modules
      - name: Install Node modules
        run: npm install
      - name: Build
        env:
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
          SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
          SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
          RELEASE_VERSION: ${{ steps.semantic_release.outputs.new_release_version }}
        run: npm run build
      - name: Deploy
        env:
          KUDU_USER: ${{ secrets.KUDU_USER }}
          KUDU_PASSWORD: ${{ secrets.KUDU_PASSWORD }}
        run: |
          cd dist
          kudu -u $KUDU_USER -p $KUDU_PASSWORD push -f 999999