Maintenance
Maintenance
Automation lives in the scripts/ package (swpngx) and
fastlane/. Run swpngx via uv run --project scripts swpngx …
from the repo root (or install the package so swpngx is on your PATH).
Device ids, simulator names, bezel PNGs, and framing geometry are defined once in
screenshot_devices.toml. screenshots.toml
references those ids for capture; frames.toml references the same file
for framing. iPhone screenshots use Pro Max only (App Store Connect scales for smaller
phones). Check alignment with:
TestFlight betas
A beta is cut entirely on CI — no commit, no tag, no local build:
just beta # from origin/main
just beta --ref develop/v1.12 # from another branch
just beta --dry-run # CI builds and exports, uploads nothing
This previews the notes the build will publish, then dispatches
.github/workflows/beta.yml (which you can also
start straight from the Actions tab). CI asks App Store Connect for the build
number, writes it into Config/Shared/Version.xcconfig for that build only,
archives, signs, uploads, and — after the upload succeeds — creates the
builds/<version>/<build> tag and its GitHub prerelease at the built commit. The
tag is the record of what shipped, not the trigger, so it can never name a build
that failed to upload.
Notes come from current_changelog.txt, which
accumulates the user-facing bullets for the current marketing version. Nothing
clears it at build time:
- a build's notes are the bullets added since the previous build tag; they
become the prerelease body, which is what the in-app What's New screen shows
(
just beta-notespreviews them) - TestFlight "What to Test" is one
1.11.0 (209)section per build of the current version, newest first — the same shape aschangelog.txt, scoped to the version in flight. App Store Connect caps that field at 4000 characters and a version's notes routinely run past it, so whole builds are dropped off the oldest end until it fits and a link to the releases takes their place. A build is either there in full or not at all; only a single build whose own notes exceed the budget falls back to trimming by line. No more clearing the changelog by hand to fit - when the version ships, empty the file in the same commit that bumps
MARKETING_VERSIONwithjust set-version
To fix notes after the fact, edit the release body on GitHub (the copy users
read) and, for TestFlight, run just set-test-notes <build>.
changelog.txt is an offline copy of the per-build notes,
regenerated from the releases with just changelog-archive.
App Store screenshots
1. Capture raw simulator PNGs (config: screenshots.toml):
uv run --project scripts swpngx capture setup
# Or pin the screenshot backend:
uv run --project scripts swpngx capture setup --pngx-tag 2.19.4
# Or add random tags for UI stress testing:
uv run --project scripts swpngx capture setup --random-tags 1000
# Or add multiple random metadata types:
uv run --project scripts swpngx capture setup --random-tags 1000 --random-correspondents 500 --random-document-types 250
uv run --project scripts swpngx capture capture
Writes files like fastlane/screenshots/en-US/iPhone_17_Pro_Max-01_documents.png
(the prefix is the device id from screenshot_devices.toml). Tear down the backend
with uv run --project scripts swpngx capture teardown.
2. Install device bezels from Apple Design Resources
(Product Bezels). Each [[device]] in screenshot_devices.toml
names a bezel_pack from bezel_packs.toml and the PNG filename
to install under fastlane/screenshots/frames/:
# Use DMGs you already downloaded (e.g. from ~/Downloads):
uv run --project scripts swpngx frames download --dmg-dir ~/Downloads
# Or let swpngx download from Apple into ~/Library/Caches/swpngx/bezels:
uv run --project scripts swpngx frames download
# Only the iPhone 17 pack, with one DMG path:
uv run --project scripts swpngx frames download --pack iphone_17 \
--dmg-path ~/Downloads/Bezel-iPhone-17.dmg
Requires macOS (hdiutil). Apple’s DMG license prompt is accepted automatically
(Y); you must comply with the Apple Design Resources license.
3. Frame screenshots for the App Store (config: frames.toml):
Reads fastlane/screenshots/<locale>/, composites device bezels and localized
titles (from fastlane/screenshots/Screenshots.xcstrings),
and writes fastlane/screenshots/framed/<locale>/*-framed.png. Device names in
filenames must match device id in screenshot_devices.toml.
The framing font (Open Sans) is not in git (fastlane/.gitignore ignores *.ttf).
swpngx frame downloads it automatically from Google Fonts
via fonts.toml, or you can prefetch:
Preview locally:
4. Upload metadata and framed screenshots with deliver
(config: fastlane/Deliverfile):
just deliver-preview # dry run
just deliver # metadata + screenshots
just deliver-metadata # metadata only (What's New, description, …)
just deliver uploads both metadata and screenshots in one fastlane deliver
run (not a separate command). It uses MARKETING_VERSION from
Config/Shared/Version.xcconfig, replaces all screenshots, and reads metadata
from fastlane/metadata/.
What's New is edited only in fastlane/metadata/default/release_notes.txt.
The Deliverfile reads that once and applies it to every metadata locale (including
en-US, which App Store Connect does not fill from default/ on its own).
Before uploading:
- Edit fastlane/metadata/default/release_notes.txt for the new version.
skip_binary_upload is enabled in the Deliverfile, so this uploads metadata and
screenshots only (no IPA). TestFlight builds go through just beta separately.
Authentication uses the same App Store Connect API key as TestFlight
(APP_STORE_CONNECT_API_KEY_ID, APP_STORE_CONNECT_ISSUER_ID, and
APP_STORE_CONNECT_KEY_FILEPATH or APP_STORE_CONNECT_KEY_CONTENT), plus
APPLE_ID, ITC_TEAM_ID, and TEAM_ID from fastlane/Appfile.
PNG screenshots under fastlane/screenshots/ are gitignored; generate them locally
before upload.
There is also bundle exec fastlane screenshots (snapshot + fastlane/Snapfile),
a UI-test-based path that is separate from the swpngx workflow above.
Panorama montage
Concatenate framed screenshots (adjust the glob for your device name):
montage fastlane/screenshots/framed/en-US/iPhone_17_Pro_Max-0*-framed.png \
-tile 4x1 -geometry +20+0 panorama.png
If montage fails with unable to read font `', the ImageMagick install has no
configured fonts; pass one explicitly, e.g.
-font /System/Library/Fonts/Supplemental/Arial.ttf.
docs/panorama.png is a symlink to the top-level panorama.png, so regenerating
it updates both the README and the docs site.