Skip to content

Fix cert parsing fallback & OSStatus cast #40

Fix cert parsing fallback & OSStatus cast

Fix cert parsing fallback & OSStatus cast #40

name: ProStore iOS Build and Release
on:
workflow_dispatch:
push:
branches: [ main ]
paths-ignore:
- 'README.md'
- 'app-repo.json'
- 'gallery/**'
- 'website/**'
permissions:
contents: write
jobs:
build-unsigned-ipa:
runs-on: macos-15
timeout-minutes: 60
outputs:
version: ${{ steps.get_version.outputs.version }}
release_exists: ${{ steps.check_release.outputs.exists }}
changelog: ${{ steps.generate_changelog.outputs.CHANGELOG }}
steps:
- name: Checkout repo (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: true
- name: Show Xcode version (debug)
run: |
echo "=== xcodebuild -version ==="
xcodebuild -version || true
echo "=== sw_vers ==="
sw_vers || true
- name: Ensure jq, yq & gh exist (best-effort — tries brew, then gh installer)
run: |
# jq
if ! command -v jq >/dev/null 2>&1; then
echo "jq not found — attempting to install via brew"
if command -v brew >/dev/null 2>&1; then
brew install jq || true
fi
fi
# yq
if ! command -v yq >/dev/null 2>&1; then
echo "yq not found — attempting to install via brew"
if command -v brew >/dev/null 2>&1; then
brew install yq || true
fi
fi
# gh (GitHub CLI)
if ! command -v gh >/dev/null 2>&1; then
echo "gh not found — attempting to install via brew"
if command -v brew >/dev/null 2>&1; then
brew install gh || true
fi
fi
# Fallback: official gh installer script (works across platforms)
if ! command -v gh >/dev/null 2>&1; then
echo "gh still not found — attempting official installer script"
curl -fsSL https://cli.github.com/install.sh | sh || true
fi
echo "Tool versions (if installed):"
echo "jq: $(jq --version 2>/dev/null || echo 'not installed')"
echo "yq: $(yq --version 2>/dev/null || echo 'not installed')"
echo "gh: $(gh --version 2>/dev/null || echo 'not installed')"
shell: bash
- name: Ensure project.yml and sources exist (debug)
run: |
echo "Workspace files:"
ls -la || true
echo "project.yml (first 200 lines):"
sed -n '1,200p' project.yml || true
- name: Build xcodegen from source (install to $HOME/.local/bin)
run: |
set -e
git clone --depth 1 https://github.com/yonaskolb/XcodeGen.git /tmp/XcodeGen
cd /tmp/XcodeGen
swift build -c release
mkdir -p $HOME/.local/bin
cp .build/release/xcodegen $HOME/.local/bin/xcodegen
echo "Installed xcodegen to $HOME/.local/bin"
- name: Generate Xcode project with xcodegen
run: |
set -e
$HOME/.local/bin/xcodegen generate --spec project.yml
echo "Generated project at: $(pwd)/prostore.xcodeproj"
echo "project.pbxproj header (first 60 lines):"
sed -n '1,60p' prostore.xcodeproj/project.pbxproj || true
- name: Resolve Swift Package dependencies
run: |
set -e
echo "Resolving Swift package dependencies for prostore..."
xcodebuild -resolvePackageDependencies -project prostore.xcodeproj -scheme prostore -configuration Release
- name: Try building archive, auto-fix objectVersion on future-format error
run: |
set -e
ARCHIVE_PATH="$PWD/build/prostore.xcarchive"
mkdir -p build
candidates=(77 70 63 60 56 55)
build_ok=0
for v in "${candidates[@]}"; do
echo "----- Attempting build with objectVersion = $v -----"
/usr/bin/perl -0777 -pe "s/objectVersion = \\d+;/objectVersion = $v;/" -i.bak prostore.xcodeproj/project.pbxproj || true
echo "Applied objectVersion $v. Header preview:"
sed -n '1,20p' prostore.xcodeproj/project.pbxproj || true
echo "Running xcodebuild archive..."
set +e
xcodebuild clean archive \
-project prostore.xcodeproj \
-scheme prostore \
-archivePath "$ARCHIVE_PATH" \
-sdk iphoneos \
-configuration Release \
CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO
rc=$?
set -e
if [ $rc -eq 0 ]; then
echo "xcodebuild succeeded with objectVersion = $v"
build_ok=1
break
else
echo "xcodebuild failed (exit $rc). Logging last 200 lines from DiagnosticReports (if present):"
tail -n 200 /Users/runner/Library/Logs/DiagnosticReports/* 2>/dev/null || true
echo "Trying next objectVersion..."
fi
done
if [ "$build_ok" -ne 1 ]; then
echo "ERROR: All objectVersion attempts failed. Dumping debug info:"
echo "===== project.pbxproj header ====="
sed -n '1,200p' prostore.xcodeproj/project.pbxproj || true
echo "===== build directory listing ====="
ls -la build || true
exit 74
fi
- name: Create unsigned .ipa by packaging device .app (iphoneos)
run: |
set -e
ARCHIVE_PATH="$PWD/build/prostore.xcarchive"
APP_PATH="$ARCHIVE_PATH/Products/Applications/prostore.app"
OUTPUT_IPA="build/com.prostoreios.prostore-unsigned-ios.ipa"
echo "Device app path: $APP_PATH"
if [ ! -d "$APP_PATH" ]; then
echo "ERROR: App not found at expected path. Listing archive contents:"
ls -la "$ARCHIVE_PATH" || true
exit 1
fi
mkdir -p build/Payload
rm -rf build/Payload/* || true
cp -R "$APP_PATH" build/Payload/
(cd build && zip -r "$(basename "$OUTPUT_IPA")" Payload) || exit 1
echo "Created device ipa: $OUTPUT_IPA"
ls -la "$OUTPUT_IPA" || true
- name: Build simulator (iphonesimulator) and package simulator .ipa (if possible)
run: |
set -e
echo "Starting simulator build (won't fail the entire job if it fails)."
SIM_DERIVED="$PWD/build/sim_derived"
rm -rf "$SIM_DERIVED"
set +e
xcodebuild clean build \
-project prostore.xcodeproj \
-scheme prostore \
-configuration Release \
-sdk iphonesimulator \
-derivedDataPath "$SIM_DERIVED" \
CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY=""
rc=$?
set -e
if [ $rc -ne 0 ]; then
echo "Warning: simulator build failed (exit $rc). Will attempt to recover packaging if a simulator .app exists."
fi
POSSIBLE_APPS=(
"$SIM_DERIVED/Build/Products/Release-iphonesimulator/prostore.app"
"$SIM_DERIVED/Build/Products/Debug-iphonesimulator/prostore.app"
"$PWD/build/Build/Products/Release-iphonesimulator/prostore.app"
"$PWD/build/Release-iphonesimulator/prostore.app"
)
FOUND_APP=""
for p in "${POSSIBLE_APPS[@]}"; do
if [ -d "$p" ]; then
FOUND_APP="$p"
break
fi
done
if [ -z "$FOUND_APP" ]; then
echo "Simulator .app not found in expected locations. Listing $SIM_DERIVED contents for debug:"
ls -la "$SIM_DERIVED" || true
echo "Skipping simulator .ipa packaging."
exit 0
fi
echo "Found simulator .app at: $FOUND_APP"
SIM_IPA="build/com.prostoreios.prostore-unsigned-iossimulator.ipa"
mkdir -p build/Payload-sim
rm -rf build/Payload-sim/* || true
cp -R "$FOUND_APP" build/Payload-sim/
(cd build && zip -r "$(basename "$SIM_IPA")" Payload-sim) || exit 1
echo "Created simulator ipa: $SIM_IPA"
ls -la "$SIM_IPA" || true
- name: Upload device unsigned IPA artifact (named as .ipa)
uses: actions/upload-artifact@v4
with:
name: com.prostoreios.prostore-unsigned-ios.ipa
path: build/com.prostoreios.prostore-unsigned-ios.ipa
- name: Upload simulator unsigned IPA artifact (named as .ipa) (ignore if not found)
uses: actions/upload-artifact@v4
with:
name: com.prostoreios.prostore-unsigned-iossimulator.ipa
path: build/com.prostoreios.prostore-unsigned-iossimulator.ipa
if-no-files-found: ignore
- name: Extract version from project.yml
id: get_version
run: |
VERSION=$(yq '.targets.prostore.info.properties.CFBundleShortVersionString' project.yml)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Detected version: $VERSION"
- name: Check if release already exists
id: check_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.get_version.outputs.version }}"
TAG="v$VERSION"
echo "Checking if release $TAG exists..."
if command -v gh >/dev/null 2>&1 && gh release view "$TAG" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: Generate changelog
id: generate_changelog
if: steps.check_release.outputs.exists == 'false'
run: |
set -e
VERSION="${{ steps.get_version.outputs.version }}"
CURRENT_VERSION="$VERSION"
SINCE_COMMIT=""
# Walk commits that touched project.yml from newest -> oldest
for c in $(git rev-list HEAD -- project.yml); do
file=$(git show "$c:project.yml" 2>/dev/null || true)
if [ -z "$file" ]; then
continue
fi
v=$(printf "%s" "$file" | yq '.targets.prostore.info.properties.CFBundleShortVersionString' 2>/dev/null || true)
if [ -z "$v" ]; then
continue
fi
if [ "$v" != "$CURRENT_VERSION" ]; then
SINCE_COMMIT="$c"
break
fi
done
# Always capture HEAD message
HEAD_MSG=$(git log -1 --pretty=format:%s HEAD || echo "")
# Derive SINCE_DATE (ISO 8601) from SINCE_COMMIT
if [ -n "$SINCE_COMMIT" ]; then
SINCE_DATE=$(git show -s --format=%cI "$SINCE_COMMIT" 2>/dev/null || true)
else
SINCE_DATE=""
fi
# Determine repo owner/repo
ORIGIN_URL=$(git remote get-url origin 2>/dev/null || true)
OWNER_REPO=$(printf "%s" "$ORIGIN_URL" | sed -E 's#.*github.com[:/]+([^/]+/[^/]+)(\.git)?#\1#')
# Fetch workflow runs
RUNS_JSON=""
if [ -n "$OWNER_REPO" ] && command -v gh >/dev/null 2>&1; then
echo "Fetching recent workflow runs for ${OWNER_REPO}..."
RUNS_JSON=$(gh api -H "Accept: application/vnd.github+json" \
/repos/"$OWNER_REPO"/actions/runs \
-f per_page=100 -f event=push 2>/dev/null || true)
if [ -z "$RUNS_JSON" ] || [ "$(printf "%s" "$RUNS_JSON" | jq '.workflow_runs | length')" -eq 0 ]; then
RUNS_JSON=$(gh api -H "Accept: application/vnd.github+json" \
/repos/"$OWNER_REPO"/actions/runs \
-f per_page=100 -f event=workflow_dispatch 2>/dev/null || true)
fi
fi
WORKFLOW_LINES=""
if [ -n "$RUNS_JSON" ] && printf "%s" "$RUNS_JSON" | jq -e '.workflow_runs | length > 0' >/dev/null 2>&1; then
if [ -n "$SINCE_DATE" ]; then
SINCE_DATE_UTC=$(date -u -j -f "%Y-%m-%dT%H:%M:%S%z" "$SINCE_DATE" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "$SINCE_DATE")
WORKFLOW_LINES=$(printf "%s" "$RUNS_JSON" | jq -r --arg SINCE "$SINCE_DATE_UTC" '
[.workflow_runs[] | select(.created_at >= $SINCE)] |
sort_by(.created_at)[] |
( "- " + (.head_commit.message // .name // .workflow_name) +
" (#" + (.run_number|tostring) + ") — " +
((.conclusion // "in_progress") | tostring) +
" — " + (.created_at // "") +
" — branch: " + (.head_branch // "") )' || true)
else
WORKFLOW_LINES=$(printf "%s" "$RUNS_JSON" | jq -r '
[.workflow_runs[]] | sort_by(.created_at)[] |
( " - " +
(if (.head_commit.message | length) > 0 then .head_commit.message
else (if (.name|length) > 0 then .name else .workflow_name end) end) +
" (#" + (.run_number|tostring) + ") — " +
((.conclusion // "in_progress") | tostring) +
" — " + (.created_at // "") +
" — branch: " + (.head_branch // "") )' || true)
fi
if [ -n "$WORKFLOW_LINES" ]; then
WORKFLOW_LINES=$(printf "%s\n" "$WORKFLOW_LINES" | awk 'NF && !seen[$0]++ { print $0 }' || true)
fi
fi
# Fallback to commit messages
if [ -z "$WORKFLOW_LINES" ]; then
if [ -n "$SINCE_COMMIT" ]; then
COMMITS_RAW=$(git log --pretty=format:%s "${SINCE_COMMIT}..HEAD" --reverse 2>/dev/null || true)
COMMITS_RAW=$(echo "$COMMITS_RAW" | grep -v "Update ProStore app repo to v" || true)
else
COMMITS_RAW=""
fi
fi
# Build description
printf -v DESC "What's new in Version %s?\n" "$VERSION"
if [ -n "$HEAD_MSG" ]; then
DESC+=$'- '"$HEAD_MSG"$'\n'
fi
if [ -n "$WORKFLOW_LINES" ]; then
DESC+="${WORKFLOW_LINES}"$'\n'
elif [ -n "$COMMITS_RAW" ]; then
OTHER_LINES=$(printf "%s\n" "$COMMITS_RAW" | awk -v head="$HEAD_MSG" 'NF && $0!=head { print "- "$0 }' || true)
if [ -n "$OTHER_LINES" ]; then
DESC+="${OTHER_LINES}"$'\n'
fi
fi
DESC="${DESC%$'\n'}"
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
printf '%s\n' "$DESC" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
shell: bash
create-github-release:
needs: build-unsigned-ipa
if: needs.build-unsigned-ipa.outputs.release_exists == 'false'
runs-on: ubuntu-latest
steps:
- name: Ensure gh exists (best-effort)
run: |
# gh (GitHub CLI)
if ! command -v gh >/dev/null 2>&1; then
echo "gh not found — attempting official installer script"
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y || true
fi
echo "gh: $(gh --version 2>/dev/null || echo 'not installed')"
- name: Download device IPA artifact
uses: actions/download-artifact@v4
with:
name: com.prostoreios.prostore-unsigned-ios.ipa
path: build
- name: Download simulator IPA artifact (optional)
uses: actions/download-artifact@v4
continue-on-error: true
with:
name: com.prostoreios.prostore-unsigned-iossimulator.ipa
path: build
- name: Create GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.build-unsigned-ipa.outputs.version }}
CHANGELOG: ${{ needs.build-unsigned-ipa.outputs.changelog }}
run: |
TAG="v$VERSION"
echo "Creating release $TAG with notes:"
printf '%s\n' "$CHANGELOG"
DEVICE_IPA="build/com.prostoreios.prostore-unsigned-ios.ipa"
SIM_IPA="build/com.prostoreios.prostore-unsigned-iossimulator.ipa"
SIM_ARG=""
if [ -f "$SIM_IPA" ]; then
SIM_ARG="$SIM_IPA"
fi
gh release create "$TAG" \
"$DEVICE_IPA" $SIM_ARG \
--title "ProStore v$VERSION" \
--notes "$CHANGELOG" \
--repo "$GITHUB_REPOSITORY"
shell: bash
create-app-source-json:
needs: build-unsigned-ipa
if: needs.build-unsigned-ipa.outputs.release_exists == 'false'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repo (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: true
- name: Ensure jq exists (best-effort)
run: |
# jq
if ! command -v jq >/dev/null 2>&1; then
echo "jq not found — attempting to install"
sudo apt update
sudo apt install jq -y || true
fi
echo "jq: $(jq --version 2>/dev/null || echo 'not installed')"
- name: Download device IPA artifact
uses: actions/download-artifact@v4
with:
name: com.prostoreios.prostore-unsigned-ios.ipa
path: build
- name: Update app-repo.json with latest ProStore version
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.build-unsigned-ipa.outputs.version }}
CHANGELOG: ${{ needs.build-unsigned-ipa.outputs.changelog }}
run: |
set -e
IPA_PATH="$PWD/build/com.prostoreios.prostore-unsigned-ios.ipa"
DOWNLOAD_URL="https://github.com/ProStore-iOS/ProStore/releases/download/v$VERSION/com.prostoreios.prostore-unsigned-ios.ipa"
REPO_FILE="app-repo.json"
if [ -f "$IPA_PATH" ]; then
IPA_SIZE=$(stat -c%s "$IPA_PATH")
SHA256=$(sha256sum "$IPA_PATH" | awk '{print $1}')
else
IPA_SIZE=0
SHA256=""
fi
VERSION_DATE_ISO=$(date -u +"%Y-%m-%dT%H:%M:%S%z")
MIN_OS="15.0"
FULL_DATE=$(date +%Y%m%d%H%M%S)
ICON_URL="https://raw.githubusercontent.com/ProStore-iOS/ProStore/refs/heads/main/Sources/prostore/Assets.xcassets/AppIcon.appiconset/Icon-1024.png"
BUNDLE="com.prostoreios.prostore"
META_DESC="The BEST alternative app store for iOS!"
DEVNAME="ProStore iOS"
SCREENSHOTS='[
"https://raw.githubusercontent.com/ProStore-iOS/ProStore/refs/heads/main/gallery/Screenshot1.png",
"https://raw.githubusercontent.com/ProStore-iOS/ProStore/refs/heads/main/gallery/Screenshot2.png",
"https://raw.githubusercontent.com/ProStore-iOS/ProStore/refs/heads/main/gallery/Screenshot3.png",
"https://raw.githubusercontent.com/ProStore-iOS/ProStore/refs/heads/main/gallery/Screenshot4.png",
"https://raw.githubusercontent.com/ProStore-iOS/ProStore/refs/heads/main/gallery/Screenshot5.png"
]'
# Ensure base repo JSON exists
if [ ! -f "$REPO_FILE" ]; then
echo '{
"name": "Official ProStore Repo",
"identifier": "com.prostoreios.prostore.repo",
"sourceURL": "https://ProStore-iOS.github.io/apps.json",
"iconURL": "'"$ICON_URL"'",
"website": "https://ProStore-iOS.github.io",
"ipawebsite": "https://github.com/ProStore-iOS/ProStore/releases",
"subtitle": "The BEST alternative app store for iOS!",
"META": {
"repoName": "Official ProStore Repo",
"repoIcon": "'"$ICON_URL"'"
},
"apps": []
}' > "$REPO_FILE"
fi
TMP=$(mktemp)
# Build the JSON representation of the new version
NEW_VERSION=$(jq -c -n \
--arg version "$VERSION" \
--arg date "$VERSION_DATE_ISO" \
--arg desc "$CHANGELOG" \
--arg url "$DOWNLOAD_URL" \
--arg sha "$SHA256" \
--arg minos "$MIN_OS" \
--argjson size "$IPA_SIZE" \
--arg fulldate "$FULL_DATE" \
'{version: $version, date: $date, localizedDescription: $desc, downloadURL: $url, size: $size, sha256: $sha, minOSVersion: $minos, fullDate: $fulldate}')
# Update or create the app entry
jq --argjson newVer "$NEW_VERSION" \
--arg name "ProStore" \
--arg bundle "$BUNDLE" \
--arg dev "$DEVNAME" \
--arg desc "$META_DESC" \
--arg icon "$ICON_URL" \
--argjson screenshots "$SCREENSHOTS" \
'if ( .apps | map(.bundleIdentifier // .bundleID) | index($bundle) ) then
.apps |= map(
if ((.bundleIdentifier // .bundleID) == $bundle) then
( { "name": $name,
"bundleIdentifier": $bundle,
"developerName": $dev,
"localizedDescription": $desc,
"iconURL": $icon,
"screenshotURLs": $screenshots
} + { "versions": ( [ $newVer ] + ( .versions // [] ) ) } )
else . end
)
else
.apps += [
{
"name": $name,
"bundleIdentifier": $bundle,
"developerName": $dev,
"localizedDescription": $desc,
"iconURL": $icon,
"screenshotURLs": $screenshots,
"versions": [ $newVer ]
}
]
end' "$REPO_FILE" > "$TMP" && mv "$TMP" "$REPO_FILE"
# Safe commit & push
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git add "$REPO_FILE"
git commit -m "Update ProStore app repo to v$VERSION" || echo "No changes to commit"
git fetch origin main --quiet
if git rev-parse --verify origin/main >/dev/null 2>&1; then
if git rebase origin/main; then
echo "Rebase succeeded, pushing changes..."
git push origin HEAD:main
else
echo "Rebase failed — aborting rebase and skipping push."
git rebase --abort || true
fi
else
git push origin HEAD:main
fi
shell: bash
- name: Push apps.json to Pages repo (ProStore-iOS.github.io) using PAT
env:
PAGES_PAT: ${{ secrets.PAGES_PAT }}
run: |
set -e
VERSION="${{ needs.build-unsigned-ipa.outputs.version }}"
PAGES_REPO="ProStore-iOS/ProStore-iOS.github.io"
PAGES_DIR=$(mktemp -d)
echo "Cloning pages repo (masked token)..."
git clone --depth 1 "https://x-access-token:${PAGES_PAT}@github.com/${PAGES_REPO}.git" "$PAGES_DIR"
echo "Copying app-repo.json -> apps.json in pages repo"
cp app-repo.json "$PAGES_DIR/apps.json"
cd "$PAGES_DIR"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Only commit if there's a change
git add apps.json
if git diff --quiet --cached; then
echo "No changes to apps.json — nothing to commit/push."
exit 0
fi
git commit -m "Update apps.json from ProStore v${VERSION}"
echo "Pushing apps.json to ${PAGES_REPO}:main"
git push "https://x-access-token:${PAGES_PAT}@github.com/${PAGES_REPO}.git" HEAD:main
shell: bash