I’ve found it difficult the past few years to be productive working on my own app, Aether. Part of the trouble has been that it was always difficult to release a new version. I had to manually build it, write release notes, update and reindex the built in help, create a new DMG, create a ZIP file (for Sparkle), edit the Sparkle appcast XML, and FTP everything to my server. This was a completely manual process, and each step takes a minute or two at the very least. This is just for the non-app-store version, too! It all added up to enough friction that I tended to avoid releasing updates.
Also, often when I’d sit down to work on development, I’d end up spending a significant chunk of time working through a backlog of customer support email. Customer support is very important, but it’s also really time consuming. I’d often find myself answering variations on the same questions over and over. I saved canned responses for a few common issues, but I’d still often spend 10-15 minutes answering a single email.
None of this is the thing I actually love to do: write code! In order to address these problems and give me more time to actually work on developing Aether, I’ve decided to make some infrastructure and process improvements. These have made a HUGE difference, and I’m finding it much more fun to work on the app itself again. I thought I’d outline the solutions that have worked for me, in the hopes that others may find them useful.
I started by thinking it would be fun to write a web app to host and manage builds of Aether. Maybe I’d even use Swift on the server! Of course, that would be a huge undertaking, and I was lucky to find that a few people had already written just such a thing. I ended up using Appcaster (with some changes in my own fork), a Node.js app specifically built to host and manage Mac apps that use Sparkle for updates.
Appcaster makes it really easy to upload new builds, automatically generates and serves Sparkle appcasts, lets me write release notes in Markdown, and even allows for multiple deployment channels (e.g. Developer, Beta, Release). It also provides URLs that always point to the download and release notes for the latest version of Aether (DMG and ZIP) on each channel. Links on the Aether website point to these so that uploading a new build no longer requires any manual changes to the website at all.
Uploading New Builds
With Appcaster up and running (I’m using Heroku to host it), I wanted a way to quickly deploy new builds to the server. I wrote a quick-and-dirty Bash script to be run as an Xcode build phase. I put it in a custom target that first builds the app itself, then runs the script. The script takes the built app, creates both DMG and ZIP files for it, uploads them (along with the dSYM) to Amazon S3, then adds a new build to Appcaster. With this script, uploading a new build is as easy as selecting “Upload Aether Build” in Xcode’s scheme selector, then hitting command-B. Builds are deployed only to the Development channel by default, but can easily be promoted to Beta and/or Release. You can find the current (as of this writing) version of the script here. Disclaimer: It’s not polished, and could definitely use some improvement. I’m also far from a Bash expert.
Reducing the Customer Support Burden
A very significant portion of the customer support emails I receive are questions that could be answered by better documentation. I’ve been bad about writing good built in help due to the difficulty involved. Up until now, adding a new help page has meant writing HTML by hand, adding links to it to other pages, updating the help index, then releasing an update to Aether. It is also difficult to point people to specific help pages, even when I do have a page that would help with support.
The obvious solution is to put help for Aether on the web. I’m not a web developer by any means, so I wanted something that would be easy for me to set up, work well, but most importantly be easy to maintain and improve. I settled on MkDocs, a static site generator specifically designed for doing project documentation. The source for an MkDocs site is just plaintext Markdown files, along with a single YAML configuration file. It’s very easy for me to write with a text editor. Adding a new page is as simple as creating a new Markdown file and adding it to the config.yml file. The site can be rebuilt and deployed to my web host by running a single, simple Bash script. It’s not much more difficult than writing a response email. Now, when I get an email asking a question I know will come up again, I can write a help page and link to it so that my answer is valuable to more than just one person.
I decided to open source the new Aether help site. You can find it on GitHub here.
Of course, a website for help is great, and really helps me with email customer support. However, I also want users to continue to be able to find and access help from within Aether. Hooking up the Aether Help menu item to open help.aetherlog.com was of course trivial. More complicated is redoing the Help menu search field that is standard in Mac apps so that it searches the website.
It turns out there’s a protocol called
NSUserInterfaceItemSearching that allows you to provide custom results when the user searches using the Help menu search field. MkDocs generates a JSON file index of the entire site by default. It was pretty simple todownload and parse that to provide an index for searching. To actually do the searching, I’m using BRFullTextSearch, an Objective-C full text search engine API and wrapper for CLucene. I’m usually very hesitant to include third party libraries, but in this case it solved a problem I wasn’t interested in solving myself.
Good infrastructure can remove many of the tedious tasks associated with releasing, maintaining, and supporting apps, allowing for more time to focus on the fun parts of development. Making these changes has helped me refocus on improving Aether. I hope the tools I’ve talked about here are helpful to others.