Skip to content

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:

uv run --project scripts swpngx devices check

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-notes previews them)
  • TestFlight "What to Test" is one 1.11.0 (209) section per build of the current version, newest first — the same shape as changelog.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_VERSION with just 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):

uv run --project scripts swpngx frame

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:

uv run --project scripts swpngx fonts download

Preview locally:

uv run --project scripts swpngx preview

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.