Automating GitHub Profile with Latest Blog Posts Using GitHub Actions

Streamline Your Workflow by Automatically Updating Your GitHub Profile with Latest Blog Posts

The full source code for GitHub Action is available on GitHub

As a developer, I'm always looking for ways to streamline my workflow and share my latest work more effectively. Recently, I updated my blog at markhazleton.com to include an RSS feed using some updated Node.js programming. With this feed in place, I decided to automate the process of displaying my latest blog posts on my GitHub profile using GitHub Actions.

In this post, I'll quickly review the steps I took to create the RSS feed and set up the GitHub Action to update my profile with the most recent blog posts.

GitHub Profile
Creating the RSS Feed with Blog Admin Tool

To create an RSS feed, I updated my Blog Admin tool to create an XML file with the latest blog posts whenever a new article is published.

For more information about the Blog Admin tool, check out my previous post Building a Web Application to Manage Your Blog Articles

Here is the updated code snippet from the Blog Admin tool that generates the RSS feed.

public void GenerateRSSFeed()
{
  try
  {
    string rssFeedPath = Path.Combine(Path.GetDirectoryName(_filePath), "rss.xml");
    var recentArticles = _articles.OrderByDescending(a => ConvertStringToDate(a.LastModified)).Take(10).ToList();
    using (XmlWriter writer = XmlWriter.Create(rssFeedPath, new XmlWriterSettings { Indent = true }))
    {
      writer.WriteStartDocument();
      writer.WriteStartElement("rss");
      writer.WriteAttributeString("version", "2.0");

      writer.WriteStartElement("channel");
      writer.WriteElementString("title", "Mark Hazleton Articles");
      writer.WriteElementString("link", "https://markhazleton.com/");
      writer.WriteElementString("description", "Latest articles from Mark Hazleton.");
      writer.WriteElementString("lastBuildDate", DateTime.Now.ToString("r"));

      foreach (var article in recentArticles)
      {
        writer.WriteStartElement("item");
        writer.WriteElementString("title", article.Name);
        writer.WriteElementString("link", $"https://markhazleton.com/{article.Slug}");
        writer.WriteElementString("description", article.Description);
        writer.WriteElementString("pubDate", ConvertStringToDate(article.LastModified).ToString("r"));
        writer.WriteEndElement();
      }

      writer.WriteEndElement(); // channel
      writer.WriteEndElement(); // rss
      writer.WriteEndDocument();
    }
    _logger.LogInformation("RSS feed generated successfully.");
  }
  catch (Exception ex)
  {
    _logger.LogError(ex, "Failed to generate RSS feed.");
  }

}

Setting Up the GitHub Action

With the RSS feed in place, I created a GitHub Action in my profile repository (`github.com/markhazleton/markhazleton`). This action fetches the latest posts from my blog’s RSS feed and updates my profile README with the top five articles.

Daily Automation
The Action runs every day at midnight (UTC) and can also be triggered manually. I used the the `cron` schedule to run the workflow at a specific time each day. So each night at midnight, the workflow fetches the latest blog posts and updates my profile README.
XML Parsing
It installs `xmlstarlet`, a command-line XML tool, to parse the RSS feed.
Fetch Posts
It fetches the top five latest blog posts from my RSS feed using `curl` and `xmlstarlet` and saves them to a file called `latest-posts.md`.
Update README
It then updates the `README.md` by replacing the content between the ` ` and ` ` tags with the new posts.
Commit Changes
If the `README.md` has been updated, it commits and pushes the changes back to the repository.

Here's the full GitHub Action workflow file that I used to automate my GitHub profile updates with the latest blog posts.

name: Update README with Latest Blog Posts
on:
  schedule:
    # Runs at 12:00 AM UTC every day
    - cron: '0 0 * * *'
    workflow_dispatch: # Allows manual trigger of the workflow
jobs:
  update-readme:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v3

    - name: Install xmlstarlet
      run: sudo apt-get install -y xmlstarlet

    - name: Fetch Latest Blog Posts
      id: fetch_blog_posts
      run: |
        # Fetch the latest blog posts from the RSS feed
        curl -s https://markhazleton.com/rss.xml | xmlstarlet sel -t -m '//item' \
        -v 'concat("- [", title, "](", link, ")")' -n | head -5 > latest-posts.md

    - name: Update README.md
      run: |
        # Read the latest posts into a variable
        latest_posts=$(<latest-posts.md)

        # Replace the content between the <!-- BLOG-POST-LIST:START --> and <!-- BLOG-POST-LIST:END --> tags
        awk -v latest_posts="$latest_posts" '
        BEGIN {in_blog_list=0}
        /<!-- BLOG-POST-LIST:START -->/ {print; print latest_posts; in_blog_list=1; next}
        /<!-- BLOG-POST-LIST:END -->/ {print; in_blog_list=0; next}
        !in_blog_list {print}
        ' README.md > updated_readme.md

    - name: Check if README.md was updated
      id: check_changes
      run: |
        # Compare updated README.md with the current one
        if ! diff updated_readme.md README.md > /dev/null; then
          mv updated_readme.md README.md
          echo "changes_detected=true" >> $GITHUB_ENV
        else
          echo "README.md is up to date. No changes needed."
          echo "changes_detected=false" >> $GITHUB_ENV
          exit 0
        fi

    - name: Clean up untracked files
      run: |
        rm -f latest-posts.md updated_readme.md

    - name: Commit Changes
      if: env.changes_detected == 'true'
      run: |
        git config --global user.name "github-actions[bot]"
        git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
        git add README.md
        git commit -m "Updated README with latest blog posts"
        git push
Testing and Debugging
After setting up the workflow, I ran it manually to ensure it worked as expected. There were a few issues initially with XML parsing and formatting, but those were resolved by tweaking the `xmlstarlet` command and the `awk` script used for replacing the content in the README.
Runtime Errors and Warnings

Initially, the GitHub Action ran into a couple of issues. The first problem occurred when there were no changes detected in the README.md file, causing the workflow to fail with the message: nothing added to commit but untracked files present. This happened because git attempted to commit even when there was nothing to change.

To solve this, I added a conditional check before the commit step. I used diff to compare the current and updated versions of README.md, and only moved forward with the commit when actual changes were detected.

The second issue was related to a deprecation warning about the set-output command, which is now outdated and replaced by GitHub’s environment files for setting output variables. I refactored the action to use environment files, appending the detected changes to $GITHUB_ENV. This new method ensures the workflow is future-proof and adheres to GitHub’s latest standards for workflow outputs, eliminating both the error and the warning.

Final Thoughts
This GitHub Action has been a great way to keep my GitHub profile updated with my latest content automatically. It not only showcases my recent work but also saves me the hassle of manually updating my profile. If you’re looking to automate your GitHub profile or any other repetitive task, I highly recommend giving GitHub Actions a try.

If you have any questions or run into issues setting up a similar workflow, feel free to reach out. I’d be happy to help!