In the evolving software development landscape, maintaining version consistency and automating the release process is more important than ever. Enter
This article offers a detailed and comprehensive guide, providing step-by-step instructions to seamlessly integrate Semantic Release into your workflow, specifically tailored for those using public non-scoped packages. Dive in and discover the streamlined approach to software releases.
When I created my
To nail it, let’s go through a step-by-step experience for proper publishing public packages to
Before jumping into implementing our package, it is better to find the proper name for it. To be sure the name is not taken already — check my_package_name
and take it for your package. I chose “tokky.” From that point, reserving the package's name is impossible. For the name in npm, you have to publish the package.
The objective is to develop a straightforward package that outputs content to the console. We need to make sure that we can install it and run it. For the build process, let’s use simple
During this article, I will use the name of the package tokky
. Let’s create package.json
with the initial data.
mkdir tokky && cd tokky && npm init -y
After executing the command, the system generated a default package.json
file for the project, which looks like this:
{
"name": "tokky",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
In this guide, the package.json
file plays a crucial role in ensuring the right configuration. At this juncture, let's specify the node version for our package:
echo "v18" > .nvmrc
and activate the specified version with the following:
nvm use
For the README.md
file:
echo "# Tokky\n\nA simple zero dependency logger for node js." > README.md
Finally, install the development dependencies:
npm i -D esbuild eslint prettier
In our initial configuration, we need to address several key points in the package.json
:
main
: This designates the primary entry point for the module.bin
: Here, you’ll specify any executables your module provides.files
: This should contain an array of file patterns that will be included when the package is packed and subsequently published to the npm registry.private
: Ensure this is set to false
as our package is intended to be public.publishConfig
: The access for this should be set to public
.
After these configurations, your package.json
should resemble the following:
{
"name": "tokky",
"version": "1.0.0",
"description": "Node js logger package",
"main": "dist/index.js",
"scripts": {
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --minify --outfile=dist/index.js",
},
"files": [
"dist"
],
"bin": {
"tokky": "./dist/index.js"
},
"keywords": [
"logger",
"nodejs",
"tokky"
],
"private": false,
"author": {
"name": "Anton Kalik",
"email": "[email protected]",
"url": "https://idedy.com"
},
"publishConfig": {
"access": "public"
},
"license": "MIT",
"engines": {
"node": "18.x.x"
},
"devDependencies": {
"esbuild": "^0.19.2",
"eslint": "^8.49.0",
"prettier": "^3.0.3"
}
}
package.json after initial setup
Additionally, let’s add two ignore files:
.idea
node_modules
dist
.gitignore
and for npm:
.idea
/src/
/node_modules/
/test/
/.nvmrc
.github/
.npmignore
Lastly, I’ll outline my setup for ESLint. However, remember that the configuration may vary based on the specific requirements of your package.
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true,
},
extends: "eslint:recommended",
overrides: [
{
env: {
node: true,
},
files: ["src/**/*.js", ".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parserOptions: {
ecmaVersion: "latest",
},
rules: {},
};
.eslintrc.js config
Next, head to GitHub and establish a new repository. Name it after your package.
Proceed by executing the subsequent commands:
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin [email protected]:<your_github_username>/tokky.git
git push -u origin main
Next, let’s craft a basic application and set it up for building. Inside the src
folder, generate an index.js
file and populate it with the following content:
#!/usr/bin/env node
const os = require('os');
const username = os.userInfo().username;
if (process.argv[2] === 'hi') {
console.log(`Hello ${username}`);
}
Simple script for package example
The concept is straightforward: executing my_package_name hi
should display “Hello [username].”
To validate this functionality, execute the command directly from your repository using:
node src/index.js hi
If the output aligns with expectations, it’s time to build the source:
npm run build
Successfully running this command will produce a dist
folder containing a minified index.js
file.
Execute Semantic Release, which will determine version bumps and handle the release process based on commit messages, requires environment variables (GITHUB_TOKEN
, NPM_TOKEN
) to operate correctly. The tokens are fetched from GitHub secrets, ensuring they remain confidential.
To set GITHUB_TOKEN
, navigate here:
Generate the token using a dropdown. Click on the new personal access token (classic) and set permission as in the picture.
Use your package name as shown below:
Once generated, copy the token value and keep it confidential — it’s crucial not to share this with others. Temporarily store this token securely, as we’ll need it shortly for the Semantic Release CLI.
To generate the NPM_TOKEN
, you first need an account on
https://www.npmjs.com/settings/<your_user_name>/tokens/new
and generate a “classic” token with the “publish” option.
Copy the generated value of the token and navigate to GitHub secrets:
https://github.com/<your_user_name>/<your_repo_name>/settings/secrets/actions/new
and put new secret as NPM_TOKEN
to repository secrets:
With our secrets now set up, we can configure GitHub Actions.
To automate our processes, we are going to use GitHub Actions. This is a CI/CD tool integrated within GitHub. It allows developers to automate workflows directly from their GitHub repositories, such as building, testing, and deploying applications. By defining workflows in YAML files, users can trigger actions based on specific events like push and pull requests or scheduled times, making the software development process more efficient and automated.
To begin, create a .github
directory at the root of your project. Within this directory, establish a workflows
subfolder.
Here, craft our configuration file named release.yml
and populate it with the following content:
name: Release package
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Semantic Release
run: npm run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
This workflow triggers a push event to the main branch. It’s configured to run the job on the latest Ubuntu virtual machine GitHub offers. While it’s not imperative to delve into every job, let’s spotlight some specific ones. Toward the end, note how we invoke npm run semantic-release
using the designated tokens.
For the automated release process, we are going to use Semantic Release. This tool handles versioning and package publishing based on commit message semantics. It follows the conventions of Semantic Versioning (SemVer) to determine version bumps (major, minor, or patch). By analyzing commit messages it eliminates the manual steps of versioning, ensures consistent version numbers, and streamlines the release process. Let’s set it up.
For that setup, we will use
npx semantic-release-cli setup
And follow the questions:
% npx semantic-release-cli setup
? What is your npm registry? https://registry.npmjs.org/
? What is your npm username? your_user_name
? What is your npm password? [hidden]
? What is your NPM two-factor authentication code? 00000000
? Provide a GitHub Personal Access Token (create a token at https://github.com/s
ettings/tokens/new?scopes=repo) ghp_your_token_here
? What CI are you using? Github Actions
You should already have your Personal Token. Simply input it when prompted. Similarly, the GitHub Actions we’ve set up will utilize the NPM_TOKEN
that we've previously established in the repository secrets. If you now check your package.json
, the version will display as:
"version": "0.0.0-development",
and new script:
"semantic-release": "semantic-release"
which was auto-generated by the Semantic Release CLI. We’ll need to enhance this script as follows:
"semantic-release": "semantic-release --branches main"
This indicates that releases will only be made from the main branch.
Additionally, Semantic Release generates a description based on the repository
field in your package.json
. This field offers details about the location of the package's source code.
"repository": {
"type": "git",
"url": "https://github.com/<your_github_username>/your_github_repo.git"
}
Now, let’s push all of our changes with:
git add . && git commit -m "semantic release" && git push
Semantic Release relies on the convention of structured commit messages to determine the type of version bump (major, minor, or patch) and generate changelogs. This commit convention is often called the “Conventional Commits” format.
For this configuration, we’ll need several plugins. Ensure your package.json
contains the following content:
"release": {
"branches": [
{
"name": "main"
}
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"releaseRules": [
{
"type": "feat",
"release": "minor"
},
{
"type": "fix",
"release": "patch"
},
{
"type": "refactor",
"release": "patch"
},
{
"type": "build",
"release": "patch"
},
{
"type": "chore",
"release": "patch"
},
{
"type": "minor",
"release": "patch"
}
]
}
],
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
]
]
}
package.json
For the setup commit format tool, we are going to use
npx commitizen init cz-conventional-changelog --save-dev --save-exact
This command will take a few minutes. Then update your package.json
with a new script:
"scripts": {
// ...
"commit": "cz"
},
and it’s time to utilize that script. Begin by executing git add .
, then run npm run commit
and provide the necessary details for your commit.
Here’s what that looks like:
? Select the type of change that you're committing: feat: A new feature
? What is the scope of this change (e.g. component or file name): (press enter
to skip) commit
? Write a short, imperative tense description of the change (max 86 chars):
(14) add commitizen
? Provide a longer description of the change: (press enter to skip)
? Are there any breaking changes? No
? Does this change affect any open issues? No
After that, do a git push
.
In GitHub actions, you will see that our commit failed because we still have not installed the rest of the packages for the automated commit message process.
npm i -D @semantic-release/commit-analyzer @semantic-release/release-notes-generator @semantic-release/npm @semantic-release/changelog
A crucial step, often overlooked in most references, is setting the workflow permissions. Navigate to https://github.com/<your_user_name>/tokky/settings/actions
and configure the permissions to allow GitHub actions to both read and write.
Next, let’s change things up a bit. Commit with a specific keyword, feat:
, followed by your message.
git add . && git commit -m "feat: my feature commit" && git push
Do you recall the releaseRules
within the package.json
? These rules dictate how we increment the version of our package release. With this in place, you can create a pull request using specific keywords like feat
, fix
, refactor
, and so on. Once this pull request is approved and subsequently merged into the main branch, it will initiate a trigger. This trigger then activates the GitHub action, automates the release process, and ensures your package is updated seamlessly.
The package has been successfully published, and the entire process has been automated for efficiency. To confirm the publication, head to your npm settings https://www.npmjs.com/settings/<your_user_name>/packages
and look under the packages section; there, you will find your newly published package.
Now, with a simple command like npx your_package_name hi
, you can immediately see the results of our development tests. Additionally, the package can be globally installed using the command npm i -g your_package_name
.
As we’ve seen throughout this article, while initial setups can be riddled with challenges, the reward lies in establishing a streamlined and consistent release process. Leveraging GitHub Actions simplifies these complexities, ensuring developers can focus on code quality rather than logistical intricacies.
Whether you’re just beginning your journey with public packages or have encountered setbacks in your publishing endeavors, there’s undeniable value in adopting a structured, automated workflow. By integrating Semantic Release, you’re ensuring consistent versioning and championing a future-forward approach to software development.
Here’s to seamless publishing, fewer headaches, and more time spent perfecting the code that drives our digital world forward.
Remember, it’s essential that both NPM_TOKEN
and GITHUB_TOKEN
are granted the appropriate permissions within GitHub Actions. Additionally, your package.json
should be correctly configured with settings for publishConfig
access, and ensure that the private
config is set to false
. If you encounter any issues or have insights, please don't hesitate to comment.
Repository:
Semantic Release CLI:
Commitizen: