From a44fefbccb595d3a43680946f4bcf6ca21eb1e1a Mon Sep 17 00:00:00 2001 From: Uruk Date: Tue, 17 Feb 2026 18:50:27 +0100 Subject: [PATCH] feat(build): enables tvos builds and artifact reporting Enables tvOS builds, including unsigned variants, and integrates them into the artifact reporting workflow. This change re-enables the tvOS build jobs, adds a job for unsigned tvOS builds, and updates the artifact reporting workflow to include these new build targets. The artifact comment now displays download links, file sizes, and build durations for all available builds. Also updates workflow dependencies. --- .github/workflows/artifact-comment.yml | 39 ++++-- .github/workflows/build-apps.yml | 184 ++++++++++++++++--------- package.json | 1 + 3 files changed, 148 insertions(+), 76 deletions(-) diff --git a/.github/workflows/artifact-comment.yml b/.github/workflows/artifact-comment.yml index 8528a95b..288a916b 100644 --- a/.github/workflows/artifact-comment.yml +++ b/.github/workflows/artifact-comment.yml @@ -220,7 +220,10 @@ jobs: const jobMappings = { 'Android Phone': ['🤖 Build Android APK (Phone)', 'build-android-phone'], 'Android TV': ['🤖 Build Android APK (TV)', 'build-android-tv'], - 'iOS Phone': ['🍎 Build iOS IPA (Phone)', 'build-ios-phone'] + 'iOS': ['🍎 Build iOS IPA (Phone)', 'build-ios-phone'], + 'iOS Unsigned': ['🍎 Build iOS IPA (Phone - Unsigned)', 'build-ios-phone-unsigned'], + 'tvOS': ['🍎 Build tvOS IPA', 'build-ios-tv'], + 'tvOS Unsigned': ['🍎 Build tvOS IPA (Unsigned)', 'build-ios-tv-unsigned'] }; // Create individual status for each job @@ -353,10 +356,12 @@ jobs: // Process each expected build target individually const buildTargets = [ - { name: 'Android Phone', platform: '🤖', device: '📱', statusKey: 'Android Phone', artifactPattern: /android.*phone/i }, - { name: 'Android TV', platform: '🤖', device: '📺', statusKey: 'Android TV', artifactPattern: /android.*tv/i }, - { name: 'iOS Phone', platform: '🍎', device: '📱', statusKey: 'iOS Phone', artifactPattern: /ios.*phone/i }, - { name: 'iOS TV', platform: '🍎', device: '📺', statusKey: 'iOS TV', artifactPattern: /ios.*tv/i } + { name: 'Android Phone', platform: '🤖', device: '📱 Phone', statusKey: 'Android Phone', artifactPattern: /android.*phone/i }, + { name: 'Android TV', platform: '🤖', device: '📺 TV', statusKey: 'Android TV', artifactPattern: /android.*tv/i }, + { name: 'iOS', platform: '🍎', device: '📱 Phone', statusKey: 'iOS Phone', artifactPattern: /ios.*phone.*ipa(?!.*unsigned)/i }, + { name: 'iOS Unsigned', platform: '🍎', device: '📱 Phone Unsigned', statusKey: 'iOS Phone Unsigned', artifactPattern: /ios.*phone.*unsigned/i }, + { name: 'tvOS', platform: '🍎', device: '📺 TV', statusKey: 'tvOS', artifactPattern: /ios.*tv.*ipa(?!.*unsigned)/i }, + { name: 'tvOS Unsigned', platform: '🍎', device: '📺 TV Unsigned', statusKey: 'tvOS Unsigned', artifactPattern: /ios.*tv.*unsigned/i } ]; for (const target of buildTargets) { @@ -371,16 +376,26 @@ jobs: let status = '⏳ Pending'; let downloadLink = '*Waiting for build...*'; - // Special case for iOS TV - show as disabled - if (target.name === 'iOS TV') { - status = '💤 Disabled'; - downloadLink = '*Disabled for now*'; - } else if (matchingStatus) { + if (matchingStatus) { if (matchingStatus.conclusion === 'success' && matchingArtifact) { status = '✅ Complete'; const directLink = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${matchingArtifact.workflow_run.id}/artifacts/${matchingArtifact.id}`; const fileType = target.name.includes('Android') ? 'APK' : 'IPA'; - downloadLink = `[📥 Download ${fileType}](${directLink})`; + + // Format file size + const sizeInMB = (matchingArtifact.size_in_bytes / (1024 * 1024)).toFixed(1); + const sizeInfo = `(${sizeInMB} MB)`; + + // Calculate build duration + let durationInfo = ''; + if (matchingStatus.started_at && matchingStatus.completed_at) { + const durationMs = new Date(matchingStatus.completed_at) - new Date(matchingStatus.started_at); + const durationMin = Math.floor(durationMs / 60000); + const durationSec = Math.floor((durationMs % 60000) / 1000); + durationInfo = ` - ${durationMin}m ${durationSec}s`; + } + + downloadLink = `[📥 Download ${fileType}](${directLink}) ${sizeInfo}${durationInfo}`; } else if (matchingStatus.conclusion === 'failure') { status = `❌ [Failed](${matchingStatus.url})`; downloadLink = '*Build failed*'; @@ -408,7 +423,7 @@ jobs: } } - commentBody += `| ${target.platform} ${target.name.split(' ')[0]} | ${target.device} ${target.name.split(' ')[1]} | ${status} | ${downloadLink} |\n`; + commentBody += `| ${target.platform} ${target.name} | ${target.device} | ${status} | ${downloadLink} |\n`; } commentBody += `\n`; diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index b300c04d..520cbb46 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -299,67 +299,123 @@ jobs: path: build/*.ipa retention-days: 7 - # Disabled for now - uncomment when ready to build iOS TV - # build-ios-tv: - # if: (!contains(github.event.head_commit.message, '[skip ci]') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'streamyfin/streamyfin')) - # runs-on: macos-26 - # name: 🍎 Build iOS IPA (TV) - # permissions: - # contents: read - # - # steps: - # - name: 📥 Checkout code - # uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - # with: - # ref: ${{ github.event.pull_request.head.sha || github.sha }} - # fetch-depth: 0 - # submodules: recursive - # show-progress: false - # - # - name: 🍞 Setup Bun - # uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2 - # with: - # bun-version: latest - # - # - name: 💾 Cache Bun dependencies - # uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - # with: - # path: ~/.bun/install/cache - # key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }} - # restore-keys: | - # ${{ runner.os }}-bun-cache - # - # - name: 📦 Install dependencies and reload submodules - # run: | - # bun install --frozen-lockfile - # bun run submodule-reload - # - # - name: 🛠️ Generate project files - # run: bun run prebuild:tv - # - # - name: 🔧 Setup Xcode - # uses: maxim-lobanov/setup-xcode@v1 - # with: - # xcode-version: '26.0.1' - # - # - name: 🏗️ Setup EAS - # uses: expo/expo-github-action@main - # with: - # eas-version: latest - # token: ${{ secrets.EXPO_TOKEN }} - # eas-cache: true - # - # - name: 🚀 Build iOS app - # env: - # EXPO_TV: 1 - # run: eas build -p ios --local --non-interactive - # - # - name: 📅 Set date tag - # run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV - # - # - name: 📤 Upload IPA artifact - # uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - # with: - # name: streamyfin-ios-tv-ipa-${{ env.DATE_TAG }} - # path: build-*.ipa - # retention-days: 7 + build-ios-tv: + if: (!contains(github.event.head_commit.message, '[skip ci]') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'streamyfin/streamyfin')) + runs-on: macos-26 + name: 🍎 Build tvOS IPA + permissions: + contents: read + + steps: + - name: 📥 Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + submodules: recursive + show-progress: false + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 + with: + bun-version: latest + + - name: 💾 Cache Bun dependencies + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun-cache + + - name: 📦 Install dependencies and reload submodules + run: | + bun install --frozen-lockfile + bun run submodule-reload + + - name: 🛠️ Generate project files + run: bun run prebuild:tv + + - name: 🔧 Setup Xcode + uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1 + with: + xcode-version: "26.2" + + - name: 🏗️ Setup EAS + uses: expo/expo-github-action@main + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + eas-cache: true + + - name: 🚀 Build iOS app + env: + EXPO_TV: 1 + run: eas build -p ios --local --non-interactive + + - name: 📅 Set date tag + run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV + + - name: 📤 Upload IPA artifact + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: streamyfin-ios-tv-ipa-${{ env.DATE_TAG }} + path: build-*.ipa + retention-days: 7 + + build-ios-tv-unsigned: + if: (!contains(github.event.head_commit.message, '[skip ci]')) + runs-on: macos-26 + name: 🍎 Build tvOS IPA (Unsigned) + permissions: + contents: read + + steps: + - name: 📥 Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + submodules: recursive + show-progress: false + + - name: 🍞 Setup Bun + uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2.1.2 + with: + bun-version: latest + + - name: 💾 Cache Bun dependencies + uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-cache-${{ hashFiles('bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun-cache + + - name: 📦 Install dependencies and reload submodules + run: | + bun install --frozen-lockfile + bun run submodule-reload + + - name: 🛠️ Generate project files + run: bun run prebuild:tv + + - name: 🔧 Setup Xcode + uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1 + with: + xcode-version: "26.2" + + - name: 🚀 Build iOS app + env: + EXPO_TV: 1 + run: bun run ios:unsigned-build:tv ${{ github.event_name == 'pull_request' && '-- --verbose' || '' }} + + - name: 📅 Set date tag + run: echo "DATE_TAG=$(date +%d-%m-%Y_%H-%M-%S)" >> $GITHUB_ENV + + - name: 📤 Upload IPA artifact + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + with: + name: streamyfin-ios-tv-unsigned-ipa-${{ env.DATE_TAG }} + path: build/*.ipa + retention-days: 7 diff --git a/package.json b/package.json index 9864985a..34f0ff24 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "android:tv": "cross-env EXPO_TV=1 expo run:android", "build:android:local": "cd android && cross-env NODE_ENV=production ./gradlew assembleRelease", "ios:unsigned-build": "cross-env EXPO_TV=0 bun scripts/ios/build-ios.ts --production", + "ios:unsigned-build:tv": "cross-env EXPO_TV=1 bun scripts/ios/build-ios.ts --production", "prepare": "husky", "typecheck": "node scripts/typecheck.js", "check": "biome check . --max-diagnostics 1000",