Rookeries v0.17.0 – Auto-Building + Multi-Template Support

Recovering from PyCon Canada 2019, and the holidays took longer than I expected. However I am pleased to announce the release of version 0.17.0 of Rookeries to start off this new year!

Version 0.17.0 includes a number of improvements to the serve command:

  • Auto-building of the site when a file in a project changes.
  • Opening of the resulting build site in a browser using the --open flag.
  • Auto-refreshing of the opened site when in development mode.

In addition, I added:

  • Multi-template support which will allow for building out complex templates in the future.
  • Improvements to the UX experience.

If you are running version 0.16.0, you can run a re-install using the rookeries-online-installer.sh to update to the latest version. Otherwise you can install v0.17.0 by following the instruction on the Download page.

Next Steps

As for what I plan to work on next, I want to improve the infrastructure around downloading, installing and working with both plugins and templates. I will be using my new site for my Juggling JSON with jq Cookbook ebook as a test bed for improving working with Rookeries. That way I have some real use cases outside of Rookeries’ project page. I am looking forward to working on both projects and see how they both develop in parallel.

Do you know that my mailing list is the first to know about releases and developments around Rookeries? Consider subscribing to the mailing list to be among the first to know!

Rookeries v0.16.0 – Cross-Platform Support

On Wednesday night, I released version 0.16.0 of Rookeries, my developer/designer friendly static site generator! What makes this release exciting is that I added cross-platform support for macOS and FreeBSD! Also I setup an cross-platform installer, that works on all 64-bit x86 Linux distros including Windows Subsystem for Linux (WSL) 2.0. And I plan on supporting Linux, macOS (OS X) and FreeBSD as first-class systems going forward.

Also a multitude of stability and UX improvements landed in this release.
You can download Rookeries and install it using the new cross-platform installer here.

Rookeries v0.16.0 screenshot

Now this took quite some time and effort, especially around figuring out cross-compiling and what each OS could reasonably support. Over the course of the next few weeks, I will be writing about the challenges and how I overcame them when adding cross-platform support. (Including a quick aside why I dropped native non-WSL Windows support, and took up FreeBSD instead.)

Do you know that my mailing list is the first to know about releases and developments around Rookeries? Consider subscribing to the mailing list to be among the first to know!

Rookeries v0.15.0 Released – Technical Preview of the Static Site Generator

It turns out that rewriting a CMS into a static site generator takes a bit of time. 😉 After 3 months of silence, I can finally announce the long awaited release of version 0.15.0 of Rookeries. This release is the first fully functional technical preview of the static site generator!

You can now create, build and develop a full site with Rookeries! In fact I rebuilt the Rookeries site, using Rookeries to prove out the concept. You start by using the generator to setup a new project. Then you can add Markdown files to build new pages to an existing site, change the HTML and CSS templates, and run a build which creates a modern site. A site that in essence is a server-side rendered single-page application. So if you are running Debian or Ubuntu Linux you can try it out Rookeries by installing the deb package.

Now, this release is a technical preview release to demonstrate the concept. I plan on making the template and plugin systems more flexible. Also I want to improve the usability by adding an automatic rebuild when a page is edited. And I need to write proper documentation as well. Support for other operating systems and distributions is also in the works. However already Rookeries is significantly faster than its nearest competitor: GatsbyJS and smaller in size. And you get a nicer, more maintainable site than from many other tools.

So please take Rookeries for a spin, and let me know how you like it!

Rookeries v0.13.1 – Release + Web Assembly Experiments

This past month I’ve been trying to determine the future direction I want to take my projects: especially Rookeries. Ultimately I want to build something that lasts and is able to sustain itself financially. As someone who is still a novice in the arts of marketing, and business development, this is hard, and doing this in an open, transparent environment like with libre software, just adds to it. While I have many musings on what is needed to run a sustainable open source project, they are just that: musings. Without getting into the weeds, I think the Ghost blogging platform team is doing the right thing. I recommend the Indie Hacker podcast that interviews John O’Nolan of Ghost.

Also I’ve been playing around with some of the interesting Rust Web Assembly projects out there. I think they have potential to improve Rookeries in a radical manner. However before I get anyone’s hopes up, I want to get something working before I announce the future direction of Rookeries. (Something, something, Web Assembly, something, something, plugin extensibility.) In the meantime I am releasing version 0.13.1 of Rookeries!

This version is mostly bug fixes, and code re-organization that will help me extend the project into the future.

P.S. My mailing list knows about my releases and plans of Rookeries, before the blog. So you should totally sign up for the Rookeries mailing list.

Onward with Postgres and Diesel – Rookeries v0.13.0

That took longer than expected… but I’ve finally migrated Rookeries to use PostgreSQL 10! I finished the majority of the raw migration work about a week before PyCon Canada. However I did not want to confuse my updates and tweets related to PyCon Canada with my work on Rookeries. Also I wanted to add a few bits of polish, create a mailing list for Rookeries and fix up any issues I found with the site. (Quite a few data and TLS certificate issues happened along with this release.)

I found that migrating Rookeries from CouchDB to PostgreSQL quite challenging. This adds yet another data point to those that argue that fixing data models and databases can be significantly harder than fixing applications. One of the challenges I stumbled in was mapping the various keys in a CouchDB document for a resource, into a table. Fortunately the JSONB columns allowed for the flexibility to let me resolve some of my earlier problematic data designs later on. I also needed to familiarize myself with diesel.rs, the amazing ORM/database engine library for Rust. I struggled with understanding how custom types worked in Rust, and why they make database mapping of columns easy for more advanced mappings (like optionally nullable, JSONB and Array). However I managed to figure out even the hard parts using a combination of the documentation and by peeking into the test cases for diesel. Having a migration tool in the companion diesel-cli utility, provides the nicest UX for building out database migrations and mapping them to code. Overall the effort paid off and I am convinced that Rust has one of the cleanest and maintainable ORM/database libraries I’ve seen.

As mentioned earlier, I created a mailing list dedicated to Rookeries updates. I will continue to blog about releases, but if you are interested in the internals and development of Rookeries, then this mailing list a good thing to follow. Consider it like a journal that you can also reply back to and enter in a conversation about the development of Rookeries. I added a panel to the side of the site, that makes subscribing to Rookeries easy.

This will hopefully will be the last of the major technological shifts for the project, at least on the backend. The next releases I plan on concentrating on getting Rookeries in a state where I can drive Gatsby powered static sites via REST APIs. Once that becomes a reality, I will work on replacing my WordPress sites with Rookeries powered sites.

To Make or Not to Make – Using cargo make for Rookeries v0.12.0

I was pleasantly surprised when my last blog post about migrating to Rust’s integration tests really took off on Twitter. I did not quite expect that much interest. 🙂

Using cargo-make

I recently continued with my exploration of Rust through Rookeries (my attempt at a static site generator/backing API server). This time I worked on switching over from using invoke and GNU make to using a nice build system called cargo-make. Overall I am quite happy with the result. To give you a taste of how cargo-make describes build tasks and environment variables, I included a short snippet from the Rookeries Makefile.toml below:

<br />
[env]
SERVER_PORT = "5000"

[tasks.build-js]
description = "Build JS frontend."
command = "npm"
args = ["run", "build"]

[tasks.run-dev-server]
description = "Run a Rookeries dev server."
command = "cargo"
args = ["run", "--", "run", "${SERVER_PORT}"]
dependencies = ["build-js"]

Much like invoke and Make, you can specify dependencies. Unlike Make, there is no weird tab-based syntax, or implicit behaviour that requires a look up in the GNU Make manual. Just a simple TOML file. Environment variables can be specified in standalone .env files, passed in or specified in the Makefile.toml itself, and allow for variables specified during a run to override defaults, etc. cargo-make also lets you use conditional logic but I did not need to use that for my purposes.

For Rookeries, I ended up creating locally running tasks, and versions for the Dockerized build setup that I have in CircleCI. Also I ended up using the scripting support that cargo-make provides for preparing the database for, and running the integration tests.

My Thoughts on CouchDB for Rookeries and Rust

I feel I cheated a bit towards the end especially by using a few curl commands to instantiate the CouchDB database, and with running the API integration tests. Maybe I should of wrote a proper CouchDB library and utility. However I decided against it, simply because I want move away from CouchDB for Rookeries. So I may not want to invest that much time in maintaining a proper database client if I’ll end up switching over to using Postgresql via diesel.rs Originally, I started using CouchDB because I use it heavily at my work at Points. As time goes on, I find myself more and more reluctant with using CouchDB, just because of all the issues I’ve experienced at work. That said, if someone wants me to maintain a CouchDB Rust library/CLI based on the code I have for Rookeries, then please reach out to me. The CouchDB crates I saw in crates.io did not inspire me, so I wrote my own CouchDB module in Rust. But I don’t want to maintain that code just for myself, so if anyone actually needs a proper CouchDB crate and wants me to maintain it, please reach out to me.

Update – Sofa Library

After I wrote this blog post originally, I did not know about this nice CouchDB crate called sofa. If I had know about it, I would of not bothered with my own implementation.

New Release of Rookeries – Now 100% Rust

Finally since this migration was a bit of work, so I decided to release a new version of Rookeries. Since the invoke setup was the last Python code, Rookeries is now 100% completely written in Rust. (And the JS frontend naturally.) The Rookeries Docker image is available on Docker Hub. The project is still quite in flux, and what I will work on next depends on what I need to do to hook up Rookeries to one of my Gatsby powered sites.

Writing Integration Tests in Rust + Releasing Rookeries v0.11.0

As part of my overall change over in Rookeries, from Python to Rust, I rewrote a suite of integration tests for the server API. To celebrate my successful transition, I released version 0.11.0 of Rookeries, whose tests use pure Rust now!

I found the rewrite not too cumbersome, thanks to the wonderful guide in the Rust book on integration tests. I did miss pytest’s fixture setup, which makes testing really easy. Especially when setting up fixtures that are run only once per test suite. That said, a single session setup makes it difficult to run tests in parallel. And while I ran into some inconsistent tests, I had the exact same kind of problems in Python. Basically my data model for Rookeries, doesn’t work well for singleton data like a single site. In the future, I plan on having a single Rookeries instance managing multiple different sites, so this point of not having a single test setup is ultimately moot.

Building a common setup module turned out to be more work than I imagined. I could not use macros because of the package setup. Also since tests are compiled individually, some methods in the common module, simply are not called, leading to warning of dead code.

What made the transition is easy was using the reqwest crate, and heavy use of serde_json’s json! macro. Comparing the Python and Rust version of the tests, makes them look very comparable.

Rust Version src:

#[test]
fn test_authenticated_user_cannot_modify_site_using_bad_request() {
    test_site();
    let api_base_uri = common::api_base_url();
    let client = Client::new();
    let auth_token = auth_token_headers();
    let response = client
        .put(api_base_uri.join("/api/site").unwrap())
        .body("")
        .header(Authorization(auth_token))
        .send()
        .unwrap();

    assert_bad_request_response(response);
}

fn assert_bad_request_response(mut response: Response) {
    let expected_response_json = json!({
        "error": {
            "status_code": 400,
            "message": "Bad request",
        }
    });

    assert_eq!(response.status(), StatusCode::BadRequest);
    let actual_json_response = response.json::<Value>().unwrap();
    assert_eq!(actual_json_response, expected_response_json);
}

Python Version src:

def test_authenticated_user_cannot_modify_site_using_bad_request(
        auth_token_headers, api_base_uri, test_site):
    response = requests.put(
        url=f'{api_base_uri}/api/site',
        data='',
        headers=auth_token_headers,
    )
    assert_bad_request_response(response)

def assert_bad_request_response(response):
    expected_response_json = {
        'error': {
            'status_code': http.HTTPStatus.BAD_REQUEST.value,
            'message': mock.ANY,
        }
    }

    assert response.json() == expected_response_json
    assert response.status_code == http.HTTPStatus.BAD_REQUEST

I am pretty happy with how everything turned out overall. My next bit of Rookeries work will be migrating away from invoke and make to cargo make.

Rookeries v0.10.0 – Rust Re-write

I just rewrote Rookeries in Rust, and the latest version is now available as a Docker image on Docker Hub. (This is why I have not responded to emails in a bit… I’ve been doing a lot of thinking of what I want to do next.)

So why a rewrite? Ultimately I decided to change the direction of Rookeries as a project (making it more of a static site generation tool + headless CMS). I also wanted to improve my knowledge of Rust. (A programming language that I believe is quietly revolutionizing computing bit by bit. I really need to write about it sometime) But to show that there is reason for my madness:

Traditional CMS

I realized that my approach to Rookeries was heavily inspired by WordPress. And WordPress represents the best in class (in terms of ease-of-use) for traditional CMS. Traditional CMS being applications that control all the aspects of a website: pulling data that it organizes inside a database, the applying logic to manage the workflows of the data, and the creating and managing the user interface that ultimately shows the data. The traditional CMS has to do a lot of things. That usually means that the CMS developer needs to add a lot of assumptions and constraints to ship said CMS. And when those assumptions no longer hold, it takes a lot to change the codebase.

In the case of WordPress (and similar styled CMSs), the server has to do a lot of work with building a page. A WordPress site consists of both the site, and its admin console. While there has been work on updating the theme management and the page/post editor, ultimately there are limits to what kind of UIs that WordPress can support. WordPress also has a massive plugin marketplace, because the core developers do not know what every website will need. (Nor does it make sense to build everything in or every possibility.) For a modern WordPress powered site to stand-out, it needs needs a custom theme and often a half-dozen plugins. Naturally having so many moving parts, from different vendors (some theme and plugin devs are better than others) means there is a a greater chance of security issues. The routine upgrade of themes, plugins and the WordPress core is a rite that one has to diligently perform regularly.

It would be impossible for Rookeries to compete with WordPress head on, just given the number of hours I would need to sink to make things a reality. Also I do not think this desirable given the current direction.

The Alternative of Static Site Generation and API Driven Sites

The assumption that the server must handle most of the layout and display issues does not hold when dealing with modern browsers. Modern browsers can run rich multimedia experiences and real-time applications. Running intensive applications such as a 3D game is now possible, and will become come place. Ultimately the web is now a platform, a target that application developers can directly target.

As a result, many web agencies are turning to the JAMstack and having their websites act as their own full-blown applications. The applications either talk to a headless CMS acting an API or even foregoing that with static site generation. Probably the best example of this is Gatsby for generating sites built in React, which I am currently using to build out the Amber Penguin Software site. I also plan on eventually converting my other existing WordPress sites to use Gatsby or a similar tool. These tools as other web agencies found, separate out the complex logic of a frontend app from the backend and its data. These means more flexibility when building out a site. It also means that the backend CMS can be hardened and simplified to avoid many security issues. In fact with static site generation, the web app can be so removed from its CMS and database, that attacking a site becomes irrelevant. Such a site can also have better performance, and take advantage of caching and CDNs.

The Future of Rookeries

While there are a number of headless CMS and static site generators out there, I feel like the overall experience of working with them needs some improvement. There are no real good headless CMS systems written in Python or Rust, and I do think there are some really unpleasant rough edges around running a static site. Hence I plan on reworking Rookeries to help me build a CMS that can power my various sites. At the moment, this version 0.10.0 has feature parity with the previous version of Rookeries. I still need to rewrite some of the admin and testing in Rust, to avoid the need for Python. Also I need to figure out a nice manner to expose a flexible API to my sites. Fortunately I have enough examples to keep me busy for a bit.

Rookeries v0.9.0 out – New UI and Live Editing of Sites + Juggling JSON with jq Book Update

It has been quite a while since my last update. I apologize for the long silence. I wanted to focus on getting Rookeries up and running, to the degree that I can host a website. Namely I was hoping to update the Amber Penguin Software website and the Juggling JSON with jq book companion website to use Rookeries. Unfortunately I had to re-architecture the UI and bring the CouchDB management of the site and pages up to spec. However I am happy with the overall results:

Interactive Editing of Menus

Rookeries v0.9.0 introduces support for adding, removing and reordering menu items. It took a bit to find a good simple implementation of a drag and drop able list.

]4 Version 0.9.0 introduces interactive editing of menus.

Interactive Editing of Site Headers and Footers

Rookeries v0.9.0 also extends support of editing pages to site headers and footers. It uses the same inline editor for Markdown and HTML, that the pages currently enjoy.

]5 You can now edit site headers in Rookeries

So what else changed?

  • The migration of the UX over to a sliding sidebar is done.
  • Internally redux was replaced with MobX, which is far more maintainable for me.
  • The addition of saving sites and the current selected page.

There is still a lot to do on Rookeries, such as making the CMS more robust and flexible. Adding and removing pages is still outstanding. So is selection of pages via URLs. Marking whether or not changes have been made, to prompt the user to save. Also to check that the site is not in an invalid state. However things are starting to look up.

A few people have inquired about the state of the Juggling JSON with jq book, and when will a sample chapter and table of contents will be available. I apologize for not replying directly to those emails (I will do so shortly), but I hope to get those out in the next few days, and hopefully before the end of the year. Work on Rookeries has essentially ate up all the time that I would of used for working on the book. I think going forward I will work on both in parallel.

Merging Git Repositories

One of the things that I realized while working on Rookeries, is that the server and client have to be more or less developed in sync. In theory one could develop these in parallel, however a function needs to be build on both sides. Since for the most part, I’m building Rookeries by myself having two separate repositories only gives me more things to maintain.

So I decided to merge the rookeries-react-app repository back into the rookeries repository. I used the instructions found in this post on merging git repositories to successfully achieve that.. In case this played out as:

git clone https://bitbucket.org/dorianpula/react-rookeries-app temp
cd temp
git filter-branch --prune-empty --tree-filter '
     if [[ ! -e client ]]; then 
    mkdir -p client 
    git ls-tree --name-only $GIT_COMMIT  | xargs -I files mv files client fi'

cd ..
git clone https://bitbucket.org/dorianpula/rookeries rookeries-next
cd rookeries-next
git remote add --fetch temp ../temp 
git merge -s ours --no-commit temp/master 
git read-tree --prefix=client/ -u temp/master 
git commit -m "Merged rookeries-react-app in rookeries" 
git pull -s subtree temp master

git push origin master