mirror of
https://github.com/exo-explore/exo.git
synced 2026-04-18 04:52:40 -04:00
## Motivation PR #1747 fixed macmon support for M5 Pro/Max by pinning the `swiftraccoon/macmon` fork in `flake.nix`. This works when running from source (via Nix) but the distributed macOS `.app` build was still broken on M5 Pro/Max because it was bundling the wrong macmon. The error on M5 Pro/Max: ``` macmon preflight failed with return code -6: thread 'main' panicked at src/sources.rs:394:41 ``` ## Changes - Removed `macmon` from `brew install` in `build-app.yml` — this was installing the upstream `vladkens/macmon` which doesn't support M5 Pro/Max - Added a new step that resolves the pinned macmon fork from the Nix dev shell (same `swiftraccoon/macmon` at rev `9154d23` already defined in `flake.nix`) and adds it to `$GITHUB_PATH` - Added a safety `brew uninstall macmon` to ensure no Homebrew macmon can shadow the pinned version ## Why It Works PyInstaller bundles macmon via `shutil.which("macmon")`. Previously this found the Homebrew (upstream) binary. Now it finds the Nix-overlayed fork that has M5 Pro/Max support, because `$GITHUB_PATH` prepends the Nix store path before the PyInstaller step runs. ## Test Plan ### Manual Testing <!-- Hardware: M5 Pro --> - Trigger a macOS build and verify the bundled macmon is the pinned fork - Run the built `.app` on M5 Pro/Max and confirm macmon preflight succeeds ### Automated Testing - Existing CI build workflow will validate that the macmon binary is found and bundled correctly Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
457 lines
19 KiB
YAML
457 lines
19 KiB
YAML
name: Build EXO macOS DMG
|
|
|
|
# Release workflow:
|
|
# 1. Create a draft GitHub Release with the tag name (e.g. v1.0.0) and write release notes in markdown
|
|
# 2. Push the tag: git tag v1.0.0 && git push origin v1.0.0
|
|
# 3. This workflow builds, signs, and notarizes the DMG
|
|
# 4. Release notes are embedded in appcast.xml for Sparkle (rendered as markdown)
|
|
# 5. DMG and appcast.xml are uploaded to S3
|
|
# 6. The draft GitHub Release is published with the DMG attached
|
|
#
|
|
# For alpha releases (e.g. v1.0.0-alpha.1): draft release and notes are optional.
|
|
# If no draft exists, a release is auto-created with generated notes.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
branches:
|
|
- "test-app"
|
|
|
|
jobs:
|
|
build-macos-app:
|
|
runs-on: "macos-26"
|
|
permissions:
|
|
contents: write
|
|
env:
|
|
SPARKLE_VERSION: 2.9.0-beta.1
|
|
SPARKLE_DOWNLOAD_PREFIX: ${{ secrets.SPARKLE_DOWNLOAD_PREFIX }}
|
|
SPARKLE_FEED_URL: ${{ secrets.SPARKLE_FEED_URL }}
|
|
SPARKLE_ED25519_PUBLIC: ${{ secrets.SPARKLE_ED25519_PUBLIC }}
|
|
SPARKLE_ED25519_PRIVATE: ${{ secrets.SPARKLE_ED25519_PRIVATE }}
|
|
SPARKLE_S3_BUCKET: ${{ secrets.SPARKLE_S3_BUCKET }}
|
|
SPARKLE_S3_PREFIX: ${{ secrets.SPARKLE_S3_PREFIX }}
|
|
EXO_BUG_REPORT_PRESIGNED_URL_ENDPOINT: ${{ secrets.EXO_BUG_REPORT_PRESIGNED_URL_ENDPOINT }}
|
|
AWS_REGION: ${{ secrets.AWS_REGION }}
|
|
EXO_BUILD_NUMBER: ${{ github.run_number }}
|
|
EXO_LIBP2P_NAMESPACE: ${{ github.ref_name }}
|
|
|
|
steps:
|
|
# ============================================================
|
|
# Checkout and tag validation
|
|
# ============================================================
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Derive release version from tag
|
|
run: |
|
|
if [[ "$GITHUB_REF_NAME" == "test-app" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
VERSION="0.0.0-alpha.0"
|
|
echo "IS_ALPHA=true" >> $GITHUB_ENV
|
|
else
|
|
VERSION="${GITHUB_REF_NAME#v}"
|
|
if [[ "$VERSION" == *-alpha* ]]; then
|
|
echo "IS_ALPHA=true" >> $GITHUB_ENV
|
|
else
|
|
echo "IS_ALPHA=false" >> $GITHUB_ENV
|
|
fi
|
|
fi
|
|
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
|
|
|
|
- name: Compute build version from semver
|
|
run: |
|
|
VERSION="$RELEASE_VERSION"
|
|
# Extract major.minor.patch (strip prerelease suffix)
|
|
BASE_VERSION="${VERSION%%-*}"
|
|
MAJOR=$(echo "$BASE_VERSION" | cut -d. -f1)
|
|
MINOR=$(echo "$BASE_VERSION" | cut -d. -f2)
|
|
PATCH=$(echo "$BASE_VERSION" | cut -d. -f3)
|
|
|
|
# Extract prerelease number (e.g., "alpha.2" -> 2, or 999 for releases)
|
|
if [[ "$VERSION" == *-* ]]; then
|
|
PRERELEASE_PART="${VERSION#*-}"
|
|
PRERELEASE_NUM="${PRERELEASE_PART##*.}"
|
|
# Default to 0 if not a number
|
|
if ! [[ "$PRERELEASE_NUM" =~ ^[0-9]+$ ]]; then
|
|
PRERELEASE_NUM=0
|
|
fi
|
|
else
|
|
PRERELEASE_NUM=999
|
|
fi
|
|
|
|
# Compute: PRERELEASE + (1000 * PATCH) + (1_000_000 * MINOR) + (1_000_000_000 * MAJOR)
|
|
BUILD_VERSION=$((PRERELEASE_NUM + 1000 * PATCH + 1000000 * MINOR + 1000000000 * MAJOR))
|
|
echo "EXO_BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV
|
|
echo "Computed build version: $BUILD_VERSION from $VERSION"
|
|
|
|
- name: Ensure tag commit is on main
|
|
if: github.ref_type == 'tag'
|
|
run: |
|
|
git fetch origin main
|
|
# Alpha tags can be on any branch, production tags must be on main
|
|
if [[ "$IS_ALPHA" == "true" ]]; then
|
|
echo "Alpha tag detected, skipping main branch check"
|
|
elif ! git merge-base --is-ancestor origin/main HEAD; then
|
|
echo "Production tag must point to a commit on main"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Fetch and validate release notes
|
|
if: github.ref_type == 'tag'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
# Find draft release by name using gh release list (more reliable with default token)
|
|
echo "Looking for draft release named '$GITHUB_REF_NAME'..."
|
|
DRAFT_EXISTS=$(gh release list --json name,isDraft --jq ".[] | select(.isDraft == true) | select(.name == \"$GITHUB_REF_NAME\") | .name" 2>/dev/null || echo "")
|
|
|
|
if [[ -z "$DRAFT_EXISTS" ]]; then
|
|
if [[ "$IS_ALPHA" == "true" ]]; then
|
|
echo "No draft release found for alpha tag $GITHUB_REF_NAME (optional for alphas)"
|
|
echo "HAS_RELEASE_NOTES=false" >> $GITHUB_ENV
|
|
exit 0
|
|
fi
|
|
echo "ERROR: No draft release found for tag $GITHUB_REF_NAME"
|
|
echo "Please create a draft release with release notes before pushing the tag."
|
|
exit 1
|
|
fi
|
|
|
|
# Fetch full release details via API to get body and ID
|
|
echo "Found draft release, fetching details..."
|
|
RELEASE_JSON=$(gh api repos/${{ github.repository }}/releases --jq ".[] | select(.draft == true) | select(.name == \"$GITHUB_REF_NAME\")" 2>/dev/null || echo "")
|
|
|
|
# Extract release notes
|
|
NOTES=$(echo "$RELEASE_JSON" | jq -r '.body // ""')
|
|
if [[ -z "$NOTES" || "$NOTES" == "null" ]]; then
|
|
if [[ "$IS_ALPHA" == "true" ]]; then
|
|
echo "Draft release has no notes (optional for alphas)"
|
|
echo "HAS_RELEASE_NOTES=false" >> $GITHUB_ENV
|
|
exit 0
|
|
fi
|
|
echo "ERROR: Draft release exists but has no release notes"
|
|
echo "Please add release notes to the draft release before pushing the tag."
|
|
exit 1
|
|
fi
|
|
|
|
# Save release ID for later publishing
|
|
RELEASE_ID=$(echo "$RELEASE_JSON" | jq -r '.id')
|
|
echo "DRAFT_RELEASE_ID=$RELEASE_ID" >> $GITHUB_ENV
|
|
echo "HAS_RELEASE_NOTES=true" >> $GITHUB_ENV
|
|
|
|
echo "Found draft release (ID: $RELEASE_ID), saving release notes..."
|
|
echo "$NOTES" > /tmp/release_notes.md
|
|
echo "RELEASE_NOTES_FILE=/tmp/release_notes.md" >> $GITHUB_ENV
|
|
|
|
# ============================================================
|
|
# Install dependencies
|
|
# ============================================================
|
|
|
|
- name: Select Xcode 26.2
|
|
run: |
|
|
sudo xcode-select -s /Applications/Xcode_26.2.app
|
|
if ! xcrun -f metal >/dev/null 2>&1; then
|
|
echo "Metal toolchain is not installed."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Install Homebrew packages
|
|
run: brew install just awscli
|
|
|
|
- name: Install UV
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: uv.lock
|
|
|
|
- name: Setup Python
|
|
run: |
|
|
uv python install
|
|
uv sync --locked
|
|
|
|
- name: Install Nix
|
|
uses: cachix/install-nix-action@v31
|
|
with:
|
|
nix_path: nixpkgs=channel:nixos-unstable
|
|
|
|
- name: Configure Cachix
|
|
uses: cachix/cachix-action@v14
|
|
with:
|
|
name: exo
|
|
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
|
|
- name: Build dashboard
|
|
run: |
|
|
DASHBOARD_OUT=$(nix build .#dashboard --print-build-logs --no-link --print-out-paths)
|
|
mkdir -p dashboard/build
|
|
cp -r "$DASHBOARD_OUT"/* dashboard/build/
|
|
|
|
- name: Install Sparkle CLI
|
|
run: |
|
|
CLI_URL="${SPARKLE_CLI_URL:-https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz}"
|
|
echo "Downloading Sparkle CLI from: $CLI_URL"
|
|
mkdir -p /tmp/sparkle
|
|
curl --fail --location --output /tmp/sparkle.tar.xz "$CLI_URL"
|
|
tar -xJf /tmp/sparkle.tar.xz -C /tmp/sparkle --strip-components=1
|
|
echo "SPARKLE_BIN=/tmp/sparkle/bin" >> $GITHUB_ENV
|
|
|
|
- name: Prepare code-signing keychain
|
|
env:
|
|
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
|
PROVISIONING_PROFILE: ${{ secrets.PROVISIONING_PROFILE }}
|
|
run: |
|
|
KEYCHAIN_PATH="$HOME/Library/Keychains/build.keychain-db"
|
|
|
|
# Create fresh keychain
|
|
security create-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
|
|
|
# Disable auto-lock (no timeout, no lock-on-sleep)
|
|
security set-keychain-settings "$KEYCHAIN_PATH"
|
|
|
|
# Add to search list while preserving existing keychains
|
|
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
|
|
|
|
# Set as default and unlock
|
|
security default-keychain -s "$KEYCHAIN_PATH"
|
|
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
|
|
|
# Import certificate with full access for codesign
|
|
echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/cert.p12
|
|
security import /tmp/cert.p12 -k "$KEYCHAIN_PATH" -P "$MACOS_CERTIFICATE_PASSWORD" \
|
|
-T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
|
rm /tmp/cert.p12
|
|
|
|
# Allow codesign to access the key without prompting
|
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CERTIFICATE_PASSWORD" "$KEYCHAIN_PATH"
|
|
|
|
# Verify keychain is unlocked and identity is available
|
|
echo "Verifying signing identity..."
|
|
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
|
|
|
|
# Setup provisioning profile
|
|
mkdir -p "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
|
|
echo "$PROVISIONING_PROFILE" | base64 --decode > "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/EXO.provisionprofile"
|
|
|
|
# Export keychain path for other steps
|
|
echo "BUILD_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
|
|
|
|
# ============================================================
|
|
# Build the bundle
|
|
# ============================================================
|
|
|
|
- name: Add pinned macmon to PATH
|
|
run: |
|
|
MACMON_DIR=$(nix develop --command sh -c 'dirname $(which macmon)')
|
|
echo "Using macmon from: $MACMON_DIR"
|
|
echo "$MACMON_DIR" >> $GITHUB_PATH
|
|
# Remove any Homebrew macmon so PyInstaller can't accidentally pick it up
|
|
brew uninstall macmon 2>/dev/null || true
|
|
|
|
- name: Build PyInstaller bundle
|
|
run: uv run pyinstaller packaging/pyinstaller/exo.spec
|
|
|
|
- name: Build Swift app
|
|
env:
|
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
|
SPARKLE_FEED_URL: ${{ secrets.SPARKLE_FEED_URL }}
|
|
SPARKLE_ED25519_PUBLIC: ${{ secrets.SPARKLE_ED25519_PUBLIC }}
|
|
run: |
|
|
cd app/EXO
|
|
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$BUILD_KEYCHAIN_PATH"
|
|
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$BUILD_KEYCHAIN_PATH" | awk -F '"' '{print $2}')
|
|
xcodebuild clean build \
|
|
-scheme EXO \
|
|
-configuration Release \
|
|
-derivedDataPath build \
|
|
MARKETING_VERSION="$RELEASE_VERSION" \
|
|
CURRENT_PROJECT_VERSION="$EXO_BUILD_VERSION" \
|
|
EXO_BUILD_TAG="$RELEASE_VERSION" \
|
|
EXO_BUILD_COMMIT="$GITHUB_SHA" \
|
|
SPARKLE_FEED_URL="$SPARKLE_FEED_URL" \
|
|
SPARKLE_ED25519_PUBLIC="$SPARKLE_ED25519_PUBLIC" \
|
|
EXO_BUG_REPORT_PRESIGNED_URL_ENDPOINT="$EXO_BUG_REPORT_PRESIGNED_URL_ENDPOINT" \
|
|
CODE_SIGNING_IDENTITY="$SIGNING_IDENTITY" \
|
|
CODE_SIGN_INJECT_BASE_ENTITLEMENTS=YES
|
|
mkdir -p ../../output
|
|
cp -R build/Build/Products/Release/EXO.app ../../output/EXO.app
|
|
|
|
- name: Inject PyInstaller runtime
|
|
run: |
|
|
rm -rf output/EXO.app/Contents/Resources/exo
|
|
mkdir -p output/EXO.app/Contents/Resources
|
|
cp -R dist/exo output/EXO.app/Contents/Resources/exo
|
|
|
|
- name: Codesign PyInstaller runtime
|
|
env:
|
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
cd output
|
|
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$BUILD_KEYCHAIN_PATH"
|
|
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$BUILD_KEYCHAIN_PATH" | awk -F '"' '{print $2}')
|
|
RUNTIME_DIR="EXO.app/Contents/Resources/exo"
|
|
find "$RUNTIME_DIR" -type f \( -perm -111 -o -name "*.dylib" -o -name "*.so" \) -print0 |
|
|
while IFS= read -r -d '' file; do
|
|
/usr/bin/codesign --force --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$file"
|
|
done
|
|
|
|
- name: Sign, notarize, and create DMG
|
|
env:
|
|
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
|
APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
|
APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
|
APPLE_NOTARIZATION_TEAM: ${{ secrets.APPLE_NOTARIZATION_TEAM }}
|
|
run: |
|
|
cd output
|
|
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$BUILD_KEYCHAIN_PATH"
|
|
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$BUILD_KEYCHAIN_PATH" | awk -F '"' '{print $2}')
|
|
/usr/bin/codesign --deep --force --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" EXO.app
|
|
mkdir -p dmg-root
|
|
cp -R EXO.app dmg-root/
|
|
ln -s /Applications dmg-root/Applications
|
|
DMG_NAME="EXO-${RELEASE_VERSION}.dmg"
|
|
hdiutil create -volname "EXO" -srcfolder dmg-root -ov -format UDZO "$DMG_NAME"
|
|
/usr/bin/codesign --force --timestamp --options runtime \
|
|
--sign "$SIGNING_IDENTITY" "$DMG_NAME"
|
|
if [[ -n "$APPLE_NOTARIZATION_USERNAME" ]]; then
|
|
SUBMISSION_OUTPUT=$(xcrun notarytool submit "$DMG_NAME" \
|
|
--apple-id "$APPLE_NOTARIZATION_USERNAME" \
|
|
--password "$APPLE_NOTARIZATION_PASSWORD" \
|
|
--team-id "$APPLE_NOTARIZATION_TEAM" \
|
|
--wait --timeout 15m 2>&1)
|
|
echo "$SUBMISSION_OUTPUT"
|
|
|
|
SUBMISSION_ID=$(echo "$SUBMISSION_OUTPUT" | awk 'tolower($1)=="id:" && $2 ~ /^[0-9a-fA-F-]+$/ {print $2; exit}')
|
|
STATUS=$(echo "$SUBMISSION_OUTPUT" | awk 'tolower($1)=="status:" {print $2; exit}')
|
|
|
|
if [[ -n "$SUBMISSION_ID" ]]; then
|
|
xcrun notarytool log "$SUBMISSION_ID" \
|
|
--apple-id "$APPLE_NOTARIZATION_USERNAME" \
|
|
--password "$APPLE_NOTARIZATION_PASSWORD" \
|
|
--team-id "$APPLE_NOTARIZATION_TEAM" > notarization-log.txt || true
|
|
echo "===== Notarization Log ====="
|
|
cat notarization-log.txt
|
|
echo "============================"
|
|
fi
|
|
|
|
if [[ "$STATUS" != "Accepted" ]]; then
|
|
echo "Notarization failed with status: ${STATUS:-Unknown}"
|
|
exit 1
|
|
fi
|
|
|
|
xcrun stapler staple "$DMG_NAME"
|
|
fi
|
|
|
|
- name: Generate Sparkle appcast
|
|
env:
|
|
SPARKLE_DOWNLOAD_PREFIX: ${{ env.SPARKLE_DOWNLOAD_PREFIX }}
|
|
SPARKLE_ED25519_PRIVATE: ${{ secrets.SPARKLE_ED25519_PRIVATE }}
|
|
IS_ALPHA: ${{ env.IS_ALPHA }}
|
|
run: |
|
|
set -euo pipefail
|
|
cd output
|
|
DOWNLOAD_PREFIX="${SPARKLE_DOWNLOAD_PREFIX:-https://assets.exolabs.net}"
|
|
echo "$SPARKLE_ED25519_PRIVATE" > sparkle_ed25519.key
|
|
chmod 600 sparkle_ed25519.key
|
|
|
|
CHANNEL_FLAG=""
|
|
if [[ "$IS_ALPHA" == "true" ]]; then
|
|
CHANNEL_FLAG="--channel alpha"
|
|
echo "Generating appcast for alpha channel"
|
|
fi
|
|
|
|
$SPARKLE_BIN/generate_appcast \
|
|
--ed-key-file sparkle_ed25519.key \
|
|
--download-url-prefix "$DOWNLOAD_PREFIX" \
|
|
$CHANNEL_FLAG \
|
|
.
|
|
|
|
- name: Inject release notes into appcast
|
|
if: github.ref_type == 'tag' && env.HAS_RELEASE_NOTES == 'true'
|
|
env:
|
|
RELEASE_VERSION: ${{ env.RELEASE_VERSION }}
|
|
run: |
|
|
# Inject markdown release notes with sparkle:format="markdown" (Sparkle 2.9+)
|
|
export NOTES=$(cat "$RELEASE_NOTES_FILE")
|
|
|
|
# Insert description after the enclosure tag for this version
|
|
awk '
|
|
/<enclosure[^>]*>/ && index($0, ENVIRON["RELEASE_VERSION"]) {
|
|
print
|
|
print " <description sparkle:format=\"markdown\"><![CDATA["
|
|
print ENVIRON["NOTES"]
|
|
print " ]]></description>"
|
|
next
|
|
}
|
|
{ print }
|
|
' output/appcast.xml > output/appcast.xml.tmp && mv output/appcast.xml.tmp output/appcast.xml
|
|
|
|
echo "Injected markdown release notes for version $RELEASE_VERSION"
|
|
|
|
# ============================================================
|
|
# Upload artifacts
|
|
# ============================================================
|
|
|
|
- name: Upload DMG
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: EXO-dmg-${{ env.RELEASE_VERSION }}
|
|
path: output/EXO-${{ env.RELEASE_VERSION }}.dmg
|
|
|
|
- name: Upload to S3
|
|
if: env.SPARKLE_S3_BUCKET != ''
|
|
env:
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
AWS_REGION: ${{ env.AWS_REGION }}
|
|
SPARKLE_S3_BUCKET: ${{ env.SPARKLE_S3_BUCKET }}
|
|
SPARKLE_S3_PREFIX: ${{ env.SPARKLE_S3_PREFIX }}
|
|
IS_ALPHA: ${{ env.IS_ALPHA }}
|
|
run: |
|
|
set -euo pipefail
|
|
cd output
|
|
PREFIX="${SPARKLE_S3_PREFIX:-}"
|
|
if [[ -n "$PREFIX" && "${PREFIX: -1}" != "/" ]]; then
|
|
PREFIX="${PREFIX}/"
|
|
fi
|
|
DMG_NAME="EXO-${RELEASE_VERSION}.dmg"
|
|
|
|
if [[ "${{ github.ref_type }}" != "tag" ]]; then
|
|
aws s3 cp "$DMG_NAME" "s3://${SPARKLE_S3_BUCKET}/${PREFIX}EXO-${GITHUB_SHA}.dmg"
|
|
exit 0
|
|
fi
|
|
|
|
aws s3 cp "$DMG_NAME" "s3://${SPARKLE_S3_BUCKET}/${PREFIX}${DMG_NAME}"
|
|
if [[ "$IS_ALPHA" != "true" ]]; then
|
|
aws s3 cp "$DMG_NAME" "s3://${SPARKLE_S3_BUCKET}/${PREFIX}EXO-latest.dmg"
|
|
aws s3 cp appcast.xml "s3://${SPARKLE_S3_BUCKET}/${PREFIX}appcast.xml" --content-type application/xml --cache-control no-cache
|
|
fi
|
|
|
|
- name: Publish GitHub Release
|
|
if: github.ref_type == 'tag'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
DMG_PATH="output/EXO-${RELEASE_VERSION}.dmg"
|
|
|
|
if [[ "$HAS_RELEASE_NOTES" == "true" ]]; then
|
|
# Update the draft release with the tag and upload DMG
|
|
gh api --method PATCH "repos/${{ github.repository }}/releases/$DRAFT_RELEASE_ID" \
|
|
-f tag_name="$GITHUB_REF_NAME" \
|
|
-F draft=false
|
|
gh release upload "$GITHUB_REF_NAME" "$DMG_PATH" --clobber
|
|
echo "Published release $GITHUB_REF_NAME with DMG attached"
|
|
else
|
|
# Alpha without draft release - create one with auto-generated notes
|
|
gh release create "$GITHUB_REF_NAME" "$DMG_PATH" \
|
|
--title "$GITHUB_REF_NAME" \
|
|
--generate-notes \
|
|
--prerelease
|
|
echo "Created alpha release $GITHUB_REF_NAME with auto-generated notes"
|
|
fi
|