Automated npm package deployments with Typescript and semantic-release

Image for post
Image for post
TypeScript, npm, and semantic-release

I’m a big fan of automating boring tasks. While it’s never been easier to create and share great software, handling the mental load of versioning and deployments can get tedious over time. Removing the subjectivity from this process can help to speed up developer velocity and decrease the potential for human error.

In this guide, we will:

  • 📦 Create a simple TypeScript npm package with Jest tests

📦 Creating our basic Typescript package

As someone with a tremendously uncommon name, I often find myself in the uncomfortable position of having to correct the way that my name is spelt. It would be great to use the liberating power of software to help others figure out if they are spelling my name incorrectly.

Let’s start our journey of creating a new npm package called is-chanon.

> yarn init

We’ll bring in some dependencies.

> yarn add typescript jest

And now, let’s create the star of the show.

// index.tsexport default function isChanon (str: string): boolean {
if (typeof str !== "string") return false;
return str.trim().toLowerCase() === "chanon";
};

With Jest already setup, we can also add some basic unit tests.

// index.test.tsimport isChanon from './index'test('is TRUE with "Chanon"', () => {
expect(isChanon('Chanon')).toBe(true)
})
test('is FALSE with bad spelling', () => {
expect(isChanon('channon')).toBe(false)
})

We can configure our tsconfig.json to output the compiled JavaScript files into a folder called dist.

// tsconfig.json{
"compilerOptions": {
"outDir": "./dist",
"declaration": true,
"typeRoots": ["./node_modules/@types"],
},
"exclude": [
"node_modules",
"dist",
"**/*.test.ts"
]
}

Finally, we can add the following scripts to your package.json file.

{
...
"main": "dist/index.js",
"scripts": {
"build": "yarn run lint && tsc",
"lint": "eslint --ignore-path .gitignore . --ext ts",
"test": "jest"
},
"files": [
"dist"
]
}

At this point, you may also want to optionally add eslint and/or prettier to further enhance your developer experience.

🏷️ A little bit about semantic versioning

Developers rely on our code. When we make updates, we want to respect this trust by accurately communicating the scope of any future changes.

Image for post
Image for post
A package with version 1.2.3 seen with semantic versioning

As seen above, we can be use semantic versioning to give additional context:

  • Major changes: Changes that break backward compatibility

In a package.json file, we can also use symbols to specify what version upgrade scope that we’re comfortable with when installing dependencies.

// a generic package.json file"dependencies": {
"packageA": "~1.1.1", // take latest patch release
"packageB": "^2.2.0", // take latest minor release
"packageC": "*3.0.0", // take latest major release
"packageD": "3.0.0", // take only 3.0.0
},

With all of this in mind, let’s make sure that our package is able to accurately communicate backwards compatibility.

💬 Enforcing consistent commit messages

To prepare our package for semantic versioning, we’ll setup a linter to ensure that all of our commit messages convey meaning that can be interpreted by both humans and machines.

To do this, we’ll use the Conventional Commit style paired with Husky for automated linting. Let’s bring in our dependencies.

> yarn add @commitlint/config-conventional @commitlint/cli husky

Now let’s add some additional hooks to our package.json.

// package.json {
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
}

Now our commits MUST follow the Conventional Commits style.

> git commit -m "chanon: add isChanon function"type must be one of [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] [type-enum]> git commit -m "feat: add isChanon function"1 file changed, 20 insertions(+), 5 deletions(-)

Two of these commit types are significant for semantic versioning.

  • feat: will be used to trigger a Minor release

In order to communicate a Major release, we can add the following line to our commit history:

BREAKING CHANGE: your message here

🤖 Setup automated releases with GitHub Actions

With our commits following the style that we want, we can now automate the package release and CHANGELOG update workflow by installing semantic-release and some additional plugins.

> yarn add semantic-release @semantic-release/git @semantic-release/changelog

Make sure to add these plugins in this order to our package.json under “release”.

// package.json{
"release": {
"plugins": [
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm", // publish packages to npm
"@semantic-release/git", // publish commit release assets
"@semantic-release/github" // publish GitHub release
]
},
}

Once that is done, we can create our Github actions release workflow file.

// .github/workflows/release.ymlname: Releaseon:
push:
branches:
- "master"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: yarn install - run: yarn build - run: yarn test - run: yarn semantic-release
env:
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Some important things to note from this workflow file:

  • release.yml should only be run once our pull request or commit has been merged into master.

Before this can work, we need to add secrets to our project on GitHub.

  • ACCESS_TOKEN - your GitHub personal access token with access to this repo.
Image for post
Image for post
semantic-release-bot automatically updating the changelog and project versioning

With all of this in place, merges to master will now trigger the automated workflow that will do the following:

  • Run all of our Jest tests

For a working code example, here is the actual code on GitHub: is-chanon

It feels great to automate all these little tasks from our busy lives 🎉

Software Developer from Canada 🇨🇦

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store