mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
601 lines
25 KiB
YAML
601 lines
25 KiB
YAML
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
|
|
|
|
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]
|
|
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: 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"
|
|
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}-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"
|