Automate release management in Angular

Reading Time: 4 minutes

 155 total views

Introduction

Whether it is enterprise application or open source project, they will schedule for release to announce new features and bug fixes. The project will need new version and tag after the release to keep track of changes that occur between two releases.

When manager ask development team the deliverable of the current release; change log becomes handy because developers can present it to manager to review.

However, making a change log by hand is time consuming because developers have to go through git log and choose changes that have great values such as enhancements and bug fixes.

Developers are lazy and if they can find tool that can generate change log, they will use it without hesitation.

standard-version is an open source library that fulfills the requirement and you will love it if you have already written commit messages according to commitlint convention.

The effort to add standard-version is minimal and you can find all the steps in this blog post.

Install dev dependencies

npm i --save-dev standard-version

Add scripts to package.json that run to cut new release for an Angular application

// package.json
{
  "release": "standard-version -t '' -a",
  "release:patch:dryrun": "npm run release  -- --dry-run --release-as patch",
  "release:patch": "npm run release  -- --no-verify --release-as patch",
}

npm run release uses standard-version to cut new release, generate CHANGELOG.md and add new tag. The flag -a commits all generated changes while -t '' replaces the prefix of tag from v to blank string.

npm run release:patch:dryrun tests patch release in dry mode. When the script is successful, the simulation bumps the version of package.json and package-lock.json, outputs git commits to CHANGELOG.md and tag the new version to main branch.

Example of standard-version in dry run mode:

Configurations of of standard-version

standard-version supports lifecycle scripts and one of them is posttag that is executed after adding new tag. The posttag event is the ideal hook to push both commit and tag to repository automatically.

Moreover, the library has the option to show the type of commits in the change log. My preference is to output features and bug fixes to the file to highlight important changes in the new release.

We can find the configurations in .versionrc.json:

// .versionrc.json
{
  "types": [
    { "type": "feat", "section": "Features" },
    { "type": "fix", "section": "Bug fixes" }
  ],
  "scripts": {
    "posttag": "git push --follow-tags --force"
  }
}

Commit messages begin with feat: prefix belongs to Feature section whereas fix: prefix goes to Bug fixes section.

git push --follow-tags --force command force push commit and tag to my repository.

Cut a new release

The next feature of the project is adoption of Tailwind CSS and apply their CSS styles to beautify the components. The results look good in storybook; therefore, I decide to create a 0.0.7 release.

The release process is straightforward: npm run release:patch script automates the process from bump version, tagging to change log update.

The –no-verify flag ensures that husky hooks do not run in the commit step of the release and the release process is expected to complete quickly.

Change log accumulates features and bug fixes in release 0.0.7.

If I click any git commit id, I will see all the changes in the files.

One script replaces all the manual work of version management.

Final thought

In the past, developers are responsible for post-release activities such as version increment and adding version tag. Sometimes, developers forget because they are busy to implement new features and fix bugs. Nowadays, there are plenty of tools to facilitate release management and developers can issue a single script to automate the mentioned tasks. There is no excuse of not defining the standard procedure of release at work or in open source projects

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular, architecture and good engineering practice.

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. Standard version: https://github.com/conventional-changelog/standard-version

Improve Angular code with Betterer

Reading Time: 5 minutes

 305 total views

Introduction

In software development, when developers work on a project for a period of time, they tend to add code smell into the code that Eslint rules identify as problems. When architect adds a new Eslint rule to fix these errors, npm run lint reports error messages in terminal that require immediate attention.

I have two approaches to handle Eslint errors at work.

When npm run lint returns few errors, I fix all of them at once, verify on my machine and push the codes to remote repository.

When the number of errors is significant, I choose a few to fix, turn off the Eslint rule and git push the code to remote repository, Then, I enable the rule, re-run npm run lint and repeat the process. Therefore, the process continues until the Eslint rule passes and I can enable the rule permanently in the project.

Approach two is tedious and it disrupts the flow of developer by enabling and disabling the rule to fix issues. Fortunately, Craig Spence created betterer to improve the code base incrementally and I can throw approach two out of the window.

Add code smell to Angular

I add code smell to the project to demonstrate betterer. The example calls async/await statements in a for loop to sum some numbers in ngOnit function.

sum = 0
async ngOnInit(): Promise<void> {
  for (let i = 1; i <= 4; i++) {
    this.sum += await this.square(i)
    this.sum += await this.cube(i)
  }
}

private async square(num: number): Promise<number> {
  return Promise.resolve(num * num)
}

private async cube(num: number): Promise<number> {
  return Promise.resolve(num * num * num)
}

Executing await in a loop is code smell and Eslint even has a no-await-in-loop rule to check for its existence.

Next, I will show the readers how to write betterer test to look for await in a loop and to improve the test values by refactoring the code.

Install dependencies

npx @betterer/cli init
npm i --save-dev @betterer/angular @betterer/eslint

The npx command generates betterer script in package.json and creates a blank betterer.ts for the tests as a result.

package.json
{
  "betterer": "betterer"
}

Write betterer tests for Eslint and Angular

Writing tests for Eslint and Angular is very simple because betterer library provides eslint and angular functions that accept the name of the rule and its options.

// betterer.ts

import { angular } from '@betterer/angular'
import { eslint } from '@betterer/eslint'

export default {
  'no more await in loop': () => eslint({ 'no-await-in-loop': 'error' }).include('src/**/*.ts'),
  'stricter template compilation': () =>
    angular('./tsconfig.json', {
      strictTemplates: true,
    }).include('src/**/*.ts', 'src/**/*.html'),
}

eslint({ 'no-await-in-loop': 'error' }).include('src/**/*.ts') test looks for await in any loop in TypeScript files of src folder and outputs error messages.

Then, we execute npm run betterer to run the tests and store the initial values in betterer.results

“no more await in loop test” fails and finds two await calls in the for loop. Therefore, our task is to refactor ngOnInit to get rid of them.

(Optional) Run betterer in pre-commit hook

If your project has husky installed, you can run betterer precommit in pre-commit hook to update the values in betterer.result file

Precommit requires configurations in package.json, gitignore file and eventually pre-commit husky file

// package.json

{
    ....
    "betterer": "betterer --cache",
    "betterer:precommit": "betterer precommit --cache"
}

Betterer normal and betterer precommit tests cache ids to .betterer.cache to bypass checking on unmodified files. Therefore, the tests complete faster and developers do not have to wait forever.

We add betterer.cache to gitignore to ensure we don’t not accidentally commit it to our repo

// .gitignore

.betterer.cache
// pre-commit  husky file

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
npm run betterer:precommit

Whenever we commit changes, betterer re-run the tests and update the values in .betterer.results file

Refactor code to improve the values of betterer

// food-shell.component.ts
const sumAndCubePromises = await Promise.all(
  [1, 2, 3, 4].map(async (i) => (await this.square(i)) + (await this.cube(i))),
)

this.sum = sumAndCubePromises.reduce((acc, value) => acc + value)

I refactor the codes to create all promises and obtains the results in await Promise.all(). The result is an array of numbers and we compute the total by Array.reduce().

Code rewrite eliminates both for loop and await calls; thus, the betterer values get better.

The “no more await in loop” test has met its goal while “stricter template compilation” test stays the same.

Since the code base does not have await in loop, I can delete the test and add the eslint rule in the configuration file

Remove betterer test that has met it’s goal
Add no-await-in-loop rule in eslintrc.json configuration file

After I commit the latest changes, betterer is run again to update betterer.results file.

The terminal displays the result of the only test and the test value stays the same.

Bonus: Resolve conflicts in betterer.results

We have a large team at work and we constantly deal with merge conflicts in .betterer.results file. Fortunately, the library provides merge functionality to resolve them automatically.

{
   ....
   "betterer:merge": "betterer merge"
}

Type npm run merge and the conflicts should disappear. As precaution, we run npm run betterer to recalculate the values before code push.

Final thought

Betterer allows developers to add tests to improve codes in stages. These tests stay on until the issues are resolved. When one issue is fully resolved, we can remove the corresponding test and include the rule to eslintrc configuration file. One benefit is to make better codes without widespread changes. The other benefit is prevent massive code refactoring that breaks existing functionality.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular, architecture and good engineering practice.

Resources:

  1. Repo: https://github.com/railsstudent/ng-spanish-menu
  2. Betterer: https://phenomnomnominal.github.io/betterer/docs/introduction
  3. Angular Nation and Betterer w/Craig Spence: https://www.youtube.com/watch?v=BCdDEhNWpUU
  4. Craig Spence’s twitter: https://twitter.com/phenomnominal