name: Release # Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS # IPA, a Nintendo Switch SD-ready zip (experimental), Xbox UWP, and the Anbernic # RG34XXSP (Stock OS 64-bit MOD / PortMaster) port, then publishes them as a # GitHub Release. # # Versioning: # - First ever release is 0.1.0. # - Every push to main auto-increments the patch: 0.1.0 -> 0.1.1 -> ... -> 0.1.99, # then rolls over to 0.2.0 and keeps going. # - To force a specific version, either: # * run this workflow manually (Actions tab) and type it into "version", or # * put "[release X.Y.Z]" anywhere in the commit message. # # Branch model: day-to-day work merges to `dev`. Releases stay on `main` only # so promoting `dev` -> `main` is the ship gate that cuts a build. on: push: branches: [main] # CI/workflow and docs-only changes don't ship anything to users, so they # don't earn a release. A push touching these *and* real source still # releases; only pushes confined entirely to these paths are skipped. paths-ignore: - '.github/**' - '**.md' - 'mobile/ios/app-repo.json' workflow_dispatch: inputs: version: description: "Exact version to release (e.g. 0.2.0). Leave blank to auto-increment." required: false default: "" permissions: contents: write issues: read pull-requests: read concurrency: group: release cancel-in-progress: false jobs: version: name: determine release version runs-on: ubuntu-latest outputs: version: ${{ steps.ver.outputs.version }} tag: ${{ steps.ver.outputs.tag }} steps: - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - name: Determine version id: ver env: DISPATCH_VERSION: ${{ github.event.inputs.version }} GH_TOKEN: ${{ github.token }} run: | set -euo pipefail semver_re='^[0-9]+\.[0-9]+\.[0-9]+$' # 1) Explicit override from a manual run. override="" if [ -n "${DISPATCH_VERSION:-}" ]; then override="$DISPATCH_VERSION" else # 2) Override from the commit message: [release X.Y.Z] msg="$(git log -1 --pretty=%B || true)" tag_ver="$(printf '%s' "$msg" | sed -n -E 's/.*\[release[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/p' | head -1)" if [ -n "$tag_ver" ]; then override="$tag_ver" fi fi if [ -n "$override" ]; then if ! printf '%s' "$override" | grep -Eq "$semver_re"; then echo "::error::Invalid version override '$override' (expected X.Y.Z)" exit 1 fi version="$override" echo "Using override version: $version" else # 3) Auto-increment from the highest existing vX.Y.Z tag. latest="$(git tag -l 'v*' \ | sed -E 's/^v//' \ | grep -E "$semver_re" \ | sort -t. -k1,1n -k2,2n -k3,3n \ | tail -1 || true)" if [ -z "$latest" ]; then version="0.1.0" echo "No existing release tag; starting at $version" else major="${latest%%.*}" rest="${latest#*.}" minor="${rest%%.*}" patch="${rest##*.}" patch=$((patch + 1)) if [ "$patch" -gt 99 ]; then minor=$((minor + 1)) patch=0 fi version="${major}.${minor}.${patch}" echo "Latest was $latest; next is $version" fi fi tag="v${version}" if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then echo "::error::Tag $tag already exists. Pick a different version." exit 1 fi if gh release view "$tag" >/dev/null 2>&1; then echo "::error::Release $tag already exists. Pick a different version." exit 1 fi echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" love-payload: name: build release game.love needs: version runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Build shared payload run: | scripts/pack_love.sh \ --output dist/payload/game.love \ --listing dist/payload/love-listing.txt \ --version "${{ needs.version.outputs.version }}" - name: Upload shared payload uses: actions/upload-artifact@v7 with: name: gen1recomp-release-love path: dist/payload/game.love if-no-files-found: error retention-days: 1 linux-arm64: name: build Linux arm64 AppImage needs: [version, love-payload] # GitHub's free arm64 runner for public repos. It has to be arm64: the # AppImage compiles LÖVE natively inside a Debian bullseye arm64 # container, and the qemu-emulated alternative takes hours. runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v7 - name: Download shared payload uses: actions/download-artifact@v8 with: name: gen1recomp-release-love path: .bazinga/work - name: Build Linux arm64 AppImage run: | set -euo pipefail scripts/build_linux_arm64.sh \ --version "${{ needs.version.outputs.version }}" \ --game-love .bazinga/work/game.love - name: Upload Linux arm64 release uses: actions/upload-artifact@v7 with: name: gen1recomp-linux-arm64-release path: | dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage.sha256 if-no-files-found: error retention-days: 1 xbox-uwp: name: build Xbox UWP release needs: [version, love-payload] runs-on: windows-2022 steps: - uses: actions/checkout@v7 - name: Download shared payload uses: actions/download-artifact@v8 with: name: gen1recomp-release-love path: .bazinga/work - name: Prepare signing certificate shell: pwsh env: CERTIFICATE_BASE64: ${{ secrets.XBOX_UWP_SIGNING_CERTIFICATE }} CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }} CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }} run: | if ($env:CANONICAL_REPOSITORY -eq 'true' -and [string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) { throw 'XBOX_UWP_SIGNING_CERTIFICATE is not configured.' } if ([string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) { "UWP_PUBLISHER=CN=Gen1Recomp" | Out-File $env:GITHUB_ENV -Append exit 0 } $pfx = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.pfx' [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERTIFICATE_BASE64)) $flags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet $cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new( $pfx, $env:CERTIFICATE_PASSWORD, $flags) $cer = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.cer' [IO.File]::WriteAllBytes( $cer, $cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)) Import-Certificate -FilePath $cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople | Out-Null "UWP_PFX=$pfx" | Out-File $env:GITHUB_ENV -Append "UWP_CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File $env:GITHUB_ENV -Append "UWP_PUBLISHER=$($cert.Subject)" | Out-File $env:GITHUB_ENV -Append - name: Build Xbox UWP package shell: bash run: | bash scripts/build_xbox_uwp.sh \ --release \ --version "${{ needs.version.outputs.version }}" \ --publisher "$UWP_PUBLISHER" \ --game-love .bazinga/work/game.love - name: Sign and stage Xbox UWP release shell: pwsh env: CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }} run: | if (-not $env:UWP_PFX) { exit 0 } scripts/xbox-uwp/stage_release.ps1 ` -Version '${{ needs.version.outputs.version }}' ` -Configuration Release ` -BuildInfo .bazinga/work/xbox-uwp-build-info.json ` -CertificatePath $env:UWP_PFX ` -CertificatePassword $env:CERTIFICATE_PASSWORD - name: Upload Xbox UWP release uses: actions/upload-artifact@v7 with: name: gen1recomp-xbox-uwp-release path: | dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip.sha256 if-no-files-found: error retention-days: 1 - name: Remove signing certificate if: always() shell: pwsh run: | if ($env:UWP_CERT_THUMBPRINT) { Remove-Item "Cert:\LocalMachine\TrustedPeople\$env:UWP_CERT_THUMBPRINT" -ErrorAction SilentlyContinue } if ($env:UWP_PFX) { Remove-Item $env:UWP_PFX -Force -ErrorAction SilentlyContinue } release: needs: [version, xbox-uwp, linux-arm64] runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }} steps: # The self-hosted runner lives under the machine owner's home # directory; mask it first so absolute paths in every later step's # output show up as *** in the public workflow logs. - name: Mask runner paths run: echo "::add-mask::$HOME" - name: Checkout uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - name: Import signing certificate into a temporary keychain if: github.repository == 'bryanthaboi/gen1recomp' run: | set -euo pipefail KEYCHAIN_PATH="$RUNNER_TEMP/pokemon-signing.keychain-db" ci_dir="${POKEMON_CI_DIR:-$HOME/.config/pokemon-ci}" p12="$ci_dir/signing.p12" passfile="$ci_dir/signing.pass" if [ ! -f "$p12" ] || [ ! -f "$passfile" ]; then echo "::error::Signing material not found in $ci_dir. Run scripts/ci-setup-signing.sh on the runner." exit 1 fi p12pw="$(cat "$passfile")" kcpw="$(openssl rand -base64 24)" echo "::add-mask::$kcpw" # Fresh, dedicated keychain — no dependence on the login keychain/session. security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true security create-keychain -p "$kcpw" "$KEYCHAIN_PATH" security set-keychain-settings "$KEYCHAIN_PATH" # disable auto-lock security unlock-keychain -p "$kcpw" "$KEYCHAIN_PATH" security import "$p12" -P "$p12pw" -k "$KEYCHAIN_PATH" \ -T /usr/bin/codesign -T /usr/bin/security # Let codesign use the key non-interactively. security set-key-partition-list -S apple-tool:,apple:,codesign: \ -s -k "$kcpw" "$KEYCHAIN_PATH" >/dev/null # Make the keychain visible to find-identity/codesign (prepend to search list). existing="$(security list-keychains -d user | sed -e 's/^[[:space:]]*//' -e 's/"//g')" security list-keychains -d user -s "$KEYCHAIN_PATH" $existing echo "Identities available to codesign:" security find-identity -v -p codesigning "$KEYCHAIN_PATH" - name: Build macOS + Windows + Linux run: | set -euo pipefail # Sign in-build (identity auto-detected from the temp keychain); # notarize separately below so it uses secret credentials, not a # login-keychain profile. "all" also builds the Linux AppImage, # which needs no signing/notarization. scripts/build.sh all --version "${{ needs.version.outputs.version }}" --no-notarize - name: Build Android run: | set -euo pipefail scripts/build_android.sh --version "${{ needs.version.outputs.version }}" - name: Install xcbeautify run: | set -euo pipefail brew list xcbeautify >/dev/null 2>&1 || brew install xcbeautify - name: Build iOS env: CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }} run: | set -euo pipefail if [ "$CANONICAL_REPOSITORY" = true ]; then scripts/build_ios.sh --fetch --device --release \ --version "${{ needs.version.outputs.version }}" else scripts/build_ios.sh --fetch --release \ --version "${{ needs.version.outputs.version }}" fi - name: Build Switch run: | set -euo pipefail # Hard-fail gate: Switch ships with every release (never soft-fail). # PR CI is path-gated (ubuntu selftest + canonical fused); release # always builds Switch regardless of which files changed. # Needs native switch-tools (nacptool/elf2nro) and/or Docker on the # Mac self-hosted runner; see docs/switch-build.md. scripts/build_switch.sh --fetch --fused \ --version "${{ needs.version.outputs.version }}" - name: Build Anbernic RG34XXSP port run: | set -euo pipefail # Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5 # runtime from PortMaster-GUI, so it needs no signing/notarization. ./build-rg34xxsp.sh --version "${{ needs.version.outputs.version }}" - name: Notarize & staple macOS app if: github.repository == 'bryanthaboi/gen1recomp' run: | set -euo pipefail ci_dir="${POKEMON_CI_DIR:-$HOME/.config/pokemon-ci}" if [ ! -f "$ci_dir/notary.env" ]; then echo "::error::Missing $ci_dir/notary.env. Run scripts/ci-setup-signing.sh on the runner." exit 1 fi set -a; . "$ci_dir/notary.env"; set +a echo "::add-mask::$APPLE_APP_PASSWORD" app=".bazinga/work/gen1recomp.app" zip="dist/mac/gen1recomp-macos.zip" [ -d "$app" ] || { echo "::error::signed app not found at $app"; exit 1; } if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_APP_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then echo "::error::notary.env is missing APPLE_ID / APPLE_APP_PASSWORD / APPLE_TEAM_ID." exit 1 fi echo "Submitting to Apple notary service (can take a few minutes)..." xcrun notarytool submit "$zip" \ --apple-id "$APPLE_ID" \ --team-id "$APPLE_TEAM_ID" \ --password "$APPLE_APP_PASSWORD" \ --wait echo "Stapling ticket to the app..." xcrun stapler staple "$app" # Re-zip the now-stapled app (same format build.sh uses). rm -f "$zip" ditto -c -k --sequesterRsrc --keepParent "$app" "$zip" echo "Notarized + stapled ✓" - name: Download Xbox UWP release if: github.repository == 'bryanthaboi/gen1recomp' uses: actions/download-artifact@v8 with: name: gen1recomp-xbox-uwp-release path: dist/xbox-uwp - name: Download Linux arm64 release if: github.repository == 'bryanthaboi/gen1recomp' uses: actions/download-artifact@v8 with: name: gen1recomp-linux-arm64-release path: dist/linux-arm64 - name: Stage release assets if: github.repository == 'bryanthaboi/gen1recomp' id: assets run: | set -euo pipefail v="${{ needs.version.outputs.version }}" outdir="dist/release" rm -rf "$outdir" mkdir -p "$outdir" cp "dist/mac/gen1recomp-macos.zip" "$outdir/gen1recomp-${v}-macos.zip" cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip" cp "dist/linux/gen1recomp-linux.zip" "$outdir/gen1recomp-${v}-linux.zip" # arm64 desktop Linux (Raspberry Pi, Armbian, arm64 VMs). Built on # its own runner because LÖVE publishes no aarch64 binary and the # AppImage has to be compiled natively; ships as a runnable # AppImage rather than a zip so `chmod +x && ./it` just works. arm64_appimage="dist/linux-arm64/gen1recomp-${v}-linux-arm64.AppImage" [ -f "$arm64_appimage" ] || { echo "::error::$arm64_appimage not found (expected from the linux-arm64 job)"; exit 1; } cp "$arm64_appimage" "$outdir/gen1recomp-${v}-linux-arm64.AppImage" chmod +x "$outdir/gen1recomp-${v}-linux-arm64.AppImage" apk="$(find dist/android/debug -name '*.apk' | head -1)" [ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/debug"; exit 1; } cp "$apk" "$outdir/gen1recomp-${v}-android.apk" ipa="dist/ios/gen1recomp.ipa" [ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; } cp "$ipa" "$outdir/gen1recomp-${v}-ios.ipa" swzip="dist/switch/gen1recomp-${v}-switch.zip" [ -f "$swzip" ] || { echo "::error::$swzip not found (expected from scripts/build_switch.sh --fused → pack_sd_zip.sh)"; exit 1; } cp "$swzip" "$outdir/gen1recomp-${v}-switch.zip" # Local fused .nro stays under dist/switch/ for PR CI / debug; release # publishes the SD-ready zip only. uwp="dist/xbox-uwp/gen1recomp-${v}-xbox-uwp.zip" [ -f "$uwp" ] || { echo "::error::$uwp not found (expected from the Xbox UWP job)"; exit 1; } cp "$uwp" "$outdir/gen1recomp-${v}-xbox-uwp.zip" # Anbernic handheld port (suffix names the CFW it targets, so a # future RG35XX/other-CFW pack can ship alongside it). rg34="dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip" [ -f "$rg34" ] || { echo "::error::$rg34 not found (expected from ./build-rg34xxsp.sh)"; exit 1; } cp "$rg34" "$outdir/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip" # Platform-independent update payload, built alongside the desktop # apps above (same game.love that gets fused into each of them). love_file=".bazinga/work/game.love" [ -f "$love_file" ] || { echo "::error::$love_file not found (expected from scripts/build.sh)"; exit 1; } cp "$love_file" "$outdir/gen1recomp-${v}.love" ls -lh "$outdir" # Checksums for every staged release asset (sums file itself is # written after this and named outside the gen1recomp-* glob, so it # never lists itself). (cd "$outdir" && shasum -a 256 gen1recomp-* > sha256sums.txt) cat "$outdir/sha256sums.txt" - name: Publish GitHub Release if: github.repository == 'bryanthaboi/gen1recomp' env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail v="${{ needs.version.outputs.version }}" tag="${{ needs.version.outputs.tag }}" # Issues this release closes. Three sources, deduped by number: # 1. GitHub's own "closing issues" links on every PR whose # commits are in the range (works for squash, rebase, and # merge commits alike) -- including PRs that were merged # into a branch this release PR is itself merging in. # 2. CLOSES/fixes/resolves text in those PRs' titles + bodies # that GitHub didn't turn into a closing link (a PR into a # non-default branch never gets one). # 3. The same text in raw commit messages, so a direct push # with "CLOSES #N #M" still lands in the notes. # Scans every commit since the previous tag so a skipped release # run doesn't drop issues on the floor. prev_tag="$(git tag -l 'v*' --sort=-v:refname | grep -v "^${tag}$" | head -1 || true)" range="${prev_tag:+${prev_tag}..}$GITHUB_SHA" # every #N on a line that carries a closing keyword (handles the # multi-issue "CLOSES #1 #2 #3" form the tracker uses) scan_closes() { grep -iE '\b(close[sd]?|fix(es|ed)?|resolve[sd]?)\b' \ | grep -oE '#[0-9]+' | tr -d '#' || true } nums="" nums+=" $(git log --pretty=%B "$range" | scan_closes | tr '\n' ' ')" prs="$(git log --pretty=%H "$range" \ | xargs -I{} gh api "repos/$GITHUB_REPOSITORY/commits/{}/pulls" \ --jq '.[].number' 2>/dev/null \ | sort -un || true)" for pr in $prs; do nums+=" $(gh api graphql \ -f owner="${GITHUB_REPOSITORY%/*}" \ -f name="${GITHUB_REPOSITORY#*/}" \ -F pr="$pr" \ -f query=' query($owner:String!, $name:String!, $pr:Int!) { repository(owner:$owner, name:$name) { pullRequest(number:$pr) { closingIssuesReferences(first:50) { nodes { number } } } } }' \ --jq '.data.repository.pullRequest.closingIssuesReferences.nodes[].number' \ 2>/dev/null | grep -E '^[0-9]+$' | tr '\n' ' ' || true)" nums+=" $(gh api "repos/$GITHUB_REPOSITORY/pulls/$pr" \ --jq '.title + " " + (.body // "")' 2>/dev/null \ | scan_closes | tr '\n' ' ' || true)" done # dedupe, drop anything that is a PR or does not exist, keep # titles (gh api prints the response body to stdout on an HTTP # error, so only a zero exit counts) closed="" for n in $(printf '%s' "$nums" | tr ' ' '\n' | grep -E '^[0-9]+$' | sort -un); do if title="$(gh api "repos/$GITHUB_REPOSITORY/issues/$n" \ --jq 'if .pull_request then empty else .title end' \ 2>/dev/null)"; then [ -n "$title" ] && closed+="- #$n $title"$'\n' fi done closed="$(printf '%s' "$closed" | grep . | sort -t'#' -k2 -n || true)" # Everyone whose commits are in the range: GitHub login when the # commit is linked to an account, the raw git author name when not; # CI bots filtered out. base="${prev_tag:-$(git rev-list --max-parents=0 "$GITHUB_SHA" | tail -1)}" contributors="$(gh api --paginate \ "repos/$GITHUB_REPOSITORY/compare/${base}...${GITHUB_SHA}" \ --jq '.commits[] | if .author and .author.login then "@" + .author.login else .commit.author.name end' 2>/dev/null \ | grep -viE '\[bot\]$' | sort -uf | sed 's/^/- /' || true)" notes="Download the correct version for your computer below." if [ -n "$closed" ]; then notes+=$'\n\n## Issues closed\n\n'"$closed" fi if [ -n "$contributors" ]; then notes+=$'\n\n## Contributors\n\n'"$contributors" fi printf 'Release notes:\n%s\n' "$notes" release_files=( "dist/release/gen1recomp-${v}-macos.zip" "dist/release/gen1recomp-${v}-windows.zip" "dist/release/gen1recomp-${v}-linux.zip" "dist/release/gen1recomp-${v}-linux-arm64.AppImage" "dist/release/gen1recomp-${v}-android.apk" "dist/release/gen1recomp-${v}-ios.ipa" "dist/release/gen1recomp-${v}-switch.zip" "dist/release/gen1recomp-${v}-xbox-uwp.zip" "dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip" "dist/release/gen1recomp-${v}.love" "dist/release/sha256sums.txt" ) gh release create "$tag" \ --target "$GITHUB_SHA" \ --title "$v" \ --notes "$notes" \ "${release_files[@]}" echo "Published release $tag" - name: Update iOS app repository if: github.repository == 'bryanthaboi/gen1recomp' run: | set -euo pipefail v="${{ needs.version.outputs.version }}" ipa="dist/release/gen1recomp-${v}-ios.ipa" app_repo="mobile/ios/app-repo.json" [ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; } [ -f "$app_repo" ] || { echo "::error::$app_repo not found"; exit 1; } date="$(date -u +"%Y-%m-%d")" size="$(wc -c < "$ipa" | tr -d '[:space:]')" download_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${v}/gen1recomp-${v}-ios.ipa" localized_description="Gen1Recomp - A native Lua / LÖVE2D recreation of Gen 1 Poke" release_notes="$(GH_TOKEN="${{ github.token }}" gh release view "v${v}" --json body --jq '.body // ""' 2>/dev/null || true)" if [ -n "$release_notes" ]; then localized_description="$release_notes" fi entry="$(jq -n \ --arg version "$v" \ --arg date "$date" \ --arg download_url "$download_url" \ --arg localized_description "$localized_description" \ --argjson size "$size" \ '{version: $version, date: $date, size: $size, downloadURL: $download_url, localizedDescription: $localized_description}')" if jq -e --arg version "$v" \ 'any(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions[]?; .version == $version)' \ "$app_repo" >/dev/null; then jq --arg version "$v" --argjson entry "$entry" \ '(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions) |= map(if .version == $version then $entry else . end)' \ "$app_repo" > "$app_repo.tmp" else jq --argjson entry "$entry" \ '(.apps[] | select(.bundleIdentifier == "com.theboisclub.gen1recomp").versions) |= [$entry] + .' \ "$app_repo" > "$app_repo.tmp" fi mv "$app_repo.tmp" "$app_repo" # main is PR-only for everyone except deploy keys (the "main protection" # ruleset's bypass actor), so this push must authenticate with the # RELEASE_DEPLOY_KEY deploy key over SSH; the workflow's GITHUB_TOKEN # would be rejected by the branch protection. - name: Commit iOS app repository if: github.repository == 'bryanthaboi/gen1recomp' env: DEPLOY_KEY: ${{ secrets.RELEASE_DEPLOY_KEY }} run: | set -euo pipefail git add mobile/ios/app-repo.json if git diff --cached --quiet; then echo "app-repo.json unchanged; nothing to push" exit 0 fi key="$RUNNER_TEMP/release-deploy-key" printf '%s\n' "$DEPLOY_KEY" > "$key" chmod 600 "$key" git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git commit -m "chore(ios): update app-repo.json [skip ci]" git -c core.sshCommand="ssh -i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \ push "git@github.com:${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_REF_NAME}" rm -f "$key" - name: Clean up signing keychain if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }} run: | security delete-keychain "$RUNNER_TEMP/pokemon-signing.keychain-db" 2>/dev/null || true rm -f "$RUNNER_TEMP/release-deploy-key"