name: Build Tauri Applications # Multi-OS Tauri desktop bundle build matrix (Windows / macOS arm+intel / # Linux). Called from build.yml on PRs that touch desktop sources (gated # via the `tauri` filter in .github/config/.files.yaml). Also runnable # on demand via workflow_dispatch with a per-platform selector. on: workflow_call: inputs: platform: description: "Platform to build (windows, macos, linux, or all)." required: false type: string default: "all" workflow_dispatch: inputs: platform: description: "Platform to build (windows, macos, linux, or all)" required: true default: "all" type: choice options: - all - windows - macos - linux permissions: contents: read pull-requests: write jobs: determine-matrix: if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 with: egress-policy: audit - name: Determine build matrix id: set-matrix env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} PLATFORM: ${{ inputs.platform }} run: | WINDOWS='{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}' MACOS_ARM='{"platform":"macos-15","args":"--target aarch64-apple-darwin","name":"macos-aarch64"}' MACOS_INTEL='{"platform":"macos-15-intel","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}' LINUX='{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}' # Resolve requested platform — populated by either workflow_dispatch # or workflow_call inputs; both paths default to "all". case "$PLATFORM" in windows) ENTRIES=("$WINDOWS") ;; macos) ENTRIES=("$MACOS_ARM" "$MACOS_INTEL") ;; linux) ENTRIES=("$LINUX") ;; *) ENTRIES=("$WINDOWS" "$MACOS_ARM" "$MACOS_INTEL" "$LINUX") ;; esac # Drop macOS entries when Apple certificate secret is unavailable if [ -z "$APPLE_CERTIFICATE" ]; then echo "⚠️ APPLE_CERTIFICATE secret not available - skipping macOS builds" FILTERED=() for entry in "${ENTRIES[@]}"; do [[ "$entry" != *'"macos'* ]] && FILTERED+=("$entry") done ENTRIES=("${FILTERED[@]}") fi JOINED=$(IFS=','; echo "${ENTRIES[*]}") echo "matrix={\"include\":[$JOINED]}" >> $GITHUB_OUTPUT 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 }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }} steps: - name: Harden Runner uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 cache: "npm" cache-dependency-path: frontend/package-lock.json - name: Setup Rust uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # 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 25 uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: "25" distribution: "temurin" - name: Setup Gradle uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0 with: gradle-version: 9.3.1 - name: Setup Task uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0 - name: Prepare desktop build run: task desktop:prepare env: MAVEN_USER: ${{ secrets.MAVEN_USER }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }} DISABLE_ADDITIONAL_FEATURES: true # 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@1d820463733701cf1484c7eb5d7d24a15ca2c454 # v1.2.1 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 != '' 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') && env.APPLE_CERTIFICATE != '' 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: Preflight smctl if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} shell: pwsh env: KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }} run: | & smctl healthcheck if ($LASTEXITCODE -ne 0) { Write-Host "[ERROR] smctl healthcheck failed"; exit 1 } & smctl keypair ls if ($LASTEXITCODE -ne 0) { Write-Host "[ERROR] smctl keypair ls failed"; exit 1 } & smctl windows certsync --keypair-alias "$env:KEYPAIR_ALIAS" if ($LASTEXITCODE -ne 0) { Write-Host "[WARN] smctl windows certsync returned non-zero - continuing" } - name: Configure Windows code signing if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }} shell: bash env: KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }} run: | cat > ./frontend/src-tauri/tauri.windows.conf.json < /usr/lib/libjvm.so" else echo "libjvm not found at $JAVA_LIBJVM" exit 1 fi - name: Build Tauri app uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0.6.2 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 }} # AppImage signing — three env vars work together: # SIGN=1 tells linuxdeploy-plugin-appimage to forward --sign to appimagetool # APPIMAGETOOL_SIGN_PASSPHRASE appimagetool uses this to unlock the GPG key non-interactively # SIGN_KEY appimagetool picks the key matching this fingerprint # Without SIGN=1, the other two are ignored and the AppImage is built unsigned even if a key is present. # Mirror the Windows/macOS gate: only sign when secret is present AND ref is main (skips PRs from forks/Dependabot). SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && github.ref == 'refs/heads/main') && '1' || '0' }} APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }} SIGN_KEY: ${{ vars.RELEASE_GPG_FINGERPRINT }} 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 || 'sb_publishable_UHz2SVRF5mvdrPHWkRteyA_yNlZTkYb' }} VITE_SAAS_SERVER_URL: ${{ secrets.VITE_SAAS_SERVER_URL || 'https://app.stirlingpdf.com' }} VITE_SAAS_BACKEND_API_URL: ${{ secrets.VITE_SAAS_BACKEND_API_URL || 'https://api.stirlingpdf.com' }} SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} CI: true with: projectPath: ./frontend tauriScript: npx tauri # Linux: build deb+rpm only here. AppImage runs in its own # continue-on-error step below so its persistent linuxdeploy # failure (#6127 onwards) does not tank deb/rpm uploads. args: ${{ matrix.platform == 'ubuntu-22.04' && '--bundles deb,rpm' || matrix.args }} # AppImage is decoupled so its linuxdeploy run gets a fresh process # (rpm scratch state torn down) and its failure can't tank deb/rpm. - name: Build Tauri app (Linux AppImage) if: matrix.platform == 'ubuntu-22.04' continue-on-error: true uses: tauri-apps/tauri-action@84b9d35b5fc46c1e45415bdb6144030364f7ebc5 # v0.6.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && github.ref == 'refs/heads/main') && '1' || '0' }} APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }} SIGN_KEY: ${{ vars.RELEASE_GPG_FINGERPRINT }} 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 || 'sb_publishable_UHz2SVRF5mvdrPHWkRteyA_yNlZTkYb' }} # gitleaks:allow VITE_SAAS_SERVER_URL: ${{ secrets.VITE_SAAS_SERVER_URL || 'https://app.stirlingpdf.com' }} VITE_SAAS_BACKEND_API_URL: ${{ secrets.VITE_SAAS_BACKEND_API_URL || 'https://api.stirlingpdf.com' }} CI: true with: projectPath: ./frontend tauriScript: npx tauri args: --bundles appimage - name: Clear release GPG key from runner keyring (Linux) if: always() && matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && github.ref == 'refs/heads/main' env: RELEASE_GPG_FINGERPRINT: ${{ vars.RELEASE_GPG_FINGERPRINT }} run: | if [ -n "$RELEASE_GPG_FINGERPRINT" ]; then gpg --batch --yes --delete-secret-keys "$RELEASE_GPG_FINGERPRINT" || true gpg --batch --yes --delete-keys "$RELEASE_GPG_FINGERPRINT" || true fi - 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 # Only ship the MSI installer. The loose exe and WiX toolset exes # are not the user-facing installer - the MSI contains the signed inner 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" \; else find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; find . -name "*.rpm" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.rpm" \; find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; fi # Verify the MSI AND the inner exe extracted from it are signed. # The inner exe is what gets installed on users' machines and what AV scans. - name: Verify Windows Code Signature if: matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' shell: pwsh run: | $allSigned = $true $msiPath = "./dist/Stirling-PDF-${{ matrix.name }}.msi" # Check MSI (outer wrapper) if (Test-Path $msiPath) { $sig = Get-AuthenticodeSignature -FilePath $msiPath Write-Host "MSI: Status=$($sig.Status), Signer=$($sig.SignerCertificate.Subject)" if ($sig.Status -ne "Valid") { Write-Host "[ERROR] MSI is not signed" $allSigned = $false } # Extract MSI and verify inner exe $extractDir = Join-Path $env:RUNNER_TEMP "msi-verify" if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force } $proc = Start-Process msiexec.exe -ArgumentList '/a', $msiPath, '/qn', "TARGETDIR=$extractDir" -Wait -PassThru -NoNewWindow if ($proc.ExitCode -eq 0) { $innerExe = Get-ChildItem -Path $extractDir -Filter "stirling-pdf.exe" -Recurse -File | Select-Object -First 1 if ($innerExe) { $sig = Get-AuthenticodeSignature -FilePath $innerExe.FullName Write-Host "Inner EXE: Status=$($sig.Status), Signer=$($sig.SignerCertificate.Subject)" if ($sig.Status -ne "Valid") { Write-Host "[ERROR] Inner exe is NOT signed - AV will flag this at runtime" $allSigned = $false } } else { Write-Host "[ERROR] Could not find stirling-pdf.exe inside MSI" $allSigned = $false } } else { Write-Host "[ERROR] MSI extraction failed (exit code: $($proc.ExitCode))" $allSigned = $false } } else { Write-Host "[ERROR] MSI not found at $msiPath" $allSigned = $false } if (-not $allSigned) { Write-Host "[ERROR] Signature verification failed" exit 1 } Write-Host "[SUCCESS] MSI and inner exe are properly signed" - name: Dump smctl logs on failure if: ${{ failure() && matrix.platform == 'windows-latest' && env.SM_API_KEY != '' }} shell: pwsh run: | $logDir = "$env:USERPROFILE\.signingmanager\logs" if (Test-Path $logDir) { Get-ChildItem $logDir | ForEach-Object { Write-Host "=== $($_.FullName) ===" Get-Content $_.FullName -Tail 200 Write-Host "" } } else { Write-Host "smctl log directory not found at $logDir" } - name: Upload artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 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" | head -5 if [ $(find . -name "*.dmg" | wc -l) -eq 0 ]; then echo "❌ No macOS artifacts found" exit 1 fi else echo "Checking for Linux artifacts..." find . -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" | head -5 if [ $(find . -name "*.deb" -o -name "*.rpm" -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 "*.rpm" -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 pr-comment: needs: build runs-on: ubuntu-latest if: github.event_name == 'pull_request' && needs.build.result == 'success' permissions: pull-requests: write steps: - name: Harden the runner uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 with: egress-policy: audit - name: Post/Update PR Comment with Download Links uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const owner = context.repo.owner; const repo = context.repo.repo; const prNumber = context.issue.number; const runId = context.runId; // Fetch artifacts for this workflow run const { data: artifactsList } = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id: runId }); // Map of expected artifact names to display info const artifactMap = { 'Stirling-PDF-windows-x86_64': { icon: '🪟', platform: 'Windows x64', files: '.exe, .msi' }, 'Stirling-PDF-macos-aarch64': { icon: '🍎', platform: 'macOS ARM64', files: '.dmg' }, 'Stirling-PDF-macos-x86_64': { icon: '🍎', platform: 'macOS Intel', files: '.dmg' }, 'Stirling-PDF-linux-x86_64': { icon: '🐧', platform: 'Linux x64', files: '.deb, .rpm, .AppImage' } }; let commentBody = `## 📦 Tauri Desktop Builds Ready!\n\n`; commentBody += `The desktop applications have been built and are ready for testing.\n\n`; commentBody += `### Download Artifacts:\n\n`; // Add links for each found artifact let foundArtifacts = 0; for (const artifact of artifactsList.artifacts) { const info = artifactMap[artifact.name]; if (info) { foundArtifacts++; // GitHub doesn't provide direct download URLs via API, but we can link to the artifact on the Actions page const artifactUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${artifact.id}`; commentBody += `${info.icon} **${info.platform}**: [Download ${artifact.name}](${artifactUrl}) `; commentBody += `(${info.files}) - ${(artifact.size_in_bytes / 1024 / 1024).toFixed(1)} MB\n`; } } if (foundArtifacts === 0) { commentBody += `⚠️ **Warning**: No artifacts found in workflow run.\n`; commentBody += `[View workflow run](https://github.com/${owner}/${repo}/actions/runs/${runId})\n`; } commentBody += `\n---\n`; commentBody += `_Built from commit ${context.sha.substring(0, 7)}_\n`; commentBody += `_Artifacts expire in 7 days_`; // Find existing comment const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber }); const botComment = comments.find(comment => comment.user.type === 'Bot' && comment.body.includes('📦 Tauri Desktop Builds Ready!') ); if (botComment) { // Update existing comment await github.rest.issues.updateComment({ owner, repo, comment_id: botComment.id, body: commentBody }); console.log('Updated existing comment'); } else { // Create new comment await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: commentBody }); console.log('Created new comment'); } report: needs: build runs-on: ubuntu-latest if: always() steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 with: egress-policy: audit - 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