This Jekyll-based blog has been built with a custom theme created called jekyll-dash. Initially, I built this theme just to share it with others, however, more and more people started using it over the past few months.
In this article I want to highlight some of the challenges I faced trying to maintain this theme for a lot of people and the workflow I introduced that helped me to overcome these challenges.
When I first started this theme I found it quite annoying that I constantly had to manually update my Jekyll files such as layouts, Sass and pages to apply a change to my blog. Especially, when trying to keep the theme in its own dedicated repository this can become a demanding chore.
The solution to this was to bundle my theme as a so called Ruby gem and then use that gem within the _config.yml
of Jekyll:
theme: jekyll-dash
Jekyll will automatically apply the theme if it is specified as a gem within Gemfile
:
gem 'jekyll-dash'
Whenever I push a new version of the gem to the public rubygems repository, rebuilding my site would use the new changes and automatically include them - neat!
Unfortunately, I quickly had to realise that a lot of people were unable to use my theme natively within Github pages. Github are actually the creators behind Jekyll and when you create a repository containing Jekyll files, it will automatically build them for you and publish them when the Github pages feature is enabled.
There was just one problem: Github pages did not support Jekyll 4.x at the moment of this writing but only Jekyll 3.x. This becomes a problem because my theme was natively built with Jekyll 4.
Therefore, I had to introduce a multi-version workflow:
1.x
will support Jekyll 32.x
will support Jekyll 4I created separate branches for those and specified the Jekyll version explicitly within the .gemspec
file of jekyll-dash:
spec.add_runtime_dependency "jekyll", "~> 4.0"
This worked very well for my usecase and I was able to use the theme natively in Github pages.
For what reason would I want to use Jekyll outside of the native Github pages integration then? Well, due to security reasons, Github Pages does only allow a very specific set of Jekyll plugins to be enabled. This restricts customisation of the site. For example, I also needed the following features:
The solution was to build the site externally outside of Github Pages but then push the generated site onto a Github Pages enabled repository. I achieved that by using travis-ci.org which became eventually difficult to manage. As a result, I left Travis behind and moved over to Github Actions.
My initial release workflow looked as follows:
main
branch containing the updated version within the .gemspec
before_install:
- gem install bundler -v 2.0.1
script:
- bundle install
deploy:
provider: rubygems
api_key: $RUBYGEMS_API_KEY
gem: jekyll-dash
on:
tags: true
repo: bitbrain/jekyll-dash
There were a couple of issues with this approach:
I discarded this workflow and introduced a completely new one built from scratch based on Github Actions:
main
(v2.x) and 1.x
(v1.x) respectively, do the following:
In order to check that the gem version exists as a tag, I am using the github-tag-action
:
- name: 💎 Extract gemspec info
id: gemspec_fetch
uses: bitbrain/gemspec-fetch@1.0.0
with:
specfile: jekyll-dash.gemspec
- name: 🕵️♂️ investigate if tag exists
uses: mukunku/tag-exists-action@v1.0.0
id: tag-check
with:
tag: {% raw %}'v${{ steps.gemspec_fetch.outputs.version }}'{% endraw %}
env:
GITHUB_TOKEN: {% raw %}${{ secrets.GH_CREDENTIALS }}{% endraw %}
This allows me then to apply an if
conditional on any other steps:
- name: 🔖Build tag
if: {% raw %}${{ steps.tag-check.outputs.exists == 'false' }}{% endraw %}
id: tag_version
uses: mathieudutour/github-tag-action@v5.6
with:
github_token: {% raw %}${{ secrets.GH_CREDENTIALS }}{% endraw %}
default_bump: false
custom_tag: {% raw %}${{ steps.gemspec_fetch.outputs.version }}{% endraw %}
tag_prefix: v
The slightly tricky part was to extract the version from the .gemspec file. For that I build my own Github Action that is using parse-gemspec-cli to extract the metadata accordingly:
#!/bin/bash
SPEC_DATA=$(parse-gemspec-cli $INPUT_SPECFILE)
echo "::set-output name=name::$(echo $SPEC_DATA | jq -r '.name')"
echo "::set-output name=description::$(echo $SPEC_DATA | jq -r '.description')"
echo "::set-output name=summary::$(echo $SPEC_DATA | jq -r '.summary')"
echo "::set-output name=version::$(echo $SPEC_DATA | jq -r '.version')"
echo "::set-output name=homepage::$(echo $SPEC_DATA | jq -r '.homepage')"
This new workflow allows me to build and test every single commit and it gives me full control of when I want to release a new gem: I simply bump the version of the gem manually within the .gemspec and Github will do the rest for me!