Fix SwiftUI struct requirement - convert A6CutterApp from class to st… #28
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |