Tweeting New Jekyll Posts From Github Actions - Part 2

I previously wrote about my experience attempting to use Github Actions to post a tweet every time I publish a new post on my self-hosted Jekyll/S3/Cloudfront blog. I managed to get to a working solution that was too complicated, so I’m trying another approach. I was following this post by Dave Brock where he described using the commit message as the entire tweet - so every commit message and git push is a tweet. I dismissed it as too simple, but now that I’ve seen how complicated the alternative is I’m going to try something similar.

Here is the new, simplified, workflow I envisioned:

  1. When I am ready to add a new post to git I run a Ruby script that generates the commit message
  2. When I deploy to product a step in the production deploy Github Action workflow tweets out the commit message

The first step is to write a Ruby script that generates the commit message. Identifying the post title and full url seems straightforward, but Jekyll allows you lots of flexibility around generating URLs, so I didn’t want to optimize for one specific use case. It turns out that I can instantiate the jekyll objects and tell it to parse the post itself.

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'uri'
require 'jekyll'

# Get uncommitted post files
status_lines = `git status --porcelain _posts`.lines
new_posts = status_lines.select { |line| line.start_with?('??') }.map { |line| line.split.last }

if new_posts.empty?
  puts 'No new, uncommitted post found.'
  exit 0
end

# Initialize a site object from _config.yml
site_config = Jekyll.configuration({ quiet: true })
site = Jekyll::Site.new(site_config)

# Path to the new post
post_path = File.expand_path(new_posts.first)
document = Jekyll::Document.new(post_path, { site: site, collection: site.posts })
document.read

# Git add and commit
commit_message = "Blogged: #{document.title} #{URI.join(site_config.fetch('url'), document.url)}"
system('git', 'add', post_path)
system('git', 'commit', '-m', commit_message)

puts "Committed with message:\n#{commit_message}"

A nice part of this approach is that I can easily test the script locally.

A key piece of nuance is that not every commit is a new post - sometimes I’m editing an old post (fixing a typo, updating a link, etc), or I’m making a change to the site itself (Jekyll config, css, etc). I wanted to update my Github Action workflow to look for a commit message with the Blogged: prefix. Github Actions exposes the latest commit message via a variable - github.event.head_commit.message - but it’s not populated on manual workflows (which is what I’m using for a production deploy). So instead I need to manually fetch the latest git commit.

- name: Get latest commit message
  id: get_commit
  run: |
    msg=$(git log -1 --pretty=%B)
    echo "message=$msg" >> $GITHUB_OUTPUT

- name: Tweet the new post
  if: startsWith(steps.get_commit.outputs.message, 'Blogged:')
  uses: nearform-actions/github-action-notify-twitter@master
  with:
    message: ${{ steps.get_commit.outputs.message }}
    twitter-app-key: ${{ secrets.TWITTER_CONSUMER_API_KEY}}
    twitter-app-secret: ${{ secrets.TWITTER_CONSUMER_API_SECRET}}
    twitter-access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
    twitter-access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}

This is a much simpler approach to what I had before, and it’s much easier to debug and predict what is going to happen. The only downside is that I need to be careful with my git commits before pushing to production - for example, if I fix a typo before pushing to production I need to be careful to amend the original commit.