Add a Pre-Commit Git Hook to a Node.js Repo with Husky

We can easily add a pre-commit hook to a NodeJS repository using the Husky package. The example below shows how this works.

First install Husky:

$ npm install husky

Now add a file .huskyrc in the root of your repo with the JSON below:

{
  "hooks": {
    "pre-commit": "echo 'commit event detected'"
  }
}

We can test this out to prove it is working:

$ git add .
$ git commit -m "hook testing" 
husky > pre-commit (node v11.6.0)
commit event detected

The hook executed but the commit succeeds.

To demonstrate how the hook can fail and prevent commits, we can set the exit status code of the command:

{
  "hooks": {
    "pre-commit": "echo 'commit event detected'; exit 1"
  }
}

Now we will get the following result:

$ git commit -m "hook test"
husky > pre-commit (node v11.6.0)
commit event detected
husky > pre-commit hook failed (add --no-verify to bypass)

This shows that commits will not be allowed if the exit status code of the command in the pre-commit hook is 1 (error).

Naturally we want to do something useful with the pre-commit hook, e.g. making sure all unit tests pass: a command like Jest will set the exit status to 1 (error) if there failing tests. Assuming we have defined npm test with a command like Jest or similar we could set our hook to:

{
  "hooks": {
    "pre-commit": "npm test"
  }
}

Now committing code which fails unit tests will be impossible, unless we explicitly bypass the hook like this:

$ git commit -m "hook test" --no-verify

References

https://www.npmjs.com/package/husky

JavaScript ES6 const Keyword Allows Changes

The ES6 JavaScript const keyword prevents re-assignment of a reference, but it does not actually prevent data modification of any object being referenced.

Of course, a primitive value declared const cannot be changed:

> const pi = 3.1415926
> pi = 3
TypeError: Assignment to constant variable.

However, objects declared with const can still have their properties changed:

const myObject = {
  a: 5, 
  b: 10, 
  c: 15
} 

myObject.b = -1 // no error

console.dir(myObject)

Result:

{ a: 5, b: -1, c: 15 }

Similarly, arrays declared with const in ES6 can be edited! They simply cannot be re-assigned.

const arr = [1, 2, 3, 4, 5]

a[1] = -1 // this is fine

console.dir(arr)

Result:

[1, -1, 3, 4, 5]

Using methods to manipulate the const array is still allowed:

const arr = [1, 2, 3, 4, 5] 

arr.pop() // allowed

console.dir(arr)

Result:

[1, 2, 3, 4]

The following use of the delete operator is ok with const as well:

const arr = [10, 20, 30]

delete arr[1] // works

console.dir(arr)

Result:

[10, <1 empty item>, 30]

Note that delete leaves gaps in the array; for objects it will leave null.

Countless more examples are possible but the key point is: arrays and objects being referenced by a const variable can still be mutated.

Promise.all with async/await in JavaScript

Promise.all can be used to await an array of promises, all of which can resolve at any time individually. Imagine launching multiple simultaneous requests to many APIs for example, all with varying unpredictable response times.

Promise.all will only resolve once all of the promises in the input array have resolved.

The following example shows two functions which return in 1 and 2 seconds respectively. Note that we are using the sleep function from npm here for maximum simplicity. This can be installed with: npm install sleep.

const sleep = require('sleep')

const f = async () => {
  sleep.sleep(1) // 1 second
  console.log('f sleep done, returning...')
  return 'f result'
}

const g = async () => {
  sleep.sleep(2) // 2 seconds
  console.log('g sleep done, returning...')
  return 'g result'
}

(async () => {
  const promise1 = f()
  const promise2 = g()

  const allResults = await Promise.all( [promise1, promise2] )

  console.log('All results: ' + JSON.stringify(allResults))
})()

Note the IIFE because we cannot have a top-level async function using the async/await syntax.

If we don’t need the return values, we can simply do:

await Promise.all( [promise1, promise2] )

To capture only some of the results, we can use destructuring assignment:

const [ result1 ] = await Promise.all( [promise1, promise2] )

The combination of Promise.all and async/await provides a very readable and compact way to make many asynchronous calls and use the results.

 

Reduce verbose output and show only failing tests in Jest

Sometimes the output of running Jest unit tests is a bit too verbose, with the pass and fail statuses of all tests as well as the test coverage table.

To prevent outputting the code coverage report table, use:

jest --collectCoverage=false

(This assumes Jest is configured to show the code coverage report already.)

We can reduce the verbosity of the Jest output aside from the coverage table by using a custom reporter. Jest Silent Reporter is a good choice for this. To install it:

$ npm install jest-silent-reporter

Then run the tests with:

$ jest --reporters jest-silent-reporter

Now we will see only the failing test output.

The coverage report can still be shown after the silent reporter output.

So, for the minimum output possible we can combine the silent reporter and omit coverage report:

$ jest --collectCoverage=false --reporters jest-silent-reporter

We can add this command to our package.json to have the option to run a silent minimal output for all our tests easily, for example like this:

"scripts": {
  "unit:silent": "jest --reporters jest-silent-reporter --collectCoverage=false" 
}

Then we can just run:

$ npm run unit:silent

References

https://www.npmjs.com/package/jest-silent-reporter

 

Send email to a mailing list using the Mailgun API with cURL

Mailgun provides one of the nicest APIs for email communication.

This example assumes we have a mailing list already created and want to send an email to all recipients via the Mailgun API.

The way to do this is to POST to /messages and use the mailing list address as the recipient address in the API call. Here is the call being made via cURL:

$ curl --request POST \
 --url https://api:MY_API_KEY@api.mailgun.net/v3/mg.mydomain.com/messages \
 --form 'from=Name <noreply@mg.mydomain.com>' \
 --form "text=My email body" \
 --form "subject=My email subject line" \
 --form to=my-email-list@mg.mydomain.com \
 --form user=api:MY_API_KEY

Note that this API call uses form fields and not any JSON payload.

A successful response will look something like:

{
   "message": "Queued. Thank you.",
   "id": "..."
}

References

https://www.mailgun.com/email-api

Computed Property Names in JavaScript

Computed Property Names is an ES6 feature which allows the names of object properties in JavaScript object literal notation to be determined dynamically, i.e. computed.

JavaScript objects are really dictionaries, so it was always possible to dynamically create a string and use it as a key with the syntax object[‘property’] = value.

However, ES6 Computed Property Names allow us to use dynamically generated names within object literals. Example:

const myPropertyName = 'c'

const myObject = {
  a: 5,
  b: 10,
  [myPropertyName]: 15
} 

console.log(myObject.c) // prints 15

To stress that expressions can be used directly as computed property names, another example:

const fieldNumber = 3

const myObject = {
  field1: 5,
  field2: 10,
  ['field' + fieldNumber]: 15
}

console.log(myObject.field3) // prints 15

This can be very handy.

One more variation is to use template literals (string interpolation) for the computed property names — note that this still requires the square bracket syntax, however:

const fieldNumber = 3

const myObject = {
  field1: 5,
  field2: 10,
  [`field${fieldNumber}`]: 15
}

console.log(myObject.field3) // prints 15

 

Access the Clipboard Through the Terminal in macOS

macOS provides a very handy way to share data between graphical (GUI) applications and command-line (terminal) tools.

This is especially useful for working with APIs using JSON (or XML) which require large payloads.

The two binary commands are pbcopy and pbpaste: short for Pasteboard Copy and Pasteboard Paste respectively (the macOS term for Clipboard).

  • pbcopy takes the standard input and copies it to the pasteboard
  • pbpaste takes the pasteboard data and copies it to the standard output

Suppose we have a sample payload in file.json. We can send its contents to the clipboard by piping the data to pbcopy:

$ cat file.json | pbcopy

Now, we simply press Command+v in a GUI app like Postman or Insomnia.

In reverse, we can press Command+c in a GUI app and then in the terminal:

$ pbpaste
{
  "data": "test"
}

Or, redirect the standard output to a file:

$ pbpaste > file.json

Or, pipe the data straight into a another command, like the super-handy JSON viewing tool fx:

$ pbpaste | fx

Note that these commands are macOS specific; they do not exist in Linux.

Variations and Gotchas of the Arrow Function Syntax in JavaScript

ES6 introduced into JavaScript the extremely useful syntax for defining functions known as arrow functions. This refers to the notation:

argument => result

 

Arrow function notation can be used for both named and unnamed (anonymous) functions. Arrow functions allow for very compact definitions.

Example: define the successor function:

const f = ( x => x + 1 )

The parentheses around the function can be dropped:

const f = x => x + 1

It is slightly unclear when seen for the first time but soon becomes very natural.

Note that the above does not require a return; the expression on the right hand side of the arrow is the returned value.

If more than one statement is needed to define the function, braces must be introduced. The above example can also be written as:

const f = x => {
  const result = x + 1
  return result
}

Note that the following is problematic:

const f = x => { x + 1 } // does not return

This is very important: if braces are used, a return statement is necessary.

In short, if braces are present, a return statement is needed; if more than one statement is used in the function, braces must be used.

Another valid form:

const f = x => { 
  return x + 1
}

The argument list can be made explicit with parentheses:

const f = (x) => {
  return x + 1
}

This is required for functions of  multiple arguments, as in the following example:

const sum = (x, y) => x + y

The parentheses around the argument list cannot be dropped in this case.

A function with zero arguments also requires the parentheses around the (empty) argument list as follows:

const f = () => value

 

Use git grep to search for strings across all branches

Git includes a grep command to search through commits to a repo as well as the local files in the repo directory: git grep. Sometimes it is useful to search for a string throughout an entire repo, e.g. to find where an error message is produced.

By itself, git grep searches the currently checked out branch and local files for a regular expression. Sometimes the text we want to find is on another branch, which may actually be deployed and running in some environment. Therefore, it is useful to search through all branches of a repository to be certain where some string of interest is generated, a file is written, etc.

To search through all branches of a git repo for a string, use:

$ git grep str $(git rev-list --all)

To search through all branches ignoring string case:

$ git grep -i str $(git rev-list --all)

This can also be done with regular expressions:

$ git grep -i RegEx $(git rev-list --all)

Jest: Expect a String Result to be Numeric

JavaScript unit tests in Jest may need to assert that a string result from a function is numeric, i.e. a string containing only numbers.

Since we do not have a convenient matcher like toBeNumeric(), none of the ways currently looks quite perfect, but several possible ways are below, some more readable than others.

First, suppose we have a simple function to return a numeric string:

func.js:

const func = () => {
  return "12345"
}

module.exports = func

Then our unit test in the first form: using toBeNaN(), negated, combined together with the Number constructor:

func.spec.js:

const func = require('./func')

describe('numeric string result', () => {
  it('should be numeric', () => {
    expect(Number(func())).not.toBeNaN() // numeric
  })
})

 

The above is somewhat clear, but the following may be more readable to some:

const func = require('./func')

describe('numeric string result', () => {
  it('should be numeric', () => {
    expect(Number.isNaN(func())).toBe(false)
  })
})

 

Another way is to use a Regular Expression (regex) to expect a numeric string, however, this is most readable for integers:

const func = require('./func')

describe('numeric string result', () => {
  it('should be numeric', () => {
    expect(func()).toMatch(/[0-9]+/) // string of integers
  })
})

The regex would become more complex if we wanted to accept any kind of numeric notation, including real numbers or large numbers with exponential notation, for example.

Perhaps the highest readability is achieved by breaking out the numeric check into its own convenience function, as below:

const func = require('./func')

const isNumeric = (string) => {
  return !Number.isNaN(string)
} 

describe('numeric string result', () => {
  it('should be numeric', () => {
    expect(isNumeric(func())).toBe(true)
  })
})