Build, Release and Announce #590
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, Release and Announce" | |
| permissions: | |
| contents: write | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| # טריגר לפרסום בלבד | |
| release: | |
| types: [published, prereleased] | |
| jobs: | |
| build_windows: | |
| if: github.event_name != 'release' && github.event_name != 'pull_request' | |
| runs-on: windows-2022 | |
| # env: | |
| # RUSTUP_HOME: ${{ github.workspace }}\.rustup | |
| # CARGO_HOME: ${{ github.workspace }}\.cargo | |
| steps: | |
| - name: Clone repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| cache: true | |
| - name: Create Google Calendar Credentials | |
| shell: pwsh | |
| run: | | |
| $clientId = '${{ secrets.GOOGLE_CALENDAR_CLIENT_ID }}' | |
| $clientSecret = '${{ secrets.GOOGLE_CALENDAR_CLIENT_SECRET }}' | |
| if ([string]::IsNullOrWhiteSpace($clientId) -or [string]::IsNullOrWhiteSpace($clientSecret)) { | |
| throw "Google Calendar secrets are missing. Please set GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET." | |
| } | |
| $credentialsContent = @" | |
| class GoogleCalendarCredentials { | |
| static const String clientId = '$clientId'; | |
| static const String clientSecret = '$clientSecret'; | |
| } | |
| "@ | |
| $credentialsContent | Out-File -FilePath "lib/tools/calendar/services/google_calendar_credentials.dart" -Encoding UTF8 | |
| Write-Host "Google Calendar credentials file created" | |
| # - name: Install Rust (stable) | |
| # uses: dtolnay/rust-toolchain@stable | |
| # - name: Cache cargo registry and git | |
| # uses: actions/cache@v4 | |
| # with: | |
| # path: | | |
| # ${{ env.CARGO_HOME }}\registry | |
| # ${{ env.CARGO_HOME }}\git | |
| # key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| # restore-keys: | | |
| # ${{ runner.os }}-cargo- | |
| # - name: Add cargo to PATH & verify toolchain | |
| # shell: pwsh | |
| # run: | | |
| # "$env:CARGO_HOME\bin" | Out-File -FilePath $env:GITHUB_PATH -Append | |
| # rustc -Vv | |
| # cargo -V | |
| # rustup show | |
| - name: Install Inno Setup (project script + fallback) | |
| shell: pwsh | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| if (Test-Path .\installer\install_inno_setup.ps1) { | |
| Write-Host "Running local installer script..." | |
| .\installer\install_inno_setup.ps1 | |
| } else { | |
| Write-Host "Local installer script not found, skipping." | |
| } | |
| if (-not (Get-Command iscc -ErrorAction SilentlyContinue)) { | |
| if (Get-Command winget -ErrorAction SilentlyContinue) { | |
| Write-Host "Installing Inno Setup via winget..." | |
| winget install -e --id JRSoftware.InnoSetup --silent --accept-package-agreements --accept-source-agreements | |
| } else { | |
| Write-Host "winget not found, installing via Chocolatey..." | |
| choco install innosetup -y --no-progress | |
| } | |
| } | |
| $iscc = (Get-ChildItem "C:\Program Files*\Inno Setup*\ISCC.exe" -Recurse -ErrorAction SilentlyContinue | | |
| Select-Object -First 1 -ExpandProperty FullName) | |
| if (-not $iscc) { throw "ISCC.exe not found after installation" } | |
| "ISCC=$iscc" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| Write-Host "ISCC resolved to: $iscc" | |
| - name: Prepare Flutter dependencies for Windows release | |
| shell: pwsh | |
| run: | | |
| flutter pub get | |
| # Prevent bundling the PDFium WASM assets into non-Web release builds. | |
| dart run pdfrx:remove_wasm_modules | |
| - name: Build Flutter Windows app | |
| shell: pwsh | |
| run: | | |
| flutter build windows --release | |
| - name: Zip Windows build | |
| shell: pwsh | |
| run: | | |
| $relDir = Get-ChildItem -Directory build\windows | | |
| Where-Object { Test-Path "$($_.FullName)\runner\Release" } | | |
| Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $relDir) { throw 'Release directory not found' } | |
| Compress-Archive -Path "$relDir\runner\Release\*" -DestinationPath otzaria-windows.zip | |
| - name: Check if commit message is a version tag | |
| id: check_version | |
| shell: pwsh | |
| run: | | |
| $commitMessage = "${{ github.event.head_commit.message }}" | |
| if ([string]::IsNullOrWhiteSpace($commitMessage)) { | |
| Write-Host "Commit message not available in event payload; using checked-out HEAD commit message instead" | |
| $commitMessage = git log -1 --pretty=%B | |
| } | |
| $commitMessage = $commitMessage.Split([Environment]::NewLine)[0].Trim() | |
| Write-Host "Commit message: $commitMessage" | |
| # בדיקה אם ההודעה היא גרסה במבנה X.Y.Z (מספרים בלבד עם נקודות) | |
| if ($commitMessage -match '^\d+\.\d+\.\d+$') { | |
| Write-Host "✓ Commit message is a version tag: $commitMessage" | |
| echo "is_version=true" >> $env:GITHUB_OUTPUT | |
| } else { | |
| Write-Host "✗ Commit message is not a version tag" | |
| echo "is_version=false" >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Download library assets for full installer | |
| if: true | |
| shell: pwsh | |
| run: | | |
| Write-Host "Downloading bundled library assets for full installer..." | |
| $ErrorActionPreference = "Stop" | |
| try { | |
| # הורדת מסד הספרייה הראשי | |
| $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/Otzaria/SeforimLibrary/releases/latest" | |
| $dbAsset = $latestRelease.assets | Where-Object { $_.name -eq "seforim.db.zst" } | |
| if (-not $dbAsset) { | |
| Write-Host "::error::Could not find seforim.db.zst in latest release" | |
| exit 1 | |
| } | |
| Write-Host "Library version: $($latestRelease.tag_name)" | |
| Write-Host "Downloading from: $($dbAsset.browser_download_url)" | |
| Write-Host "Size: $([math]::Round($dbAsset.size / 1MB, 2)) MB" | |
| if (Test-Path "installer\library_db") { | |
| Remove-Item -Path "installer\library_db" -Recurse -Force | |
| } | |
| New-Item -ItemType Directory -Path "installer\library_db" -Force | Out-Null | |
| # הורדת ה-DB הדחוס שה-installer יחלץ בזמן ההתקנה | |
| $ProgressPreference = 'SilentlyContinue' | |
| Invoke-WebRequest -Uri $dbAsset.browser_download_url -OutFile "installer\library_db\seforim.db.zst" -UseBasicParsing | |
| Write-Host "Compressed library DB downloaded successfully" | |
| # הורדת מסד הקטלוגים החיצוני | |
| Write-Host "Downloading external catalog DB for full installer from Otzaria/otzar-HB_catalog..." | |
| $catalogRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/Otzaria/otzar-HB_catalog/releases/latest" | |
| $catalogAsset = $catalogRelease.assets | Where-Object { $_.name -eq "otzar-HB_catalog.db.zst" } | |
| if (-not $catalogAsset) { | |
| Write-Host "::error::Could not find otzar-HB_catalog.db.zst in latest release" | |
| exit 1 | |
| } | |
| Write-Host "Catalog version: $($catalogRelease.tag_name)" | |
| Write-Host "Downloading from: $($catalogAsset.browser_download_url)" | |
| Write-Host "Size: $([math]::Round($catalogAsset.size / 1MB, 2)) MB" | |
| Invoke-WebRequest -Uri $catalogAsset.browser_download_url -OutFile "installer\library_db\otzar-HB_catalog.db.zst" -UseBasicParsing | |
| Write-Host "Compressed external catalog DB downloaded successfully" | |
| # הורדת ארכיון ה-PDFים של תלמוד בבלי | |
| $talmudArchiveUrl = "https://github.com/Otzaria/otzaria-library/releases/latest/download/talmud_bavli_latest.tar.zst" | |
| Write-Host "Downloading bundled Talmud Bavli PDFs for full installer..." | |
| Write-Host "Downloading from: $talmudArchiveUrl" | |
| Invoke-WebRequest -Uri $talmudArchiveUrl -OutFile "installer\library_db\talmud_bavli_latest.tar.zst" -UseBasicParsing | |
| Write-Host "Compressed Talmud Bavli PDFs downloaded successfully" | |
| # הורדת כלי החילוץ כדי שה-installer יוכל לחלץ את קבצי הספרייה בזמן ההתקנה | |
| $zstdRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/facebook/zstd/releases/latest" | |
| $zstdAsset = $zstdRelease.assets | Where-Object { $_.name -like "zstd-*-win64.zip" } | Select-Object -First 1 | |
| $sevenZipRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/ip7z/7zip/releases/latest" | |
| $sevenZipBootstrap = $sevenZipRelease.assets | Where-Object { $_.name -eq "7zr.exe" } | Select-Object -First 1 | |
| $sevenZipExtra = $sevenZipRelease.assets | Where-Object { $_.name -like "7z*-extra.7z" } | Select-Object -First 1 | |
| if (-not $zstdAsset) { | |
| Write-Host "::error::Could not find win64 zstd asset in latest release" | |
| exit 1 | |
| } | |
| if ((-not $sevenZipBootstrap) -or (-not $sevenZipExtra)) { | |
| Write-Host "::error::Could not find 7-Zip bootstrap assets in latest release" | |
| exit 1 | |
| } | |
| Write-Host "Downloading zstd from: $($zstdAsset.browser_download_url)" | |
| Invoke-WebRequest -Uri $zstdAsset.browser_download_url -OutFile "installer\zstd-win64.zip" -UseBasicParsing | |
| if (Test-Path "installer\zstd_tmp") { | |
| Remove-Item -Path "installer\zstd_tmp" -Recurse -Force | |
| } | |
| Expand-Archive -Path "installer\zstd-win64.zip" -DestinationPath "installer\zstd_tmp" -Force | |
| $zstdExe = Get-ChildItem -Path "installer\zstd_tmp" -Filter "zstd.exe" -Recurse | Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $zstdExe) { | |
| Write-Host "::error::zstd.exe was not found inside downloaded archive" | |
| exit 1 | |
| } | |
| Copy-Item -Path $zstdExe -Destination "installer\zstd.exe" -Force | |
| Write-Host "Downloading 7zr from: $($sevenZipBootstrap.browser_download_url)" | |
| Invoke-WebRequest -Uri $sevenZipBootstrap.browser_download_url -OutFile "installer\7zr.exe" -UseBasicParsing | |
| Write-Host "Downloading 7-Zip extra package from: $($sevenZipExtra.browser_download_url)" | |
| Invoke-WebRequest -Uri $sevenZipExtra.browser_download_url -OutFile "installer\7zip-extra.7z" -UseBasicParsing | |
| if (Test-Path "installer\7zip_tmp") { | |
| Remove-Item -Path "installer\7zip_tmp" -Recurse -Force | |
| } | |
| & "installer\7zr.exe" x "installer\7zip-extra.7z" "-oinstaller\7zip_tmp" -y | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "::error::Failed to extract 7-Zip extra package" | |
| exit 1 | |
| } | |
| $sevenZipExe = Get-ChildItem -Path "installer\7zip_tmp" -Filter "7za.exe" -Recurse | Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $sevenZipExe) { | |
| Write-Host "::error::7za.exe was not found inside 7-Zip extra package" | |
| exit 1 | |
| } | |
| Copy-Item -Path $sevenZipExe -Destination "installer\7za.exe" -Force | |
| Remove-Item -Path "installer\zstd-win64.zip" -Force | |
| Remove-Item -Path "installer\zstd_tmp" -Recurse -Force | |
| Remove-Item -Path "installer\7zip-extra.7z" -Force | |
| Remove-Item -Path "installer\7zip_tmp" -Recurse -Force | |
| Remove-Item -Path "installer\7zr.exe" -Force | |
| Write-Host "zstd.exe and 7za.exe prepared successfully" | |
| } | |
| catch { | |
| Write-Host "::error::Failed to download library: $_" | |
| exit 1 | |
| } | |
| - name: Download Visual C++ Redistributable for full installer | |
| if: true | |
| shell: pwsh | |
| run: | | |
| Write-Host "Downloading Visual C++ Redistributable for full installer..." | |
| $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/abbodi1406/vcredist/releases/latest" | |
| $asset = $latestRelease.assets | Where-Object { $_.name -eq "VisualCppRedist_AIO_x86_x64.exe" } | |
| if (-not $asset) { | |
| Write-Host "::error::Could not find VisualCppRedist_AIO_x86_x64.exe in latest release" | |
| exit 1 | |
| } | |
| Write-Host "Downloading from: $($asset.browser_download_url)" | |
| Write-Host "Size: $([math]::Round($asset.size / 1MB, 2)) MB" | |
| Invoke-WebRequest -Uri $asset.browser_download_url -OutFile "installer\VisualCppRedist_AIO_x86_x64.exe" | |
| Write-Host "Visual C++ Redistributable downloaded successfully" | |
| - name: Build Inno Setup installer | |
| shell: pwsh | |
| run: | | |
| Write-Host "Building FULL installer with library and VC++ Redistributable..." | |
| & "$env:ISCC" installer\otzaria_full.iss | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "FULL installer build failed" | |
| } | |
| # ניקוי קבצי הספרייה והחולץ לאחר בניית ה-FULL installer | |
| Write-Host "Cleaning up bundled DB assets to free disk space..." | |
| if (Test-Path "installer\library_db") { | |
| Remove-Item -Path "installer\library_db" -Recurse -Force | |
| } | |
| if (Test-Path "installer\zstd.exe") { | |
| Remove-Item -Path "installer\zstd.exe" -Force | |
| } | |
| if (Test-Path "installer\7za.exe") { | |
| Remove-Item -Path "installer\7za.exe" -Force | |
| } | |
| if (Test-Path "installer\zstd_tmp") { | |
| Remove-Item -Path "installer\zstd_tmp" -Recurse -Force | |
| } | |
| if (Test-Path "installer\zstd-win64.zip") { | |
| Remove-Item -Path "installer\zstd-win64.zip" -Force | |
| } | |
| if (Test-Path "installer\7zip_tmp") { | |
| Remove-Item -Path "installer\7zip_tmp" -Recurse -Force | |
| } | |
| if (Test-Path "installer\7zip-extra.7z") { | |
| Remove-Item -Path "installer\7zip-extra.7z" -Force | |
| } | |
| if (Test-Path "installer\7zr.exe") { | |
| Remove-Item -Path "installer\7zr.exe" -Force | |
| } | |
| Write-Host "Bundled DB assets cleaned up successfully" | |
| Write-Host "Building REGULAR installer (without VC++ Redistributable)..." | |
| & "$env:ISCC" installer\otzaria.iss | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Regular installer build failed" | |
| } | |
| - name: Upload Windows installer | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-windows-installer | |
| path: installer/otzaria-*-windows*.exe | |
| - name: Upload Windows ZIP | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-windows-zip | |
| path: otzaria-windows.zip | |
| build_linux: | |
| if: github.event_name != 'release' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| env: | |
| RUSTUP_HOME: ${{ github.workspace }}/.rustup | |
| CARGO_HOME: ${{ github.workspace }}/.cargo | |
| steps: | |
| - name: Clone repository | |
| uses: actions/checkout@v4 | |
| - name: Check if commit message is a version tag | |
| id: check_version | |
| shell: bash | |
| run: | | |
| COMMIT_MESSAGE='${{ github.event.head_commit.message }}' | |
| if [ -z "$COMMIT_MESSAGE" ]; then | |
| echo "Commit message not available in event payload; using checked-out HEAD commit message instead" | |
| COMMIT_MESSAGE=$(git log -1 --pretty=%B) | |
| fi | |
| COMMIT_MESSAGE=$(printf '%s' "$COMMIT_MESSAGE" | head -n 1 | xargs) | |
| echo "Commit message: $COMMIT_MESSAGE" | |
| if printf '%s' "$COMMIT_MESSAGE" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "is_version=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_version=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| cache: true | |
| - name: Create Google Calendar Credentials | |
| shell: bash | |
| run: | | |
| CLIENT_ID='${{ secrets.GOOGLE_CALENDAR_CLIENT_ID }}' | |
| CLIENT_SECRET='${{ secrets.GOOGLE_CALENDAR_CLIENT_SECRET }}' | |
| if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then | |
| echo "::error::Google Calendar secrets are missing. Please set GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET." | |
| exit 1 | |
| fi | |
| cat > lib/tools/calendar/services/google_calendar_credentials.dart << EOF | |
| class GoogleCalendarCredentials { | |
| static const String clientId = '$CLIENT_ID'; | |
| static const String clientSecret = '$CLIENT_SECRET'; | |
| } | |
| EOF | |
| echo "Google Calendar credentials file created" | |
| - name: Install Rust (stable) with rustup | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Add cargo to PATH and verify toolchain | |
| run: | | |
| echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" | |
| rustc -Vv | |
| cargo -V | |
| rustup show | |
| - name: Cache cargo registry and git | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ${{ env.CARGO_HOME }}/registry | |
| ${{ env.CARGO_HOME }}/git | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Install Linux build dependencies | |
| run: | | |
| sudo apt-get update -y | |
| sudo apt-get install -y \ | |
| build-essential ninja-build libgtk-3-dev libblkid1 liblzma5 \ | |
| clang cmake git pkg-config liblzma-dev rpm patchelf curl xz-utils zstd \ | |
| libcurl4-openssl-dev | |
| # Fallback אם מסיבה כלשהי cargo לא קיים (למשל כשל זמני ב-rustup) | |
| if ! command -v cargo >/dev/null 2>&1; then | |
| sudo apt-get install -y rustc cargo | |
| fi | |
| - name: Download library assets for Linux FULL bundle | |
| if: true | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p full_installer/library_db | |
| curl -L --fail https://github.com/Otzaria/SeforimLibrary/releases/latest/download/seforim.db.zst -o full_installer/library_db/seforim.db.zst | |
| curl -L --fail https://github.com/Otzaria/otzar-HB_catalog/releases/latest/download/otzar-HB_catalog.db.zst -o full_installer/library_db/otzar-HB_catalog.db.zst | |
| curl -L --fail https://github.com/Otzaria/otzaria-library/releases/latest/download/talmud_bavli_latest.tar.zst -o full_installer/library_db/talmud_bavli_latest.tar.zst | |
| - name: Install flutter_distributor | |
| run: dart pub global activate flutter_distributor | |
| - name: Prepare Flutter dependencies for Linux release | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| flutter pub get | |
| # Prevent bundling the PDFium WASM assets into non-Web release builds. | |
| dart run pdfrx:remove_wasm_modules | |
| - name: Build Flutter Linux app (with retry) | |
| run: | | |
| set -e | |
| tries=0 | |
| until [ $tries -ge 3 ] | |
| do | |
| if flutter build linux; then | |
| break | |
| fi | |
| tries=$((tries+1)) | |
| echo "flutter build linux failed. retry $tries/3 after short sleep..." | |
| sleep 15 | |
| done | |
| if [ $tries -ge 3 ]; then | |
| echo "::error::flutter build linux failed after 3 attempts" | |
| exit 1 | |
| fi | |
| - name: Build and Patch Linux DEB package | |
| shell: bash | |
| run: | | |
| set -e | |
| # 1. הרץ את פקודת הבנייה, אבל תפוס את הכישלון כדי שנוכל לתקן אחריו | |
| echo "Attempting to build with flutter_distributor, it might fail..." | |
| flutter_distributor release --name=production --jobs=release-linux-deb || true | |
| # 2. מצא את קובץ ה-control שנוצר. אם לא נמצא, זה כישלון אמיתי | |
| CONTROL_FILE=$(find dist -type f -name "control" | head -n 1) | |
| if [ -z "$CONTROL_FILE" ]; then | |
| echo "::error::Fatal: Could not find the DEBIAN/control file. The distributor failed before creating it." | |
| exit 1 | |
| fi | |
| echo "Found control file to patch at: $CONTROL_FILE" | |
| echo "--- Control file BEFORE fix ---" | |
| cat "$CONTROL_FILE" | |
| # 3. תקן את כל הבעיות האפשריות בקובץ | |
| # המרת סיומות שורה (CRLF -> LF) | |
| sed -i 's/\r$//' "$CONTROL_FILE" | |
| # הסרת שורת Depends ריקה | |
| sed -i '/^Depends:\s*$/d' "$CONTROL_FILE" | |
| # ודא שיש שורת סיום (newline) בסוף הקובץ | |
| if [ -n "$(tail -c1 "$CONTROL_FILE")" ]; then | |
| echo >> "$CONTROL_FILE" | |
| fi | |
| echo "--- Control file AFTER fix ---" | |
| cat "$CONTROL_FILE" | |
| # 4. בנה את חבילת ה-deb מחדש מהקבצים המתוקנים | |
| DEB_DIR=$(dirname "$CONTROL_FILE" | xargs dirname) | |
| DEB_OUTPUT_FILE="dist/$(basename "$DEB_DIR" | sed 's/-linux_deb$//')-linux.deb" | |
| echo "Re-packaging from directory: $DEB_DIR into $DEB_OUTPUT_FILE" | |
| dpkg-deb --build --root-owner-group "$DEB_DIR" "$DEB_OUTPUT_FILE" | |
| echo "Successfully patched and rebuilt the .deb package." | |
| - name: Build and Patch Linux RPM package | |
| shell: bash | |
| run: | | |
| set -e | |
| # 1. הרץ את פקודת הבנייה הבעייתית, ותפוס את הכישלון | |
| echo "Attempting to build with flutter_distributor, expecting it to fail..." | |
| flutter_distributor release --name=production --jobs=release-linux-rpm || true | |
| # 2. מצא את קובץ ה-spec הפגום שהכלי יצר | |
| SPEC_FILE=$(find dist -type f -name "*.spec" | head -n 1) | |
| if [ -z "$SPEC_FILE" ]; then | |
| echo "::error::Fatal: Could not find the .spec file to patch." | |
| exit 1 | |
| fi | |
| echo "Found spec file to patch at: $SPEC_FILE" | |
| echo "--- Spec file BEFORE fix ---" | |
| cat "$SPEC_FILE" | |
| # 3. תקן את הקובץ: מחק את שורת ה-Requires הריקה | |
| sed -i '/^Requires:\s*$/d' "$SPEC_FILE" | |
| echo "--- Spec file AFTER fix ---" | |
| cat "$SPEC_FILE" | |
| # 4. בנה את חבילת ה-rpm מחדש, תוך שימוש בנתיב מוחלט | |
| RPM_TOP_DIR_REL=$(dirname "$SPEC_FILE" | xargs dirname) | |
| RPM_TOP_DIR_ABS="$(pwd)/$RPM_TOP_DIR_REL" | |
| echo "Re-packaging using absolute top directory: $RPM_TOP_DIR_ABS" | |
| rpmbuild --define "_topdir $RPM_TOP_DIR_ABS" -bb "$SPEC_FILE" | |
| echo "Successfully patched and rebuilt the .rpm package." | |
| - name: Find and prepare Linux packages | |
| id: find_packages | |
| run: | | |
| DEB_FILE=$(find dist -maxdepth 1 -name "*-linux.deb" | head -n 1) | |
| if [ -n "$DEB_FILE" ]; then | |
| echo "deb_file=$DEB_FILE" >> $GITHUB_OUTPUT | |
| echo "deb_name=$(basename $DEB_FILE)" >> $GITHUB_OUTPUT | |
| fi | |
| RPM_FILE=$(find dist -type f -name "*.rpm" | head -n 1) | |
| if [ -n "$RPM_FILE" ]; then | |
| echo "rpm_file=$RPM_FILE" >> $GITHUB_OUTPUT | |
| echo "rpm_name=$(basename $RPM_FILE)" >> $GITHUB_OUTPUT | |
| fi | |
| mkdir -p linux-build | |
| cp -r build/linux/x64/release/bundle/* linux-build/ | |
| - name: Upload Linux DEB package | |
| if: steps.find_packages.outputs.deb_file != '' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-linux-deb | |
| path: ${{ steps.find_packages.outputs.deb_file }} | |
| - name: Upload Linux RPM package | |
| if: steps.find_packages.outputs.rpm_file != '' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-linux-rpm | |
| path: ${{ steps.find_packages.outputs.rpm_file }} | |
| - name: Upload linux build (raw) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-linux-raw | |
| path: linux-build/* | |
| - name: Create Linux FULL portable bundle | |
| if: true | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BUNDLE_ROOT="full_installer/otzaria-linux-full" | |
| rm -rf "$BUNDLE_ROOT" | |
| mkdir -p "$BUNDLE_ROOT/app" "$BUNDLE_ROOT/אוצריא" | |
| cp -r linux-build/. "$BUNDLE_ROOT/app/" | |
| zstd -d -c --long=31 --memory=2048MB full_installer/library_db/seforim.db.zst > "$BUNDLE_ROOT/אוצריא/seforim.db" | |
| zstd -d -c --long=31 --memory=2048MB full_installer/library_db/otzar-HB_catalog.db.zst > "$BUNDLE_ROOT/אוצריא/otzar-HB_catalog.db" | |
| zstd -d -c --long=31 --memory=2048MB full_installer/library_db/talmud_bavli_latest.tar.zst | tar -xf - -C "$BUNDLE_ROOT/אוצריא" | |
| cat > "$BUNDLE_ROOT/run-otzaria.sh" << 'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| cd "$(dirname "$0")" | |
| exec ./app/otzaria | |
| EOF | |
| chmod +x "$BUNDLE_ROOT/run-otzaria.sh" | |
| cat > "$BUNDLE_ROOT/README.txt" << 'EOF' | |
| Otzaria Linux FULL bundle | |
| This portable bundle includes the app and the bundled library. | |
| Start the app using ./run-otzaria.sh so the app can detect the bundled library folder. | |
| EOF | |
| tar -czf otzaria-linux-full.tar.gz -C full_installer otzaria-linux-full | |
| - name: Upload Linux FULL bundle | |
| if: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-linux-full | |
| path: otzaria-linux-full.tar.gz | |
| build_android: | |
| if: github.event_name != 'release' && github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| env: | |
| RUSTUP_HOME: ${{ github.workspace }}/.rustup | |
| CARGO_HOME: ${{ github.workspace }}/.cargo | |
| steps: | |
| - name: Clone repository | |
| uses: actions/checkout@v4 | |
| - name: Check if commit message is a version tag | |
| id: check_version | |
| shell: bash | |
| run: | | |
| COMMIT_MESSAGE='${{ github.event.head_commit.message }}' | |
| if [ -z "$COMMIT_MESSAGE" ]; then | |
| echo "Commit message not available in event payload; using checked-out HEAD commit message instead" | |
| COMMIT_MESSAGE=$(git log -1 --pretty=%B) | |
| fi | |
| COMMIT_MESSAGE=$(printf '%s' "$COMMIT_MESSAGE" | head -n 1 | xargs) | |
| echo "Commit message: $COMMIT_MESSAGE" | |
| if printf '%s' "$COMMIT_MESSAGE" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "is_version=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_version=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Free up disk space | |
| run: | | |
| echo "Disk space before cleanup:" | |
| df -h | |
| # מחיקת קבצים מיותרים כדי לפנות מקום | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /usr/local/share/boost | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
| echo "Disk space after cleanup:" | |
| df -h | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| cache: true | |
| - name: Install Rust (stable) | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Add cargo to PATH & verify toolchain | |
| run: | | |
| echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" | |
| rustc -Vv | |
| cargo -V | |
| rustup show | |
| - name: Cache Rust | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ${{ env.CARGO_HOME }}/registry | |
| ${{ env.CARGO_HOME }}/git | |
| ${{ env.RUSTUP_HOME }} | |
| key: ${{ runner.os }}-rust-android-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-rust-android- | |
| - name: Pre-install Android Rust targets (with retry) | |
| run: | | |
| set -e | |
| # פונקציה להתקנת target עם retry | |
| install_target_with_retry() { | |
| local target=$1 | |
| local max_attempts=3 | |
| local attempt=1 | |
| while [ $attempt -le $max_attempts ]; do | |
| echo "Installing $target (attempt $attempt/$max_attempts)..." | |
| if rustup target add --toolchain stable "$target"; then | |
| echo "✓ Successfully installed $target" | |
| return 0 | |
| fi | |
| echo "Failed to install $target, cleaning up..." | |
| # ניקוי קבצים זמניים שעלולים לגרום לבעיה | |
| rm -rf ~/.rustup/downloads/*.partial || true | |
| rm -rf ~/.rustup/tmp/* || true | |
| attempt=$((attempt + 1)) | |
| if [ $attempt -le $max_attempts ]; then | |
| echo "Waiting 10 seconds before retry..." | |
| sleep 10 | |
| fi | |
| done | |
| echo "::error::Failed to install $target after $max_attempts attempts" | |
| return 1 | |
| } | |
| # התקנת כל ה-targets הנדרשים | |
| install_target_with_retry "armv7-linux-androideabi" | |
| install_target_with_retry "aarch64-linux-android" | |
| install_target_with_retry "x86_64-linux-android" | |
| install_target_with_retry "i686-linux-android" | |
| echo "All Android Rust targets installed successfully" | |
| - name: Cache Gradle | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Cache Pub | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.pub-cache | |
| key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pub- | |
| - name: Create Google Calendar Credentials | |
| shell: bash | |
| run: | | |
| CLIENT_ID='${{ secrets.GOOGLE_CALENDAR_CLIENT_ID }}' | |
| CLIENT_SECRET='${{ secrets.GOOGLE_CALENDAR_CLIENT_SECRET }}' | |
| if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then | |
| echo "::error::Google Calendar secrets are missing. Please set GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET." | |
| exit 1 | |
| fi | |
| cat > lib/tools/calendar/services/google_calendar_credentials.dart << EOF | |
| class GoogleCalendarCredentials { | |
| static const String clientId = '$CLIENT_ID'; | |
| static const String clientSecret = '$CLIENT_SECRET'; | |
| } | |
| EOF | |
| echo "Google Calendar credentials file created" | |
| - name: Setup Android signing | |
| shell: bash | |
| env: | |
| ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android/app/keystore/otzaria-keystore.jks | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| set -e | |
| mkdir -p android/app/keystore | |
| echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore/otzaria-keystore.jks | |
| chmod 600 android/app/keystore/otzaria-keystore.jks | |
| # Verify keystore file exists | |
| echo "Verifying keystore setup..." | |
| ls -la android/app/keystore/ | |
| if [ ! -f android/app/keystore/otzaria-keystore.jks ]; then | |
| echo "::error::Keystore file was not created!" | |
| exit 1 | |
| fi | |
| echo "✓ Keystore file exists at: android/app/keystore/otzaria-keystore.jks" | |
| echo "✓ Environment variables set for Gradle signing" | |
| - run: sudo apt install ninja-build | |
| - name: Gradle performance flags | |
| run: | | |
| mkdir -p ~/.gradle | |
| cat >> ~/.gradle/gradle.properties << 'EOF' | |
| org.gradle.daemon=true | |
| org.gradle.parallel=true | |
| org.gradle.caching=true | |
| org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 | |
| kotlin.incremental=true | |
| EOF | |
| - name: Ensure Android SDK Platform 36 | |
| run: | | |
| set -e | |
| if [ ! -d "${ANDROID_SDK_ROOT}/platforms/android-36" ]; then | |
| ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "platforms;android-36" | |
| fi | |
| if [ ! -d "${ANDROID_SDK_ROOT}/build-tools/36.0.0" ]; then | |
| ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;36.0.0" | |
| fi | |
| - name: Download library assets for Android FULL bundle | |
| if: true | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p full_installer/library_db | |
| curl -L --fail https://github.com/Otzaria/SeforimLibrary/releases/latest/download/seforim.db.zst -o full_installer/library_db/seforim.db.zst | |
| curl -L --fail https://github.com/Otzaria/otzar-HB_catalog/releases/latest/download/otzar-HB_catalog.db.zst -o full_installer/library_db/otzar-HB_catalog.db.zst | |
| curl -L --fail https://github.com/Otzaria/otzaria-library/releases/latest/download/talmud_bavli_latest.tar.zst -o full_installer/library_db/talmud_bavli_latest.tar.zst | |
| - name: Build Android APK | |
| env: | |
| ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android/app/keystore/otzaria-keystore.jks | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| flutter pub get | |
| # Prevent bundling the PDFium WASM assets into non-Web release builds. | |
| dart run pdfrx:remove_wasm_modules | |
| flutter build apk | |
| # ניקוי מיידי של קבצים מיותרים מהבנייה (לא ה-APK הסופי) | |
| rm -rf build/app/intermediates || true | |
| rm -rf build/app/.transforms || true | |
| echo "Disk space after intermediate cleanup:" | |
| df -h | |
| - name: Upload apk | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-android.apk | |
| path: build/app/outputs/flutter-apk/app-release.apk | |
| - name: Create Android FULL bundle | |
| if: true | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BUNDLE_ROOT="full_installer/otzaria-android-full" | |
| rm -rf "$BUNDLE_ROOT" | |
| mkdir -p "$BUNDLE_ROOT/library_db" | |
| cp build/app/outputs/flutter-apk/app-release.apk "$BUNDLE_ROOT/otzaria-android.apk" | |
| cp full_installer/library_db/* "$BUNDLE_ROOT/library_db/" | |
| cat > "$BUNDLE_ROOT/README.txt" << 'EOF' | |
| Otzaria Android FULL bundle | |
| This bundle includes the APK and the offline library payload. | |
| The APK itself cannot preinstall external library files automatically. | |
| Use this bundle when distributing the APK together with the library payload. | |
| EOF | |
| cd full_installer | |
| zip -r ../otzaria-android-full.zip otzaria-android-full | |
| - name: Upload Android FULL bundle | |
| if: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-android-full | |
| path: otzaria-android-full.zip | |
| - name: Final cleanup | |
| if: always() | |
| run: | | |
| # ניקוי סופי של כל קבצי build וcache (נשמר Cache לריצות הבאות) | |
| rm -rf build || true | |
| echo "Disk space after final cleanup:" | |
| df -h | |
| build_macos: | |
| if: github.event_name != 'release' && github.event_name != 'pull_request' | |
| runs-on: macos-latest | |
| env: | |
| # אחסון יציב בתוך ה-workspace כדי למנוע תקלות rename של rustup | |
| RUSTUP_HOME: ${{ github.workspace }}/.rustup | |
| CARGO_HOME: ${{ github.workspace }}/.cargo | |
| steps: | |
| - name: Clone repository | |
| uses: actions/checkout@v4 | |
| - name: Check if commit message is a version tag | |
| id: check_version | |
| shell: bash | |
| run: | | |
| COMMIT_MESSAGE='${{ github.event.head_commit.message }}' | |
| if [ -z "$COMMIT_MESSAGE" ]; then | |
| echo "Commit message not available in event payload; using checked-out HEAD commit message instead" | |
| COMMIT_MESSAGE=$(git log -1 --pretty=%B) | |
| fi | |
| COMMIT_MESSAGE=$(printf '%s' "$COMMIT_MESSAGE" | head -n 1 | xargs) | |
| echo "Commit message: $COMMIT_MESSAGE" | |
| if printf '%s' "$COMMIT_MESSAGE" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then | |
| echo "is_version=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_version=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| channel: stable | |
| cache: true | |
| - name: Create Google Calendar Credentials | |
| shell: bash | |
| run: | | |
| CLIENT_ID='${{ secrets.GOOGLE_CALENDAR_CLIENT_ID }}' | |
| CLIENT_SECRET='${{ secrets.GOOGLE_CALENDAR_CLIENT_SECRET }}' | |
| if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then | |
| echo "::error::Google Calendar secrets are missing. Please set GOOGLE_CALENDAR_CLIENT_ID and GOOGLE_CALENDAR_CLIENT_SECRET." | |
| exit 1 | |
| fi | |
| cat > lib/tools/calendar/services/google_calendar_credentials.dart << EOF | |
| class GoogleCalendarCredentials { | |
| static const String clientId = '$CLIENT_ID'; | |
| static const String clientSecret = '$CLIENT_SECRET'; | |
| } | |
| EOF | |
| echo "Google Calendar credentials file created" | |
| # התקנת Rust יציבה לפני flutter build | |
| - name: Install Rust (stable) | |
| # מחליף את ההתקנה האוטומטית של cargokit | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Add cargo to PATH & verify toolchain | |
| run: | | |
| echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" | |
| rustc -Vv | |
| cargo -V | |
| rustup show | |
| # (רשות אבל מומלץ) קאשינג לספריות cargo כדי לזרז בניות | |
| - name: Cache cargo registry and git | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ${{ env.CARGO_HOME }}/registry | |
| ${{ env.CARGO_HOME }}/git | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Prepare Flutter dependencies for macOS release | |
| run: | | |
| flutter pub get | |
| # Prevent bundling the PDFium WASM assets into non-Web release builds. | |
| dart run pdfrx:remove_wasm_modules | |
| - name: Build Flutter macOS app (Universal Binary) | |
| run: | | |
| # Build Universal Binary that supports both Intel (x86_64) and Apple Silicon (arm64) | |
| flutter build macos --release | |
| - name: Download library assets for macOS FULL bundle | |
| if: true | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p full_installer/library_db | |
| curl -L --fail https://github.com/Otzaria/SeforimLibrary/releases/latest/download/seforim.db.zst -o full_installer/library_db/seforim.db.zst | |
| curl -L --fail https://github.com/Otzaria/otzar-HB_catalog/releases/latest/download/otzar-HB_catalog.db.zst -o full_installer/library_db/otzar-HB_catalog.db.zst | |
| curl -L --fail https://github.com/Otzaria/otzaria-library/releases/latest/download/talmud_bavli_latest.tar.zst -o full_installer/library_db/talmud_bavli_latest.tar.zst | |
| - name: Verify Universal Binary | |
| run: | | |
| APP_PATH="build/macos/Build/Products/Release/otzaria.app/Contents/MacOS/otzaria" | |
| if [ -f "$APP_PATH" ]; then | |
| echo "Checking architectures in binary:" | |
| lipo -info "$APP_PATH" || file "$APP_PATH" | |
| else | |
| echo "Warning: Binary not found at expected path" | |
| find build/macos/Build/Products/Release -name "otzaria" -o -name "*.app" | |
| fi | |
| - name: Create DMG installer | |
| run: | | |
| # Install create-dmg if not available | |
| if ! command -v create-dmg &> /dev/null; then | |
| brew install create-dmg | |
| fi | |
| APP_NAME="otzaria" | |
| APP_PATH="build/macos/Build/Products/Release/${APP_NAME}.app" | |
| DMG_NAME="${APP_NAME}-macos.dmg" | |
| # Create DMG with drag-to-Applications shortcut | |
| create-dmg \ | |
| --volname "Otzaria Installer" \ | |
| --volicon "${APP_PATH}/Contents/Resources/AppIcon.icns" \ | |
| --window-pos 200 120 \ | |
| --window-size 600 400 \ | |
| --icon-size 100 \ | |
| --icon "${APP_NAME}.app" 175 190 \ | |
| --hide-extension "${APP_NAME}.app" \ | |
| --app-drop-link 425 190 \ | |
| --no-internet-enable \ | |
| "${DMG_NAME}" \ | |
| "${APP_PATH}" \ | |
| || hdiutil create -volname "Otzaria" -srcfolder "${APP_PATH}" -ov -format UDZO "${DMG_NAME}" | |
| - name: Create macOS FULL portable bundle | |
| if: true | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if ! command -v zstd &> /dev/null; then | |
| brew install zstd | |
| fi | |
| BUNDLE_ROOT="full_installer/otzaria-macos-full" | |
| rm -rf "$BUNDLE_ROOT" | |
| mkdir -p "$BUNDLE_ROOT/אוצריא" | |
| cp -R build/macos/Build/Products/Release/otzaria.app "$BUNDLE_ROOT/otzaria.app" | |
| zstd -d -c --long=31 --memory=2048MB full_installer/library_db/seforim.db.zst > "$BUNDLE_ROOT/אוצריא/seforim.db" | |
| zstd -d -c --long=31 --memory=2048MB full_installer/library_db/otzar-HB_catalog.db.zst > "$BUNDLE_ROOT/אוצריא/otzar-HB_catalog.db" | |
| zstd -d -c --long=31 --memory=2048MB full_installer/library_db/talmud_bavli_latest.tar.zst | tar -xf - -C "$BUNDLE_ROOT/אוצריא" | |
| cat > "$BUNDLE_ROOT/Run Otzaria.command" << 'EOF' | |
| #!/bin/bash | |
| set -euo pipefail | |
| cd "$(dirname "$0")" | |
| exec "./otzaria.app/Contents/MacOS/otzaria" | |
| EOF | |
| chmod +x "$BUNDLE_ROOT/Run Otzaria.command" | |
| cat > "$BUNDLE_ROOT/README.txt" << 'EOF' | |
| Otzaria macOS FULL bundle | |
| This portable bundle includes the app and the bundled library. | |
| Start the app using Run Otzaria.command so the app can detect the bundled library folder. | |
| EOF | |
| ditto -c -k --sequesterRsrc --keepParent "$BUNDLE_ROOT" otzaria-macos-full.zip | |
| - name: Upload macOS DMG | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-macos.dmg | |
| path: otzaria-macos.dmg | |
| - name: Upload macOS FULL bundle | |
| if: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: otzaria-macos-full | |
| path: otzaria-macos-full.zip | |
| create_release: | |
| if: "!cancelled() && github.event_name != 'release' && github.event_name != 'pull_request'" | |
| needs: [build_windows, build_linux, build_android, build_macos] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Clone repository | |
| uses: actions/checkout@v4 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Get version from pubspec.yaml | |
| id: version | |
| run: | | |
| VERSION=$(grep '^version:' pubspec.yaml | cut -d ' ' -f 2 | cut -d '+' -f 1) | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| # Validate semver format | |
| if ! echo "$VERSION" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'; then | |
| echo "::error::Version '$VERSION' is not valid semver format" | |
| exit 1 | |
| fi | |
| if [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| echo "tag=$VERSION" >> $GITHUB_OUTPUT | |
| echo "prerelease=false" >> $GITHUB_OUTPUT | |
| echo "title=Otzaria $VERSION" >> $GITHUB_OUTPUT | |
| else | |
| # Use proper semver pre-release format with dots instead of hyphens | |
| BRANCH_NAME="${{ github.ref_name }}" | |
| # Replace any invalid characters in branch name for semver | |
| CLEAN_BRANCH=$(echo "$BRANCH_NAME" | sed 's/[^A-Za-z0-9.-]/-/g') | |
| echo "tag=$VERSION+${{ github.run_number }}" >> $GITHUB_OUTPUT | |
| echo "prerelease=true" >> $GITHUB_OUTPUT | |
| echo "title=Otzaria $VERSION (Preview from ${{ github.ref_name }})" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Get commit message | |
| id: commit | |
| run: echo "message=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT | |
| - name: Organize release files | |
| run: | | |
| mkdir -p release-files | |
| # Windows installer | |
| if [ -d "artifacts/otzaria-windows-installer" ]; then | |
| cp artifacts/otzaria-windows-installer/*.exe release-files/ || true | |
| fi | |
| # Windows ZIP | |
| if [ -d "artifacts/otzaria-windows-zip" ]; then | |
| cp artifacts/otzaria-windows-zip/*.zip release-files/ || true | |
| fi | |
| # Linux DEB package | |
| if [ -d "artifacts/otzaria-linux-deb" ]; then | |
| cp artifacts/otzaria-linux-deb/*.deb release-files/ || true | |
| fi | |
| # Linux RPM package | |
| if [ -d "artifacts/otzaria-linux-rpm" ]; then | |
| cp artifacts/otzaria-linux-rpm/*.rpm release-files/ || true | |
| fi | |
| # Linux raw build (fallback) | |
| if [ -d "artifacts/otzaria-linux-raw" ] && [ ! -f release-files/*.deb ]; then | |
| cd artifacts/otzaria-linux-raw | |
| zip -r ../../release-files/otzaria-linux-raw.zip * | |
| cd ../.. | |
| fi | |
| # Linux FULL bundle | |
| if [ -d "artifacts/otzaria-linux-full" ]; then | |
| cp artifacts/otzaria-linux-full/*.tar.gz release-files/ || true | |
| fi | |
| # Android APK | |
| if [ -d "artifacts/otzaria-android.apk" ]; then | |
| cp artifacts/otzaria-android.apk/*.apk release-files/ || true | |
| fi | |
| # Android FULL bundle | |
| if [ -d "artifacts/otzaria-android-full" ]; then | |
| cp artifacts/otzaria-android-full/*.zip release-files/ || true | |
| fi | |
| # macOS DMG | |
| if [ -d "artifacts/otzaria-macos.dmg" ]; then | |
| cp artifacts/otzaria-macos.dmg/*.dmg release-files/ || true | |
| fi | |
| # macOS FULL bundle | |
| if [ -d "artifacts/otzaria-macos-full" ]; then | |
| cp artifacts/otzaria-macos-full/*.zip release-files/ || true | |
| fi | |
| # List files for debugging | |
| echo "Release files:" | |
| ls -la release-files/ | |
| - name: Generate release notes | |
| env: | |
| RELEASE_TITLE: ${{ steps.version.outputs.title }} | |
| RELEASE_TAG: ${{ steps.version.outputs.tag }} | |
| COMMIT_MESSAGE: ${{ steps.commit.outputs.message }} | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| RELEASE_URL="https://github.com/${{ github.repository }}/releases/download/${RELEASE_TAG}" | |
| windows_installers=() | |
| windows_portable=() | |
| windows_full=() | |
| android_apk=() | |
| android_full=() | |
| linux_deb=() | |
| linux_rpm=() | |
| linux_portable=() | |
| linux_full=() | |
| mac_dmg=() | |
| mac_full=() | |
| emit_link_group() { | |
| local label="$1" | |
| shift | |
| for file in "$@"; do | |
| printf -- "- [%s](%s/%s) - \`%s\`\n" "$label" "$RELEASE_URL" "$file" "$file" >> release-body.md | |
| done | |
| } | |
| for path in release-files/*; do | |
| [ -f "$path" ] || continue | |
| file=$(basename "$path") | |
| lower=$(printf '%s' "$file" | tr '[:upper:]' '[:lower:]') | |
| case "$lower" in | |
| *windows-full*.exe) | |
| windows_full+=("$file") | |
| ;; | |
| *windows*.exe) | |
| windows_installers+=("$file") | |
| ;; | |
| *windows*.zip) | |
| windows_portable+=("$file") | |
| ;; | |
| *android-full*.zip) | |
| android_full+=("$file") | |
| ;; | |
| *.apk) | |
| android_apk+=("$file") | |
| ;; | |
| *linux-full*.tar.gz|*linux-full*.tar.*) | |
| linux_full+=("$file") | |
| ;; | |
| *.deb) | |
| linux_deb+=("$file") | |
| ;; | |
| *.rpm) | |
| linux_rpm+=("$file") | |
| ;; | |
| *linux-raw*.zip|*linux*.zip) | |
| linux_portable+=("$file") | |
| ;; | |
| *.dmg) | |
| mac_dmg+=("$file") | |
| ;; | |
| *macos-full*.zip) | |
| mac_full+=("$file") | |
| ;; | |
| *) | |
| echo "Ignoring non-user-facing release file in notes: $file" | |
| ;; | |
| esac | |
| done | |
| safe_commit_message=$(printf '%s' "$COMMIT_MESSAGE" \ | |
| | tr '\r\n' ' ' \ | |
| | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//' \ | |
| | sed \ | |
| -e 's/\\/\\\\/g' \ | |
| -e 's/`/\\`/g' \ | |
| -e 's/\*/\\*/g' \ | |
| -e 's/_/\\_/g' \ | |
| -e 's/\[/\\[/g' \ | |
| -e 's/\]/\\]/g') | |
| cat > release-body.md <<EOF | |
| # ${RELEASE_TITLE} | |
| > **מה להוריד?** | |
| > | |
| > אם אתם רק מעדכנים את האפליקציה, ברוב המקרים תרצו את **גרסת תוכנה בלבד**. | |
| > | |
| > אם אתם צריכים חבילה להפצה או להתקנה לא־מקוונת, בחרו **גרסת ספרייה מלאה (FULL)**. | |
| > | |
| > קובצי הספרייה זמינים גם בנפרד כאן: | |
| > [seforim.db.zst](https://github.com/Otzaria/SeforimLibrary/releases/latest/download/seforim.db.zst), | |
| > [otzar-HB_catalog.db.zst](https://github.com/Otzaria/otzar-HB_catalog/releases/latest/download/otzar-HB_catalog.db.zst) | |
| > | |
| > קובצי ה־PDF של תלמוד בבלי זמינים גם בנפרד כאן: | |
| > [talmud_bavli_latest.tar.zst](https://github.com/Otzaria/otzaria-library/releases/latest/download/talmud_bavli_latest.tar.zst) | |
| EOF | |
| if [ ${#windows_installers[@]} -gt 0 ] || [ ${#windows_portable[@]} -gt 0 ] || [ ${#windows_full[@]} -gt 0 ]; then | |
| printf "## ווינדוס\n\n" >> release-body.md | |
| if [ ${#windows_installers[@]} -gt 0 ] || [ ${#windows_portable[@]} -gt 0 ]; then | |
| printf "### גרסת תוכנה בלבד\n\n" >> release-body.md | |
| emit_link_group "מתקין לווינדוס" "${windows_installers[@]}" | |
| emit_link_group "גרסה ניידת לווינדוס" "${windows_portable[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| if [ ${#windows_full[@]} -gt 0 ]; then | |
| printf "### גרסת ספרייה מלאה\n\n" >> release-body.md | |
| emit_link_group "חבילה מלאה לווינדוס" "${windows_full[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| fi | |
| if [ ${#android_apk[@]} -gt 0 ] || [ ${#android_full[@]} -gt 0 ]; then | |
| printf "## אנדרואיד\n\n" >> release-body.md | |
| if [ ${#android_apk[@]} -gt 0 ]; then | |
| printf "### גרסת תוכנה בלבד\n\n" >> release-body.md | |
| emit_link_group "APK לאנדרואיד" "${android_apk[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| if [ ${#android_full[@]} -gt 0 ]; then | |
| printf "### גרסת ספרייה מלאה\n\n" >> release-body.md | |
| emit_link_group "חבילה מלאה לאנדרואיד" "${android_full[@]}" | |
| printf "\n> **הבהרה:** חבילת ה־FULL לאנדרואיד כוללת את ה־APK יחד עם חומרי הספרייה, ומיועדת להפצה או להתקנה ידנית.\n\n" >> release-body.md | |
| fi | |
| fi | |
| if [ ${#linux_deb[@]} -gt 0 ] || [ ${#linux_rpm[@]} -gt 0 ] || [ ${#linux_portable[@]} -gt 0 ] || [ ${#linux_full[@]} -gt 0 ]; then | |
| printf "## לינוקס\n\n" >> release-body.md | |
| if [ ${#linux_deb[@]} -gt 0 ] || [ ${#linux_rpm[@]} -gt 0 ] || [ ${#linux_portable[@]} -gt 0 ]; then | |
| printf "### גרסת תוכנה בלבד\n\n" >> release-body.md | |
| emit_link_group "חבילת DEB ללינוקס" "${linux_deb[@]}" | |
| emit_link_group "חבילת RPM ללינוקס" "${linux_rpm[@]}" | |
| emit_link_group "גרסה ניידת ללינוקס" "${linux_portable[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| if [ ${#linux_full[@]} -gt 0 ]; then | |
| printf "### גרסת ספרייה מלאה\n\n" >> release-body.md | |
| emit_link_group "חבילה מלאה ללינוקס" "${linux_full[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| fi | |
| if [ ${#mac_dmg[@]} -gt 0 ] || [ ${#mac_full[@]} -gt 0 ]; then | |
| printf "## מק\n\n" >> release-body.md | |
| if [ ${#mac_dmg[@]} -gt 0 ]; then | |
| printf "### גרסת תוכנה בלבד\n\n" >> release-body.md | |
| emit_link_group "מתקין DMG למק" "${mac_dmg[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| if [ ${#mac_full[@]} -gt 0 ]; then | |
| printf "### גרסת ספרייה מלאה\n\n" >> release-body.md | |
| emit_link_group "חבילה מלאה למק" "${mac_full[@]}" | |
| printf "\n" >> release-body.md | |
| fi | |
| fi | |
| cat >> release-body.md <<EOF | |
| <details> | |
| <summary>מידע טכני</summary> | |
| - נבנה מ־commit ${GITHUB_SHA::7} | |
| - תיאור commit: ${safe_commit_message} | |
| - ענף: ${{ github.ref_name }} | |
| </details> | |
| EOF | |
| echo "Generated release notes:" | |
| cat release-body.md | |
| - name: Create Release | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| token: ${{ secrets.GH_RELEASE_TOKEN }} | |
| tag_name: ${{ steps.version.outputs.tag }} | |
| name: ${{ steps.version.outputs.title }} | |
| prerelease: ${{ steps.version.outputs.prerelease }} | |
| files: release-files/* | |
| body_path: release-body.md | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # --- הכרזה על גרסה שפורסמה --- | |
| announce_release: | |
| # תנאי לריצה רק כשיש אירוע של פרסום release | |
| if: github.event_name == 'release' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout (full history + tags) | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # חובה כדי לאפשר גישה לכל ההיסטוריה והתגים | |
| fetch-tags: true | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.12' | |
| - name: Install dependencies for webhook script | |
| run: pip install bs4 requests pyluach | |
| - name: Build commit list since previous release | |
| id: changelog | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| CURRENT_TAG="${{ github.event.release.tag_name }}" | |
| git fetch --tags --force | |
| # מצא את התג הקודם (הכי עדכני שהוא לא התג הנוכחי) | |
| PREV_TAG=$(git for-each-ref --sort=-creatordate --format '%(refname:strip=2)' refs/tags | grep -v "^$CURRENT_TAG$" | head -n1 || true) | |
| if [ -n "$PREV_TAG" ]; then | |
| RANGE="$PREV_TAG..$CURRENT_TAG" | |
| # צור רשימה של כותרות הcommits בטווח | |
| COMMITS=$(git log --pretty=format:'- %s' "$RANGE" || true) | |
| else | |
| # אם אין תג קודם, קח רק את הcommit של התג הנוכחי | |
| SHA=$(git rev-list -n 1 "$CURRENT_TAG") | |
| COMMITS=$(git log --pretty=format:'- %s' "${SHA}^..${SHA}" || true) | |
| fi | |
| if [ -z "${COMMITS// /}" ]; then | |
| COMMITS="(לא נמצאו כותרות commits חדשות להצגה)" | |
| fi | |
| # העבר את רשימת הcommits והתג הקודם לשלבים הבאים | |
| { | |
| echo "prev_tag=${PREV_TAG}" | |
| echo "commits<<EOF" | |
| echo "$COMMITS" | |
| echo "EOF" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Run announcement script (Mitmachim + Yemot) | |
| env: | |
| # פרטי ה-Release נלקחים מאירוע ה-Webhook של GitHub | |
| RELEASE_TAG: ${{ github.event.release.tag_name }} | |
| RELEASE_NAME: ${{ github.event.release.name }} | |
| RELEASE_URL: ${{ github.event.release.html_url }} | |
| GITHUB_EVENT_PATH: ${{ github.event_path }} # חיוני כדי שהסקריפט ימצא את קבצי ההורדה | |
| # הרכבת גוף ההודעה: התיאור מה-Release + רשימת הקומיטים שיצרנו | |
| RELEASE_BODY: | | |
| ${{ github.event.release.body }} | |
| השינויים מאז הגרסה הקודמת${{ steps.changelog.outputs.prev_tag && format(' ({0})', steps.changelog.outputs.prev_tag) || '' }}: | |
| ${{ steps.changelog.outputs.commits }} | |
| # סודות להתחברות לשירותים החיצוניים | |
| USER_NAME: ${{ secrets.USER_NAME }} | |
| PASSWORD: ${{ secrets.PASSWORD }} | |
| TOKEN_YEMOT: ${{ secrets.TOKEN_YEMOT }} | |
| run: | | |
| python webhooks/main.py |