Clean Releases and Changelogs with Github Actions


Posted on Sat, Aug 12, 2023 actions devops automation yarn git command line

Release management can become disorganized as a project grows, with inconsistent tags, hard to understand commits and empty changelogs. This makes it very tough for developers to track releases or see what changes are included.

Rather than manually cleaning things up, which is tedious and error-prone if done carefree, GitHub Actions provides an automated solution. With just a simple workflow file added to the codes repository, releases can be kept perfectly organized.

This article will explain how to implement a GitHub Action that standardizes tags, generates changelogs, and streamlines the entire release process. The result will give you a tidy project history that benefits both developers and users alike.

Libraries Used in Custom Action

We will be using node and yarn for running the following libraries inside of our actions

What are GitHub Actions ?

GitHub Actions revolutionize the way developers streamline their software development workflows. They are an integral part of GitHub's versatile ecosystem, enabling automation and optimization of various tasks within a repository. Essentially, GitHub Actions are a set of customizable automation tools that respond to specific events, enabling developers to build, test, and deploy code seamlessly.

With GitHub Actions, developers can define workflows as code, which dictates how different tasks are orchestrated in response to specific triggers.

These triggers can range from pushing code to a repository, creating a pull request, or even scheduling periodic actions. click πŸ‘‰πŸΌ here to learn more.

This flexibility empowers teams to enhance collaboration, increase efficiency, and maintain code quality effortlessly.

Types of GitHub Actions

GitHub Actions come in a few primary flavors

Composite Actions : These are reusable building blocks that encapsulate one or more individual steps. Think of them as pre-packaged actions that can be combined to create more complex workflows. These actions promote modularity and reusability, as they encapsulate specific logic or tasks that can be easily incorporated into multiple workflows. This approach simplifies workflow management and reduces redundancy across projects.

Reusable Workflow Actions : In contrast, reusable workflow actions encapsulate an entire workflow and can be shared across different repositories. They offer a higher level of abstraction by combining multiple steps, environment setups, and triggers into a single entity. This type of action is particularly useful when you want to standardize a specific process across multiple projects or teams, ensuring consistent practices and reducing the chances of errors.

Differences between Composite & Reusable Workflow Actions

While both types of actions serve the purpose of streamlining development workflows, they differ in their granularity and scope.

Composite Actions

πŸͺΊ Nesting : Composite Actions allow you to create a chain of actions within actions, up to 10 layers deep. This helps in making complex workflows by putting actions inside other actions, creating a structured layout. In contrast, Reusable Workflows cannot be nested.

⚠️

You can't use one reusable workflow inside another, which makes it harder to create intricate workflows with multiple layers.

🀫 Secrets : Composite Actions can't directly use secrets, which are sensitive information. This could affect how secure and adaptable these actions are. On the other hand, Reusable Workflows can use secrets, but you need to pass them as parameters. This is a more secure way of handling sensitive data.

πŸͺ› Jobs : Composite Actions have a flat structure, meaning they only have steps and no traditional "job" structure. They can only be used within a job in the same repository. Reusable Workflows have jobs inside them, allowing for more flexible structuring and running multiple tasks in a single workflow.

Reusable Workflows

☝🏼 Conditionals : Reusable Workflows can use "if conditionals" letting you control which parts of the workflow run based on specific conditions. This makes them more flexible and similar to regular workflows. Composite Actions don't support conditionals. You can only have a list of steps that run in a linear order without considering conditions.

πŸ“¦ Storage : Reusable Workflows can be stored as normal files in a specific folder or in a central repository, making it organized and easy to reuse. Composite Actions need their own public repository, along with a metadata file. If you want to use scripts, they must be in the same place, which can be less organized.

πŸ—„οΈ Logging : with Reusable Workflows, you get detailed logs for each step and job in real-time, helping you track what's happening. Composite Actions have simpler logs, showing one log entry for a step, even if that step contains many smaller steps. This might not give you as much detail as Reusable Workflows for troubleshooting and monitoring.

Benefits of working with Composite Actions

⬇️

The benefits Composite Actions offers are endless ! below are a few of them

πŸͺ£ Modularity : Composite Actions encourage the creation of reusable building blocks, enabling teams to construct complex workflows with ease. This modularity reduces redundancy and simplifies workflow management.

πŸ§‘πŸ½β€πŸ’» Customization : developers can tailor composite actions to fit their specific needs, fine-tuning individual steps within a workflow while maintaining a consistent overall structure.

πŸ”ƒ Reusability : once created, composite actions can be reused across multiple workflows and repositories, promoting a standardized approach and accelerating development.

πŸ‘―β€β™‚οΈ Collaboration : Composite Actions facilitate collaboration by providing a common language for defining tasks and processes. This enables teams to work seamlessly together and share best practices.

🌱 Maintenance : when updates are required, modifying a composite action's logic only needs to be done once, propagating changes across all workflows that use it.

Exploring Event Options for a Composite Action

Event options are like switches that trigger actions in response to particular events occurring within a GitHub repository. These events could encompass various actions, such as pushing code, creating pull requests, or scheduling periodic tasks.

πŸš€

By harnessing event options, developers can customize workflows to be more adaptive and responsive, thus streamlining the development process.

Unleashing Manual Control : workflow_dispatch

The workflow_dispatch event stands out as a powerful tool for introducing manual control into your GitHub Actions workflows.

β€œUnlike events triggered automatically by system events, workflow_dispatch allows you to start a workflow manually.”

This is particularly useful when you want to perform actions on-demand, enabling you to initiate a workflow precisely when you need it, without being tied to a specific event trigger.

Bringing inputs into the Equation

While the workflow_dispatch event lets you manually trigger a workflow, inputs take the interaction a step further. Inputs provide a way to send specific data or parameters when starting a workflow.

This dynamic feature enables you to tailor the behavior of a workflow based on the provided inputs. Inputs can be text, numbers, booleans, or even more complex JSON objects, allowing for a wide range of customization.

Combining workflow_dispatch and inputs

The synergy between the workflow_dispatch event and inputs is a game-changer for GitHub Actions.

When used together, they enable developers to trigger workflows manually and fine-tune their behavior through customizable inputs.

This dynamic duo enhances flexibility and adaptability, making workflows more versatile and responsive to unique scenarios. 😍

πŸ€”

Imagine a scenario where you have a complex deployment process. By utilizing the workflow_dispatch event, you can kick off the deployment workflow at the precise moment you're ready. Adding inputs allows you to specify β€œparameters” such as deployment environment, version numbers, or configuration settings. This level of precision and customization empowers you to handle diverse deployment scenarios seamlessly.

Let’s put these concepts into practice. We’ll go through a step-by-step guide on how to set up a GitHub Action workflow for automating releases and changelogs

How to Automate Releases with GitHub Actions

~ the final result

First step to a GitHub CI Workflow : Implement your Service Repository

name: ci-release
description: ci release in service

permissions:
  actions: write
  checks: write
  contents: write
  deployments: write
  id-token: write
  issues: write
  discussions: write
  packages: write
  pages: write
  pull-requests: write
  repository-projects: write
  security-events: write
  statuses: write

on: 
  workflow_dispatch:
    inputs:
      release_type:
        description: 'bump version kind'
        required: true
        default: 'patch'
        type: choice
        options:
          - patch
          - minor  
          - major
          
jobs:
  create-staging:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: create staging branch 
      if: !exists('staging')
      run: |
        git checkout -b staging
        git push --set-upstream origin staging

  release:
    needs: create-staging 
    runs-on: ubuntu-22.04

    steps:
    - uses: actions/checkout@v2
    - name: release
      uses: organization-name/action-release@v0.0.1
      with:
        release_type: ${{ inputs.release_type }}
        push_to: staging 
        publish_type: 'service'
        secret_input: ${{ secrets.GITHUB_TOKEN }}

☝🏽

take note of the uses line in the job above. This indicates what repository we are going to use for our pipeline inside our organization. you can now create a action-release repository inside of your organization to call it from your service.

Service Yml Codes Breakdown

let's break down the provided GitHub composite actions workflow, i.e. action.yml step-by-step to understand what it does.

Custom job

From the Githubs Api

Configuration Files

You will also need to add the following configuration files at the root of your project

{
	"name": "@organization-name/service-name",
	"version": "0.0.1",
	"repository": "git@github.com:organization-name/service-name.git",
	"author": "organization-name",
	"scripts": {
		"prepare": "husky install"
	},
	"dependencies": {
		"husky": ">=8.0.3"
	},
	"devDependencies": {
		"@commitlint/cli": "^17.6.6",
		"standard-version": "^9.5.0",
		"@commitlint/config-conventional": "^17.6.6"
	}
}

const versionConfig = {
  bumpFiles: [{ filename: "package.json", type: "json" }],
  commitAll: true,
  commitMessageFormat: "chore(release): {{currentTag}}",
  issuePrefixes: "organization_name-",
  commitPaths: ["package.json", "package-lock.json"],
  types: [
    { type: "feature", section: "✨ feature" },
    { type: "design", section: "⚜️ design" },
    { type: "build", section: "πŸ‘· build" },
    { type: "bug", section: "πŸ› bug fixes" },
    { type: "chore", hidden: false, section: "🚚 chore" },
    { type: "devop", hidden: false, section: "πŸ†™ devop" },
    { type: "documentation", hidden: false, section: "πŸ“ documentation" },
    { type: "style", hidden: false, section: "πŸ’„ styling" },
    { type: "refactor", hidden: false, section: "♻️ code refactoring" },
    { type: "performance", hidden: false, section: "⚑️ performance improvement" },
    { type: "ux", hidden: false, section: "πŸ”₯ ux change" },
    { type: "test", hidden: false, section: "βœ… testing" },
  ],
};

module.exports = versionConfig;

const commitlintConfig = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "subject-empty": [1, "never"],
    "references-empty": [1, "never"],
    "scope-empty": [1, "never"],
    "type-enum": [
      2,
      "always",
      [
        "design",
        "build",
        "chore",
        "devops",
        "documentation",
        "feature",
        "bug",
        "performance",
        "refactor",
        "style",
        "design",
        "ux",
        "test",
      ],
    ],
  },
  parserPreset: {
    parserOpts: {
      issuePrefixes: ["organization_name-"],
    },
  },
};

module.exports = commitlintConfig;

module.exports = {
	hooks: {
		"pre-commit": "lint-staged",
	},
};

Create a Changelog Document

If you haven't already, create a CHANGELOG.md file in the root directory of your GitHub repository. This file will be used to track the changes for each release and will include all of your past git commits.

# command to create a changelog
touch CHANGELOG.md

Second step to a GitHub CI Workflow : Implementation of a Composite Action in a ci repository

name: ci-release
description: ci release in ci repo

inputs:
  secret_input:
    description: 'github secret from service repo'
    required: true
    default: ''
  release_type:
    description: 'bump version type'
    required: true
    default: 'patch'
  publish_type:
    description: 'publish type'
    required: false
    default: 'service'
  push_to:
    description: 'push to new branch after releasing'
    default: 'none'
  pre_action:
    description: 'allowed values : checkout, none'
    default: 'checkout'

runs:
  using: "composite"
  steps:
    - name: checkout repo
      if: ${{ inputs.pre_action == 'checkout' }}
      uses: actions/checkout@v3
      with:
        fetch-depth: 0
    - name: setup node
      uses: actions/setup-node@v3
      with:
        registry-url: 'https://npm.pkg.github.com'
        node-version: '16'
    - name: git authentication
      shell: bash
      run: |
          git config --global user.email "release_username@provider.com"
          git config --global user.name "release username"
    - name: bump version, tag and create changelog
      shell: bash
      run: npx standard-version --release-as ${{ inputs.release_type }}
    - name: push changes
      shell: bash
      run: git push --follow-tags
    - name: publish version
      if: ${{ inputs.publish_type == 'service' }}
      shell: bash
      run: yarn install --frozen-lockfile && yarn publish --non-interactive
      env:
        NODE_AUTH_TOKEN: ${{ inputs.secret_input }}
    - if: ${{ inputs.push_to == 'staging' }}
      uses: ad-m/github-push-action@master
      with:
        branch: staging

CI Repo Yml Codes Breakdown

Custom inputs

From the Githubs Api

The Jobs Steps

The steps are the individual actions performed within the job

🚧

Be sure to release a version of the action-release repository so that the service repo can consume it when it performs β€œrelease” job. This is so that when it tries to look for the β€œuses: organization-name/action-release@v0.0.1” line, the service will know to use the version 0.0.1 of the action-release published

Running the Workflow πŸƒπŸ½β€β™‚οΈ

Save the workflow file with a descriptive name (e.g., action.yml). Then commit and push the workflow file to your repository's root branch. click on actions and ci-release workflow

You can run the workflow from the Action tab on the right of the UI ⬇️

😊

Click on what release you would like to run and thats it! Now, every time you push changes and wish to release a set of commits, the GitHub Actions workflow will be triggered and all you will have to do is choose if you would rather publish a patch, minor or major version.

In Conclusion πŸ”₯

The concept of streamlining the process of generating clean tagged releases and changelogs through a singular GitHub Action is there to help you you keep your projects git flow organized.

This approach not only enhances the development workflow but can ensure transparency and seamless collaboration among team members.

By automating the arduous tasks of versioning, tagging, and documenting changes, developers can devote more energy to creativity and problem-solving πŸ§‘πŸ½β€πŸ’».

The resulting polished releases and well-structured changelogs facilitate user understanding and strengthen the project's credibility !