This content originally appeared on DEV Community and was authored by OctoLab Team
One of the most common uses of GitHub Actions in Node.js projects is to automate releases: calculate the next version, tag the repository, generate release notes and publish a GitHub Release (and optionally publish to NPM).
This release flow usually involves a few key steps:
- Install dependencies to ensure the project can build.
- Build the project if your package/app requires compiled artifacts.
- Run Semantic Release to analyze commits, bump the version (major/minor/patch), create a tag and publish the release notes.
In this article we will set up a GitHub Actions workflow that automates this process every time you push to main
. We will explain step by step what each part of the workflow does and why it is important.
The purpose of the workflow
We want that, every time a push happens in our main
branch, a set of tasks runs to automate releases using Conventional Commits:
- Analyzes commit messages and determines the next version (major/minor/patch)
- Creates a Git tag and generates release notes
- Publishes a GitHub Release
- (Optional) Publishes to npm if configured via plugins
General workflow structure
name: Releasing with Semantic Release
on:
push:
branches:
- main
jobs:
release:
name: release
runs-on: ubuntu-latest
steps:
- id: checkout-step
name: Checkout code
uses: actions/checkout@v4
- id: setup-node-step
name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
- id: install-dependencies-step
name: Install dependencies
run: npm install
- id: build-step
name: Build package
run: npm run build
- id: semantic-release-step
name: Release with Semantic Release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
run: npx semantic-release
What parts compose a GitHub Actions workflow?
Before going into detail, it is useful to understand how a GitHub Actions workflow is structured. The main elements are:
Trigger (on:)
Defines when the workflow is executed. It can be when pushing, opening a pull request, publishing a release, executing manually, etc.
In our case, we use push to main
, which triggers a release attempt every time new commits land in the main branch.
Job (jobs:)
Each workflow can have one or several jobs. A job is a set of tasks that are executed in the same environment.
All the steps of a job share the same filesystem and context. In our example, we have a single job called release
.
Runner (runs-on:)
It is the operating system where the job is executed. GitHub provides hosted runners, such as ubuntu-latest
, windows-latest
or macos-latest
, but we also have the possibility to use self-hosted runners.
Steps (steps:)
These are the actions or commands that are executed within a job. They can be:
- Reusable actions such as actions/checkout or actions/setup-node.
- Specific commands defined with run, such as
npm install
ornpx semantic-release
.
Step-by-step of our workflow
Now that we understand how a workflow is structured, let’s analyze each of the steps that make up our workflow. We will see what each action does, why it is necessary and what additional configurations we could apply depending on the case.
on: push (branches: main)
This block indicates that the workflow will be triggered when you push to main
. Semantic Release will then decide whether a new release should be created based on the commit history (using Conventional Commits).
uses: actions/checkout@v4
This step downloads the code from the repository so that it is available within the runtime environment.
Some additional options that this action allows are:
- fetch-depth: 0 clones the entire repository history (by default only the current commit). Semantic Release may benefit from full history in some setups.
- ref: branch-name** you can set a specific branch
- path: subdirectory clone the code in a specific path
For this case, just use it without extra configuration:
- uses: actions/checkout@v4
uses: actions/setup-node@v4
This action installs a specific version of Node.js, in our case version 22. It also allows you to configure dependency caching and more.
- uses: actions/setup-node@v4
with:
node-version: "22"
Other useful options are:
- cache: ‘npm’ enables dependency caching using actions/cache underneath
- check-latest: true** forces to use the latest available version that complies with the semver.
Working scripts
- run: npm install
- run: npm run build
- run: npx semantic-release
These steps execute the commands defined in your project and run Semantic Release to automate the versioning and release process:
- npm install will install all the project’s dependencies.
- npm run build** will generate the final package or artifacts if your project needs a build step (libraries, apps, compiled bundles, etc.).
- npx semantic-release will: — analyze commits following Conventional Commits, — determine the next version (major/minor/patch), — create a Git tag, — generate release notes, and — publish a GitHub Release (and optionally publish to npm if configured via plugins).
Token & config tips
- Expose a token with permissions to create tags/releases:
env: { GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} }
You can also use the defaultGITHUB_TOKEN
with proper write permissions.- Provide a Semantic Release configuration (in
package.json
underrelease
or in a.releaserc
file) with the plugins you need (e.g.,@semantic-release/github
,@semantic-release/changelog
,@semantic-release/npm
,@semantic-release/git
).- Ensure your commit messages follow Conventional Commits so Semantic Release can infer the correct next version.
Final result
With this flow you guarantee that every push to main
goes through an automated release process, without manual tagging or copy-pasting release notes. You can integrate it with additional plugins/steps such as:
- Automatic CHANGELOG.md updates and commits
-
npm publishing with
@semantic-release/npm
- Multi-branch release strategies (e.g.,
next
,beta
) - Notifications (Slack, Discord, etc.)
Do you want to avoid writing it by hand? There is a more convenient way
Understanding how a workflow works in GitHub Actions is indispensable. It gives you control and helps you customize everything to your needs.
Now… if you don’t feel like struggling with the syntax, that’s fine too.
At OctoLab we offer you the ability to set up this same flow visually: you choose the actions, fill in the fields and it generates the YAML for you on the fly.
Without indentation errors
With built-in validations
Copy, paste and go.
Conclusion
This workflow is an excellent basis for automated, reliable releases in Node.js projects. It can be easily adapted to other needs: publishing to npm, updating changelogs automatically, multi-branch strategies, or running on multiple Node versions.
If you prefer to use OctoLab we will be very grateful for your feedback!
This content originally appeared on DEV Community and was authored by OctoLab Team