GitHub Badges For The People!

GitHub Badges For The People!

What Are Badges

When someone enters your repo the first thing they will notice is your ReadMe. Badges give instant details about the health and status of your project. It's also a sign (at least for me) that project owners are giving that extra attention to detail to the repo which hopefully also reflects in their code and maintenance.

How Do They Work

Badges are just images, they are generated and updated dynamically. When you see one on GitHub, it's just an image pointing to a static link.

Example: image.png

// README.md
# [React](https://reactjs.org/) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/facebook/react/blob/master/LICENSE) [![npm version] ...

Head over to shields.io and check to see if they integrate with the technologies you use. If they do, then they took care of the generation for you and you don't have to read any further. You can start using badges immediately.

... if you are like me on the other hand you weren't so lucky.

Generating Badges Yourself

I've been investing time into using GCP (Google Cloud Platform) to host my own personal projects. Since there aren't any built in integrations to leverage shields.io to work with my cloud build, I had to come up with my own solution.

Badge-Package

The people behind shields.io were kind enough to include a npm package for building your own badges. npmjs.com/package/badge-maker is super simple to use.

Example image.png

const { makeBadge } = require('badge-maker')
const badge = makeBadge({
  label: 'coverage',
  message: '100%',
  color: 'bright-green',
}); // badge is a string that contains svg code (i.e) badge = "<svg>...</svg>"

List of color options are on the shields.io homepage.

Dynamically Generating Badges

Generating badges is pretty simple and you only do it ever so often. I thought this was a perfect use-case for Cloud Functions (AWS Lambda Equivalent).

Roadmap

Badge Printer.png

When merging to master I added a build step that gets called after my build is complete and its artifacts are stored. This step calls my cloud function to generate new badges and store them in my /latest bucket. My ReadMe is always pointing to the same image location on /latest, so the updates preformed on the images get reflected on my ReadMe automatically.

Cloud Function Code

package.json

{
  "name": "badge-printer",
  "version": "0.0.1",
  "dependencies": {
    "@google-cloud/storage": "5.7.4",
    "badge-maker": "3.3.0"
  }
}

index.js

/**
 * Creates a badge based on the contents of bucketName + path
 * and stores badge in our bucketName + path/badges bucket
 * @param {!express:Request} req HTTP request context.
 * @param {!express:Response} res HTTP response context.
 */
const { Storage } = require('@google-cloud/storage');
const { makeBadge } = require('badge-maker')
const storage = new Storage();

// entry point
exports.print = async (req, res) => {
  const { bucketName, path } = req.body;

  try {
    const packageJson = await readJsonFromStorage(bucketName, `${path}/package.json`);
    await buildVersionBadge(packageJson.version);
    res.status(200).send('SUCCESS');
  } catch (err) {
    res.status(500).send(err.message);
  }
};

/**
 * This is a utility function that makes reading json files
 * straight from cloud storage simple by just loading it into
 * memory w/o downloading it to the hd
 */
const readJsonFromStorage = async (bucketName, path) => {
  const stringifiled = async file => new Promise((resolve, reject) => {
      let buf = ''
      file.createReadStream()
        .on('data', d => (buf += d))
        .on('end', () => resolve(buf))
        .on('error', e => reject(e))
  });
  const file = await storage.bucket(bucketName).file(path);
  const rawString = await stringifiled(file);
  return JSON.parse(rawString);
};

// Generates and stores badges
const buildVersionBadge = async (version) => {
  const badgeFile = await storage.bucket(bucketName).file(`${path}/badges/version.svg`);

  const badge = makeBadge({
    label: 'version',
    message: `${version}`,
    color: 'blue',
  });
  return badgeFile.save(badge);
};

Executing Badge-Printer

So with my cloud function, named "badge-printer" available. All I had to do to invoke the generation of my badges is add the following step to my build process. cloudbuild.yaml

# Generate Github Badges
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  args: ['functions', 'call', 'badge-printer', '--data', '''{ "bucketName": "spacelys", "path": "sockets/outputs/latest" }''']

Since when I merge to master I'm always updating whats in latest I'm calling my cloud functions with.

gcloud call badge-printer --data '{ "bucketName": "spacelys", "path": "sockets/outputs/latest" }'

and my cloud function does the rest.

A word on GitHub cacheing

GitHub seems to cache images from what I've been noticing. If you notice that your badges aren't automatically updated when they should be, give it some time. It looks like GitHub takes it upon themselves to cache the images that you are referencing in your ReadMe. Also make sure that your badges are made publicly available where you store them.

Conclusion

Adding badges to your GitHub projects gives your repo a nice flair that users can appreciate. shields.io offers a lot of integrated solutions that can make integrating badges to your repo super simple. If not we can easily make our own with some of the steps defined here.