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:
- When I am ready to add a new post to git I run a Ruby script that generates the commit message
- 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.