name: iOS Dev Repo # Builds an unsigned dev IPA and publishes it together with a freshly-generated # AltStore source (devrepo.json) to a rolling "dev" prerelease. # https://github.com/FTStuff/FTStore/releases/download/dev/devrepo.json # # Monorepo: the Xcode project lives in ios/, so every build step runs there. # app-repo.json stays at the repo root — its raw.githubusercontent URL is what # users have subscribed to, so it must not move. on: push: branches: [main] paths: - "ios/**" - ".github/workflows/ios-dev-repo.yml" workflow_dispatch: concurrency: group: dev-repo-${{ github.ref }} cancel-in-progress: true permissions: contents: write jobs: build: # macOS label required: the bare `self-hosted` label also matches the org's # Linux runners, which have no xcodebuild and fail the build outright. runs-on: [self-hosted, macOS] env: DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer # statt sudo xcode-select steps: - name: Checkout uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 1 - name: Cache SwiftPM uses: actions/cache@v5 with: path: ~/Library/Caches/org.swift.swiftpm key: spm-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }} restore-keys: spm-${{ runner.os }}- - name: Compute dev version run: | BASE=$(grep '^FTSTORE_PROJECT_VERSION' ios/FTStore.xcconfig | cut -d= -f2 | tr -d '[:space:]') echo "VERSION=${BASE}-dev.${{ github.run_number }}" >> "$GITHUB_ENV" echo "DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV" - name: Stamp marketing version # NOT agvtool: it writes CFBundleShortVersionString into Info.plist, but # the target sets MARKETING_VERSION = $(FTSTORE_PROJECT_VERSION) and # GENERATE_INFOPLIST_FILE = YES, so Xcode regenerates that key from the # build setting and the edit is thrown away. The IPA then reports the # base version while devrepo.json advertises the dev one, and every # AltStore client sees a permanent "update available". Set the variable # the build actually reads, and fail loudly if it ever stops matching. run: | sed -i '' "s|^FTSTORE_PROJECT_VERSION=.*|FTSTORE_PROJECT_VERSION=${VERSION}|" ios/FTStore.xcconfig grep -q "^FTSTORE_PROJECT_VERSION=${VERSION}$" ios/FTStore.xcconfig - name: Stamp build number (best effort) # Records the source commit in CFBundleVersion. Best effort because # agvtool trips over GENERATE_INFOPLIST_FILE=YES ("Cannot find …/YES") # while still doing the write. continue-on-error: true working-directory: ios run: agvtool new-version -all "${{ github.sha }}" - name: Build (iphoneos, unsigned) working-directory: ios run: make iphoneos - name: Verify the Live Activity extension shipped and can load # The Dynamic Island lives in an embedded appex, so an IPA that is wrong # about it installs and runs perfectly — the island just never appears, # and nothing in the build says why. Every check here is for a way that # can happen silently: # # missing the extension is not in the IPA at all. This is what # happened when manager/ shared this build's derived data # (identical project, scheme, target and pbxproj GUIDs) # and its stale-file sweep went after an extension it has # no target for. # wrong id iOS refuses to load an app extension whose bundle id is # not prefixed by its host app's, and says nothing. The # two ids are built from separate settings in the pbxproj, # so one can drift from the other — and a disambiguator # applied to the app but not the extension does exactly # that. # wrong point a widget extension must declare the widgetkit extension # point. Without it the appex is dead weight. # # Presence alone was the old check. It passed on all three of these. run: ios/scripts/verify-live-activity.sh ios/packages/FTStore.ipa - name: Generate devrepo.json run: | IPA=ios/packages/FTStore.ipa SIZE=$(stat -f%z "$IPA") URL="https://github.com/${{ github.repository }}/releases/download/dev/FTStore.ipa" jq \ --arg v "$VERSION" --arg d "$DATE" --argjson s "$SIZE" --arg u "$URL" ' .name = "FTStore (Dev)" | .identifier = "app.ftstore.devrepo" | .apps[0].name = "FTStore (Dev)" | .apps[0].subtitle = "Latest commit build — for testing" | .apps[0].version = $v | .apps[0].versionDate = $d | .apps[0].size = $s | .apps[0].downloadURL = $u | .apps[0].versions = [ { "version": $v, "date": $d, "size": $s, "downloadURL": $u } ] | .news = [] ' app-repo.json > devrepo.json echo "Generated devrepo.json for $VERSION (${SIZE} bytes)" - name: Publish dev prerelease uses: softprops/action-gh-release@v3 with: tag_name: dev name: Dev build prerelease: true make_latest: false body: "Rolling development build of the latest `main` commit (${{ github.sha }}). Unsigned — FTStore (formerly Feather) signs it on-device at install." files: | ios/packages/FTStore.ipa devrepo.json