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
- Reusable Workflow Actions
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
- In your GitHub repository, navigate to the Actions tab.
- Click on the Set up a workflow yourself button to create a new workflow file.
- Create a folder at the root of your repo and name it workflow. From there, add a file inside thsi folder and name it - workflow/ci-release.yml. This is where you'll define your workflow for automating releases and changelogs. You can now add the following code in this file :
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
create-staging
: creates a staging branch if it doesnt exist and pushes changes thererelease
: a custom job to create a release. This job uses an external action located in the organization-name/action-release@v0.0.1 repository that well create next.
From the Githubs Api
name
: this specifies the name of the workflow. Here it is ci-release which describes that this is a CI (continuous integration) workflow for releasing code.description
: as you noted, this provides a human-readable description of what the workflow does.uses
: the uses keyword indicates that this step will run an action - a reusable component encapsulating code. Here it is using a custom action defined in the organization.permissions
: this specifies the GitHub permissions required for the workflow to access different resources like code, issues, etc.on
: triggers the workflow on push events to main branch, as you noted.jobs
: jobs define the actual work to be executed. Here there is a single job called release.runs-on
: specifies the type of virtual environment the job will run on. Here it is an Ubuntu runner.steps
: defines the sequence of tasks to execute in the job. Each is a separate step.uses
&with
: in each step, uses indicates the action to run, and with passes inputs to that action.
Configuration Files
You will also need to add the following configuration files at the root of your project
package.json
file to host the libraries we mentioned
{
"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"
}
}
.versionrc.js
file to configure standard-version
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;
commitlint.config.js
to configure your commit validations
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;
husky.config.js
file to provide what hooks need to run prior to your commits and run linting
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
- Create a new repository for the composite action : in this new repository, you'll implement the Composite Action that will be used in your service to automate releases.
- You can name it : action-release to stick to the example above.
- Create the action file
action.yml
: in the root of your new repository, create a file named action.yml with the following content :
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
secret_input
: a GitHub secret input that can be used to provide a Github token or any other sensitive information required by the workflow. It is marked as required and has a default value of an empty string ('') but the secret is called from the service repo.release_type
: this input is used to specify the type of version bump (major, minor, or patch). It is marked as required and has a default value of 'patch'.publish_type
: an input to specify the type of publish action (library or service). It is marked as optional and has a default value of 'service'. Due to the publish type, we can have different operations in our CI based on if the targeted repo is a library or a servicepush_to
: input to specify whether to push changes to a new branch (staging or none). It is marked as optional and has a default value of 'none'.pre_action
: an input to specify a pre-action (checkout or none). It is marked as optional and has a default value of 'checkout'.
From the Githubs Api
run
:- the run keyword is used to execute shell commands or scripts as part of a step.
- anything specified under run will be executed by the runner during that step.
- it is typically used to run shell scripts, build tools, test commands, etc.
permissions
withusing: composite
:- when using a composite action, permissions need to be explicitly defined vs inherited from the base action.
- this gives more control over what the composite action can access.
- for example : a composite may need read/write permissions to repository content.
steps
:- steps define the series of tasks that make up the action.
- each step can run commands/scripts inline using run, or reference reusable actions.
- steps are executed sequentially by the runner.
The Jobs Steps
The steps are the individual actions performed within the job
- checkout repo : checks out code if pre_action is 'checkout'
- setup node : sets up Node.js environment
- git authentication : configures Git user details
- bump version, tag and create changelog : bumps version and generates changelog
- push changes : pushes changes and tags
- publish version : conditionally publishes package if publish_type is 'service'
- push to staging branch : conditionally pushes to staging branch if push_to is 'staging'
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 !