Ryan Schachte's Blog
Exposing deployment versions and commit hashes with Astro
December 24th, 2023

While developing software, I have been in many cases where I test my changes after a deployment only to be confused by the results. Why is the bug still present? Is this a cache issue? Oh! The build failed and my changes never deployed. For this reason, I always prefer to have some mechanism where I can sanity check the release version or commit hash tied to a deployment to know everything is running in the target environment as expected.

In this article, we’ll be using Astro to inject version metadata into our site at build time. We’ll also explore setting up a static route to view this data in a less intrusive way for more professional projects that wouldn’t want to expose such information.

Obtaining version metadata

In my case, I have two pieces of data I’m interested in:

  1. Tagged release version from my package.json
  2. Git commit hash for existing change set/build.

Since I’m using Astro and my site is statically generated, we can easily pull this information into our components at build time.

Git commit hash

Obtaining the shortened hash is easy, you can simply run git rev-parse --short HEAD in your repo. To codify that into Javascript, we can do something like:

const hash = childProcess
  .execSync("git rev-parse --short HEAD")
  .toString()
  .trim();

Release version

Release versions are also nice to see, especially in production where typical workflows only publish based on a tagged commit that follows something like semantic versioning. In my case, I maintain the release version in my package.json underneath the version key.

package.json
{
  "name": "blog",
  "type": "module",
  "version": "0.0.3",
  ...
}

Given that the file already exists, we just need to pull it from disk during the build and inject it into our component. Let’s bring everything together now.

Injecting versions into our component

If you look at my footer, you’ll see the version metadata present.

Given that this is a personal site, I thought it was fun to include some of this info for the curious programmers. In the next section we will explore a less conspicuous approach with server routes.

Create a Footer.astro file if you don’t already have one or use the relevant component you’re interested in adding the metadata into.

Footer.astro
import childProcess from "child_process";
 
// obtain Git commit hash
const hash = childProcess
  .execSync("git rev-parse --short HEAD")
  .toString()
  .trim();
 
// obtain release version
import { version } from "../../../package.json";

My package.json is referenced relative to my Footer.astro component.

Since we now have a reference to both the hash and the version, we are free to do what we want with it. In my case, I’ll slap it straight into my HTML.

Footer.astro
---
import childProcess from "child_process";
 
// obtain Git commit hash
const hash = childProcess
  .execSync("git rev-parse --short HEAD")
  .toString()
  .trim();
 
// obtain release version
import { version } from "../../../package.json";
---
 
<div class="footer">
    <div class="versions">
      <span>Version: {version}</span>
      <span>Hash: {hash}</span>
    </div>
</div>

Custom server endpoints for versioning

Not everyone wants low-level development information leaking into their beautiful landing page. To workaround this, we can use the same principles but expose the data via a custom static file endpoint in Astro.

Crack open your src/pages directory and add a file called version.json.[js,ts]. In my case, I will create version.json.js.

Note that the file extension of [js, ts] will be stripped, but json will not. I was having issues forcing the content-type header on the response without json present as it would yield application/octet-stream. Bug report: https://github.com/withastro/astro/issues/9514

Static file endpoints are super cool because they allow us to hit them live on our site with a simple GET block like so:

src/pages/version.json.js
import childProcess from "child_process";
 
// obtain Git commit hash
const hash = childProcess
  .execSync("git rev-parse --short HEAD")
  .toString()
  .trim();
 
// obtain release version
import { version } from "../../../package.json";
 
export async function GET({ params, request }) {
  const versionMetadata = {
    hash,
    version,
  };
  return new Response(JSON.stringify(versionMetadata));
}

From here, if you run npm run dev, you should be able to visit this from the local url similar to http://localhost:4321/version.

curl -s http://localhost:4321/version.json | jq
{
  "hash": "105fced",
  "version": "0.0.3"
}

You can try it here live: https://ryan-schachte.com/version.json

Care to comment?