I am now published! (in NPM)
I published by first NPM package yesterday!
It all started when I was pushing some commits to GitHub that other people had helped me on, but since I wasn’t creating the commit from a pull request, the Co-authored-by commit trailer wasn’t generated automatically.
In the case that you don’t know, the format for this trailer is:
Co-authored-by: [user full-name] <[user email]>
And neither of those attributes are easily found if you don’t already know them. You have to click through github profile pages, copy and pasting precariously.
So as a fun example I decided to make an app to do this for me. It had the following requirements:
- Had to be easy, I just wanted to type in the github user name (e.g. neenjaw) and then get a string back (e.g.Co-authored-by: Tim Austin <tim@neenjaw.com>)
- It had to be live, pulling the data from github using its rest-api interface
- It had to be secure, not storing my github personal access token in plain text
From those, the most difficult one seemed to be the third – securely handling my github personal access token.
Loose Lips Sink Ships
A keychain is the perfect place for something like a personal access token and I do most (if not all) development in Ubuntu (either Ubuntu proper or WSL2 Ubuntu), so I knew about the libsecret interface for storing secrets using DBUS channels. I had never interacted with it before directly. It is relatively straight forward though. You can either use C-lang bindings, or instead you can use libsecret-tools to get a command-line tool to interact with it.
From there using secret-tool you can store a secret (echo "secret" | secret-tool store --label="your label" {attribute} {value}), retrieve a secret (secret-tool lookup {attribute} {value}), or clear it (secret-tool clear {attribute} {value}). So using nodejs, this is as easy as executing the command string in a child-precess and running each command:
const { exec } = require('child_process')
// Getting a secret from libsecret
exec(`secret-tool lookup {attribute} {value}`, (error, stdout, stderr) => {
  if (error) {
    // Handle the error
    return
  }
  if (stderr) {
    // Handle the stderr output
    return
  }
  // use the stdout output
})
This is a pretty standard nodejs pattern, an asynchronous function, taking a callback to then handle the result whenever the process should finish. They are pretty easy to use, but over time they produce a lot of complexity and deeply nested structures – a.k.a. Callback Hell. So just for fun I wanted to wrap this into a javascript Promise and then practice using ES7’s async/await:
// The `exec` call wrapped in a promise
const getSecretValue = (attribute, value) => {
  return new Promise((resolve, reject) => {
    exec(
      `secret-tool lookup ${attribute} ${value}`,
      (error, stdout, stderr) => {
        if (error) {
          return reject(error)
        }
        if (stderr) {
          return reject(stderr)
        }
        resolve(stdout.trim())
      }
    )
  })
}
It’s a bit deep to get into promises and
async/awaithere, but suffice to say they are patterns of writing asynchronous in a top-down or function-chaining way.
This function returns a promise which can then be used pretty easily:
getSecretValue('my-secret-app', 'token')
  .then((secretToken) => {
    // do something with it here
  })
  .catch((error) => {
    // handle the error here
  })
Now that we have a mechanism to safely handling my github personal access token, let’s get the information from GitHub!
Using Octokit
Github as several maintained packages to interact with its REST-api, and I just went with their @Octokit/rest npm package to do a basic GET request for the user’s information with token authentication.
const { Octokit } = require('@octokit/rest')
async function getCoauthorCommitTrailer(user) {
  const octo = new Octokit({ auth: token })
  const response = await octo.request(`/users/${user}`)
  const { name, email } = response.data
  return `Co-authored-by: ${name} <${email}>`
}
Neat! Super simple! But what if I want more than one user at once? Let’s amp it up with a higher order function!
async function getCoauthorCommitTrailers(...usernames) {
  const responses = await Promise.all(
    usernames.map((username) => oktokit.request(`/users/${username}`))
  )
  function toCommitTrailer(response) {
    const { name, email } = response.data
    return `Co-authored-by: ${name} <${email}>`
  }
  return responses.map(toCommitTrailer) // This returns the array in a promise
}
getCoauthorCommitTrailers(neenjaw, neenjaw_friend).then((trailers) =>
  trailers.forEach((trailer) => console.log(trailer))
)
Now we can get a bunch at once!
Wrapping it up
Putting that all together, I wrapped it in a little CLI script using the yargs npm package to parse command line options to set/unset/show the saved token. Updated my package.json file to include my global install script, then published it! Check out my npm package here: Give Credit Where Due
It was neat to put into practice things that I’ve seen done, but never implemented my self.
I really like that about programming – solving practical problems!