Scott's Recipes Logo

Creating a Bridgetown Theme with an Automation

This blog post talks about the process I used to create a Bridgetown theme that is enabled by an automation.

What is Bridgetown?

Bridgetown is an Ruby based static site generator that can be used to build stand along websites or blogs. Personally I only care about the use case of blogging and my work with Bridgetown tends to reflect that. Bridgetown is a port of the old Jekyll static site generator / blogging tool that began after Jekyll was abandoned. Although Jekyll has been forked a large number of times, Bridgetown is notable for being the only fork that has succeeded. Jared White is the forker and he gets incredible credit for making the fork successful.

What is a Bridgetown Theme?

As a static site generator, Bridgetown operates by compiling content files, written in Markdown format, into a single site or blog. A Bridgetown theme is the collection of files that configure the output of the compilation process. A Bridgetown theme generally consists of:

What is an Automation?

An automation is a Ruby script file which acts to install the theme into an existing Bridgetown installation. I believe that an automation is an absolutely essential part of any Bridgetown theme:

A Theme Automation Needs to Backup The User’s Work

One thing that is different about my automation file is it starts by backing up the user’s existing theme. It does this by creating a theme_backup/timestamp directory in src (where the theme files live) and then moving all the existing files in the theme to the theme_backup/timestamp directory.

Note: I’ll freely admit that I put theme_backup in place because I know that I’m going to need it myself in the very near future. Needing something yourself doesn’t mean that your users won’t need it.

Here’s What I Did

Here are the steps I followed to create an automatable theme:

  1. Create a directory to contain your theme. I called mine brinima (Bridgetown Minima). This theme is ultimately going to live on Github so keep that in mind.
  2. Add files to the root for README.md, LICENSE.md, CHANGELOG.md and bridgetown.automation.rb.
  3. Create directories under your root as follows:

    src _components frontend javascript styles _layouts

I should note that of the example themes I’ve looked at only mine groups files under src. I did this because I wanted to mirror the final directory structure of theme on installation and I needed a parent directory to place the index.md and posts.md files that are part of the theme. Also it felt more conceptually accurate to mirror the destination structure.

  1. Here are a series of mkdir commands you can execute to create these directories from the root of your theme.

    mkdir src mkdir src/_components mkdir -p src/frontend/javascript mkdir -p src/frontend/styles mkdir src/_layouts

  2. Within each of those directories, you need a bunch of files. Here are touch commands for the ones I started with:

    touch src/_layouts/home.liquid touch src/_layouts/page.liquid touch src/_layouts/post.liquid touch src/_components/footer.liquid touch src/_components/head.liquid touch src/_components/navbar.liquid touch src/index.md touch src/posts.md

The Automation …

An automation is a ruby script file, specifically a Thor file. Thor is a ruby task automation tool similar to Rake by the late Jim Weirich.

Using an Automation

Automations can be run, directly from the Internet just by apply them:

bin/bridgetown apply https://github.com/super-great-themes/theme-one

So, for the example of my Brinima theme, you would do:

bin/bridgetown apply https://github.com/fuzzygroup/brinima

Creating an Automation – Important Things to Know

Thing 01: Naming Your Automation File

The first thing to know is that your automation file needs to be named:

bridgetown.automation.rb

and not named:

bridgetown_automation.rb

as you may see in some automations.

Thing 02: Your Automation is Interpreted Top to Bottom

To make your automation dry (do not repeat yourself), you’re likely to want to define methods with def. Those methods need to come before the calls to them. It is possible that a require or include statement may circumvent this but I did not explore this at all.

My theme differs from others in that I place ALL def methods at the top and clearly delimit flow with comments.

Thing 03: –trace Works

The third important thing to know is that –trace makes the stack track for a bug useful.

Here’s an example without –trace:

❯ bin/bridgetown apply https://github.com/fuzzygroup/brinima
       apply  https://raw.githubusercontent.com/fuzzygroup/brinima/main/bridgetown.automation.rb
  Exception raised: Errno::ENOENT
No such file or directory @ rb_sysopen - src/theme_backups/20221218132136/src/_components
 1: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1395:in `initialize'
 2: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1395:in `open'
 3: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1395:in `block in copy_file'
 4: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1394:in `open'
 5: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1394:in `copy_file'
 Backtrace: Use the --trace option for complete information.

And here’s an example with –trace:

❯ bin/bridgetown apply https://github.com/fuzzygroup/brinima --trace
       apply  https://raw.githubusercontent.com/fuzzygroup/brinima/main/bridgetown.automation.rb
  Exception raised: Errno::ENOENT
No such file or directory @ rb_sysopen - src/theme_backups/20221218132228/src/_components

 1: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1395:in `initialize'
 2: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1395:in `open'
 3: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1395:in `block in copy_file'
 4: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1394:in `open'
 5: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1394:in `copy_file'
 6: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:504:in `copy_file'
 7: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:423:in `block in cp'
 8: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1577:in `block in fu_each_src_dest'
 9: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1593:in `fu_each_src_dest0'
10: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:1575:in `fu_each_src_dest'
11: /Users/sjohnson/.rubies/ruby-3.1.2/lib/ruby/3.1.0/fileutils.rb:422:in `cp'
12: https://raw.githubusercontent.com/fuzzygroup/brinima/main/bridgetown.automation.rb:54:in `block in backup_existing_theme_files'
13: https://raw.githubusercontent.com/fuzzygroup/brinima/main/bridgetown.automation.rb:52:in `each'
14: https://raw.githubusercontent.com/fuzzygroup/brinima/main/bridgetown.automation.rb:52:in `backup_existing_theme_files'
15: https://raw.githubusercontent.com/fuzzygroup/brinima/main/bridgetown.automation.rb:72:in `apply'
16: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/actions.rb:231:in `instance_eval'
17: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/actions.rb:231:in `apply'
18: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/lib/bridgetown-core/commands/concerns/actions.rb:76:in `apply_from_url'
19: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/lib/bridgetown-core/commands/apply.rb:67:in `block in apply_in_pwd'
20: /Users/sjohnson/.gem/ruby/3.1.2/gems/bundler-2.3.26/lib/bundler.rb:409:in `block in with_unbundled_env'
21: /Users/sjohnson/.gem/ruby/3.1.2/gems/bundler-2.3.26/lib/bundler.rb:708:in `with_env'
22: /Users/sjohnson/.gem/ruby/3.1.2/gems/bundler-2.3.26/lib/bundler.rb:409:in `with_unbundled_env'
23: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/lib/bridgetown-core.rb:179:in `call'
24: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/lib/bridgetown-core.rb:179:in `with_unbundled_env'
25: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/lib/bridgetown-core/commands/apply.rb:66:in `apply_in_pwd'
26: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/lib/bridgetown-core/commands/apply.rb:34:in `apply_automation'
27: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
28: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
29: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `block in invoke_all'
30: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `each'
31: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `map'
32: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:134:in `invoke_all'
33: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/group.rb:232:in `dispatch'
34: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:116:in `invoke'
35: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor.rb:40:in `block in register'
36: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
37: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
38: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
39: /Users/sjohnson/.gem/ruby/3.1.2/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
40: /Users/sjohnson/.gem/ruby/3.1.2/gems/bridgetown-core-1.1.0/bin/bridgetown:38:in `<top (required)>'
41: bin/bridgetown:27:in `load'
42: bin/bridgetown:27:in `<main>'

Thing 04: Github’s Caching Will Bite You in Theme Development

My process for developing this theme was to constantly make a change, push to its destination and then test it with:

bin/bridgetown apply https://github.com/fuzzygroup/brinima --trace

One thing that really surprised me was how often GitHub’s caching led to the previous version of the theme being executed. And as with all cache related bugs, your mileage will vary but I’d be surprised if it doesn’t affect you at some point.