December 8, 2019

Enforcing Commit Message Format Using Husky

TL;DR: Github repo of the demo.

In the past, I've implemented githooks to check for the commit message format – mainly that it had to begin with a Jira ID. It works well, but the initial setup is a bit clunky – I had a separate repo to store githooks, and a batch file in the main repo to set core.hooksPath to the path of that other repo. Each developer had to pull down the githooks repo in a well-known folder path relative to the main repo and run the batch file...

I've recently introduced Husky to force linting locally before developers can commit (and push) their changes. It meant that I couldn't use the githooks I had for the commit message check, since Husky overwrites them during its installation, so I created a simple script to check for the commit message format and then added as a Husky task.

The demo repo is in Github. I could've used a shell script, or a PowerShell script, but since my team members have Windows and Mac, decided to write it in JavaScript and run it via node, since each front-end developer is guaranteed to have it for running npm. The script uses terminal color codes to highlight errors so that developers can quickly notice it.

  /*
    Assumptions:
    1. Only working with commit messages (tiny commit message files), so no need to worry about large file handling such as using a buffer.
    2. Assumed utf8 format, which is the git default unless changed explicitly (https://git-scm.com/docs/git-commit/#_discussion).
  */

  // Used for console color coding.
  const colorReset = "\x1b[0m";
  const colorBrightRed = "\x1b[1;31m";
  const colorGreen = "\x1b[32m";

  const fs = require("fs");

  // Customize the regex pattern below to match your desired commit message format.  The example pattern below checks
  // that commit messages begin with a Jira ID where the Jira Project Key is "JIRA".
  const pattern = /^JIRA-[0-9]+: .*/;

  // The environment variable is set by Husky.  The value is: .git/COMMIT_EDITMSG
  // Refer to https://git-scm.com/docs/githooks#_hooks for information on how git changes the working directory before
  // executing hooks.
  fs.readFile(process.env.HUSKY_GIT_PARAMS, "utf8", (err, data) => {
    if (err) throw err;

    if (data.match(pattern)) {
      console.log(colorGreen + "check-commit-msg: Passed." + colorReset);
    } else {
      console.log(
        colorBrightRed +
          'check-commit-msg: Failed - please begin your commit message with a Jira ID, followed by ":" and space e.g., "JIRA-123: Added commit message check."' +
          colorReset
      );
      process.exit(1);
    }
  });

One thing I've noticed is that the commit message hook runs after the pre-commit hook. Since I have pre-commit hook that does the linting, it means I have to wait for the linting to finish, then the commit message hook will kick in to let me know whether the commit message is good or not. The official reference doesn't specifically call out the order of hooks, though it looks like it's documented in a top-down order. The official book does say "The pre-commit hook is run first, before you even type in a commit message." This is probably due to how git works internally, since the commit message is actually part of the commit object and not some metadata (as in other traditional source control systems).

Of course, this husky solution only works for front-end (JavaScript) projects. I'll be keeping the standard githooks for other back-end projects (C#, C++, etc.) we have for now.

Also, while working on this, found commitlint. Might be worth a look...

December 4, 2019

Setup Bash "ll" Alias in Windows

I'm used to ll more than ls. To get it working for bash in Windows (not WSL, but for bash that's usually installed in Windows, such as with git), follow the steps below:

  1. Start a bash shell prompt, e.g., via VS Code terminal or Git Bash prompt.
  2. Set the current directory to the your home directory (if not set already):
cd ~
  1. Add the alias to .bashrc:
echo "alias ll='ls --color=auto -alF'" >> .bashrc
  1. Restart the bash shell, and ll should now work. Here's an example:
$ ll
total 43775
drwxr-xr-x 1 dusklight 1049089        0 Nov 19 11:26  ./
drwxr-xr-x 1 dusklight 1049089        0 Dec  4 20:33  ../
drwxr-xr-x 1 dusklight 1049089        0 Nov  7 11:10  assembly/
-rwxr-xr-x 2 dusklight 1049089    67072 Jan  1  2018  bfsvc.exe*
drwxr-xr-x 1 dusklight 1049089        0 Jan  1  2018  Boot/
-rw-r--r-- 1 dusklight 1049089    67584 Dec  4 20:31  bootstat.dat
drwxr-xr-x 1 dusklight 1049089        0 Jan  1  2018  debug/
-rw-r--r-- 1 dusklight 1049089     8143 Nov  1  2018  setup.log
...

When you restart the shell, you might see a warning about .bash_profile not being found. If it was Git Bash, it will create it for you automatically.

To have colors for ls as well, create a separate alias for ls as ls --color=auto then set the ll alias after that with just -ilF parameters. For additional information, search for "default Ubuntu .bashrc".

For more ls options, check out the man page.

August 26, 2019

SemVer Prerelease and Metadata

SemVer for major, minor, and patch are simple to understand. What about prelease? Here's a quick post for the that, and also about the build metadata.

Prerelease

So, is there a predefined list of prereleases in SemVer? No, it can actually be anything (well, as long as it follows the limitation specified in the BNF). The following are common in the industry for published APIs:

  • alpha
  • beta
  • rc

So how do SemVer implementations, such as npm, know that alpha is before beta, and beta is before rc? As per SevVer 2.0 #11 spec, it's basically checking the ASCII string sort order — and alpha, beta, and rc happen to be in the correct sort order that we want.

Metadata

Sometimes you might see a date string appended to the version number, such as 1.0.0+20190826, and those are called build metadata, and should be ignored in SemVer comparison. Note that they must start with a plus sign, and prereleases must start with a hyphen.

Examples

Here are some examples using the semver package in interactive Node.js, including what happens when the prerelease characters are same but are different in lengths, and mixing capital letters, etc.

User@COMPUTER MINGW64 /c/dev/npm/semvertest
$ npm i --save-dev semver
User@COMPUTER MINGW64 /c/dev/npm/semvertest
$ node
Welcome to Node.js v12.7.0.
Type ".help" for more information.
> const semver = require('semver');
undefined
> semver.gt('1.0.0-aaa', '1.0.0-aaaa') // length difference
false
> semver.gt('1.0.0-abaa', '1.0.0-aaaa') // letter difference
true
> semver.gt('1.0.0-A', '1.0.0-a') // ASCII(A) = 65, ASCII(a) = 97
false
> semver.gt('1.0.0-aaa.1', '1.0.0-aaaa.5') // length difference variation
false
> semver.gt('1.0.0-a.256', '1.0.0-a.64') // number in prerelease
true
> semver.gt('1.0.0-a.aaa', '1.0.0-a.bb') // same length as above, but not a number
false
> semver.gt('1.0.0-a.3.b.8', '1.0.0-a.4.b.7') // period separation - not just last number.
false
> semver.eq('1.0.0-aaa+2019-08-26', '1.0.0-aaa+MetaDataHere') // Build metadata are ignored
true