name: CI and Docker image on: workflow_dispatch: inputs: tag: description: 'Optional extra Docker tag to publish' required: false default: '' type: string push: branches: - main - 'codex/**' tags: - 'v*' pull_request: branches: - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read packages: write env: IMAGE_NAME: ghcr.io/lihaowang/orangetv NODE_VERSION: 24.14.1 PNPM_VERSION: 10.14.0 jobs: quality: name: Quality gates runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v4 - name: Set up pnpm uses: pnpm/action-setup@v4 with: version: ${{ env.PNPM_VERSION }} run_install: false - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Typecheck run: pnpm typecheck - name: Lint run: pnpm lint - name: Test run: pnpm test --runInBand - name: Build run: pnpm build docker-smoke: name: Docker smoke runs-on: ubuntu-latest needs: quality steps: - name: Checkout source code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build local smoke image uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile platforms: linux/amd64 load: true tags: orangetv-actions-smoke:ci cache-from: type=gha cache-to: type=gha,mode=max - name: Run container smoke checks shell: bash run: | set -euo pipefail container_id="$( docker run -d \ -p 3000:3000 \ -e USERNAME=admin \ -e PASSWORD=orange \ -e VITE_STORAGE_TYPE=localstorage \ orangetv-actions-smoke:ci )" cleanup() { docker rm -f "$container_id" >/dev/null 2>&1 || true } trap cleanup EXIT for attempt in {1..30}; do if curl -fsS http://localhost:3000/api/health >/dev/null; then break fi if [ "$attempt" -eq 30 ]; then docker logs "$container_id" exit 1 fi sleep 2 done curl -fsS http://localhost:3000/api/health curl -fsS http://localhost:3000/runtime-config.js | grep 'window.RUNTIME_CONFIG' curl -fsSI http://localhost:3000/login | grep '200 OK' docker-publish: name: Publish Docker image runs-on: ubuntu-latest needs: - quality - docker-smoke if: >- github.event_name != 'pull_request' && ( github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/codex/') || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' ) steps: - name: Checkout source code uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_NAME }} flavor: latest=false tags: | type=raw,value=latest,enable={{is_default_branch}} type=ref,event=branch,prefix=branch-,enable=${{ startsWith(github.ref, 'refs/heads/codex/') }} type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/v') }} type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && inputs.tag != '' }} type=sha,prefix=sha-,format=short - name: Build and push multi-arch image uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile platforms: linux/amd64,linux/arm64 push: true labels: ${{ steps.meta.outputs.labels }} tags: ${{ steps.meta.outputs.tags }} cache-from: type=gha cache-to: type=gha,mode=max