fix: Performance #146
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
| # GitHub Actions Workflow: CI/CD Pipeline | |
| # | |
| # Jobs: | |
| # 1. 🔨 Build & Test — restore, build, unit tests, coverage (every PR + push) | |
| # 2. 📦 Package — create plugin zip + checksums (every PR + release) | |
| # 3. 🚀 Release — Release Please, upload assets, update manifest (push to main only) | |
| # 4. 🧪 Integration Tests — live TVHeadend + Jellyfin smoke tests (after build) | |
| name: "CI/CD Pipeline" | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| paths-ignore: ['manifest.json'] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| checks: write | |
| # ──────────────────────────────────────────────────────────────────── | |
| jobs: | |
| # ── 1. Build, test, coverage ───────────────────────────────────── | |
| build: | |
| name: "🔨 Build & Test" | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 8.0.x | |
| - name: Determine Version | |
| id: version | |
| shell: bash | |
| run: echo "version=0.0.0.0" >> "$GITHUB_OUTPUT" | |
| - name: Restore Dependencies | |
| run: dotnet restore Jellyfin.Plugin.TvHeadendApi.sln | |
| - name: Build Plugin | |
| run: > | |
| dotnet build Jellyfin.Plugin.TvHeadendApi.sln | |
| --configuration Release | |
| --no-restore | |
| -p:AssemblyVersion=${{ steps.version.outputs.version }} | |
| -p:FileVersion=${{ steps.version.outputs.version }} | |
| -p:InformationalVersion=${{ steps.version.outputs.version }} | |
| - name: Run Unit Tests | |
| run: > | |
| dotnet test Jellyfin.Plugin.TvHeadendApi.Tests/Jellyfin.Plugin.TvHeadendApi.Tests.csproj | |
| --configuration Release | |
| --no-build | |
| --collect:"XPlat Code Coverage" | |
| --logger "trx;LogFileName=test-results.trx" | |
| --results-directory TestResults | |
| --filter "Category!=LiveIntegration" | |
| - name: Upload Test Results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-results | |
| path: 'TestResults/' | |
| if-no-files-found: ignore | |
| - name: Upload Build Output | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build-output | |
| path: Jellyfin.Plugin.TvHeadendApi/bin/Release/net8.0/ | |
| if-no-files-found: error | |
| - name: Publish Test Summary | |
| if: always() && github.event_name == 'pull_request' | |
| uses: dorny/test-reporter@v1 | |
| with: | |
| name: "Unit Tests" | |
| path: 'TestResults/test-results.trx' | |
| reporter: dotnet-trx | |
| fail-on-error: true | |
| - name: Generate Coverage Report | |
| if: always() && github.event_name == 'pull_request' | |
| uses: irongut/CodeCoverageSummary@v1.3.0 | |
| with: | |
| filename: 'TestResults/**/coverage.cobertura.xml' | |
| badge: true | |
| format: markdown | |
| output: both | |
| thresholds: '90 95' | |
| fail_below_min: true | |
| - name: Post Coverage to PR | |
| if: always() && github.event_name == 'pull_request' | |
| uses: marocchino/sticky-pull-request-comment@v2 | |
| with: | |
| recreate: true | |
| path: code-coverage-results.md | |
| # ── 2. Package plugin zip ──────────────────────────────────────── | |
| package: | |
| name: "📦 Package Plugin" | |
| runs-on: ubuntu-24.04 | |
| needs: build | |
| outputs: | |
| artifact_md5: ${{ steps.checksums.outputs.artifact_md5 }} | |
| artifact_sha256: ${{ steps.checksums.outputs.artifact_sha256 }} | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| - name: Download Build Output | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: build-output | |
| path: build-output/ | |
| - name: Create Package | |
| id: create_package | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ needs.build.outputs.version }}" | |
| PACKAGE_DIR="tvheadend_api_${VERSION}" | |
| TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" | |
| mkdir -p "$PACKAGE_DIR" | |
| find build-output -maxdepth 1 -type f \( -name '*.dll' -o -name '*.pdb' -o -name '*.xml' \) -exec cp {} "$PACKAGE_DIR"/ \; | |
| jq --arg timestamp "$TIMESTAMP" \ | |
| --arg targetAbi "10.10.3.0" \ | |
| --arg version "$VERSION" \ | |
| '.[0] | del(.versions) | . + {targetAbi: $targetAbi, timestamp: $timestamp, version: $version}' manifest.json > "$PACKAGE_DIR/meta.json" | |
| zip -r "${PACKAGE_DIR}.zip" "$PACKAGE_DIR" | |
| - name: Calculate Checksums | |
| id: checksums | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ needs.build.outputs.version }}" | |
| md5sum "tvheadend_api_${VERSION}.zip" > "tvheadend_api_${VERSION}.md5" | |
| sha256sum "tvheadend_api_${VERSION}.zip" > "tvheadend_api_${VERSION}.sha256" | |
| echo "artifact_md5=$(awk '{print $1}' "tvheadend_api_${VERSION}.md5")" >> "$GITHUB_OUTPUT" | |
| echo "artifact_sha256=$(awk '{print $1}' "tvheadend_api_${VERSION}.sha256")" >> "$GITHUB_OUTPUT" | |
| - name: Upload Package Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tvheadend_api_${{ needs.build.outputs.version }} | |
| path: | | |
| tvheadend_api_${{ needs.build.outputs.version }}.zip | |
| tvheadend_api_${{ needs.build.outputs.version }}.md5 | |
| tvheadend_api_${{ needs.build.outputs.version }}.sha256 | |
| # ── 3. Release (main push only) ───────────────────────────────── | |
| release: | |
| name: "🚀 Release" | |
| runs-on: ubuntu-24.04 | |
| needs: [build, package] | |
| if: github.event_name == 'push' | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Release Please | |
| id: release-please | |
| uses: googleapis/release-please-action@v4 | |
| with: | |
| config-file: .github/release-please-config.json | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Determine Release Version | |
| if: steps.release-please.outputs.release_created == 'true' | |
| id: version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION_WITHOUT_V="${{ steps.release-please.outputs.tag_name }}" | |
| VERSION_WITHOUT_V="${VERSION_WITHOUT_V#v}" | |
| echo "version=${VERSION_WITHOUT_V}.0" >> "$GITHUB_OUTPUT" | |
| - name: Download Package | |
| if: steps.release-please.outputs.release_created == 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: tvheadend_api_${{ needs.build.outputs.version || '0.0.0.0' }} | |
| - name: Upload Release Assets | |
| if: steps.release-please.outputs.release_created == 'true' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.release-please.outputs.tag_name }} | |
| files: | | |
| tvheadend_api_*.zip | |
| tvheadend_api_*.md5 | |
| tvheadend_api_*.sha256 | |
| - name: Update manifest.json | |
| if: steps.release-please.outputs.release_created == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ steps.version.outputs.version }}" | |
| TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" | |
| CHANGELOG="https://github.com/john-pierre/jellyfin-plugin-tvheadend-api/releases/tag/${{ steps.release-please.outputs.tag_name }}" | |
| SOURCE_URL="https://github.com/john-pierre/jellyfin-plugin-tvheadend-api/releases/download/${{ steps.release-please.outputs.tag_name }}/tvheadend_api_${VERSION}.zip" | |
| TARGET_ABI="10.10.3.0" | |
| REPOSITORY_NAME="jellyfin-plugin-tvheadend-api" | |
| REPOSITORY_URL="https://github.com/john-pierre/jellyfin-plugin-tvheadend-api" | |
| jq --arg version "$VERSION" \ | |
| --arg checksum "${{ needs.package.outputs.artifact_md5 }}" \ | |
| --arg changelog "$CHANGELOG" \ | |
| --arg timestamp "$TIMESTAMP" \ | |
| --arg source_url "$SOURCE_URL" \ | |
| --arg target_abi "$TARGET_ABI" \ | |
| --arg repository_name "$REPOSITORY_NAME" \ | |
| --arg repository_url "$REPOSITORY_URL" \ | |
| '.[0].versions |= ([{ | |
| version: $version, | |
| changelog: $changelog, | |
| targetAbi: $target_abi, | |
| sourceUrl: $source_url, | |
| checksum: $checksum, | |
| timestamp: $timestamp, | |
| repositoryName: $repository_name, | |
| repositoryUrl: $repository_url | |
| }] + . | unique_by(.version) | sort_by(.version | split(".") | map(tonumber)) | reverse)' manifest.json > updated_manifest.json | |
| mv updated_manifest.json manifest.json | |
| - name: Commit manifest.json | |
| if: steps.release-please.outputs.release_created == 'true' | |
| uses: stefanzweifel/git-auto-commit-action@v5 | |
| with: | |
| branch: main | |
| commit_message: "build(release): update manifest for ${{ steps.release-please.outputs.tag_name }} [skip ci]" | |
| file_pattern: manifest.json | |
| # ── 4. Live integration tests ─────────────────────────────────── | |
| integration: | |
| name: "🧪 Integration Tests" | |
| runs-on: ubuntu-24.04 | |
| needs: build | |
| steps: | |
| - name: Checkout Repository | |
| uses: actions/checkout@v4 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: 8.0.x | |
| - name: Clean Previous Stack | |
| run: docker compose -f docker/docker-compose.test.yaml down --volumes --remove-orphans 2>/dev/null || true | |
| - name: Start TVHeadend & IPTV Simulator | |
| run: docker compose -f docker/docker-compose.test.yaml up -d --build iptv-simulator tvheadend | |
| - name: Wait for TVHeadend Healthy | |
| run: | | |
| echo "Waiting for TVHeadend to become healthy..." | |
| for i in $(seq 1 60); do | |
| if curl -sf http://localhost:19981/api/serverinfo > /dev/null 2>&1; then | |
| echo "TVHeadend is ready." | |
| exit 0 | |
| fi | |
| sleep 3 | |
| done | |
| echo "TVHeadend did not become healthy in time." | |
| docker compose -f docker/docker-compose.test.yaml logs tvheadend | |
| exit 1 | |
| - name: Run Bootstrap | |
| run: | | |
| docker compose -f docker/docker-compose.test.yaml up tvheadend-bootstrap --exit-code-from tvheadend-bootstrap | |
| echo "Bootstrap complete." | |
| - name: Restore Dependencies | |
| run: dotnet restore Jellyfin.Plugin.TvHeadendApi.sln | |
| - name: Build | |
| run: dotnet build Jellyfin.Plugin.TvHeadendApi.sln --configuration Release --no-restore | |
| - name: Run Live Integration Tests | |
| env: | |
| TVHEADEND_URL: http://localhost:19981 | |
| run: > | |
| dotnet test Jellyfin.Plugin.TvHeadendApi.Tests/Jellyfin.Plugin.TvHeadendApi.Tests.csproj | |
| --configuration Release | |
| --no-build | |
| --logger "trx;LogFileName=integration-results.trx" | |
| --results-directory TestResults | |
| --filter "Category=LiveIntegration" | |
| - name: Upload Integration Test Results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: integration-test-results | |
| path: 'TestResults/' | |
| if-no-files-found: ignore | |
| - name: Publish Integration Test Summary | |
| if: always() && github.event_name == 'pull_request' | |
| uses: dorny/test-reporter@v1 | |
| with: | |
| name: "Integration Tests" | |
| path: 'TestResults/integration-results.trx' | |
| reporter: dotnet-trx | |
| fail-on-error: true | |
| - name: Show Bootstrap Logs on Failure | |
| if: failure() | |
| run: docker compose -f docker/docker-compose.test.yaml logs tvheadend-bootstrap iptv-simulator tvheadend | |
| - name: Tear Down Services | |
| if: always() | |
| run: docker compose -f docker/docker-compose.test.yaml down -v | |