Files
Termix/.github/workflows/electron-build.yml
2025-10-24 21:55:49 -05:00

457 lines
16 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Build Electron App
on:
workflow_dispatch:
inputs:
build_type:
description: "Build type to run"
required: true
default: "all"
type: choice
options:
- all
- windows
- linux
- macos
upload_to_release:
description: "Upload artifacts to latest GitHub release"
required: false
default: false
type: boolean
submit_to_stores:
description: "Submit builds to app stores"
required: false
default: false
type: boolean
jobs:
build-windows:
runs-on: windows-latest
if: github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == ''
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build Windows (All Architectures)
run: npm run build && npx electron-builder --win --x64 --ia32
- name: List release files
run: |
echo "Contents of release directory:"
dir release
- name: Upload Windows x64 NSIS Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/*-x64.exe') != ''
with:
name: Termix-Windows-x64-NSIS
path: release/*-x64.exe
retention-days: 30
- name: Upload Windows ia32 NSIS Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/*-ia32.exe') != ''
with:
name: Termix-Windows-ia32-NSIS
path: release/*-ia32.exe
retention-days: 30
- name: Upload Windows x64 MSI Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/*-x64.msi') != ''
with:
name: Termix-Windows-x64-MSI
path: release/*-x64.msi
retention-days: 30
- name: Upload Windows ia32 MSI Installer
uses: actions/upload-artifact@v4
if: hashFiles('release/*-ia32.msi') != ''
with:
name: Termix-Windows-ia32-MSI
path: release/*-ia32.msi
retention-days: 30
- name: Create Windows x64 Portable zip
if: hashFiles('release/win-unpacked/*') != ''
run: |
Compress-Archive -Path "release\win-unpacked\*" -DestinationPath "Termix-Windows-x64-Portable.zip"
- name: Create Windows ia32 Portable zip
if: hashFiles('release/win-ia32-unpacked/*') != ''
run: |
Compress-Archive -Path "release\win-ia32-unpacked\*" -DestinationPath "Termix-Windows-ia32-Portable.zip"
- name: Upload Windows x64 Portable
uses: actions/upload-artifact@v4
if: hashFiles('Termix-Windows-x64-Portable.zip') != ''
with:
name: Termix-Windows-x64-Portable
path: Termix-Windows-x64-Portable.zip
retention-days: 30
- name: Upload Windows ia32 Portable
uses: actions/upload-artifact@v4
if: hashFiles('Termix-Windows-ia32-Portable.zip') != ''
with:
name: Termix-Windows-ia32-Portable
path: Termix-Windows-ia32-Portable.zip
retention-days: 30
build-linux:
runs-on: blacksmith-4vcpu-ubuntu-2404
if: github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == ''
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: |
rm -f package-lock.json
npm install
npm install --force @rollup/rollup-linux-x64-gnu
npm install --force @rollup/rollup-linux-arm64-gnu
npm install --force @rollup/rollup-linux-arm-gnueabihf
- name: Build Linux (All Architectures)
run: npm run build && npx electron-builder --linux --x64 --arm64 --armv7l
- name: List release files
run: |
echo "Contents of release directory:"
ls -la release/
- name: Upload Linux x64 AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/*-x86_64.AppImage') != ''
with:
name: Termix-Linux-x64-AppImage
path: release/*-x86_64.AppImage
retention-days: 30
- name: Upload Linux arm64 AppImage
uses: actions/upload-artifact@v4
if: hashFiles('release/*-arm64.AppImage') != ''
with:
name: Termix-Linux-arm64-AppImage
path: release/*-arm64.AppImage
retention-days: 30
- name: Upload Linux x64 DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/*_amd64.deb') != ''
with:
name: Termix-Linux-x64-DEB
path: release/*_amd64.deb
retention-days: 30
- name: Upload Linux arm64 DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/*_arm64.deb') != ''
with:
name: Termix-Linux-arm64-DEB
path: release/*_arm64.deb
retention-days: 30
- name: Upload Linux armv7l DEB
uses: actions/upload-artifact@v4
if: hashFiles('release/*_armhf.deb') != ''
with:
name: Termix-Linux-armv7l-DEB
path: release/*_armhf.deb
retention-days: 30
- name: Upload Linux x64 tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/*-x64.tar.gz') != ''
with:
name: Termix-Linux-x64-Portable
path: release/*-x64.tar.gz
retention-days: 30
- name: Upload Linux arm64 tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/*-arm64.tar.gz') != ''
with:
name: Termix-Linux-arm64-Portable
path: release/*-arm64.tar.gz
retention-days: 30
- name: Upload Linux armv7l tar.gz
uses: actions/upload-artifact@v4
if: hashFiles('release/*-armv7l.tar.gz') != ''
with:
name: Termix-Linux-armv7l-Portable
path: release/*-armv7l.tar.gz
retention-days: 30
build-macos:
runs-on: macos-latest
if: github.event.inputs.build_type == 'macos' || github.event.inputs.build_type == 'all'
needs: []
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Install macOS-specific build dependencies
run: |
npm install --force @rollup/rollup-darwin-arm64
npm uninstall @swc/core
npm install @swc/core --include=optional
- name: Check for Code Signing Certificates
id: check_certs
run: |
if [ -n "${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}" ] && [ -n "${{ secrets.MAC_P12_PASSWORD }}" ]; then
echo "has_certs=true" >> $GITHUB_OUTPUT
else
echo "has_certs=false" >> $GITHUB_OUTPUT
echo "⚠️ Code signing certificates not configured. MAS build will be unsigned."
fi
- name: Import Code Signing Certificates
if: steps.check_certs.outputs.has_certs == 'true'
env:
MAC_BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_BUILD_CERTIFICATE_BASE64 }}
MAC_INSTALLER_CERTIFICATE_BASE64: ${{ secrets.MAC_INSTALLER_CERTIFICATE_BASE64 }}
MAC_P12_PASSWORD: ${{ secrets.MAC_P12_PASSWORD }}
MAC_KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
run: |
APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12
INSTALLER_CERT_PATH=$RUNNER_TEMP/installer_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Decode certificates
echo -n "$MAC_BUILD_CERTIFICATE_BASE64" | base64 --decode -o $APP_CERT_PATH
if [ -n "$MAC_INSTALLER_CERTIFICATE_BASE64" ]; then
echo "Decoding installer certificate..."
echo -n "$MAC_INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $INSTALLER_CERT_PATH
else
echo "⚠️ MAC_INSTALLER_CERTIFICATE_BASE64 is empty"
fi
# Create and configure keychain
security create-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$MAC_KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import application certificate
echo "Importing application certificate..."
security import $APP_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
# Import installer certificate if it exists
if [ -f "$INSTALLER_CERT_PATH" ]; then
echo "Importing installer certificate..."
security import $INSTALLER_CERT_PATH -P "$MAC_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
else
echo "⚠️ Installer certificate file not found, skipping import"
fi
security list-keychain -d user -s $KEYCHAIN_PATH
echo "Imported certificates:"
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Build macOS App Store Package
if: steps.check_certs.outputs.has_certs == 'true'
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
run: |
# Get current version for display
CURRENT_VERSION=$(node -p "require('./package.json').version")
BUILD_VERSION="${{ github.run_number }}"
echo "✅ Package version: $CURRENT_VERSION (unchanged)"
echo "✅ Build number for Apple: $BUILD_VERSION"
# Build MAS with custom buildVersion
npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"
- name: Build macOS DMG
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
run: |
# Build DMG without running npm run build again (already built above or skip if no certs)
if [ "${{ steps.check_certs.outputs.has_certs }}" == "true" ]; then
# Frontend already built, just package DMG
npx electron-builder --mac dmg --universal --x64 --arm64
else
# No certs, need to build frontend first
npm run build && npx electron-builder --mac dmg --universal --x64 --arm64
fi
- name: List release directory
if: steps.check_certs.outputs.has_certs == 'true'
run: |
echo "Contents of release directory:"
ls -R release/ || echo "Release directory not found"
- name: Upload macOS MAS PKG
if: steps.check_certs.outputs.has_certs == 'true' && hashFiles('release/*.pkg') != ''
uses: actions/upload-artifact@v4
with:
name: Termix-macOS-MAS
path: release/*.pkg
retention-days: 30
if-no-files-found: warn
- name: Upload macOS Universal DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/*-universal.dmg') != ''
with:
name: Termix-macOS-Universal-DMG
path: release/*-universal.dmg
retention-days: 30
- name: Upload macOS x64 DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/*-x64.dmg') != ''
with:
name: Termix-macOS-x64-DMG
path: release/*-x64.dmg
retention-days: 30
- name: Upload macOS arm64 DMG
uses: actions/upload-artifact@v4
if: hashFiles('release/*-arm64.dmg') != ''
with:
name: Termix-macOS-arm64-DMG
path: release/*-arm64.dmg
retention-days: 30
- name: Check for App Store Connect API credentials
if: steps.check_certs.outputs.has_certs == 'true'
id: check_asc_creds
run: |
if [ -n "${{ secrets.APPLE_KEY_ID }}" ] && [ -n "${{ secrets.APPLE_ISSUER_ID }}" ] && [ -n "${{ secrets.APPLE_KEY_CONTENT }}" ]; then
echo "has_credentials=true" >> $GITHUB_OUTPUT
if [ "${{ github.event.inputs.submit_to_stores }}" == "true" ]; then
echo "✅ App Store Connect API credentials found. Will deploy to TestFlight."
else
echo " App Store Connect API credentials found, but store submission is disabled."
fi
else
echo "has_credentials=false" >> $GITHUB_OUTPUT
echo "⚠️ App Store Connect API credentials not configured. Skipping deployment."
echo "Add APPLE_KEY_ID, APPLE_ISSUER_ID, and APPLE_KEY_CONTENT secrets to enable automatic deployment."
fi
- name: Setup Ruby for Fastlane
if: steps.check_asc_creds.outputs.has_credentials == 'true' && github.event.inputs.submit_to_stores == 'true'
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: false
- name: Install Fastlane
if: steps.check_asc_creds.outputs.has_credentials == 'true' && github.event.inputs.submit_to_stores == 'true'
run: |
gem install fastlane -N
fastlane --version
- name: Deploy to App Store Connect (TestFlight)
if: steps.check_asc_creds.outputs.has_credentials == 'true' && github.event.inputs.submit_to_stores == 'true'
run: |
PKG_FILE=$(find release -name "*.pkg" -type f | head -n 1)
if [ -z "$PKG_FILE" ]; then
echo "Error: No .pkg file found in release directory"
exit 1
fi
echo "Found package: $PKG_FILE"
# Create API key file
mkdir -p ~/private_keys
echo "${{ secrets.APPLE_KEY_CONTENT }}" | base64 --decode > ~/private_keys/AuthKey_${{ secrets.APPLE_KEY_ID }}.p8
# Upload to App Store Connect using xcrun altool
xcrun altool --upload-app -f "$PKG_FILE" \
--type macos \
--apiKey "${{ secrets.APPLE_KEY_ID }}" \
--apiIssuer "${{ secrets.APPLE_ISSUER_ID }}"
echo "✅ Upload complete! Build will appear in App Store Connect after processing (10-30 minutes)"
continue-on-error: true
- name: Clean up keychain
if: always() && steps.check_certs.outputs.has_certs == 'true'
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
upload-to-release:
runs-on: ubuntu-latest
if: github.event.inputs.upload_to_release == 'true'
needs: [build-windows, build-linux, build-macos]
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Get latest release tag
id: get_release
run: |
LATEST_TAG=$(gh release list --repo ${{ github.repository }} --limit 1 | awk '{print $1}')
echo "tag=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "Latest release tag: $LATEST_TAG"
env:
GH_TOKEN: ${{ github.token }}
- name: Display artifact structure
run: |
echo "Artifact structure:"
ls -R artifacts/
- name: Upload artifacts to latest release
run: |
cd artifacts
for dir in */; do
echo "Processing directory: $dir"
cd "$dir"
for file in *; do
if [ -f "$file" ]; then
echo "Uploading: $file"
gh release upload ${{ steps.get_release.outputs.tag }} "$file" --repo ${{ github.repository }} --clobber
fi
done
cd ..
done
env:
GH_TOKEN: ${{ github.token }}