Skip to content

fix: Performance

fix: Performance #150

# 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: '80 90'
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