|
| 1 | +# Releasing Cupertino 4.0.0: New Features and Hard Lessons in Release Engineering |
| 2 | + |
| 3 | +**December 9, 2025** |
| 4 | + |
| 5 | +Today I released Cupertino 4.0.0, and it was... an adventure. What should have been a straightforward release turned into a masterclass in why release engineering is hard and why automation exists. |
| 6 | + |
| 7 | +## What's New in 4.0.0 |
| 8 | + |
| 9 | +First, the good stuff. This release brings some significant improvements: |
| 10 | + |
| 11 | +### Human Interface Guidelines Support |
| 12 | + |
| 13 | +Cupertino can now crawl and index Apple's Human Interface Guidelines. This was issue #69, and it's been on my list for a while. |
| 14 | + |
| 15 | +```bash |
| 16 | +cupertino fetch --type hig |
| 17 | +``` |
| 18 | + |
| 19 | +There's also a new `search_hig` MCP tool that lets AI agents search design guidelines with platform and category filters. So now when you ask Claude "what are Apple's recommendations for buttons?", it can actually look it up. |
| 20 | + |
| 21 | +### Framework Aliases |
| 22 | + |
| 23 | +This one's subtle but important. Before, if you searched for "Core Animation", you might not find results indexed under "QuartzCore" (the actual framework import name). Now there are 249 framework aliases in the database, so searches work regardless of which name variant you use: |
| 24 | + |
| 25 | +- `QuartzCore` ↔ `CoreAnimation` ↔ `Core Animation` |
| 26 | +- `CoreGraphics` ↔ `Quartz2D` ↔ `Quartz 2D` |
| 27 | + |
| 28 | +### Swift.org Fixes |
| 29 | + |
| 30 | +The Swift.org crawler was broken. The base URL had changed from `docs.swift.org` to `www.swift.org/documentation/`, and the indexer was looking for `.md` files when the crawler was saving `.json` files. Classic. |
| 31 | + |
| 32 | +## The Release Process Disaster |
| 33 | + |
| 34 | +Here's where it gets interesting. I had all the code ready, all the tests passing, and I was ready to ship. The process seemed simple: |
| 35 | + |
| 36 | +1. Bump version in `Constants.swift` |
| 37 | +2. Update `README.md` and `CHANGELOG.md` |
| 38 | +3. Create git tag |
| 39 | +4. Push tag (triggers GitHub Actions build) |
| 40 | +5. Upload databases to `cupertino-docs` |
| 41 | +6. Update Homebrew formula |
| 42 | + |
| 43 | +What could go wrong? |
| 44 | + |
| 45 | +### The Tag Timing Problem |
| 46 | + |
| 47 | +I merged my feature branch, created the tag, pushed it... and then realized I hadn't committed the version bump to `main` yet. The tag pointed to a commit where `Constants.swift` still said `0.3.5`. |
| 48 | + |
| 49 | +GitHub Actions dutifully built a beautiful, signed, notarized universal binary... that reported version `0.3.5`. |
| 50 | + |
| 51 | +```bash |
| 52 | +$ cupertino --version |
| 53 | +0.3.5 |
| 54 | +``` |
| 55 | + |
| 56 | +When users ran `cupertino setup`, it tried to download databases from `v0.3.5` instead of `v4.0.0`. Everything was broken. |
| 57 | + |
| 58 | +### The Fix |
| 59 | + |
| 60 | +I had to: |
| 61 | + |
| 62 | +1. Delete the tag on GitHub |
| 63 | +2. Delete the local tag |
| 64 | +3. Make sure the version bump commit was pushed to `main` |
| 65 | +4. Verify the built binary reports correct version **before** tagging |
| 66 | +5. Recreate the tag |
| 67 | +6. Wait for GitHub Actions to rebuild |
| 68 | +7. Update the Homebrew formula with the new SHA256 (because the binary changed) |
| 69 | + |
| 70 | +### The SHA256 Dance |
| 71 | + |
| 72 | +Speaking of Homebrew—when I first updated the formula, I grabbed the SHA256 from the old (broken) binary. After rebuilding, the checksum changed. Users got: |
| 73 | + |
| 74 | +``` |
| 75 | +Error: Formula reports different checksum: cf035352... |
| 76 | +SHA-256 checksum of downloaded file: 5c5cf7ab... |
| 77 | +``` |
| 78 | + |
| 79 | +Another round of updating the tap. |
| 80 | + |
| 81 | +## The Current Release Process |
| 82 | + |
| 83 | +After today's adventures, here's what the release process actually looks like: |
| 84 | + |
| 85 | +```bash |
| 86 | +# 1. Update version everywhere |
| 87 | +edit Packages/Sources/Shared/Constants.swift # version = "X.Y.Z" |
| 88 | +edit README.md # Version: X.Y.Z |
| 89 | +edit CHANGELOG.md # Add new section |
| 90 | + |
| 91 | +# 2. Commit and push (BEFORE tagging!) |
| 92 | +git add -A && git commit -m "chore: bump version to X.Y.Z" |
| 93 | +git push origin main |
| 94 | + |
| 95 | +# 3. Verify version is correct |
| 96 | +grep 'version = ' Packages/Sources/Shared/Constants.swift |
| 97 | +make build && ./Packages/.build/release/cupertino --version |
| 98 | + |
| 99 | +# 4. Only now create the tag |
| 100 | +git tag -a vX.Y.Z -m "vX.Y.Z - Description" |
| 101 | +git push origin vX.Y.Z |
| 102 | + |
| 103 | +# 5. Wait for GitHub Actions to build (~5 min) |
| 104 | + |
| 105 | +# 6. Create GitHub release with notes |
| 106 | + |
| 107 | +# 7. Build locally and install |
| 108 | +make build && sudo make install |
| 109 | + |
| 110 | +# 8. Upload databases |
| 111 | +export GITHUB_TOKEN="cupertino-docs-token" |
| 112 | +cupertino release |
| 113 | + |
| 114 | +# 9. Get new SHA256 |
| 115 | +curl -sL https://github.com/.../cupertino-vX.Y.Z-macos-universal.tar.gz.sha256 |
| 116 | + |
| 117 | +# 10. Update Homebrew tap |
| 118 | +cd /tmp && git clone .../homebrew-tap.git |
| 119 | +# Edit Formula/cupertino.rb with new version and SHA256 |
| 120 | +git commit && git push |
| 121 | + |
| 122 | +# 11. Verify on fresh machine |
| 123 | +brew update && brew install cupertino |
| 124 | +cupertino --version |
| 125 | +cupertino setup |
| 126 | +``` |
| 127 | + |
| 128 | +That's 11 steps across 4 different repositories (`cupertino`, `cupertino-docs`, `homebrew-tap`, and GitHub Actions). Each step depends on the previous one. Miss one, and you're rebuilding everything. |
| 129 | + |
| 130 | +## Time for Automation? |
| 131 | + |
| 132 | +I'm seriously considering writing a release script. The question is: Swift or Bash? |
| 133 | + |
| 134 | +**Arguments for Swift:** |
| 135 | +- It's what the project is written in |
| 136 | +- Type safety would catch some mistakes |
| 137 | +- Could reuse existing code (like the GitHub API client from `ReleaseCommand.swift`) |
| 138 | +- Cross-platform if we ever need it |
| 139 | + |
| 140 | +**Arguments for Bash:** |
| 141 | +- Simpler for orchestrating CLI commands |
| 142 | +- No compilation step |
| 143 | +- Everyone knows Bash (sort of) |
| 144 | +- GitHub Actions scripts are basically Bash anyway |
| 145 | + |
| 146 | +I'm leaning toward Swift, honestly. The `cupertino release` command already handles database uploads with proper GitHub API integration. Extending it to handle the full release workflow would be cleaner than maintaining a separate Bash script. |
| 147 | + |
| 148 | +Something like: |
| 149 | + |
| 150 | +```bash |
| 151 | +cupertino release --full |
| 152 | +``` |
| 153 | + |
| 154 | +That would: |
| 155 | +1. Check for uncommitted changes |
| 156 | +2. Verify version consistency across all files |
| 157 | +3. Build and verify the binary version matches |
| 158 | +4. Create and push the tag |
| 159 | +5. Wait for GitHub Actions |
| 160 | +6. Upload databases |
| 161 | +7. Update the Homebrew tap |
| 162 | + |
| 163 | +## Lessons Learned |
| 164 | + |
| 165 | +1. **Order matters.** Commit the version bump before creating the tag. Seems obvious in retrospect. |
| 166 | + |
| 167 | +2. **Verify before you ship.** Build the binary locally and check `--version` before tagging. Don't trust that "it should work." |
| 168 | + |
| 169 | +3. **Document your release process.** I now have a detailed `DEPLOYMENT.md` with warnings about the order of operations. |
| 170 | + |
| 171 | +4. **Automate what hurts.** If you make the same mistake twice, write a script. I made this mistake once—hopefully that's enough motivation. |
| 172 | + |
| 173 | +## What's Next |
| 174 | + |
| 175 | +- Fix the setup command animations (#96—they're completely broken) |
| 176 | +- Consider that release automation script |
| 177 | +- Maybe look at semantic-release or similar tools |
| 178 | + |
| 179 | +For now, 4.0.0 is out. HIG support works. Framework aliases work. Swift.org is properly indexed again. |
| 180 | + |
| 181 | +And I've learned more about release engineering than I wanted to in one afternoon. |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +*Cupertino is an Apple Documentation MCP Server. Check it out at [github.com/mihaelamj/cupertino](https://github.com/mihaelamj/cupertino).* |
0 commit comments