Skip to content

chore: update icon

chore: update icon #31

Workflow file for this run

name: Build and Release
on:
push:
branches:
- main
paths:
- 'Axel/**'
- 'AxelTests/**'
- 'Packages/**'
- 'Axel.xcodeproj/**'
- '.github/workflows/build.yml'
pull_request:
branches:
- main
paths:
- 'Axel/**'
- 'AxelTests/**'
- 'Packages/**'
- 'Axel.xcodeproj/**'
- '.github/workflows/build.yml'
workflow_dispatch:
inputs:
force_release:
description: 'Force release even if version exists'
required: false
default: 'false'
type: boolean
env:
SCHEME: Axel
PRODUCT_NAME: Axel
XCODE_VERSION: '16.3'
AWS_REGION: us-east-1
S3_BUCKET: txtx-public
jobs:
test:
name: Run Tests
runs-on: macos-15
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
lfs: true
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
- name: Run Unit Tests
run: |
xcodebuild test \
-scheme "${{ env.SCHEME }}" \
-project Axel.xcodeproj \
-destination "platform=macOS" \
-only-testing:AxelTests \
-resultBundlePath TestResults.xcresult \
-skipPackagePluginValidation \
MACOSX_DEPLOYMENT_TARGET=15.0 \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
- name: Upload Test Results
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results
path: TestResults.xcresult
build-macos:
name: Build macOS App
needs: test
runs-on: macos-15
outputs:
version: ${{ steps.version.outputs.version }}
dmg_name: ${{ steps.dmg.outputs.name }}
should_release: ${{ steps.check_release.outputs.should_release }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
lfs: true
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app
- name: Show Xcode version
run: xcodebuild -version
- name: Get version from Xcode project
id: version
run: |
# Extract MARKETING_VERSION from project.pbxproj
VERSION=$(grep -m1 'MARKETING_VERSION' Axel.xcodeproj/project.pbxproj | sed 's/.*= *\([^;]*\);/\1/' | tr -d ' "')
if [ -z "$VERSION" ]; then
VERSION="1.0.0"
fi
# Use GitHub run number as build number (auto-increments each workflow run)
BUILD_NUMBER=${{ github.run_number }}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "build_number=$BUILD_NUMBER" >> $GITHUB_OUTPUT
echo "Detected version: $VERSION (build $BUILD_NUMBER)"
- name: Check if release exists
id: check_release
env:
FORCE_RELEASE: ${{ github.event.inputs.force_release }}
run: |
VERSION="${{ steps.version.outputs.version }}"
# Check if DMG already exists on S3
if curl -sf -o /dev/null "https://${{ env.S3_BUCKET }}.s3.amazonaws.com/releases/axel/Axel-${VERSION}-macos.dmg"; then
echo "Release ${VERSION} already exists on S3"
if [ "$FORCE_RELEASE" = "true" ]; then
echo "Force release enabled, will overwrite"
echo "should_release=true" >> $GITHUB_OUTPUT
else
echo "should_release=false" >> $GITHUB_OUTPUT
fi
else
echo "Release ${VERSION} does not exist, will create"
echo "should_release=true" >> $GITHUB_OUTPUT
fi
- name: Install create-dmg
if: steps.check_release.outputs.should_release == 'true'
run: brew install create-dmg
- name: Download Sparkle tools
if: steps.check_release.outputs.should_release == 'true'
run: |
SPARKLE_VERSION="2.6.4"
curl -L -o Sparkle.tar.xz "https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
mkdir -p sparkle-tools
tar -xf Sparkle.tar.xz -C sparkle-tools
chmod +x sparkle-tools/bin/*
- name: Clean SPM caches
if: steps.check_release.outputs.should_release == 'true'
run: |
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf ~/Library/Developer/Xcode/DerivedData
- name: Download Axel CLI binary
if: steps.check_release.outputs.should_release == 'true'
run: |
# Detect architecture
ARCH=$(uname -m)
if [ "$ARCH" = "arm64" ]; then
PLATFORM="darwin-arm64"
else
PLATFORM="darwin-x64"
fi
# Get latest version from GitHub API
AXEL_VERSION=$(curl -s https://api.github.com/repos/txtx/axel/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
echo "Downloading axel $AXEL_VERSION for $PLATFORM..."
# Download and extract
curl -fsSL "https://github.com/txtx/axel/releases/download/${AXEL_VERSION}/axel-${PLATFORM}.tar.gz" -o axel.tar.gz
mkdir -p axel-cli
tar -xzf axel.tar.gz -C axel-cli
chmod +x axel-cli/axel
# Verify binary works
./axel-cli/axel --version || echo "Warning: Could not verify axel binary"
echo "axel_version=$AXEL_VERSION" >> $GITHUB_OUTPUT
echo "axel_platform=$PLATFORM" >> $GITHUB_OUTPUT
- name: Build macOS app
if: steps.check_release.outputs.should_release == 'true'
run: |
xcodebuild \
-scheme "${{ env.SCHEME }}" \
-project Axel.xcodeproj \
-configuration Release \
-destination "platform=macOS" \
-derivedDataPath build \
-skipPackagePluginValidation \
MARKETING_VERSION="${{ steps.version.outputs.version }}" \
CURRENT_PROJECT_VERSION="${{ steps.version.outputs.build_number }}" \
MACOSX_DEPLOYMENT_TARGET=15.0 \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
build
- name: Prepare app for distribution
if: steps.check_release.outputs.should_release == 'true'
run: |
mkdir -p dist
cp -R "build/Build/Products/Release/${{ env.PRODUCT_NAME }}.app" dist/
# Ensure icon exists (use pre-built fallback if .icon format wasn't compiled)
ICNS_PATH="dist/${{ env.PRODUCT_NAME }}.app/Contents/Resources/Axel.icns"
if [ ! -f "$ICNS_PATH" ] && [ -f "Axel/Axel.icns" ]; then
echo "Using pre-built icon fallback..."
cp "Axel/Axel.icns" "$ICNS_PATH"
fi
- name: Embed Axel CLI in app bundle
if: steps.check_release.outputs.should_release == 'true'
run: |
# Copy axel binary to app bundle Resources
APP_RESOURCES="dist/${{ env.PRODUCT_NAME }}.app/Contents/Resources"
cp axel-cli/axel "$APP_RESOURCES/axel"
chmod +x "$APP_RESOURCES/axel"
echo "Embedded axel CLI into app bundle at $APP_RESOURCES/axel"
ls -la "$APP_RESOURCES/axel"
- name: Ad-hoc sign app (when no certificate)
if: steps.check_release.outputs.should_release == 'true' && env.MACOS_CERTIFICATE == ''
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
run: |
echo "No signing certificate provided, using ad-hoc signing..."
codesign --force --deep --sign - "dist/${{ env.PRODUCT_NAME }}.app"
echo "Ad-hoc signing complete"
codesign -vvv --deep --strict "dist/${{ env.PRODUCT_NAME }}.app" || true
- name: Import signing certificate
if: steps.check_release.outputs.should_release == 'true' && env.MACOS_CERTIFICATE != ''
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
security import certificate.p12 -P "$MACOS_CERTIFICATE_PWD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
codesign --force --deep --sign "Developer ID Application" "dist/${{ env.PRODUCT_NAME }}.app"
- name: Create DMG
if: steps.check_release.outputs.should_release == 'true'
id: dmg
run: |
chmod +x scripts/create-dmg.sh
DMG_NAME="${{ env.PRODUCT_NAME }}-${{ steps.version.outputs.version }}-macos.dmg"
scripts/create-dmg.sh "dist/${{ env.PRODUCT_NAME }}.app" "dist/${DMG_NAME}"
echo "name=${DMG_NAME}" >> $GITHUB_OUTPUT
echo "path=dist/${DMG_NAME}" >> $GITHUB_OUTPUT
- name: Download existing appcast
if: steps.check_release.outputs.should_release == 'true'
continue-on-error: true
run: |
curl -f -o dist/appcast.xml \
"https://${{ env.S3_BUCKET }}.s3.amazonaws.com/releases/axel/appcast.xml" || true
- name: Generate appcast
if: steps.check_release.outputs.should_release == 'true'
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
if [ -n "$SPARKLE_PRIVATE_KEY" ]; then
# Write key without trailing newline
printf '%s' "$SPARKLE_PRIVATE_KEY" > /tmp/sparkle_private_key
# Debug: show key length (not the key itself)
echo "Key length: $(wc -c < /tmp/sparkle_private_key) bytes"
./sparkle-tools/bin/generate_appcast \
--ed-key-file /tmp/sparkle_private_key \
--download-url-prefix "https://${{ env.S3_BUCKET }}.s3.amazonaws.com/releases/axel/" \
dist/
rm -f /tmp/sparkle_private_key
echo "Generated appcast.xml:"
cat dist/appcast.xml
else
echo "Warning: SPARKLE_PRIVATE_KEY not set, skipping appcast generation"
fi
- name: Notarize DMG
if: steps.check_release.outputs.should_release == 'true' && env.APPLE_ID != ''
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
xcrun notarytool submit "${{ steps.dmg.outputs.path }}" \
--apple-id "$APPLE_ID" \
--password "$APPLE_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
xcrun stapler staple "${{ steps.dmg.outputs.path }}"
- name: Upload artifacts
if: steps.check_release.outputs.should_release == 'true'
uses: actions/upload-artifact@v4
with:
name: Axel-macOS-${{ steps.version.outputs.version }}
path: dist/
if-no-files-found: warn
publish-s3:
name: Publish to S3
needs: build-macos
runs-on: ubuntu-latest
if: needs.build-macos.outputs.should_release == 'true'
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: Axel-macOS-${{ needs.build-macos.outputs.version }}
path: ./artifacts
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Upload to S3
run: |
VERSION="${{ needs.build-macos.outputs.version }}"
for file in ./artifacts/*.dmg; do
BASENAME=$(basename "$file")
echo "Uploading $BASENAME to S3..."
aws s3 cp "$file" "s3://${{ env.S3_BUCKET }}/releases/axel/${BASENAME}" \
--acl public-read \
--cache-control "public, max-age=31536000"
aws s3 cp "$file" "s3://${{ env.S3_BUCKET }}/releases/axel/Axel-latest-macos.dmg" \
--acl public-read \
--cache-control "public, max-age=300"
done
if [ -f "./artifacts/appcast.xml" ]; then
echo "Uploading appcast.xml..."
aws s3 cp "./artifacts/appcast.xml" "s3://${{ env.S3_BUCKET }}/releases/axel/appcast.xml" \
--acl public-read \
--cache-control "public, max-age=300" \
--content-type "application/xml"
fi
echo ""
echo "Published to S3:"
echo " - https://${{ env.S3_BUCKET }}.s3.amazonaws.com/releases/axel/${{ needs.build-macos.outputs.dmg_name }}"
echo " - https://${{ env.S3_BUCKET }}.s3.amazonaws.com/releases/axel/Axel-latest-macos.dmg"
echo " - https://${{ env.S3_BUCKET }}.s3.amazonaws.com/releases/axel/appcast.xml"
create-release:
name: Create GitHub Release
needs: [build-macos, publish-s3]
runs-on: ubuntu-latest
if: needs.build-macos.outputs.should_release == 'true'
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: Axel-macOS-${{ needs.build-macos.outputs.version }}
path: ./artifacts
- name: Create tag and release
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION="${{ needs.build-macos.outputs.version }}"
TAG="v${VERSION}"
# Create tag if it doesn't exist
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
git tag "$TAG"
git push origin "$TAG"
fi
# Create release
gh release create "$TAG" \
--title "Axel $VERSION" \
--generate-notes \
--draft \
./artifacts/*.dmg
echo "Created release: $TAG"