Skip to content

Fix SwiftUI struct requirement - convert A6CutterApp from class to st… #28

Fix SwiftUI struct requirement - convert A6CutterApp from class to st…

Fix SwiftUI struct requirement - convert A6CutterApp from class to st… #28

name: Build and Release A6Cutter
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Release version (e.g., v1.0.0)'
required: false
type: string
jobs:
build-and-release:
runs-on: macos-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Cache Xcode DerivedData
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData
key: ${{ runner.os }}-deriveddata-${{ hashFiles('**/*.swift') }}
restore-keys: |
${{ runner.os }}-deriveddata-
- name: Get version and hash info
id: version_info
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
elif [[ -n "${{ github.event.inputs.version }}" ]]; then
VERSION=${{ github.event.inputs.version }}
else
VERSION=dev-$(date +%Y%m%d-%H%M%S)
fi
# Get git hash
GIT_HASH=$(git rev-parse HEAD)
GIT_HASH_SHORT=$(git rev-parse --short HEAD)
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_OUTPUT
echo "GIT_HASH_SHORT=$GIT_HASH_SHORT" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
echo "Git Hash: $GIT_HASH_SHORT"
- name: Update Info.plist with version and hash
run: |
# Update the source Info.plist before building
INFO_PLIST="A6Cutter/Info.plist"
# Update version and build number
plutil -replace CFBundleShortVersionString -string "${{ steps.version_info.outputs.VERSION }}" "$INFO_PLIST"
plutil -replace CFBundleVersion -string "${{ github.run_number }}" "$INFO_PLIST"
# Add git hash to Info.plist
plutil -replace GitHash -string "${{ steps.version_info.outputs.GIT_HASH }}" "$INFO_PLIST"
echo "Updated Info.plist with version ${{ steps.version_info.outputs.VERSION }} and hash ${{ steps.version_info.outputs.GIT_HASH_SHORT }}"
- name: Download Sparkle tools
run: |
echo "📦 Downloading Sparkle tools..."
# Try to get the latest Sparkle release URL with better parsing
SPARKLE_URL=$(curl -s https://api.github.com/repos/sparkle-project/Sparkle/releases/latest | jq -r '.assets[] | select(.name | endswith(".tar.xz")) | .browser_download_url' | head -1)
# Fallback to a known working version if API fails
if [ -z "$SPARKLE_URL" ] || [ "$SPARKLE_URL" = "null" ]; then
echo "Using fallback URL for Sparkle 2.6.0"
SPARKLE_URL="https://github.com/sparkle-project/Sparkle/releases/download/2.6.0/Sparkle-2.6.0.tar.xz"
fi
echo "Downloading from: $SPARKLE_URL"
curl -L -o sparkle.tar.xz "$SPARKLE_URL"
# Verify the download
if [ ! -f "sparkle.tar.xz" ] || [ ! -s "sparkle.tar.xz" ]; then
echo "❌ Failed to download Sparkle tools"
exit 1
fi
echo "✅ Downloaded $(ls -lh sparkle.tar.xz | awk '{print $5}')"
# Extract with verbose output for debugging
echo "🔍 Extracting Sparkle tools..."
tar -xvf sparkle.tar.xz
# List contents to see what was extracted
echo "📁 Contents after extraction:"
ls -la
# Check if bin directory exists in current directory (Sparkle source extraction)
if [ -d "./bin" ]; then
echo "✅ Found Sparkle bin directory in current directory"
SPARKLE_BIN_DIR="./bin"
else
# Look for any directory with bin subdirectory
SPARKLE_DIR=$(find . -maxdepth 2 -name "bin" -type d | head -1 | dirname)
if [ -z "$SPARKLE_DIR" ]; then
echo "🔍 Looking for any extracted directories..."
find . -maxdepth 1 -type d -name "*" | grep -v "^\.$"
echo "❌ Failed to find Sparkle bin directory after extraction"
exit 1
fi
echo "✅ Found Sparkle directory: $SPARKLE_DIR"
SPARKLE_BIN_DIR="$SPARKLE_DIR/bin"
fi
echo "📁 Contents of $SPARKLE_BIN_DIR:"
ls -la "$SPARKLE_BIN_DIR"
# Check if bin directory exists and has tools
if [ ! -d "$SPARKLE_BIN_DIR" ]; then
echo "❌ No bin directory found at $SPARKLE_BIN_DIR"
echo "📁 Available directories:"
find . -type d -name "*bin*"
exit 1
fi
# Check if there are any executable files in bin (macOS compatible)
EXECUTABLE_FILES=$(find "$SPARKLE_BIN_DIR" -type f -perm +111 2>/dev/null || find "$SPARKLE_BIN_DIR" -type f -perm +x 2>/dev/null || ls -la "$SPARKLE_BIN_DIR" | grep -E '^-rwx' | wc -l)
if [ "$EXECUTABLE_FILES" -eq 0 ]; then
echo "❌ No executable files found in $SPARKLE_BIN_DIR"
echo "📁 Contents:"
ls -la "$SPARKLE_BIN_DIR"
exit 1
fi
echo "✅ Found $EXECUTABLE_FILES executable files in $SPARKLE_BIN_DIR"
# Check if we're already in the right directory
if [ "$SPARKLE_BIN_DIR" = "./bin" ]; then
echo "✅ Sparkle tools already in correct location: ./bin"
chmod +x ./bin/*
else
echo "📁 Copying tools from $SPARKLE_BIN_DIR to ./bin"
chmod +x "$SPARKLE_BIN_DIR"/*
mkdir -p bin
cp "$SPARKLE_BIN_DIR"/* ./bin/
fi
echo "✅ Sparkle tools ready in ./bin/"
echo "📁 Final bin contents:"
ls -la ./bin/
- name: Generate Sparkle keys (if not exist)
run: |
if [ ! -f "keys/ed25519_private_key.pem" ]; then
echo "🔑 Generating Sparkle keys..."
mkdir -p keys
# List available tools for debugging
echo "📁 Available Sparkle tools:"
ls -la ./bin/
# Generate new private key and get public key (saves to keychain, outputs public key)
echo "🔑 Generating new private key and extracting public key..."
PUBLIC_KEY=$(./bin/generate_keys)
# Verify public key was generated
if [ -z "$PUBLIC_KEY" ]; then
echo "❌ Failed to generate keys"
exit 1
fi
echo "✅ Keys generated successfully"
echo "Public key: $PUBLIC_KEY"
# Export private key to file for later use
echo "🔑 Exporting private key to file..."
./bin/generate_keys -x keys/ed25519_private_key.pem
# Verify private key file was created
if [ ! -f "keys/ed25519_private_key.pem" ] || [ ! -s "keys/ed25519_private_key.pem" ]; then
echo "❌ Failed to export private key"
exit 1
fi
echo "✅ Private key exported successfully"
# Update Info.plist with public key
plutil -replace SUPublicEDSAKey -string "$PUBLIC_KEY" A6Cutter/Info.plist
else
echo "🔑 Sparkle keys already exist"
fi
- name: Build A6Cutter
run: |
xcodebuild -scheme A6Cutter -configuration Release -destination "platform=macOS" clean build
- name: Create DMG
run: |
# Create DMG directory structure
mkdir -p dmg/A6Cutter.app
mkdir -p dmg/Applications
# Copy the built app
cp -R /Users/runner/Library/Developer/Xcode/DerivedData/A6Cutter-*/Build/Products/Release/A6Cutter.app dmg/
# Create Applications shortcut (symlink)
ln -s /Applications dmg/Applications
# Create arrow image for installation instruction
# We'll use a simple text-based arrow since we can't easily create images in CI
echo "Drag A6Cutter.app to Applications folder" > dmg/README.txt
# Create DMG with custom layout
hdiutil create -volname "A6Cutter" -srcfolder dmg -ov -format UDZO A6Cutter.dmg
- name: Generate appcast.xml
run: |
echo "📡 Generating appcast.xml..."
mkdir -p releases
cp A6Cutter.dmg releases/A6Cutter-${{ steps.version_info.outputs.VERSION }}.dmg
# Generate appcast.xml
./bin/generate_appcast \
--ed-key-file keys/ed25519_private_key.pem \
--download-url-prefix "https://github.com/devopsmariocom/A6Cutter/releases/download/" \
--full-release-notes-url "https://github.com/devopsmariocom/A6Cutter/releases" \
releases/
# Move appcast.xml to root
if [ -f "releases/appcast.xml" ]; then
mv releases/appcast.xml .
echo "✅ appcast.xml generated successfully!"
else
echo "❌ Failed to generate appcast.xml"
exit 1
fi
- name: Get version from tag
id: get_version
run: |
# Use the version from the previous step
echo "VERSION=${{ steps.version_info.outputs.VERSION }}" >> $GITHUB_OUTPUT
- name: Generate Release Notes
id: release_notes
run: |
# Get the previous tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
# Generate changelog from commits
if [ -n "$PREVIOUS_TAG" ]; then
COMMITS=$(git log --pretty=format:"- %s" $PREVIOUS_TAG..HEAD)
else
COMMITS=$(git log --pretty=format:"- %s" --max-count=20)
fi
# Create release notes
cat > release_notes.md << EOF
## What's New in ${{ steps.get_version.outputs.VERSION }}
### Changes
$COMMITS
### Installation
1. Download the DMG file below
2. Open the DMG and drag A6Cutter to Applications
3. Right-click and select "Open" if you get security warnings
### System Requirements
- macOS 14.0 or later
- Apple Silicon or Intel processor
### Features
- PDF cutting into A6-sized tiles
- Customizable settings with live preview
- Page rotation and skipping
- Preset management
- Direct printing integration
EOF
# Output the release notes
echo "RELEASE_NOTES<<EOF" >> $GITHUB_OUTPUT
cat release_notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create Release
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
uses: softprops/action-gh-release@v2
with:
files: |
A6Cutter.dmg
appcast.xml
name: A6Cutter ${{ steps.get_version.outputs.VERSION }}
body: ${{ steps.release_notes.outputs.RELEASE_NOTES }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Build Artifacts
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: A6Cutter-${{ steps.get_version.outputs.VERSION }}
path: A6Cutter.dmg