Avatar

Rick Cogley

Make a dynamic Github profile with Lume SSG!

Github's Special Profile Repos

Github has a special feature where, the README.md from a repository with the same name as your Github username is displayed on your Github profile. My Github handle and profile is @rickcogley, and you're looking at a page served via Github Pages from that very repository. In my case, it's rickcogley / rickcogley.

What readme?

The Automation Concept

Lately I'm using possibly the world's coolest Static Site Generator «Lume ルメ», and its companion Vento templates, both coded by Óscar Otero. I had a thought that if I could use a Vento template to generate a markdown file in my rickcogley repository, I could somehow automatically copy the markdown file into the repository root, so that it appears on my Github profile.

It turns out, per Oscar, that all you need to do is set the Vento template's url to the name of your desired markdown file: url: my-readme.md. When Lume generates your site, it will just populate the readme with the content of the template. However with Vento, you can do SSG-template-y things like loop through lists, or pull in data from rss feeds or json sources, such as REST APIs.

Getting it done with Lume

Of course if you search, there are plenty of ways to make a dynamic profile readme, but how do we do it with Lume? Here's the overview:

Setting up the Data

First take a look at the "Today is" line in the repo-readme.vto Vento template.

**Today is:** {{ todaysDateENUS }}{{ set today = todaysDateYYYYMMDD }}{{ if holidays[today] }} ({{ holidays[today] }}){{ /if }}

It's referencing the constants todaysDateENUS and todaysDateYYYYMMDD, and checking a dataset "holidays" if there is any data that matches "today". The constants and data are set up in the file _data.ts. For example:

export const todaysDateYYYYMMDD = `${new Date().toISOString().split("T")[0]}`;

This sets up a date in YYYY-MM-DD format, because that's what the holiday dataset has to match on.

As for the holidays, they are fetched into an object holidays which the Vento template is parsing.

const response = await fetch('https://holidays-jp.github.io/api/v1/date.json', {
  method: 'GET',
  ...

If you needed to access a protected API, you could put its access token in a repository variable, and access it as a bearer token in the header of your get.

Regarding RSS, I used "parse feed" from a library on denoland/x. This part:

import { parseFeed } from "https://deno.land/x/rss/mod.ts";
async function fetchAndConvertRSS(url: string, limit: number) {
  // Fetch the RSS feed
  const response = await fetch(url);
  ...

Then the feed can be fetched into a const to use in the Vento template. The Vento template just uses a for loop to parse the feed's json and put it into markdown li elements.

## Latest Statuses:
{{ for item of statuses.entries }}* [{{ item.description.value }}]({{ item.id }})
{{ /for }}

Automate Generation

To automate the generation, we just use a github actions template, .github/workflows/update-profile-readme.yml which triggers Lume to build the site which copies the README.md into place, commits the new file, publishes to GH Pages. The actions template is triggered on push or PR, "workflow dispatch" which means you can also manually trigger, and on a cron schedule.

on:
  push:
    # Run on main branch pushes or PRs
    branches: [main]
  pull_request:
    branches: [ "main" ]
  # Allow to manually trigger the workflow
  workflow_dispatch:
  schedule:
    # Rebuild every day at 9:06 PM UTC
    - cron: "6 21 * * *"

How to get yours?

Go to my profile repo, and click "use this template" to make your own copy. If you don't have a profile repo already, save the repo as your username. Then tweak it how you like including updating the RSS feeds. Or, setup Deno and Lume, and copy in the files manually. Just Edit the repo-readme.vto Vento template in markdown format, save, commit and push. If the GH action is working right, Lume will generate the readme file and copy it into place, after which you can see it on your personal repo.

You can see a more simple version without any website creation tooling, in my org eSolia's profile repo.

Is it good?

Yes.