name: Build Tauri Applications on: workflow_dispatch: inputs: platform: description: "Platform to build (windows, macos, linux, or all)" required: true default: "all" type: choice options: - all - windows - macos - linux pull_request: branches: [main, V2, V2-tauri-windows] paths: - 'frontend/src-tauri/**' - 'frontend/src/desktop/**' - 'frontend/tsconfig.desktop.json' - '.github/workflows/tauri-build.yml' push: branches: [main, V2] permissions: contents: read jobs: determine-matrix: if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Determine build matrix id: set-matrix run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then case "${{ github.event.inputs.platform }}" in "windows") echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}]}' >> $GITHUB_OUTPUT ;; "macos") echo 'matrix={"include":[{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT ;; "linux") echo 'matrix={"include":[{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT ;; *) echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT ;; esac else # For PR/push events, build all platforms echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT fi build: needs: determine-matrix strategy: fail-fast: false matrix: ${{ fromJson(needs.determine-matrix.outputs.matrix) }} runs-on: ${{ matrix.platform }} env: SM_API_KEY: ${{ secrets.SM_API_KEY }} WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} steps: - name: Harden Runner uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: toolchain: stable targets: ${{ (matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - name: Set up JDK 21 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: "21" distribution: "temurin" - name: Build Java backend with JLink working-directory: ./ shell: bash run: | chmod +x ./gradlew echo "🔧 Building Stirling-PDF JAR..." # STIRLING_PDF_DESKTOP_UI=false ./gradlew clean bootJar --no-daemon ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube # Find the built JAR STIRLING_JAR=$(ls app/core/build/libs/stirling-pdf-*.jar | head -n 1) echo "✅ Built JAR: $STIRLING_JAR" # Create Tauri directories mkdir -p ./frontend/src-tauri/libs mkdir -p ./frontend/src-tauri/runtime # Copy JAR to Tauri libs cp "$STIRLING_JAR" ./frontend/src-tauri/libs/ echo "✅ JAR copied to Tauri libs" # Analyze JAR dependencies for jlink modules echo "🔍 Analyzing JAR dependencies..." if command -v jdeps &> /dev/null; then DETECTED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "") if [ -n "$DETECTED_MODULES" ]; then echo "📋 jdeps detected modules: $DETECTED_MODULES" MODULES="$DETECTED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" else echo "⚠️ jdeps analysis failed, using predefined modules" MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" fi else echo "⚠️ jdeps not available, using predefined modules" MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" fi # Create custom JRE with jlink (always rebuild) echo "🔧 Creating custom JRE with jlink..." echo "📋 Using modules: $MODULES" # Remove any existing JRE rm -rf ./frontend/src-tauri/runtime/jre # Create the custom JRE jlink \ --add-modules "$MODULES" \ --strip-debug \ --compress=2 \ --no-header-files \ --no-man-pages \ --output ./frontend/src-tauri/runtime/jre if [ ! -d "./frontend/src-tauri/runtime/jre" ]; then echo "❌ Failed to create JLink runtime" exit 1 fi # Test the bundled runtime if [ -f "./frontend/src-tauri/runtime/jre/bin/java" ]; then RUNTIME_VERSION=$(./frontend/src-tauri/runtime/jre/bin/java --version 2>&1 | head -n 1) echo "✅ Custom JRE created successfully: $RUNTIME_VERSION" else echo "❌ Custom JRE executable not found" exit 1 fi # Calculate runtime size RUNTIME_SIZE=$(du -sh ./frontend/src-tauri/runtime/jre | cut -f1) echo "📊 Custom JRE size: $RUNTIME_SIZE" env: DISABLE_ADDITIONAL_FEATURES: true - name: Install frontend dependencies working-directory: ./frontend run: npm install # DigiCert KeyLocker Setup (Cloud HSM) - name: Setup DigiCert KeyLocker id: digicert-setup if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} uses: digicert/ssm-code-signing@v1.1.0 env: SM_API_KEY: ${{ secrets.SM_API_KEY }} SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }} SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }} SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }} SM_HOST: ${{ secrets.SM_HOST }} - name: Setup DigiCert KeyLocker Certificate if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} shell: pwsh run: | Write-Host "Setting up DigiCert KeyLocker environment..." # Decode client certificate $certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}") $certPath = "D:\Certificate_pkcs12.p12" [IO.File]::WriteAllBytes($certPath, $certBytes) # Set environment variables echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV # Get PKCS11 config path from DigiCert action $pkcs11Config = $env:PKCS11_CONFIG if ($pkcs11Config) { Write-Host "Found PKCS11_CONFIG: $pkcs11Config" echo "PKCS11_CONFIG=$pkcs11Config" >> $env:GITHUB_ENV } else { Write-Host "PKCS11_CONFIG not set by DigiCert action, using default path" $defaultPath = "C:\Users\RUNNER~1\AppData\Local\Temp\smtools-windows-x64\pkcs11properties.cfg" if (Test-Path $defaultPath) { Write-Host "Found config at default path: $defaultPath" echo "PKCS11_CONFIG=$defaultPath" >> $env:GITHUB_ENV } else { Write-Host "Warning: Could not find PKCS11 config file" } } # Traditional PFX Certificate Import (fallback if KeyLocker not configured) - name: Import Windows Code Signing Certificate if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY == '' && github.ref == 'refs/heads/main' }} env: WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} shell: powershell run: | if ($env:WINDOWS_CERTIFICATE) { Write-Host "Importing Windows Code Signing Certificate..." # Decode base64 certificate and save to file $certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE) $certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx" [IO.File]::WriteAllBytes($certPath, $certBytes) # Import certificate to CurrentUser\My store $cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force) # Extract and set thumbprint as environment variable $thumbprint = $cert.Thumbprint Write-Host "Certificate imported with thumbprint: $thumbprint" echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV # Clean up certificate file Remove-Item $certPath Write-Host "Windows certificate import completed." } else { Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary" } - name: Import Apple Developer Certificate if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} run: | echo "Importing Apple Developer Certificate..." echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 # Create temporary keychain KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db KEYCHAIN_PASSWORD=$(openssl rand -base64 32) security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # Import certificate security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # Clean up rm certificate.p12 - name: Verify Certificate if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' run: | echo "Verifying Apple Developer Certificate..." KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db CERT_INFO=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application") echo "Certificate Info: $CERT_INFO" CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "Certificate ID: $CERT_ID" echo "APPLE_SIGNING_IDENTITY=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported successfully." - name: Check DMG creation dependencies (macOS only) if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' run: | echo "🔍 Checking DMG creation dependencies on ${{ matrix.platform }}..." echo "hdiutil version: $(hdiutil --version || echo 'NOT FOUND')" echo "create-dmg availability: $(which create-dmg || echo 'NOT FOUND')" echo "Available disk space: $(df -h /tmp | tail -1)" echo "macOS version: $(sw_vers -productVersion)" echo "Available tools:" ls -la /usr/bin/hd* || echo "No hd* tools found" - name: Build Tauri app uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APPIMAGETOOL_SIGN_PASSPHRASE }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY: ${{ secrets.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY }} VITE_SAAS_SERVER_URL: ${{ secrets.VITE_SAAS_SERVER_URL }} # Only enable Windows signing in Tauri when on main SIGN: ${{ github.ref == 'refs/heads/main' && (env.SM_API_KEY == '' && env.WINDOWS_CERTIFICATE != '') && '1' || '0' }} CI: true with: projectPath: ./frontend tauriScript: npx tauri args: ${{ matrix.args }} # Sign with DigiCert KeyLocker (post-build) - name: Sign Windows binaries with DigiCert KeyLocker if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} shell: pwsh run: | Write-Host "=== DigiCert KeyLocker Signing ===" # Test smctl connectivity first Write-Host "Testing smctl connection..." $healthCheck = & smctl healthcheck 2>&1 if ($LASTEXITCODE -eq 0) { Write-Host "[SUCCESS] Connected to DigiCert KeyLocker" } else { Write-Host "[ERROR] Failed to connect to DigiCert KeyLocker" Write-Host $healthCheck exit 1 } Write-Host "" # Sync certificates to Windows certificate store Write-Host "Syncing certificates to Windows certificate store..." $syncOutput = & smctl windows certsync 2>&1 Write-Host "Cert sync result: $syncOutput" Write-Host "" # List available certificates and check if they have certificates attached Write-Host "Checking for available certificates..." $certList = & smctl keypair ls 2>&1 Write-Host "Keypair list output:" Write-Host $certList Write-Host "" # Parse the output to check certificate status $lines = $certList -split "`n" $foundKeypair = $false $hasCertificate = $false foreach ($line in $lines) { if ($line -match "${{ secrets.SM_KEYPAIR_ALIAS }}") { $foundKeypair = $true Write-Host "[SUCCESS] Found keypair in list" # Check if this line has certificate info (not just empty spaces after alias) $parts = $line -split "\s+" if ($parts.Count -gt 2 -and $parts[1] -ne "" -and $parts[1] -ne "CERTIFICATE") { $hasCertificate = $true Write-Host "[SUCCESS] Certificate is associated with keypair" } } } if (-not $foundKeypair) { Write-Host "[ERROR] Keypair not found: ${{ secrets.SM_KEYPAIR_ALIAS }}" Write-Host "Available keypairs are listed above" Write-Host "" Write-Host "Please verify:" Write-Host " 1. Keypair alias is correct in GitHub secret" Write-Host " 2. API key has access to this keypair" exit 1 } if (-not $hasCertificate) { Write-Host "[ERROR] No certificate associated with keypair" Write-Host "This usually means:" Write-Host " 1. Certificate not yet synced to KeyLocker (run sync manually)" Write-Host " 2. Certificate is pending approval" Write-Host " 3. Certificate needs to be attached to the keypair" Write-Host "" Write-Host "Try running in DigiCert ONE portal:" Write-Host " smctl keypair sync" exit 1 } Write-Host "[SUCCESS] Certificate check passed" Write-Host "" # Find only the files we need to sign (not build scripts) $filesToSign = @() # Main application executable $mainExe = Get-ChildItem -Path "./frontend/src-tauri/target/x86_64-pc-windows-msvc/release" -Filter "stirling-pdf.exe" -File -ErrorAction SilentlyContinue if ($mainExe) { $filesToSign += $mainExe } # MSI installer $msiFiles = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*.msi" -Recurse -File $filesToSign += $msiFiles if ($filesToSign.Count -eq 0) { Write-Host "[ERROR] No files found to sign" exit 1 } Write-Host "Found $($filesToSign.Count) files to sign:" foreach ($f in $filesToSign) { Write-Host " - $($f.Name)" } Write-Host "" $signedCount = 0 foreach ($file in $filesToSign) { Write-Host "Signing: $($file.Name)" # Get PKCS11 config file path (set by DigiCert action) $pkcs11Config = $env:PKCS11_CONFIG if (-not $pkcs11Config) { Write-Host "[ERROR] PKCS11_CONFIG environment variable not set" Write-Host "DigiCert KeyLocker action may not have run correctly" exit 1 } Write-Host "Using PKCS11 config: $pkcs11Config" # Try signing with certificate fingerprint first (if available) $fingerprint = "${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}" if ($fingerprint -and $fingerprint -ne "") { Write-Host "Attempting to sign with certificate fingerprint..." $output = & smctl sign --fingerprint "$fingerprint" --input "$($file.FullName)" --config-file "$pkcs11Config" --verbose 2>&1 $exitCode = $LASTEXITCODE } else { Write-Host "No fingerprint provided, using keypair alias..." # Use smctl to sign with keypair alias $output = & smctl sign --keypair-alias "${{ secrets.SM_KEYPAIR_ALIAS }}" --input "$($file.FullName)" --config-file "$pkcs11Config" --verbose 2>&1 $exitCode = $LASTEXITCODE } Write-Host "Exit code: $exitCode" Write-Host "Output: $output" # Check if output contains "FAILED" even with exit code 0 if ($output -match "FAILED" -or $output -match "error" -or $output -match "Error") { Write-Host "" Write-Host "[ERROR] Signing failed for $($file.Name)" Write-Host "[ERROR] smctl returned success but output indicates failure" Write-Host "" Write-Host "Possible issues:" Write-Host " 1. Certificate not fully synced to KeyLocker (wait a few minutes)" Write-Host " 2. Incorrect keypair alias" Write-Host " 3. API key lacks signing permissions" Write-Host "" Write-Host "Please verify in DigiCert ONE portal:" Write-Host " - Certificate status is 'Issued' (not Pending)" Write-Host " - Keypair status is 'Online'" Write-Host " - 'Can sign' is set to 'Yes'" exit 1 } if ($exitCode -ne 0) { Write-Host "[ERROR] Failed to sign $($file.Name)" Write-Host "Full error output:" Write-Host $output exit 1 } $signedCount++ Write-Host "[SUCCESS] Signed: $($file.Name)" Write-Host "" } Write-Host "=== Summary ===" Write-Host "[SUCCESS] Signed $signedCount/$($filesToSign.Count) files successfully" - name: Verify notarization (macOS only) if: matrix.platform == 'macos-15' || matrix.platform == 'macos-15-intel' run: | echo "🔍 Verifying notarization status..." cd ./frontend/src-tauri/target DMG_FILE=$(find . -name "*.dmg" | head -1) if [ -n "$DMG_FILE" ]; then echo "Found DMG: $DMG_FILE" echo "Checking notarization ticket..." spctl -a -vvv -t install "$DMG_FILE" || echo "⚠️ Notarization check failed or not yet complete" stapler validate "$DMG_FILE" || echo "⚠️ No notarization ticket attached" else echo "⚠️ No DMG file found to verify" fi - name: Rename artifacts shell: bash run: | mkdir -p ./dist cd ./frontend/src-tauri/target # Find and rename artifacts based on platform if [ "${{ matrix.platform }}" = "windows-latest" ]; then find . -name "*.exe" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.exe" \; find . -name "*.msi" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.msi" \; elif [ "${{ matrix.platform }}" = "macos-15" ] || [ "${{ matrix.platform }}" = "macos-15-intel" ]; then find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; else find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; fi - name: Verify Windows Code Signature if: matrix.platform == 'windows-latest' && github.ref == 'refs/heads/main' shell: pwsh run: | Write-Host "Verifying Windows code signatures..." $exePath = "./dist/Stirling-PDF-${{ matrix.name }}.exe" $msiPath = "./dist/Stirling-PDF-${{ matrix.name }}.msi" $allSigned = $true $usingKeyLocker = "${{ env.SM_API_KEY }}" -ne "" $usingPfx = "${{ env.WINDOWS_CERTIFICATE }}" -ne "" # Check EXE signature if (Test-Path $exePath) { $exeSig = Get-AuthenticodeSignature -FilePath $exePath Write-Host "EXE Signature Status: $($exeSig.Status)" Write-Host "EXE Signer: $($exeSig.SignerCertificate.Subject)" Write-Host "EXE Timestamp: $($exeSig.TimeStamperCertificate.NotAfter)" if ($exeSig.Status -ne "Valid") { Write-Host "[WARNING] EXE is not properly signed (Status: $($exeSig.Status))" if ($usingKeyLocker -or $usingPfx) { Write-Host "[ERROR] Certificate was provided but signing failed" $allSigned = $false } else { Write-Host "[INFO] Building unsigned binary (no certificate provided)" } } else { Write-Host "[SUCCESS] EXE is properly signed" } } # Check MSI signature if (Test-Path $msiPath) { $msiSig = Get-AuthenticodeSignature -FilePath $msiPath Write-Host "MSI Signature Status: $($msiSig.Status)" Write-Host "MSI Signer: $($msiSig.SignerCertificate.Subject)" Write-Host "MSI Timestamp: $($msiSig.TimeStamperCertificate.NotAfter)" if ($msiSig.Status -ne "Valid") { Write-Host "[WARNING] MSI is not properly signed (Status: $($msiSig.Status))" if ($usingKeyLocker -or $usingPfx) { Write-Host "[ERROR] Certificate was provided but signing failed" $allSigned = $false } else { Write-Host "[INFO] Building unsigned binary (no certificate provided)" } } else { Write-Host "[SUCCESS] MSI is properly signed" } } if (($usingKeyLocker -or $usingPfx) -and -not $allSigned) { Write-Host "[ERROR] Code signing verification failed" exit 1 } else { Write-Host "[SUCCESS] Code signature verification completed" } - name: Upload artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: Stirling-PDF-${{ matrix.name }} path: ./dist/* retention-days: 7 - name: Verify build artifacts shell: bash run: | cd ./frontend/src-tauri/target # Check for expected artifacts based on platform if [ "${{ matrix.platform }}" = "windows-latest" ]; then echo "Checking for Windows artifacts..." find . -name "*.exe" -o -name "*.msi" | head -5 if [ $(find . -name "*.exe" | wc -l) -eq 0 ]; then echo "❌ No Windows executable found" exit 1 fi elif [ "${{ matrix.platform }}" = "macos-15" ] || [ "${{ matrix.platform }}" = "macos-15-intel" ]; then echo "Checking for macOS artifacts..." find . -name "*.dmg" -o -name "*.app" | head -5 if [ $(find . -name "*.dmg" -o -name "*.app" | wc -l) -eq 0 ]; then echo "❌ No macOS artifacts found" exit 1 fi else echo "Checking for Linux artifacts..." find . -name "*.deb" -o -name "*.AppImage" | head -5 if [ $(find . -name "*.deb" -o -name "*.AppImage" | wc -l) -eq 0 ]; then echo "❌ No Linux artifacts found" exit 1 fi fi echo "✅ Build artifacts found for ${{ matrix.name }}" - name: Test artifact sizes shell: bash run: | cd ./frontend/src-tauri/target echo "Artifact sizes for ${{ matrix.name }}:" find . -name "*.exe" -o -name "*.dmg" -o -name "*.deb" -o -name "*.AppImage" -o -name "*.msi" | while read file; do if [ -f "$file" ]; then size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "unknown") echo "$file: $size bytes" # Check if file is suspiciously small (less than 1MB) if [ "$size" != "unknown" ] && [ "$size" -lt 1048576 ]; then echo "⚠️ Warning: $file is smaller than 1MB" fi fi done report: needs: build runs-on: ubuntu-latest if: always() steps: - name: Report build results run: | if [ "${{ needs.build.result }}" = "success" ]; then echo "✅ All Tauri builds completed successfully!" echo "Artifacts are ready for distribution." elif [ "${{ needs.build.result }}" = "skipped" ]; then echo "⏭️ Tauri builds skipped (CI lite mode enabled)" else echo "❌ Some Tauri builds failed." echo "Please check the logs and fix any issues." exit 1 fi